Merge branch 'master' of github.com:sharelatex/web-sharelatex

This commit is contained in:
Henry Oswald 2014-03-05 15:05:43 +00:00
commit 1842a65b83
7 changed files with 166 additions and 255 deletions

View file

@ -1,40 +1,50 @@
define [ define [
"auto-complete/MenuView"
"auto-complete/SuggestionManager" "auto-complete/SuggestionManager"
"auto-complete/Snippets"
"ace/autocomplete/util"
"ace/range" "ace/range"
], (MenuView, SuggestionManager) -> "ace/ext/language_tools"
], (SuggestionManager, Snippets, Util) ->
Range = require("ace/range").Range 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 class AutoCompleteManager
constructor: (@ide) -> constructor: (@ide) ->
@aceEditor = @ide.editor.aceEditor @aceEditor = @ide.editor.aceEditor
@menu = new MenuView() @aceEditor.setOptions({
@menu.render( enableBasicAutocompletion: true,
@getAceContentEl().css("font-family"), enableSnippets: true
@getAceContentEl().css("font-size") })
)
@ide.mainAreaManager.getAreaElement("editor").append(@menu.$el) SnippetCompleter =
@menu.on "click", (e, suggestion) => @insertSuggestion(suggestion) getCompletions: (editor, session, pos, prefix, callback) ->
@menuVisible = false callback null, Snippets
@suggestionManager = new SuggestionManager() @suggestionManager = new SuggestionManager()
@aceEditor.completers = [@suggestionManager, SnippetCompleter]
@bindToEditorEvents() @bindToEditorEvents()
@bindToAceInputEvents()
bindToEditorEvents: () -> bindToEditorEvents: () ->
@ide.editor.on "change:doc", (@aceSession) => @ide.editor.on "change:doc", (@aceSession) =>
@refreshSuggestionList()
@aceSession.on "change", (change) => @onChange(change) @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) -> onChange: (change) ->
@scheduleSuggestionListRefresh()
cursorPosition = @aceEditor.getCursorPosition() cursorPosition = @aceEditor.getCursorPosition()
end = change.data.range.end end = change.data.range.end
# Check that this change was made by us, not a collaborator # Check that this change was made by us, not a collaborator
@ -43,110 +53,9 @@ define [
if change.data.action == "insertText" if change.data.action == "insertText"
range = new Range(end.row, 0, end.row, end.column) range = new Range(end.row, 0, end.row, end.column)
lineUpToCursor = @aceSession.getTextRange(range) lineUpToCursor = @aceSession.getTextRange(range)
commandFragment = @getLastCommandFragment(lineUpToCursor) 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")
if commandFragment? and commandFragment.length > 2
setTimeout () =>
@aceEditor.execCommand("startAutocomplete")
, 0

View file

@ -1,57 +0,0 @@
define [
"libs/backbone"
"libs/mustache"
], () ->
MenuView = Backbone.View.extend
tagName: "ul"
className: "auto-complete-menu"
templates:
suggestion: $("#autoCompleteSuggestionTemplate").html()
render: (fontFamily, fontSize) ->
@$el.css
position: "absolute"
"font-family": fontFamily
"font-size": fontSize
return @$el
setSuggestions: (suggestions) ->
@$el.children().off()
@$el.empty()
@suggestions = []
for suggestion in suggestions
do (suggestion) =>
el = $(Mustache.to_html(@templates.suggestion, suggestion))
@$el.append(el)
el.on "click", (e) => @trigger("click", e, suggestion)
@suggestions.push suggestion: suggestion, el: el
@selectSuggestionAtIndex 0
selectSuggestionAtIndex: (index) ->
if index >= 0 and index < @suggestions.length
@$("li").removeClass "selected"
@suggestions[index].el.addClass "selected"
@selectedIndex = index
moveSelectionDown: () ->
if @selectedIndex? and @selectedIndex < @suggestions.length - 1
@selectSuggestionAtIndex @selectedIndex + 1
moveSelectionUp: () ->
if @selectedIndex? and @selectedIndex > 0
@selectSuggestionAtIndex @selectedIndex - 1
getSelectedSuggestion: () ->
if @selectedIndex? and @suggestions[@selectedIndex]?
@suggestions[@selectedIndex].suggestion
position: (pos) ->
@$el.css
top: pos.top
left: pos.left
show: () -> @$el.show()
hide: () -> @$el.hide()

View file

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

View file

@ -1,6 +1,4 @@
define [ define [], () ->
"auto-complete/commands"
], (commands) ->
class Parser class Parser
constructor: (@doc) -> constructor: (@doc) ->
@ -66,8 +64,32 @@ define [
return false return false
class SuggestionManager class SuggestionManager
constructor: () -> getCompletions: (editor, session, pos, prefix, callback) ->
@commands = [] 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) -> loadCommandsFromDoc: (doc) ->
parser = new Parser(doc) parser = new Parser(doc)

View file

@ -1,64 +0,0 @@
define () -> [
# [<command>, <square brackets args>, <curly bracket args>]
# E.g. ["includegraphics", 1 ,1] => \includegraphics[]{}
# Common
["emph", 0, 1]
# Greek letters
["alpha", 0, 0]
["beta", 0, 0]
["gamma", 0, 0]
["delta", 0, 0]
["eta", 0, 0]
["theta", 0, 0]
["iota", 0, 0]
["kappa", 0, 0]
["lambda", 0, 0]
["phi", 0, 0]
["psi", 0, 0]
["mu", 0, 0]
["nu", 0, 0]
["chi", 0, 0]
["xsi", 0, 0]
["upsilon", 0, 0]
["Lambda", 0, 0]
["Omega", 0, 0]
["Gamma", 0, 0]
["Delta", 0, 0]
# Maths
["infty", 0, 0]
["frac", 0, 2]
["int", 0, 0]
["sum", 0, 0]
["sin", 0, 0]
["cos", 0, 0]
# LaTeX commands
["begin", 0, 1]
["end", 0, 1]
["includegraphics", 0, 1]
["includegraphics", 1, 1]
["section", 0, 1]
["chapter", 0, 1]
["subsection", 0, 1]
["subsubsection", 0, 1]
["part", 0, 1]
["author", 0, 1]
["title", 0, 1]
["documentclass", 0, 1]
["documentclass", 1, 1]
["usepackage", 0, 1]
["usepackage", 1, 1]
# Font commands
["textit", 0, 1]
["textrm", 0, 1]
["textsf", 0, 1]
["texttt", 0, 1]
["newcommand", 0, 2]
["renewcommand", 0, 2]
["newenvironment", 0, 3]
]

View file

@ -125,12 +125,10 @@ define [
mode = window.userSettings.mode mode = window.userSettings.mode
theme = window.userSettings.theme theme = window.userSettings.theme
fontSize = window.userSettings.fontSize
chosenKeyBindings = keybindings[mode] chosenKeyBindings = keybindings[mode]
aceEditor.setKeyboardHandler(chosenKeyBindings) aceEditor.setKeyboardHandler(chosenKeyBindings)
aceEditor.setTheme("ace/theme/#{window.userSettings.theme}") aceEditor.setTheme("ace/theme/#{window.userSettings.theme}")
document.getElementById('editor').style.fontSize = fontSize+'px'
aceEditor.setShowPrintMargin(false) aceEditor.setShowPrintMargin(false)
# Prevert Ctrl|Cmd-S from triggering save dialog # Prevert Ctrl|Cmd-S from triggering save dialog

View file

@ -17,6 +17,8 @@ define [
new DropboxSettingsManager @ide new DropboxSettingsManager @ide
@setFontSize()
if @ide? if @ide?
@ide.on "afterJoinProject", (project) => @ide.on "afterJoinProject", (project) =>
@project = project @project = project
@ -102,6 +104,15 @@ define [
$confirm.off 'click' $confirm.off 'click'
$modal.find('.cancel').click (e)-> $modal.find('.cancel').click (e)->
$modal.modal('hide') $modal.modal('hide')
setFontSize: () ->
@fontSizeCss = $("<style/>")
@fontSizeCss.text """
.ace_editor, .ace_content {
font-size: #{window.userSettings.fontSize}px;
}
"""
$(document.body).append(@fontSizeCss)
bindToProjectName: () -> bindToProjectName: () ->
@project.on "change:name", (project, newName) -> @project.on "change:name", (project, newName) ->