diff --git a/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee index 9bbe54e02b..bbed4b87f9 100644 --- a/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee +++ b/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee @@ -1,40 +1,50 @@ define [ - "auto-complete/MenuView" "auto-complete/SuggestionManager" + "auto-complete/Snippets" + "ace/autocomplete/util" "ace/range" -], (MenuView, SuggestionManager) -> + "ace/ext/language_tools" +], (SuggestionManager, Snippets, 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 + @aceEditor.setOptions({ + enableBasicAutocompletion: true, + enableSnippets: true + }) + + SnippetCompleter = + getCompletions: (editor, session, pos, prefix, callback) -> + callback null, Snippets @suggestionManager = new SuggestionManager() + + @aceEditor.completers = [@suggestionManager, SnippetCompleter] + @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 +53,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? and commandFragment.length > 2 + 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/Snippets.coffee b/services/web/public/coffee/auto-complete/Snippets.coffee new file mode 100644 index 0000000000..0cfe74f9d3 --- /dev/null +++ b/services/web/public/coffee/auto-complete/Snippets.coffee @@ -0,0 +1,92 @@ +define () -> + environments = [ + "abstract", + "align", "align*", + "equation", "equation*", + "gather", "gather*", + "mutliline", "multiline*", + "split", + "verbatim" + ] + + snippets = for env in environments + { + caption: "\\begin{#{env}}..." + snippet: """ + \\begin{#{env}} + $1 + \\end{#{env}} + """ + meta: "env" + } + + snippets = snippets.concat [{ + caption: "\\begin{array}..." + snippet: """ + \\begin{array}{${1:cc}} + $2 & $3 \\\\\\\\ + $4 & $5 + \\end{array}" + """ + meta: "env" + }, { + caption: "\\begin{figure}..." + snippet: """ + \\begin{figure} + \\centering + \\includegraphics{$1} + \\caption{${2:Caption}} + \\label{${3:fig:my_label}} + \\end{figure} + """ + meta: "env" + }, { + caption: "\\begin{tabular}..." + snippet: """ + \\begin{tabular}{${1:c|c}} + $2 & $3 \\\\\\\\ + $4 & $5 + \\end{tabular} + """ + meta: "env" + }, { + caption: "\\begin{table}..." + snippet: """ + \\begin{table}[$1] + \\centering + \\begin{tabular}{${2:c|c}} + $3 & $4 \\\\\\\\ + $5 & $6 + \\end{tabular} + \\caption{${7:Caption}} + \\label{${8:tab:my_label}} + \\end{table} + """ + meta: "env" + }, { + caption: "\\begin{list}..." + snippet: """ + \\begin{list} + \\item $1 + \\end{list} + """ + meta: "env" + }, { + caption: "\\begin{enumerate}..." + snippet: """ + \\begin{enumerate} + \\item $1 + \\end{enumerate} + """ + meta: "env" + }, { + caption: "\\begin{frame}..." + snippet: """ + \\begin{frame}{${1:Frame Title}} + $2 + \\end{frame} + """ + meta: "env" + }] + + return snippets \ No newline at end of file diff --git a/services/web/public/coffee/auto-complete/SuggestionManager.coffee b/services/web/public/coffee/auto-complete/SuggestionManager.coffee index 471720ec9e..559a2c5981 100644 --- a/services/web/public/coffee/auto-complete/SuggestionManager.coffee +++ b/services/web/public/coffee/auto-complete/SuggestionManager.coffee @@ -1,6 +1,4 @@ -define [ - "auto-complete/commands" -], (commands) -> +define [], () -> class Parser constructor: (@doc) -> @@ -66,8 +64,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++ + unless caption == prefix + completions.push { + caption: caption + snippet: snippet + meta: "cmd" + } + + 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] -] diff --git a/services/web/public/coffee/editor/Editor.coffee b/services/web/public/coffee/editor/Editor.coffee index 15f5307995..7c2da966c9 100644 --- a/services/web/public/coffee/editor/Editor.coffee +++ b/services/web/public/coffee/editor/Editor.coffee @@ -125,12 +125,10 @@ define [ mode = window.userSettings.mode theme = window.userSettings.theme - fontSize = window.userSettings.fontSize chosenKeyBindings = keybindings[mode] aceEditor.setKeyboardHandler(chosenKeyBindings) aceEditor.setTheme("ace/theme/#{window.userSettings.theme}") - document.getElementById('editor').style.fontSize = fontSize+'px' aceEditor.setShowPrintMargin(false) # Prevert Ctrl|Cmd-S from triggering save dialog diff --git a/services/web/public/coffee/settings/SettingsManager.coffee b/services/web/public/coffee/settings/SettingsManager.coffee index fbba05c864..5474263a38 100644 --- a/services/web/public/coffee/settings/SettingsManager.coffee +++ b/services/web/public/coffee/settings/SettingsManager.coffee @@ -17,6 +17,8 @@ define [ new DropboxSettingsManager @ide + @setFontSize() + if @ide? @ide.on "afterJoinProject", (project) => @project = project @@ -102,6 +104,15 @@ define [ $confirm.off 'click' $modal.find('.cancel').click (e)-> $modal.modal('hide') + + setFontSize: () -> + @fontSizeCss = $("