| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- define [
- "editor/Document"
- "undo/UndoManager"
- "utils/Modal"
- "ace/ace"
- "ace/edit_session"
- "ace/mode/latex"
- "ace/range"
- "ace/keyboard/vim"
- "ace/keyboard/emacs"
- "libs/backbone"
- "libs/jquery.storage"
- ], (Document, UndoManager, Modal) ->
- AceEditor = require("ace/ace")
- EditSession = require('ace/edit_session').EditSession
- LatexMode = require("ace/mode/latex").Mode
- Range = require("ace/range").Range
- Vim = require("ace/keyboard/vim").handler
- Emacs = require("ace/keyboard/emacs").handler
- keybindings = ace: null, vim: Vim, emacs: Emacs
- class Editor
- templates:
- editorPanel: $("#editorPanelTemplate").html()
- loadingIndicator: $("#loadingIndicatorTemplate").html()
- viewOptions: {flatView:"flatView", splitView:"splitView"}
- currentViewState: undefined
- compilationErrors: {}
- constructor: (@ide) ->
- _.extend @, Backbone.Events
- @editorPanel = $(@templates.editorPanel)
- @ide.mainAreaManager.addArea
- identifier: "editor"
- element: @editorPanel
- @initializeEditor()
- @bindToFileTreeEvents()
- @enable()
- @loadingIndicator = $(@templates.loadingIndicator)
- @editorPanel.find("#editor").append(@loadingIndicator)
- @leftPanel = @editorPanel.find("#leftEditorPanel")
- @rightPanel = @editorPanel.find("#rightEditorPanel")
- @initSplitView()
- @switchToFlatView()
- bindToFileTreeEvents: () ->
- @ide.fileTreeManager.on "open:doc", (doc_id, options = {}) =>
- if @enabled
- @openDoc doc_id, options
- initSplitView: () ->
- @$splitter = splitter = @editorPanel.find("#editorSplitter")
- options =
- spacing_open: 8
- spacing_closed: 16
- east:
- size: "50%"
- maskIframesOnResize: true
- onresize: () =>
- @trigger("resize")
- if (state = $.localStorage("layout.editor"))?
- options.east = state.east
- splitter.layout options
-
- $(window).unload () =>
- @_saveSplitterState()
- _saveSplitterState: () ->
- if $("#editorSplitter").is(":visible")
- state = $("#editorSplitter").layout().readState()
- eastWidth = state.east.size + $("#editorSplitter .ui-layout-resizer-east").width()
- percentWidth = eastWidth / $("#editorSplitter").width() * 100 + "%"
- state.east.size = percentWidth
- $.localStorage("layout.editor", state)
- switchToSplitView: () ->
- if @currentViewState != @viewOptions.splitView
- @currentViewState = @viewOptions.splitView
- @leftPanel.prepend(
- @editorPanel.find("#editorWrapper")
- )
- splitter = @editorPanel.find("#editorSplitter")
- splitter.show()
- @ide.layoutManager.resizeAllSplitters()
- switchToFlatView: () ->
- if @currentViewState != @viewOptions.flatView
- @_saveSplitterState()
- @currentViewState = @viewOptions.flatView
- @editorPanel.prepend(
- @editorPanel.find("#editorWrapper")
- )
- @editorPanel.find("#editorSplitter").hide()
- @aceEditor.resize(true)
- showLoading: () ->
- delay = 600 # ms
- @loading = true
- setTimeout ( =>
- if @loading
- @loadingIndicator.show()
- ), delay
- hideLoading: () ->
- @loading = false
- @loadingIndicator.hide()
- showUndoConflictWarning: () ->
- $("#editor").prepend($("#undoConflictWarning"))
- $("#undoConflictWarning").show()
- hideBtn = $("#undoConflictWarning .js-hide")
- hideBtn.off("click")
- hideBtn.on "click", (e) ->
- e.preventDefault()
- $("#undoConflictWarning").hide()
- if @hideUndoWarningTimeout?
- clearTimeout @hideUndoWarningTimeout
- delete @hideUndoWarningTimeout
- @hideUndoWarningTimeout = setTimeout ->
- $("#undoConflictWarning").fadeOut("slow")
- , 4000
-
- initializeEditor: () ->
- @aceEditor = aceEditor = AceEditor.edit("editor")
- @on "resize", => @aceEditor.resize()
- @ide.layoutManager.on "resize", => @trigger "resize"
- mode = window.userSettings.mode
- theme = window.userSettings.theme
- chosenKeyBindings = keybindings[mode]
- aceEditor.setKeyboardHandler(chosenKeyBindings)
- aceEditor.setTheme("ace/theme/#{window.userSettings.theme}")
- aceEditor.setShowPrintMargin(false)
- # Prevert Ctrl|Cmd-S from triggering save dialog
- aceEditor.commands.addCommand
- name: "save",
- bindKey: win: "Ctrl-S", mac: "Command-S"
- exec: () ->
- readOnly: true
- aceEditor.commands.removeCommand "transposeletters"
- aceEditor.commands.removeCommand "showSettingsMenu"
- aceEditor.commands.removeCommand "foldall"
- aceEditor.showCommandLine = (args...) =>
- @trigger "showCommandLine", aceEditor, args...
- aceEditor.on "dblclick", (e) => @trigger "dblclick", e
- aceEditor.on "click", (e) => @trigger "click", e
- aceEditor.on "mousemove", (e) =>
- position = @aceEditor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
- e.position = position
- @trigger "mousemove", e
- setIdeToEditorPanel: (options = {}) ->
- @aceEditor.focus()
- @aceEditor.resize()
- loadDocument = =>
- @refreshCompilationErrors()
- @ide.layoutManager.resizeAllSplitters()
- if options.line?
- @gotoLine(options.line)
- else
- pos = $.localStorage("doc.position.#{@current_doc_id}") || {}
- @ignoreCursorPositionChanges = true
- @setCursorPosition(pos.cursorPosition or {row: 0, column: 0})
- @setScrollTop(pos.scrollTop or 0)
- @ignoreCursorPositionChanges = false
- @ide.mainAreaManager.change 'editor', =>
- setTimeout loadDocument, 0
- refreshCompilationErrors: () ->
- @getSession().setAnnotations @compilationErrors[@current_doc_id]
-
- openDoc: (doc_id, options = {}) ->
- if @current_doc_id == doc_id && !options.forceReopen
- @setIdeToEditorPanel(line: options.line)
- else
- @showLoading()
- @current_doc_id = doc_id
- @_openNewDocument doc_id, (error, document) =>
- if error?
- @ide.showGenericServerErrorMessage()
- return
- @setIdeToEditorPanel(line: options.line)
- @hideLoading()
- @trigger "change:doc", @getSession()
- _openNewDocument: (doc_id, callback = (error, document) ->) ->
- if @document?
- @document.leaveAndCleanUp()
- @_unbindFromDocumentEvents(@document)
- @_detachDocumentFromEditor(@document)
- @document = Document.getDocument @ide, doc_id
- @document.join (error) =>
- return callback(error) if error?
- @_bindToDocumentEvents(@document)
- @_bindDocumentToEditor(@document)
- callback null, @document
- _bindToDocumentEvents: (document) ->
- document.on "remoteop", () =>
- @undoManager.nextUpdateIsRemote = true
- document.on "error", (error) =>
- @openDoc(document.doc_id, forceReopen: true)
- Modal.createModal
- title: "Out of sync"
- message: "Sorry, this file has gone out of sync and we need to do a full refresh. Please let us know if this happens frequently."
- buttons:[
- text: "Ok"
- ]
- document.on "externalUpdate", () =>
- Modal.createModal
- title: "Document Updated Externally"
- message: "This document was just updated externally. Any recent changes you have made may have been overwritten. To see previous versions please look in the history."
- buttons:[
- text: "Ok"
- ]
- _unbindFromDocumentEvents: (document) ->
- document.off()
-
- _bindDocumentToEditor: (document) ->
- $("#editor").show()
- @_bindNewDocToAce(document)
- _detachDocumentFromEditor: (document) ->
- document.detachFromAce()
- _bindNewDocToAce: (document) ->
- @_createNewSessionFromDocLines(document.getSnapshot().split("\n"))
- @_setReadWritePermission()
- @_bindToAceEvents()
- # Updating the doc can cause the cursor to jump around
- # but we shouldn't record that
- @ignoreCursorPositionChanges = true
- document.attachToAce(@aceEditor)
- @ignoreCursorPositionChanges = false
- _bindToAceEvents: () ->
- aceDoc = @getDocument()
- aceDoc.on 'change', (change) => @onDocChange(change)
- session = @getSession()
- session.on "changeScrollTop", (e) => @onScrollTopChange(e)
- session.selection.on 'changeCursor', (e) => @onCursorChange(e)
- _createNewSessionFromDocLines: (docLines) ->
- @aceEditor.setSession(new EditSession(docLines))
- session = @getSession()
- session.setUseWrapMode(true)
- session.setMode(new LatexMode())
- @undoManager = new UndoManager(@)
- session.setUndoManager @undoManager
- _setReadWritePermission: () ->
- if !@ide.isAllowedToDoIt 'readAndWrite'
- @makeReadOnly()
- else
- @makeWritable()
- onDocChange: (change) ->
- @lastUpdated = new Date()
- @trigger "update:doc", change
-
- onScrollTopChange: (event) ->
- @trigger "scroll", event
- if !@ignoreCursorPositionChanges
- docPosition = $.localStorage("doc.position.#{@current_doc_id}") || {}
- docPosition.scrollTop = @getScrollTop()
- $.localStorage("doc.position.#{@current_doc_id}", docPosition)
-
- onCursorChange: (event) ->
- @trigger "cursor:change", event
- if !@ignoreCursorPositionChanges
- docPosition = $.localStorage("doc.position.#{@current_doc_id}") || {}
- docPosition.cursorPosition = @getCursorPosition()
- $.localStorage("doc.position.#{@current_doc_id}", docPosition)
- makeReadOnly: () ->
- @aceEditor.setReadOnly true
- makeWritable: () ->
- @aceEditor.setReadOnly false
- getSession: () -> @aceEditor.getSession()
- getDocument: () -> @getSession().getDocument()
- gotoLine: (line) ->
- @aceEditor.gotoLine(line)
- getCurrentLine: () ->
- @aceEditor.selection?.getCursor()?.row
- getCurrentColumn: () ->
- @aceEditor.selection?.getCursor()?.column
- getLines: (from, to) ->
- if from? and to?
- @getSession().doc.getLines(from, to)
- else
- @getSession().doc.getAllLines()
- addMarker: (position, klass, type, inFront) ->
- range = new Range(
- position.row, position.column,
- position.row, position.column + position.length
- )
- @getSession().addMarker range, klass, type, inFront
- removeMarker: (markerId) ->
- @getSession().removeMarker markerId
- getCursorPosition: () -> @aceEditor.getCursorPosition()
- setCursorPosition: (pos) -> @aceEditor.moveCursorToPosition(pos)
- getScrollTop: () -> @getSession().getScrollTop()
- setScrollTop: (pos) -> @getSession().setScrollTop(pos)
- replaceText: (range, text) ->
- @getSession().replace(new Range(
- range.start.row, range.start.column,
- range.end.row, range.end.column
- ), text)
- getContainerElement: () ->
- $(@aceEditor.renderer.getContainerElement())
- getCursorElement: () ->
- @getContainerElement().find(".ace_cursor")
- textToEditorCoordinates: (x, y) ->
- editorAreaOffset = @getContainerElement().offset()
- {pageX, pageY} = @aceEditor.renderer.textToScreenCoordinates(x, y)
- return {
- x: pageX - editorAreaOffset.left
- y: pageY - editorAreaOffset.top
- }
- chaosMonkey: (line = 0, char = "a") ->
- @_cm = setInterval () =>
- @aceEditor.session.insert({row: line, column: 0}, char)
- , 100
- clearChaosMonkey: () ->
- clearInterval @_cm
- getCurrentDocId: () ->
- @current_doc_id
- enable: () ->
- @enabled = true
- disable: () ->
- @enabled = false
- hasUnsavedChanges: () ->
- Document.hasUnsavedChanges()
|