diff --git a/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee index 9bbe54e02b..a99f625559 100644 --- a/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee +++ b/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee @@ -1,40 +1,47 @@ define [ - "auto-complete/MenuView" "auto-complete/SuggestionManager" + "ace/autocomplete/util" "ace/range" -], (MenuView, SuggestionManager) -> + "ace/ext/language_tools" +], (SuggestionManager, Util) -> Range = require("ace/range").Range + Util.retrievePrecedingIdentifier = (text, pos, regex) -> + currentLineOffset = 0 + for i in [(pos-1)..0] + if text[i] == "\n" + currentLineOffset = i + 1 + break + currentLine = text.slice(currentLineOffset, pos) + fragment = getLastCommandFragment(currentLine) or "" + return fragment + + getLastCommandFragment = (lineUpToCursor) -> + if m = lineUpToCursor.match(/(\\[^\\ ]+)$/) + return m[1] + else + return null + class AutoCompleteManager constructor: (@ide) -> - @aceEditor = @ide.editor.aceEditor - @menu = new MenuView() - @menu.render( - @getAceContentEl().css("font-family"), - @getAceContentEl().css("font-size") - ) - @ide.mainAreaManager.getAreaElement("editor").append(@menu.$el) - @menu.on "click", (e, suggestion) => @insertSuggestion(suggestion) - @menuVisible = false @suggestionManager = new SuggestionManager() + + @aceEditor = @ide.editor.aceEditor + @aceEditor.setOptions({ + enableBasicAutocompletion: true, + enableSnippets: true + }) + snippetManager = @aceEditor.completers[0] + console.log snippetManager + @aceEditor.completers = [snippetManager, @suggestionManager] + @bindToEditorEvents() - @bindToAceInputEvents() bindToEditorEvents: () -> @ide.editor.on "change:doc", (@aceSession) => - @refreshSuggestionList() @aceSession.on "change", (change) => @onChange(change) - @ide.editor.on "scroll", () => - @hideMenu() - - bindToAceInputEvents: () -> - @oldOnCommandKey = @aceEditor.keyBinding.onCommandKey - @aceEditor.keyBinding.onCommandKey = () => @onKeyPress.apply(@, arguments) - $(@aceEditor.renderer.getContainerElement()).on "click", (e) => @onClick(e) onChange: (change) -> - @scheduleSuggestionListRefresh() - cursorPosition = @aceEditor.getCursorPosition() end = change.data.range.end # Check that this change was made by us, not a collaborator @@ -43,110 +50,9 @@ define [ if change.data.action == "insertText" range = new Range(end.row, 0, end.row, end.column) lineUpToCursor = @aceSession.getTextRange(range) - commandFragment = @getLastCommandFragment(lineUpToCursor) - - if commandFragment - suggestions = @suggestionManager.getSuggestions(commandFragment) - if suggestions.length > 0 - @positionMenu(commandFragment.length) - @menu.setSuggestions suggestions - @showMenu() - else - @hideMenu() - else - @hideMenu() - else - @hideMenu() - - onKeyPress: (e) -> - keyCode = e.keyCode - - args = arguments - delegate = () => - @oldOnCommandKey.apply(@aceEditor.keyBinding, args) - - if @menuVisible - switch keyCode - when @keyCodes.UP - @menu.moveSelectionUp() - when @keyCodes.DOWN - @menu.moveSelectionDown() - when @keyCodes.ENTER, @keyCodes.TAB - @insertSuggestion(@menu.getSelectedSuggestion()) - e.preventDefault() - @hideMenu() - when @keyCodes.ESCAPE - @hideMenu() - else - delegate() - else - delegate() - - positionMenu: (characterOffset) -> - characterWidth = @getAceRenderer().characterWidth - lineHeight = @getAceRenderer().lineHeight - - pos = @getCursorOffset() - pos.top = pos.top + lineHeight - styleOffset = 10 # CSS borders and margins - pos.left = pos.left - styleOffset - characterOffset * characterWidth - - # We need to position the menu with coordinates relative to the - # editor area. - editorAreaOffset = @ide.mainAreaManager.getAreaElement("editor").offset() - aceOffset = @getAceContentEl().offset() - @menu.position - top: aceOffset.top - editorAreaOffset.top + pos.top - left: aceOffset.left - editorAreaOffset.left + pos.left - - - insertSuggestion: (suggestion) -> - if suggestion? - oldCursorPosition = @aceEditor.getCursorPosition() - @aceEditor.insert(suggestion.completion) - @aceEditor.moveCursorTo( - oldCursorPosition.row, - oldCursorPosition.column + suggestion.completionBeforeCursor.length - ) - @hideMenu() - @aceEditor.focus() - - scheduleSuggestionListRefresh: () -> - clearTimeout(@updateTimeoutId) if @updateTimeoutId? - @updateTimeoutId = setTimeout((() => - @refreshSuggestionList() - delete @updateTimeoutId - ), 5000) - - refreshSuggestionList: () -> - @suggestionManager.loadCommandsFromDoc(@aceSession.doc.getAllLines().join("\n")) - - onClick: () -> - @hideMenu() - - getLastCommandFragment: (line) -> - if m = line.match(/\\([^\\ ]+)$/) - m[1] - else - null - - showMenu: () -> - @menu.show() - @menuVisible = true - - hideMenu: () -> - @menu.hide() - @menuVisible = false - - keyCodes: "UP": 38, "DOWN": 40, "ENTER": 13, "TAB": 9, "ESCAPE": 27 - - getCursorOffset: () -> - # This is fragile and relies on the internal Ace API not changing. - # See $moveTextAreaToCursor in - # https://github.com/ajaxorg/ace/blob/master/lib/ace/virtual_renderer.js - @aceEditor.renderer.$cursorLayer.$pixelPos - - getAceRenderer: () -> @aceEditor.renderer - - getAceContentEl: () -> $(@aceEditor.renderer.getContainerElement()).find(".ace_content") + commandFragment = getLastCommandFragment(lineUpToCursor) + if commandFragment? + setTimeout () => + @aceEditor.execCommand("startAutocomplete") + , 0 diff --git a/services/web/public/coffee/auto-complete/MenuView.coffee b/services/web/public/coffee/auto-complete/MenuView.coffee deleted file mode 100644 index 0acbafbfca..0000000000 --- a/services/web/public/coffee/auto-complete/MenuView.coffee +++ /dev/null @@ -1,57 +0,0 @@ -define [ - "libs/backbone" - "libs/mustache" -], () -> - MenuView = Backbone.View.extend - tagName: "ul" - className: "auto-complete-menu" - - templates: - suggestion: $("#autoCompleteSuggestionTemplate").html() - - render: (fontFamily, fontSize) -> - @$el.css - position: "absolute" - "font-family": fontFamily - "font-size": fontSize - return @$el - - setSuggestions: (suggestions) -> - @$el.children().off() - @$el.empty() - @suggestions = [] - for suggestion in suggestions - do (suggestion) => - el = $(Mustache.to_html(@templates.suggestion, suggestion)) - @$el.append(el) - el.on "click", (e) => @trigger("click", e, suggestion) - @suggestions.push suggestion: suggestion, el: el - @selectSuggestionAtIndex 0 - - selectSuggestionAtIndex: (index) -> - if index >= 0 and index < @suggestions.length - @$("li").removeClass "selected" - @suggestions[index].el.addClass "selected" - @selectedIndex = index - - moveSelectionDown: () -> - if @selectedIndex? and @selectedIndex < @suggestions.length - 1 - @selectSuggestionAtIndex @selectedIndex + 1 - - moveSelectionUp: () -> - if @selectedIndex? and @selectedIndex > 0 - @selectSuggestionAtIndex @selectedIndex - 1 - - getSelectedSuggestion: () -> - if @selectedIndex? and @suggestions[@selectedIndex]? - @suggestions[@selectedIndex].suggestion - - position: (pos) -> - @$el.css - top: pos.top - left: pos.left - - show: () -> @$el.show() - hide: () -> @$el.hide() - - diff --git a/services/web/public/coffee/auto-complete/SuggestionManager.coffee b/services/web/public/coffee/auto-complete/SuggestionManager.coffee index 471720ec9e..d7fe0ea044 100644 --- a/services/web/public/coffee/auto-complete/SuggestionManager.coffee +++ b/services/web/public/coffee/auto-complete/SuggestionManager.coffee @@ -66,8 +66,32 @@ define [ return false class SuggestionManager - constructor: () -> - @commands = [] + getCompletions: (editor, session, pos, prefix, callback) -> + doc = session.getValue() + parser = new Parser(doc) + commands = parser.parse() + + completions = [] + for command in commands + caption = "\\#{command[0]}" + snippet = caption + i = 1 + _.times command[1], () -> + snippet += "[${#{i}}]" + caption += "[]" + i++ + _.times command[2], () -> + snippet += "{${#{i}}}" + caption += "{}" + i++ + + completions.push { + caption: caption + snippet: snippet + meta: "snippet" + } + + callback null, completions loadCommandsFromDoc: (doc) -> parser = new Parser(doc) diff --git a/services/web/public/coffee/auto-complete/commands.coffee b/services/web/public/coffee/auto-complete/commands.coffee deleted file mode 100644 index 1228c87916..0000000000 --- a/services/web/public/coffee/auto-complete/commands.coffee +++ /dev/null @@ -1,64 +0,0 @@ -define () -> [ - # [, , ] - # E.g. ["includegraphics", 1 ,1] => \includegraphics[]{} - - # Common - ["emph", 0, 1] - - # Greek letters - ["alpha", 0, 0] - ["beta", 0, 0] - ["gamma", 0, 0] - ["delta", 0, 0] - ["eta", 0, 0] - ["theta", 0, 0] - ["iota", 0, 0] - ["kappa", 0, 0] - ["lambda", 0, 0] - ["phi", 0, 0] - ["psi", 0, 0] - ["mu", 0, 0] - ["nu", 0, 0] - ["chi", 0, 0] - ["xsi", 0, 0] - ["upsilon", 0, 0] - ["Lambda", 0, 0] - ["Omega", 0, 0] - ["Gamma", 0, 0] - ["Delta", 0, 0] - - # Maths - ["infty", 0, 0] - ["frac", 0, 2] - ["int", 0, 0] - ["sum", 0, 0] - ["sin", 0, 0] - ["cos", 0, 0] - - # LaTeX commands - ["begin", 0, 1] - ["end", 0, 1] - ["includegraphics", 0, 1] - ["includegraphics", 1, 1] - ["section", 0, 1] - ["chapter", 0, 1] - ["subsection", 0, 1] - ["subsubsection", 0, 1] - ["part", 0, 1] - ["author", 0, 1] - ["title", 0, 1] - ["documentclass", 0, 1] - ["documentclass", 1, 1] - ["usepackage", 0, 1] - ["usepackage", 1, 1] - - # Font commands - ["textit", 0, 1] - ["textrm", 0, 1] - ["textsf", 0, 1] - ["texttt", 0, 1] - - ["newcommand", 0, 2] - ["renewcommand", 0, 2] - ["newenvironment", 0, 3] -]