Search code examples
ace-editor

Is there any function in ace which returns me matching bracket position?


I have tried jumpToMatching() function, it takes me to the position of the matching bracket, but I need a function which returns me the position of the matching bracket. Is there any method which can help me?


Solution

  • looking through the default_commands.js there is no such function.

    there's jumptomatching, selecttomatching and expandToMatching.

    all three of them use the function editor.jumpToMatching() with different arguments.

    digging through the code I found that function in editor.js / ace.js

    with some tweaking I was able to create the following working function:

    this.returnMatchingPos = function() {
            var cursor = this.getCursorPosition();
            var iterator = new TokenIterator(this.session, cursor.row, cursor.column);
            var prevToken = iterator.getCurrentToken();
            var token = prevToken || iterator.stepForward();
    
            if (!token) return;
    
            //get next closing tag or bracket
            var matchType;
            var found = false;
            var depth = {};
            var i = cursor.column - token.start;
            var bracketType;
            var brackets = {
                ")": "(",
                "(": "(",
                "]": "[",
                "[": "[",
                "{": "{",
                "}": "{"
            };
            
            do {
                if (token.value.match(/[{}()\[\]]/g)) {
                    for (; i < token.value.length && !found; i++) {
                        if (!brackets[token.value[i]]) {
                            continue;
                        }
    
                        bracketType = brackets[token.value[i]] + '.' + token.type.replace("rparen", "lparen");
    
                        if (isNaN(depth[bracketType])) {
                            depth[bracketType] = 0;
                        }
    
                        switch (token.value[i]) {
                            case '(':
                            case '[':
                            case '{':
                                depth[bracketType]++;
                                break;
                            case ')':
                            case ']':
                            case '}':
                                depth[bracketType]--;
    
                                if (depth[bracketType] === -1) {
                                    matchType = 'bracket';
                                    found = true;
                                }
                            break;
                        }
                    }
                }
                else if (token && token.type.indexOf('tag-name') !== -1) {
                    if (isNaN(depth[token.value])) {
                        depth[token.value] = 0;
                    }
                    
                    if (prevToken.value === '<') {
                        depth[token.value]++;
                    }
                    else if (prevToken.value === '</') {
                        depth[token.value]--;
                    }
                    
                    if (depth[token.value] === -1) {
                        matchType = 'tag';
                        found = true;
                    }
                }
    
                if (!found) {
                    prevToken = token;
                    token = iterator.stepForward();
                    i = 0;
                }
            } while (token && !found);
    
            //no match found
            if (!matchType)
                return;
    
            var range, pos;
            if (matchType === 'bracket') {
                range = this.session.getBracketRange(cursor);
                if (!range) {
                    range = new Range(
                        iterator.getCurrentTokenRow(),
                        iterator.getCurrentTokenColumn() + i - 1,
                        iterator.getCurrentTokenRow(),
                        iterator.getCurrentTokenColumn() + i - 1
                    );
                    pos = range.start;
                }
            }
            else if (matchType === 'tag') {
                if (token && token.type.indexOf('tag-name') !== -1) 
                    var tag = token.value;
                else
                    return;
    
                range = new Range(
                    iterator.getCurrentTokenRow(),
                    iterator.getCurrentTokenColumn() - 2,
                    iterator.getCurrentTokenRow(),
                    iterator.getCurrentTokenColumn() - 2
                );
    
                //find matching tag
                if (range.compare(cursor.row, cursor.column) === 0) {
                    found = false;
                    do {
                        token = prevToken;
                        prevToken = iterator.stepBackward();
                        
                        if (prevToken) {
                            if (prevToken.type.indexOf('tag-close') !== -1) {
                                range.setEnd(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + 1);
                            }
    
                            if (token.value === tag && token.type.indexOf('tag-name') !== -1) {
                                if (prevToken.value === '<') {
                                    depth[tag]++;
                                }
                                else if (prevToken.value === '</') {
                                    depth[tag]--;
                                }
                                
                                if (depth[tag] === 0)
                                    found = true;
                            }
                        }
                    } while (prevToken && !found);
                }
    
                //we found it
                if (token && token.type.indexOf('tag-name')) {
                    pos = range.start;
                    if (pos.row == cursor.row && Math.abs(pos.column - cursor.column) < 2)
                        pos = range.end;
                }
            }
    
            pos = range && range.cursor || pos;
            if (pos) {
                return pos;
            }
        };
    

    open/edit ace.js and find this line: this.jumpToMatching = function(select, expand) {now paste the function I provided above just before the line you just found. save changes and close.

    after that you can call it using editor.returnMatchingPos() and it should return an object with row and column just like editor.getCursorPosition()

    Why not include it in my own code?

    the existing functions use TokenIterator which is defined inside the file. using the function outside of editor object is possible if you can find a way to var TokenIterator = require("./token_iterator").TokenIterator; in your own code, and then change all instances of this into editor in the function I provided.