overleaf/services/web/public/coffee/auto-complete/AutoCompleteManager.coffee
2014-02-12 10:23:40 +00:00

152 lines
4.4 KiB
CoffeeScript

define [
"auto-complete/MenuView"
"auto-complete/SuggestionManager"
"ace/range"
], (MenuView, SuggestionManager) ->
Range = require("ace/range").Range
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()
@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
# (Cursor is still one place behind)
if end.row == cursorPosition.row and end.column == cursorPosition.column + 1
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")