From 797dcf5c840b61635bc13e3966ec70b8fab8a078 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 30 Jun 2014 13:41:32 +0100 Subject: [PATCH] Jump to error when clicking on log entry --- .../web/app/views/project/editor/editor.jade | 6 +- .../web/app/views/project/editor/pdf.jade | 3 +- .../views/project/editor/track-changes.jade | 2 +- .../app/ide/editor/EditorManager.coffee | 15 +- .../CursorPositionManager.coffee | 12 +- .../ide/editor/directives/aceEditor.coffee | 21 +- .../highlights/HighlightsManager.coffee | 224 ++++++++++++++++++ .../app/ide/file-tree/FileTreeManager.coffee | 15 ++ .../online-users/OnlineUsersManager.coffee | 8 +- .../coffee/app/ide/pdf/PdfManager.coffee | 14 +- .../ide/pdf/controllers/PdfController.coffee | 34 ++- .../track-changes/TrackChangesManager.coffee | 12 +- .../public/stylesheets/app/editor/pdf.less | 1 + 13 files changed, 330 insertions(+), 37 deletions(-) create mode 100644 services/web/public/coffee/app/ide/editor/highlights/HighlightsManager.coffee diff --git a/services/web/app/views/project/editor/editor.jade b/services/web/app/views/project/editor/editor.jade index 394d432c27..3956b417fa 100644 --- a/services/web/app/views/project/editor/editor.jade +++ b/services/web/app/views/project/editor/editor.jade @@ -16,12 +16,14 @@ div.full-size( font-size="settings.fontSize", auto-complete="settings.autoComplete", spell-check-language="project.spellCheckLanguage", - annotations="onlineUserCursorAnnotations[editor.open_doc_id]" + highlights="onlineUserCursorHighlights[editor.open_doc_id]" show-print-margin="false", sharejs-doc="editor.sharejs_doc", last-updated="editor.last_updated", cursor-position="editor.cursorPosition", - resize-on="layout:main:resize,layout:pdf:resize" + goto-line="editor.gotoLine", + resize-on="layout:main:resize,layout:pdf:resize", + annotations="pdf.logEntryAnnotations[editor.open_doc_id]" ) .ui-layout-east diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index b2b7fc9df2..0487d818fa 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -59,13 +59,14 @@ div.full-size(ng-controller="PdfController") strong Compile Error. span Sorry, your LaTeX code couldn't compile for some reason. Please check the errors below for details, or view the raw log. - div(ng-repeat="entry in pdf.logEntries.all") + div(ng-repeat="entry in pdf.logEntries.all", ng-controller="PdfLogEntryController") .alert( ng-class="{\ 'alert-danger': entry.level == 'error',\ 'alert-warning': entry.level == 'warning',\ 'alert-info': entry.level == 'typesetting'\ }" + ng-click="openInEditor(entry)" ) span.line-no span(ng-show="entry.file") {{ entry.file }} diff --git a/services/web/app/views/project/editor/track-changes.jade b/services/web/app/views/project/editor/track-changes.jade index 77cb6230de..f66a7bbbc2 100644 --- a/services/web/app/views/project/editor/track-changes.jade +++ b/services/web/app/views/project/editor/track-changes.jade @@ -71,7 +71,7 @@ div#trackChanges(ng-show="ui.view == 'track-changes'") theme="settings.theme", font-size="settings.fontSize", text="trackChanges.diff.text", - annotations="trackChanges.diff.annotations", + highlights="trackChanges.diff.highlights", read-only="true", resize-on="layout:main:resize" ) diff --git a/services/web/public/coffee/app/ide/editor/EditorManager.coffee b/services/web/public/coffee/app/ide/editor/EditorManager.coffee index 09dc79bbdb..ed331f4583 100644 --- a/services/web/public/coffee/app/ide/editor/EditorManager.coffee +++ b/services/web/public/coffee/app/ide/editor/EditorManager.coffee @@ -10,6 +10,7 @@ define [ open_doc_id: null opening: true cursorPosition: null + gotoLine: null } @$scope.$on "entity:selected", (event, entity) => @@ -33,18 +34,23 @@ define [ openDoc: (doc, options = {}) -> @$scope.ui.view = "editor" + + done = () => + if options.gotoLine? + @$scope.editor.gotoLine = options.gotoLine - console.log "Trying to open doc", doc.id - return if doc.id == @$scope.editor.open_doc_id and !options.forceReopen + if doc.id == @$scope.editor.open_doc_id and !options.forceReopen + @$scope.$apply () => + done() + return + @$scope.editor.open_doc_id = doc.id - console.log "Actually opening doc", doc.id $.localStorage "doc.open_id.#{@$scope.project_id}", doc.id @ide.fileTreeManager.selectEntity(doc) @$scope.editor.opening = true @_openNewDocument doc, (error, sharejs_doc) => - console.log "OPENED DOC", error, sharejs_doc if error? @ide.showGenericServerErrorMessage() return @@ -54,6 +60,7 @@ define [ @$scope.$apply () => @$scope.editor.opening = false @$scope.editor.sharejs_doc = sharejs_doc + done() _openNewDocument: (doc, callback = (error, sharejs_doc) ->) -> current_sharejs_doc = @$scope.editor.sharejs_doc diff --git a/services/web/public/coffee/app/ide/editor/cursor-position/CursorPositionManager.coffee b/services/web/public/coffee/app/ide/editor/cursor-position/CursorPositionManager.coffee index e1346ef9ee..46cae9465a 100644 --- a/services/web/public/coffee/app/ide/editor/cursor-position/CursorPositionManager.coffee +++ b/services/web/public/coffee/app/ide/editor/cursor-position/CursorPositionManager.coffee @@ -11,6 +11,13 @@ define [], () -> @gotoStoredPosition() + @$scope.$watch "gotoLine", (value) => + console.log "Going to line", value + if value? + setTimeout () => + @gotoLine(value) + , 0 + onScrollTopChange: (event) -> if !@ignoreCursorPositionChanges and doc_id = @$scope.sharejsDoc?.doc_id docPosition = $.localStorage("doc.position.#{doc_id}") || {} @@ -30,4 +37,7 @@ define [], () -> @ignoreCursorPositionChanges = true @editor.moveCursorToPosition(pos.cursorPosition or {row: 0, column: 0}) @editor.getSession().setScrollTop(pos.scrollTop or 0) - delete @ignoreCursorPositionChanges \ No newline at end of file + delete @ignoreCursorPositionChanges + + gotoLine: (line) -> + @editor.moveCursorToPosition({row: line, column: 0}) \ 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 7b8f09973a..5f5f238a44 100644 --- a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee @@ -4,7 +4,7 @@ define [ "ide/editor/undo/UndoManager" "ide/editor/auto-complete/AutoCompleteManager" "ide/editor/spell-check/SpellCheckManager" - "ide/editor/annotations/AnnotationsManager" + "ide/editor/highlights/HighlightsManager" "ide/editor/cursor-position/CursorPositionManager" "ace/keyboard/vim" "ace/keyboard/emacs" @@ -26,9 +26,11 @@ define [ lastUpdated: "=" spellCheckLanguage: "=" cursorPosition: "=" - annotations: "=" + highlights: "=" text: "=" readOnly: "=" + gotoLine: "=" + annotations: "=" } link: (scope, element, attrs) -> # Don't freak out if we're already in an apply callback @@ -100,15 +102,26 @@ define [ session.setUseWrapMode(true) session.setMode(new LatexMode()) + scope.$watch "annotations", (annotations) -> + console.log "SETTING ANNOTATIONS", annotations + if annotations? + session = editor.getSession() + session.setAnnotations annotations + scope.$watch "readOnly", (value) -> editor.setReadOnly !!value + resetSession = () -> + session = editor.getSession() + session.setUseWrapMode(true) + session.setMode(new LatexMode()) + session.setAnnotations scope.annotations + attachToAce = (sharejs_doc) -> lines = sharejs_doc.getSnapshot().split("\n") editor.setSession(new EditSession(lines)) + resetSession() session = editor.getSession() - session.setUseWrapMode(true) - session.setMode(new LatexMode()) autoCompleteManager.bindToSession(session) annotationsManager.redrawAnnotations() diff --git a/services/web/public/coffee/app/ide/editor/highlights/HighlightsManager.coffee b/services/web/public/coffee/app/ide/editor/highlights/HighlightsManager.coffee new file mode 100644 index 0000000000..6f7f65351e --- /dev/null +++ b/services/web/public/coffee/app/ide/editor/highlights/HighlightsManager.coffee @@ -0,0 +1,224 @@ +define [ + "ace/range" +], () -> + Range = require("ace/range").Range + + class HighlightsManager + constructor: (@$scope, @editor, @element) -> + @markerIds = [] + @labels = [] + + @$scope.annotationLabel = { + show: false + right: "auto" + left: "auto" + top: "auto" + bottom: "auto" + backgroundColor: "black" + text: "" + } + + @$scope.$watch "highlights", (value) => + @redrawAnnotations() + + @$scope.$watch "theme", (value) => + @redrawAnnotations() + + @editor.on "mousemove", (e) => + position = @editor.renderer.screenToTextCoordinates(e.clientX, e.clientY) + e.position = position + @showAnnotationLabels(position) + + redrawAnnotations: () -> + @_clearMarkers() + @_clearLabels() + + for annotation in @$scope.highlights or [] + do (annotation) => + colorScheme = @_getColorScheme(annotation.hue) + if annotation.cursor? + @labels.push { + text: annotation.text + range: new Range( + annotation.cursor.row, annotation.cursor.column, + annotation.cursor.row, annotation.cursor.column + 1 + ) + colorScheme: colorScheme + snapToStartOfRange: true + } + @_drawCursor(annotation, colorScheme) + else if annotation.highlight? + @labels.push { + text: annotation.label + range: new Range( + annotation.highlight.start.row, annotation.highlight.start.column, + annotation.highlight.end.row, annotation.highlight.end.column + ) + colorScheme: colorScheme + } + @_drawHighlight(annotation, colorScheme) + else if annotation.strikeThrough? + @labels.push { + text: annotation.label + range: new Range( + annotation.strikeThrough.start.row, annotation.strikeThrough.start.column, + annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + ) + colorScheme: colorScheme + } + @_drawStrikeThrough(annotation, colorScheme) + + showAnnotationLabels: (position) -> + labelToShow = null + for label in @labels or [] + if label.range.contains(position.row, position.column) + labelToShow = label + + if !labelToShow? + @$scope.$apply () => + @$scope.annotationLabel.show = false + else + $ace = $(@editor.renderer.container).find(".ace_scroller") + # Move the label into the Ace content area so that offsets and positions are easy to calculate. + $ace.append(@element.find(".annotation-label")) + + if labelToShow.snapToStartOfRange + coords = @editor.renderer.textToScreenCoordinates(labelToShow.range.start.row, labelToShow.range.start.column) + else + coords = @editor.renderer.textToScreenCoordinates(position.row, position.column) + + offset = $ace.offset() + height = $ace.height() + coords.pageX = coords.pageX - offset.left + coords.pageY = coords.pageY - offset.top + + if coords.pageY > @editor.renderer.lineHeight * 2 + top = "auto" + bottom = height - coords.pageY + else + top = coords.pageY + @editor.renderer.lineHeight + bottom = "auto" + + # Apply this first that the label has the correct width when calculating below + @$scope.$apply () => + @$scope.annotationLabel.text = labelToShow.text + @$scope.annotationLabel.show = true + + $label = @element.find(".annotation-label") + console.log "pageX", coords.pageX, "label", $label.outerWidth(), "ace", $ace.width() + + if coords.pageX + $label.outerWidth() < $ace.width() + left = coords.pageX + right = "auto" + else + right = 0 + left = "auto" + + @$scope.$apply () => + @$scope.annotationLabel = { + show: true + left: left + right: right + bottom: bottom + top: top + backgroundColor: labelToShow.colorScheme.labelBackgroundColor + text: labelToShow.text + } + + _clearMarkers: () -> + for marker_id in @markerIds + @editor.getSession().removeMarker(marker_id) + @markerIds = [] + + _clearLabels: () -> + @labels = [] + + _drawCursor: (annotation, colorScheme) -> + @markerIds.push @editor.getSession().addMarker new Range( + annotation.cursor.row, annotation.cursor.column, + annotation.cursor.row, annotation.cursor.column + 1 + ), "annotation remote-cursor", (html, range, left, top, config) -> + div = """ +
+
+
+ """ + html.push div + , true + + _drawHighlight: (annotation, colorScheme) -> + @_addMarkerWithCustomStyle( + new Range( + annotation.highlight.start.row, annotation.highlight.start.column, + annotation.highlight.end.row, annotation.highlight.end.column + 1 + ), + "annotation highlight", + false, + "background-color: #{colorScheme.highlightBackgroundColor}" + ) + + _drawStrikeThrough: (annotation, colorScheme) -> + lineHeight = @editor.renderer.lineHeight + @_addMarkerWithCustomStyle( + new Range( + annotation.strikeThrough.start.row, annotation.strikeThrough.start.column, + annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + 1 + ), + "annotation strike-through-background", + false, + "background-color: #{colorScheme.strikeThroughBackgroundColor}" + ) + @_addMarkerWithCustomStyle( + new Range( + annotation.strikeThrough.start.row, annotation.strikeThrough.start.column, + annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + 1 + ), + "annotation strike-through-foreground", + true, + """ + height: #{Math.round(lineHeight/2) + 2}px; + border-bottom: 2px solid #{colorScheme.strikeThroughForegroundColor}; + """ + ) + + _addMarkerWithCustomStyle: (range, klass, foreground, style) -> + if foreground? + markerLayer = @editor.renderer.$markerBack + else + markerLayer = @editor.renderer.$markerFront + + @markerIds.push @editor.getSession().addMarker range, klass, (html, range, left, top, config) -> + if range.isMultiLine() + markerLayer.drawTextMarker(html, range, klass, config, style) + else + markerLayer.drawSingleLineMarker(html, range, "#{klass} ace_start", config, 0, style) + , foreground + + _getColorScheme: (hue) -> + if @_isDarkTheme() + return { + cursor: "hsl(#{hue}, 100%, 50%)" + labelBackgroundColor: "hsl(#{hue}, 100%, 50%)" + highlightBackgroundColor: "hsl(#{hue}, 100%, 28%);" + strikeThroughBackgroundColor: "hsl(#{hue}, 100%, 20%);" + strikeThroughForegroundColor: "hsl(#{hue}, 100%, 60%);" + } + else + return { + cursor: "hsl(#{hue}, 100%, 50%)" + labelBackgroundColor: "hsl(#{hue}, 100%, 50%)" + highlightBackgroundColor: "hsl(#{hue}, 70%, 85%);" + strikeThroughBackgroundColor: "hsl(#{hue}, 70%, 95%);" + strikeThroughForegroundColor: "hsl(#{hue}, 70%, 40%);" + } + + _isDarkTheme: () -> + rgb = @element.find(".ace_editor").css("background-color"); + [m, r, g, b] = rgb.match(/rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)/) + r = parseInt(r, 10) + g = parseInt(g, 10) + b = parseInt(b, 10) + return r + g + b < 3 * 128 \ No newline at end of file diff --git a/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee index 423a02f569..d592fd0ecd 100644 --- a/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee +++ b/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee @@ -88,6 +88,21 @@ define [ return null + findEntityByPath: (path) -> + @_findEntityByPathInFolder @$scope.rootFolder, path + + _findEntityByPathInFolder: (folder, path) -> + parts = path.split("/") + name = parts.shift() + rest = parts.join("/") + for entity in folder.children + if entity.name == name + if rest == "" + return entity + else if entity.type == "folder" + return @_findEntityByPathInFolder(entity, rest) + return null + forEachEntity: (callback = (entity, parent_folder) ->) -> @_forEachEntityInFolder(@$scope.rootFolder, callback) diff --git a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee b/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee index 1ff4b0b3dd..5a908fd336 100644 --- a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee +++ b/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee @@ -4,7 +4,7 @@ define [ class OnlineUsersManager constructor: (@ide, @$scope) -> @$scope.onlineUsers = {} - @$scope.onlineUserCursorAnnotations = {} + @$scope.onlineUserCursorHighlights = {} @$scope.$watch "editor.cursorPosition", (position) => if position? @@ -23,12 +23,12 @@ define [ updateCursorHighlights: () -> console.log "UPDATING CURSOR HIGHLIGHTS" - @$scope.onlineUserCursorAnnotations = {} + @$scope.onlineUserCursorHighlights = {} for client_id, client of @$scope.onlineUsers doc_id = client.doc_id continue if !doc_id? - @$scope.onlineUserCursorAnnotations[doc_id] ||= [] - @$scope.onlineUserCursorAnnotations[doc_id].push { + @$scope.onlineUserCursorHighlights[doc_id] ||= [] + @$scope.onlineUserCursorHighlights[doc_id].push { label: client.name cursor: row: client.row diff --git a/services/web/public/coffee/app/ide/pdf/PdfManager.coffee b/services/web/public/coffee/app/ide/pdf/PdfManager.coffee index b8f9ae2690..f41572aa88 100644 --- a/services/web/public/coffee/app/ide/pdf/PdfManager.coffee +++ b/services/web/public/coffee/app/ide/pdf/PdfManager.coffee @@ -4,5 +4,15 @@ define [ ], () -> class PdfManager constructor: (@ide, @$scope) -> - - # All the logic actually happens in the controller + @$scope.pdf = + url: null # Pdf Url + error: false # Server error + timeout: false # Server timed out + failure: false # PDF failed to compile + compiling: false + uncompiled: true + logEntries: [] + logEntryAnnotations: {} + rawLog: "" + view: null # 'pdf' 'logs' + showRawLog: false diff --git a/services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee index 6e31ee603f..626feb0db8 100644 --- a/services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee @@ -3,18 +3,6 @@ define [ "libs/latex-log-parser" ], (App, LogParser) -> App.controller "PdfController", ["$scope", "$http", "ide", "$modal", ($scope, $http, ide, $modal) -> - $scope.pdf = - url: null # Pdf Url - error: false # Server error - timeout: false # Server timed out - failure: false # PDF failed to compile - compiling: false - uncompiled: true - logEntries: [] - rawLog: "" - view: null # 'pdf' 'logs' - showRawLog: false - autoCompile = true $scope.$on "doc:opened", () -> return if !autoCompile @@ -68,9 +56,21 @@ define [ logEntries = LogParser.parse(log, ignoreDuplicates: true) $scope.pdf.logEntries = logEntries $scope.pdf.logEntries.all = logEntries.errors.concat(logEntries.warnings).concat(logEntries.typesetting) + + $scope.pdf.logEntryAnnotations = {} for entry in logEntries.all entry.file = entry.file.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "") entry.file = entry.file.replace(/^\/compile\//, "") + + entity = ide.fileTreeManager.findEntityByPath(entry.file) + if entity? + $scope.pdf.logEntryAnnotations[entity.id] ||= [] + $scope.pdf.logEntryAnnotations[entity.id].push { + row: entry.line - 1 + type: if entry.level == "error" then "error" else "warning" + text: entry.message + } + .error () -> $scope.pdf.logEntries = [] $scope.pdf.rawLog = "" @@ -131,6 +131,16 @@ define [ ) ] + App.controller "PdfLogEntryController", ["$scope", "ide", ($scope, ide) -> + $scope.openInEditor = (entry) -> + console.log "OPENING", entry.file, entry.line + entity = ide.fileTreeManager.findEntityByPath(entry.file) + return if entity.type != "doc" + if entry.line? + line = entry.line - 1 + ide.editorManager.openDoc(entity, gotoLine: line) + ] + App.controller 'ClearCacheModalController', ["$scope", "$modalInstance", ($scope, $modalInstance) -> $scope.state = inflight: false diff --git a/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee index 7f8f5216dc..6339eb0457 100644 --- a/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee +++ b/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee @@ -110,9 +110,9 @@ define [ .get(url) .success (data) => diff.loading = false - {text, annotations} = @_parseDiff(data) + {text, highlights} = @_parseDiff(data) diff.text = text - diff.annotations = annotations + diff.highlights = highlights .error () -> diff.loading = false diff.error = true @@ -129,7 +129,7 @@ define [ _parseDiff: (diff) -> row = 0 column = 0 - annotations = [] + highlights = [] text = "" for entry, i in diff.diff or [] content = entry.u or entry.i or entry.d @@ -162,19 +162,19 @@ define [ name = "you" date = moment(entry.meta.end_ts).format("Do MMM YYYY, h:mm a") if entry.i? - annotations.push { + highlights.push { label: "Added by #{name} on #{date}" highlight: range hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id) } else if entry.d? - annotations.push { + highlights.push { label: "Deleted by #{name} on #{date}" strikeThrough: range hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id) } - return {text, annotations} + return {text, highlights} _loadUpdates: (updates = []) -> previousUpdate = @$scope.trackChanges.updates[@$scope.trackChanges.updates.length - 1] diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index ae8d026e75..cb2e1983f1 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -89,6 +89,7 @@ .alert { font-size: 0.9rem; margin-bottom: @line-height-computed / 2; + cursor: pointer; .line-no { float: right; color: @gray;