overleaf/services/web/public/coffee/auto-complete/SuggestionManager.coffee
2014-03-05 11:06:12 +00:00

128 lines
3 KiB
CoffeeScript

define [
"auto-complete/commands"
], (commands) ->
class Parser
constructor: (@doc) ->
parse: () ->
commands = []
seen = {}
while command = @nextCommand()
docState = @doc
optionalArgs = 0
while @consumeArgument("[", "]")
optionalArgs++
args = 0
while @consumeArgument("{", "}")
args++
commandHash = "#{command}\\#{optionalArgs}\\#{args}"
if !seen[commandHash]?
seen[commandHash] = true
commands.push [command, optionalArgs, args]
# Reset to before argument to handle nested commands
@doc = docState
return commands
# Ignore single letter commands since auto complete is moot then.
commandRegex: /\\([a-zA-Z][a-zA-Z]+)/
nextCommand: () ->
i = @doc.search(@commandRegex)
if i == -1
return false
else
match = @doc.match(@commandRegex)[1]
@doc = @doc.substr(i + match.length + 1)
return match
consumeWhitespace: () ->
match = @doc.match(/^[ \t\n]*/m)[0]
@doc = @doc.substr(match.length)
consumeArgument: (openingBracket, closingBracket) ->
@consumeWhitespace()
if @doc[0] == openingBracket
i = 1
bracketParity = 1
while bracketParity > 0 and i < @doc.length
if @doc[i] == openingBracket
bracketParity++
else if @doc[i] == closingBracket
bracketParity--
i++
if bracketParity == 0
@doc = @doc.substr(i)
return true
else
return false
else
return false
class SuggestionManager
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: "cmd"
}
callback null, completions
loadCommandsFromDoc: (doc) ->
parser = new Parser(doc)
@commands = parser.parse()
getSuggestions: (commandFragment) ->
matchingCommands = _.filter @commands, (command) ->
command[0].slice(0, commandFragment.length) == commandFragment
return _.map matchingCommands, (command) ->
base = "\\" + commandFragment
args = ""
_.times command[1], () -> args = args + "[]"
_.times command[2], () -> args = args + "{}"
completionBase = command[0].slice(commandFragment.length)
squareArgsNo = command[1]
curlyArgsNo = command[2]
totalArgs = squareArgsNo + curlyArgsNo
if totalArgs == 0
completionBeforeCursor = completionBase
completionAfterCurspr = ""
else
completionBeforeCursor = completionBase + args[0]
completionAfterCursor = args.slice(1)
return {
base: base,
completion: completionBase + args,
completionBeforeCursor: completionBeforeCursor
completionAfterCursor: completionAfterCursor
}