Просмотр исходного кода

Jump to error when clicking on log entry

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

+ 4 - 2
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

+ 2 - 1
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 }}

+ 1 - 1
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"
 		)

+ 11 - 4
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

+ 11 - 1
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
+			delete @ignoreCursorPositionChanges
+
+		gotoLine: (line) ->
+			@editor.moveCursorToPosition({row: line, column: 0})

+ 17 - 4
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()

+ 224 - 0
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 = """
+					<div
+						class='remote-cursor custom ace_start'
+						style='height: #{config.lineHeight}px; top:#{top}px; left:#{left}px; border-color: #{colorScheme.cursor};'
+					>
+						<div class="nubbin" style="bottom: #{config.lineHeight}px; background-color: #{colorScheme.cursor};"></div>
+					</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

+ 15 - 0
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)
 

+ 4 - 4
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

+ 12 - 2
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

+ 22 - 12
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

+ 6 - 6
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]

+ 1 - 0
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;