How to simulate selectionStart and selectionEnd

1,509 views
Skip to first unread message

DjBoo

unread,
Jun 23, 2011, 8:38:05 PM6/23/11
to Ajax.org Cloud9 Editor (Ace)
Hi,

Is it possible to simulate selectionStart and selectionEnd in ACE. I
need something similar to a normal textbox element where
selectionStart and selectionEnd are number of chars from start point
of the text.

I need to be able to set and get these values


Thanks
Joe

Hai Dang

unread,
Jun 24, 2011, 3:38:31 AM6/24/11
to Ajax.org Cloud9 Editor (Ace)
After a bit searching I think this is what you need:

To set the selection
editor.getSelection().setSelectionRage(Range);

(check out the function at https://github.com/ajaxorg/ace/blob/master/lib/ace/selection.js#L166
)
The get the selection range
var range = editor.getSelection().getRange();

In both cases, Range is an object that contains 2 position objects:
start and end.

Unfortunately these position objects are the combination of row and
column index, not the number of chars from the start of the text
(check https://github.com/ajaxorg/ace/blob/master/lib/ace/range.js#L38
)

Anyway if you look more closely to the code I think you may find
something :)

Brion Vibber

unread,
Jun 24, 2011, 11:54:51 AM6/24/11
to ace-d...@googlegroups.com
On Fri, Jun 24, 2011 at 12:38 AM, Hai Dang <melodie...@gmail.com> wrote:
To set the selection
editor.getSelection().setSelectionRage(Range);

(check out the function at https://github.com/ajaxorg/ace/blob/master/lib/ace/selection.js#L166
)
The get the selection range
var range = editor.getSelection().getRange();

In both cases, Range is an object that contains 2 position objects:
start and end.

Unfortunately these position objects are the combination of row and
column index, not the number of chars from the start of the text
(check https://github.com/ajaxorg/ace/blob/master/lib/ace/range.js#L38
)

In my MediaWiki extension I'm plugging Ace into an editor-hosting widget which needs to sometimes set the selection from start/end character values like this. This seems to work for me (context.codeEditor here is the Ace editor instance):

    'setSelection': function( options ) {
        // Ace stores positions for ranges as row/column pairs.
        // To convert from character offsets, we'll need to iterate through the document
        var doc = context.codeEditor.getSession().getDocument();
        var lines = doc.getAllLines();

        var offsetToPos = function( offset ) {
            var row = 0, col = 0;
            var pos = 0;
            while ( row < lines.length && pos + lines[row].length < offset) {
                pos += lines[row].length;
                pos++; // for the newline
                row++;
            }
            col = offset - pos;
            return {row: row, column: col};
        }
        var start = offsetToPos( options.start ),
            end = offsetToPos( options.end );

        var sel = context.codeEditor.getSelection();
        var range = sel.getRange();
        range.setStart( start.row, start.column );
        range.setEnd( end.row, end.column );
        sel.setSelectionRange( range );
        return context.$textarea;
    },

The inverse to convert Ace's row/col positions to character positions to report them back should also be fairly straightforward. This is a naive implementation and can probably be done more efficiently by peeking into Ace's internals as well. :)

-- brion vibber (brion @ pobox.com)

DjBoo

unread,
Jun 24, 2011, 7:39:22 PM6/24/11
to Ajax.org Cloud9 Editor (Ace)
I know that it is possible to accomplish Range for finding start and
end positions, but as Hai mentioned it is a function of row and
column. So it looks like that there must be a lower level function to
do so. In my situation I need to change selection start and end a lot
and hence I am guessing this is not a good method for me.

At the moment I do something similar to what Brion suggested. Using
getSelectionRange() to find the position of end and start points and
then use selection.moveCursorTo(row,pos) to set the start point and
selection.selectTo(row,pos) to set the end point.

It works as expected but finding the row and column from iterating
over all rows doesn't seems OK to me particularly for case of long
contexts

That would be great if I could find the lower level function.

Cheers,
Joe


On Jun 24, 11:54 am, Brion Vibber <br...@pobox.com> wrote:
> On Fri, Jun 24, 2011 at 12:38 AM, Hai Dang <melodiesmas...@gmail.com> wrote:
> > To set the selection
> > editor.getSelection().setSelectionRage(Range);
>
> > (check out the function at
> >https://github.com/ajaxorg/ace/blob/master/lib/ace/selection.js#L166
> > )
> > The get the selection range
> > var range = editor.getSelection().getRange();
>
> > In both cases, Range is an object that contains 2 position objects:
> > start and end.
>
> > Unfortunately these position objects are the combination of row and
> > column index, not the number of chars from the start of the text
> > (checkhttps://github.com/ajaxorg/ace/blob/master/lib/ace/range.js#L38

Brion Vibber

unread,
Jun 24, 2011, 8:13:48 PM6/24/11
to ace-d...@googlegroups.com
On Fri, Jun 24, 2011 at 4:39 PM, DjBoo <bela...@gmail.com> wrote:
At the moment I do something similar to what Brion suggested. Using
getSelectionRange() to find the position of end and start points and
then use selection.moveCursorTo(row,pos) to set the start point and
selection.selectTo(row,pos) to set the end point.

It works as expected but finding the row and column from iterating
over all rows doesn't seems OK to me particularly for case of long
contexts

That would be great if I could find the lower level function.

Ace's document model stores the text being edited as an array, containing one string for each line; this makes the row/col model for referring to positions in the document very natural in the low-level internals, and that's what it exposes in its API as well.

You could probably calculate up character offsets for the beginning of each line, and update them as needed when text changes. The Document object exposes 'change' events which send deltas to the listener, which could probably be used to update your indexes. Whether that'll be slower or faster than iterating a full count every time I don't know.

Harikrishnan G.

unread,
May 24, 2012, 7:37:05 AM5/24/12
to ace-d...@googlegroups.com
(I know it's been a long time this discussion has started! I have been looking for a solution to this problem recently and couldn't find a straight forward solution yet! So I implemented something that works (pretty well according to my bench marks). I am posting this in hope that someone will find it useful someday!)

I agree to many of the solutions here. And please have a look at this implementation.

// session is an instance of Ace editSession
// Usage
// var lengthArray = calculateCumulativeLength(editor.getSession());
// Need to call this only if the document is updated after the last call.
function calculateCumulativeLength(session){
        var cumLength = [];    // honestly! It took me 25 hours to notice this! *actually stands for CUMulativeLENGTH.
        var cnt = session.getLength();
        var cuml = 0, nlLength = session.getDocument().getNewLineCharacter().length;
        cumLength.push(cuml);
        var text = sessiongetLines(0, cnt);
        for(var i=0; i< cnt; i++){
            cuml += text[i].length + nlLength;
            cumLength.push(cuml);
        }
       
        return cumLength;
    }
   
    // Fast binary search implementation
    // Pass the cumulative length array here.
    // Usage
    // var row = findRow(lengthArray, 0, lengthArray.length, 2512);
    // tries to find 2512th character lies in which row.
    function findRow(cumLength, rows, rowe, pos){
    if(rows > rowe)
        return null;

    if(rows + 1 === rowe)
          return rows;
   
        var mid = Math.floor((rows + rowe) / 2);
       
        if(pos < cumLength[mid])
            return findRow(cumLength, rows, mid, pos);
       
        else if(pos > cumLength[mid])
            return findRow(cumLength, mid, rowe, pos);
       
        else return mid;
   
    }

// Finding the relevant column then is a simple matter of charPosition - arrayLength[row];

The code runs really fast on Firefox (13) and Chrome (19 I supose) and Safari 5.x.

I tested the code on jQuery source which has about 9300 lines of code as of version 1.7.1
Finding an arbitrary position takes less than 2ms.

Cheers !
Hari
Reply all
Reply to author
Forward
0 new messages