diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade
index 6b99b3fdcb..20334d9fc4 100644
--- a/services/web/app/views/project/editor.jade
+++ b/services/web/app/views/project/editor.jade
@@ -142,9 +142,11 @@ block content
font-size="settings.fontSize",
auto-complete="settings.autoComplete",
spell-check-language="project.spellCheckLanguage",
+ annotations="onlineUserCursorAnnotations[editor.open_doc_id]"
show-print-margin="false",
sharejs-doc="editor.sharejs_doc",
- last-updated="editor.last_updated"
+ last-updated="editor.last_updated",
+ cursor-position="editor.cursorPosition"
)
//- #loadingScreen
diff --git a/services/web/public/coffee/app/ide.coffee b/services/web/public/coffee/app/ide.coffee
index 030dfb6a27..355993df4e 100644
--- a/services/web/public/coffee/app/ide.coffee
+++ b/services/web/public/coffee/app/ide.coffee
@@ -4,6 +4,7 @@ define [
"ide/connection/ConnectionManager"
"ide/editor/EditorManager"
"ide/settings/SettingsManager"
+ "ide/online-users/OnlineUsersManager"
"ide/directives/layout"
"ide/services/ide"
"directives/focus"
@@ -15,6 +16,7 @@ define [
ConnectionManager
EditorManager
SettingsManager
+ OnlineUsersManager
) ->
App.controller "IdeController", ["$scope", "$timeout", "ide", ($scope, $timeout, ide) ->
# Don't freak out if we're already in an apply callback
@@ -43,6 +45,7 @@ define [
ide.fileTreeManager = new FileTreeManager(ide, $scope)
ide.editorManager = new EditorManager(ide, $scope)
ide.settingsManager = new SettingsManager(ide, $scope)
+ ide.onlineUsersManager = new OnlineUsersManager(ide, $scope)
]
angular.bootstrap(document.body, ["SharelatexApp"])
\ No newline at end of file
diff --git a/services/web/public/coffee/app/ide/editor/EditorManager.coffee b/services/web/public/coffee/app/ide/editor/EditorManager.coffee
index 3433390b90..b20d9e718b 100644
--- a/services/web/public/coffee/app/ide/editor/EditorManager.coffee
+++ b/services/web/public/coffee/app/ide/editor/EditorManager.coffee
@@ -9,6 +9,7 @@ define [
last_updated: null
open_doc_id: null
opening: true
+ cursorPosition: null
}
@$scope.$on "entity:selected", (event, entity) =>
@@ -32,8 +33,8 @@ define [
openDoc: (doc, options = {}) ->
console.log "Trying to open doc", doc.id
- return if doc.id == @$scope.open_doc_id and !options.forceReopen
- @$scope.open_doc_id = doc.id
+ return if doc.id == @$scope.editor.open_doc_id and !options.forceReopen
+ @$scope.editor.open_doc_id = doc.id
console.log "Actually opening doc", doc.id
$.localStorage "doc.open_id.#{@$scope.project_id}", doc.id
diff --git a/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee b/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee
new file mode 100644
index 0000000000..4bba724d5e
--- /dev/null
+++ b/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee
@@ -0,0 +1,37 @@
+define [
+ "ace/range"
+], () ->
+ Range = require("ace/range").Range
+
+ class AnnotationsManager
+ constructor: (@$scope, @editor) ->
+ @markerIds = []
+
+ @$scope.$watch "annotations", (value) =>
+ if value?
+ @redrawAnnotations()
+
+ redrawAnnotations: () ->
+ console.log "REDRAWING ANNOTATIONS"
+ for marker_id in @markerIds
+ @editor.getSession().removeMarker(marker_id)
+ @markerIds = []
+
+ for annotation in @$scope.annotations or []
+ do (annotation) =>
+ console.log "DRAWING ANNOTATION", annotation
+ @markerIds.push @editor.getSession().addMarker new Range(
+ annotation.cursor.row, annotation.cursor.column,
+ annotation.cursor.row, annotation.cursor.column + 1
+ ), "remote-cursor", (html, range, left, top, config) ->
+ div = """
+
+
+
#{$('
').text(annotation.text).html()}
+
+ """
+ html.push div
+ , true
\ No newline at end of file
diff --git a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
index 7d340403a1..de577f321c 100644
--- a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
+++ b/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
@@ -4,11 +4,12 @@ define [
"ide/editor/undo/UndoManager"
"ide/editor/auto-complete/AutoCompleteManager"
"ide/editor/spell-check/SpellCheckManager"
+ "ide/editor/annotations/AnnotationsManager"
"ace/keyboard/vim"
"ace/keyboard/emacs"
"ace/mode/latex"
"ace/edit_session"
-], (App, Ace, UndoManager, AutoCompleteManager, SpellCheckManager) ->
+], (App, Ace, UndoManager, AutoCompleteManager, SpellCheckManager, AnnotationsManager) ->
LatexMode = require("ace/mode/latex").Mode
EditSession = require('ace/edit_session').EditSession
@@ -23,13 +24,25 @@ define [
sharejsDoc: "="
lastUpdated: "="
spellCheckLanguage: "="
+ cursorPosition: "="
+ annotations: "="
}
link: (scope, element, attrs) ->
+ # Don't freak out if we're already in an apply callback
+ scope.$originalApply = scope.$apply
+ scope.$apply = (fn = () ->) ->
+ phase = @$root.$$phase
+ if (phase == '$apply' || phase == '$digest')
+ fn()
+ else
+ @$originalApply(fn);
+
editor = Ace.edit(element.find(".ace-editor-body")[0])
autoCompleteManager = new AutoCompleteManager(scope, editor)
spellCheckManager = new SpellCheckManager(scope, editor, element)
undoManager = new UndoManager(scope, editor)
+ annotationsManagaer = new AnnotationsManager(scope, editor)
# Prevert Ctrl|Cmd-S from triggering save dialog
editor.commands.addCommand
@@ -41,6 +54,11 @@ define [
editor.commands.removeCommand "showSettingsMenu"
editor.commands.removeCommand "foldall"
+ editor.on "changeSelection", () ->
+ cursor = editor.getCursorPosition()
+ scope.$apply () ->
+ scope.cursorPosition = cursor
+
scope.$watch "theme", (value) ->
editor.setTheme("ace/theme/#{value}")
diff --git a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee b/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee
new file mode 100644
index 0000000000..1d63ccb02b
--- /dev/null
+++ b/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee
@@ -0,0 +1,56 @@
+define [], () ->
+ class OnlineUsersManager
+ constructor: (@ide, @$scope) ->
+ @$scope.onlineUsers = {}
+ @$scope.onlineUserCursorAnnotations = {}
+
+ @$scope.$watch "editor.cursorPosition", (position) =>
+ console.log "CURSOR POSITION UPDATE", position
+ if position?
+ @sendCursorPositionUpdate()
+
+ @ide.socket.on "clientTracking.clientUpdated", (client) =>
+ console.log "REMOTE CURSOR POSITION UPDATE", client
+ if client.id != @ide.socket.socket.sessionid # Check it's not me!
+ @$scope.$apply () =>
+ @$scope.onlineUsers[client.id] = client
+ @updateCursorHighlights()
+
+ @ide.socket.on "clientTracking.clientDisconnected", (client_id) =>
+ @$scope.$apply () =>
+ delete @$scope.onlineUsers[client_id]
+ @updateCursorHighlights()
+
+ updateCursorHighlights: () ->
+ console.log "UPDATING CURSOR HIGHLIGHTS"
+ @$scope.onlineUserCursorAnnotations = {}
+ for client_id, client of @$scope.onlineUsers
+ doc_id = client.doc_id
+ @$scope.onlineUserCursorAnnotations[doc_id] ||= []
+ @$scope.onlineUserCursorAnnotations[doc_id].push {
+ text: client.name
+ cursor:
+ row: client.row
+ column: client.column
+ }
+
+ UPDATE_INTERVAL: 500
+ sendCursorPositionUpdate: () ->
+ if !@cursorUpdateTimeout?
+ console.log "CREATING DELAYED UPDATED"
+ @cursorUpdateTimeout = setTimeout ()=>
+ position = @$scope.editor.cursorPosition
+ doc_id = @$scope.editor.open_doc_id
+
+ @ide.socket.emit "clientTracking.updatePosition", {
+ row: position.row
+ column: position.column
+ doc_id: doc_id
+ }
+
+ delete @cursorUpdateTimeout
+ , @UPDATE_INTERVAL
+ else
+ console.log "NOT UPDATING"
+
+
diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less
index fe5202d8d1..41736b4538 100644
--- a/services/web/public/stylesheets/app/editor.less
+++ b/services/web/public/stylesheets/app/editor.less
@@ -193,6 +193,28 @@
background-repeat: repeat-x;
background-position: bottom left;
}
+ @cursor-color: rgb(14, 158, 0);
+ .remote-cursor {
+ position: absolute;
+ z-index: 2;
+ border-left: 2px solid @cursor-color;
+ .name {
+ font-size: 0.8em;
+ background-color: @cursor-color;
+ color: white;
+ padding: 2px 6px;
+ border-radius: 3px 3px 3px 0;
+ position: absolute;
+ left: -4px;
+ }
+ .nubbin {
+ height: 6px;
+ width: 6px;
+ background-color: @cursor-color;
+ position: absolute;
+ left: -4px;
+ }
+ }
}
.ui-layout-resizer {