Add in auto complete

This commit is contained in:
James Allen 2014-06-24 21:09:20 +01:00
parent f154f17704
commit d2ac9a1e9f
7 changed files with 338 additions and 4 deletions

View file

@ -48,7 +48,7 @@ module.exports =
if req.body.fontSize?
user.ace.fontSize = req.body.fontSize
if req.body.autoComplete?
user.ace.autoComplete = (req.body.autoComplete == "true")
user.ace.autoComplete = req.body.autoComplete
if req.body.spellCheckLanguage?
user.ace.spellCheckLanguage = req.body.spellCheckLanguage
if req.body.pdfViewer?

View file

@ -38,7 +38,15 @@ block content
ng-cloak
)
h4 Settings
form
form
.form-controls
label(for="autoComplete") Auto-Complete
input.form-control(
type="checkbox"
name="autoComplete"
ng-model="settings.autoComplete"
)
.form-controls
label(for="theme") Theme
select.form-control(
@ -108,6 +116,7 @@ block content
theme="settings.theme",
keybindings="settings.mode",
font-size="settings.fontSize",
auto-complete="settings.autoComplete",
show-print-margin="false",
sharejs-doc="editor.sharejs_doc",
last-updated="editor.last_updated"

View file

@ -0,0 +1,80 @@
define [
"ide/editor/auto-complete/SuggestionManager"
"ide/editor/auto-complete/Snippets"
"ace/autocomplete/util"
"ace/autocomplete"
"ace/range"
"ace/ext/language_tools"
], (SuggestionManager, Snippets, Util, AutoComplete) ->
Range = require("ace/range").Range
Autocomplete = AutoComplete.Autocomplete
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: (@editor) ->
@suggestionManager = new SuggestionManager()
insertMatch = Autocomplete::insertMatch
editor = @editor
Autocomplete::insertMatch = (data) ->
pos = editor.getCursorPosition()
range = new Range(pos.row, pos.column, pos.row, pos.column + 1)
nextChar = editor.session.getTextRange(range)
# If we are in \begin{it|}, then we need to remove the trailing }
# since it will be adding in with the autocomplete of \begin{item}...
if this.completions.filterText.match(/^\\begin\{/) and nextChar == "}"
editor.session.remove(range)
insertMatch.call editor.completer, data
enable: () ->
@editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true
})
SnippetCompleter =
getCompletions: (editor, session, pos, prefix, callback) ->
callback null, Snippets
@editor.completers = [@suggestionManager, SnippetCompleter]
disable: () ->
@editor.setOptions({
enableBasicAutocompletion: false,
enableSnippets: false
})
bindToSession: (@aceSession) ->
@aceSession.on "change", (change) => @onChange(change)
onChange: (change) ->
cursorPosition = @editor.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? and commandFragment.length > 2
setTimeout () =>
@editor.execCommand("startAutocomplete")
, 0

View file

@ -0,0 +1,100 @@
define () ->
environments = [
"abstract",
"align", "align*",
"equation", "equation*",
"gather", "gather*",
"multline", "multline*",
"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{itemize}..."
snippet: """
\\begin{itemize}
\\item $1
\\end{itemize}
"""
meta: "env"
}, {
caption: "\\begin{frame}..."
snippet: """
\\begin{frame}{${1:Frame Title}}
$2
\\end{frame}
"""
meta: "env"
}]
return snippets

View file

@ -0,0 +1,126 @@
define [], () ->
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++
unless caption == prefix
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
}

View file

@ -1,12 +1,13 @@
define [
"base"
"ide/editor/undo/UndoManager"
"ace/ace"
"ide/editor/undo/UndoManager"
"ide/editor/auto-complete/AutoCompleteManager"
"ace/keyboard/vim"
"ace/keyboard/emacs"
"ace/mode/latex"
"ace/edit_session"
], (App, UndoManager, Ace) ->
], (App, Ace, UndoManager, AutoCompleteManager) ->
LatexMode = require("ace/mode/latex").Mode
EditSession = require('ace/edit_session').EditSession
@ -17,6 +18,7 @@ define [
showPrintMargin: "="
keybindings: "="
fontSize: "="
autoComplete: "="
sharejsDoc: "="
lastUpdated: "="
}
@ -25,6 +27,8 @@ define [
scope.undo =
show_remote_warning: false
autoCompleteManager = new AutoCompleteManager(editor)
# Prevert Ctrl|Cmd-S from triggering save dialog
editor.commands.addCommand
name: "save",
@ -59,6 +63,14 @@ define [
if sharejs_doc?
attachToAce(sharejs_doc)
scope.$watch "autoComplete", (autocomplete) ->
if autocomplete
console.log "Enabling auto complete"
autoCompleteManager.enable()
else
console.log "Disabling auto complete"
autoCompleteManager.disable()
attachToAce = (sharejs_doc) ->
lines = sharejs_doc.getSnapshot().split("\n")
editor.setSession(new EditSession(lines))
@ -66,6 +78,8 @@ define [
session.setUseWrapMode(true)
session.setMode(new LatexMode())
autoCompleteManager.bindToSession(session)
doc = session.getDocument()
doc.on "change", () ->
scope.$apply () ->

View file

@ -18,6 +18,11 @@ define [], () ->
if mode != oldMode
@saveSettings({mode: mode})
@$scope.$watch "settings.autoComplete", (autoComplete, oldAutoComplete) =>
console.log "autoComplete", autoComplete
if autoComplete != oldAutoComplete
@saveSettings({autoComplete: autoComplete})
saveSettings: (data) ->
data._csrf = window.csrfToken
@ide.$http.post "/user/settings", data