|
|
@@ -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
|