James Allen 12 лет назад
Родитель
Сommit
d2ac9a1e9f

+ 1 - 1
services/web/app/coffee/Features/User/UserController.coffee

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

+ 10 - 1
services/web/app/views/project/editor.jade

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

+ 80 - 0
services/web/public/coffee/app/ide/editor/auto-complete/AutoCompleteManager.coffee

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

+ 100 - 0
services/web/public/coffee/app/ide/editor/auto-complete/Snippets.coffee

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

+ 126 - 0
services/web/public/coffee/app/ide/editor/auto-complete/SuggestionManager.coffee

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

+ 16 - 2
services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee

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

+ 5 - 0
services/web/public/coffee/app/ide/settings/SettingsManager.coffee

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