diff --git a/services/web/app/views/project/editor/editor.jade b/services/web/app/views/project/editor/editor.jade index 3956b417fa..b5fd2b65ac 100644 --- a/services/web/app/views/project/editor/editor.jade +++ b/services/web/app/views/project/editor/editor.jade @@ -27,4 +27,24 @@ div.full-size( ) .ui-layout-east - include ./pdf \ No newline at end of file + include ./pdf + + .ui-layout-resizer-controls.synctex-controls( + ng-show="!!pdf.url" + ng-controller="PdfSynctexController" + ) + a.btn.btn-default.btn-xs( + tooltip="Go to code location in PDF" + tooltip-placement="right" + tooltip-append-to-body="true" + ng-click="syncToPdf()" + ) + i.fa.fa-long-arrow-right + br + a.btn.btn-default.btn-xs( + tooltip-html-unsafe="Go to PDF location in code
(or double click PDF)" + tooltip-placement="right" + tooltip-append-to-body="true" + ng-click="syncToCode()" + ) + i.fa.fa-long-arrow-left \ No newline at end of file diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 0487d818fa..142fac6a08 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -33,6 +33,9 @@ div.full-size(ng-controller="PdfController") pdf-src="pdf.url" key="project_id" resize-on="layout:main:resize,layout:pdf:resize" + highlights="pdf.highlights" + position="pdf.position" + dbl-click-callback="syncToCode" ) .pdf-uncompiled(ng-show="pdf.uncompiled && !pdf.compiling") diff --git a/services/web/public/coffee/app/ide/directives/layout.coffee b/services/web/public/coffee/app/ide/directives/layout.coffee index fd0c39a836..d5657e7c12 100644 --- a/services/web/public/coffee/app/ide/directives/layout.coffee +++ b/services/web/public/coffee/app/ide/directives/layout.coffee @@ -12,6 +12,7 @@ define [ onresize: () => console.log "Triggering", "layout:#{name}:resize", name scope.$broadcast "layout:#{name}:resize" + repositionControls() #maskIframesOnResize: true # Restore previously recorded state @@ -28,4 +29,13 @@ define [ # Save state when exiting $(window).unload () -> $.localStorage("layout.#{name}", element.layout().readState()) + + repositionControls = () -> + state = element.layout().readState() + if state.east? + element.find(".ui-layout-resizer-controls").css({ + position: "absolute" + right: state.east.size + "z-index": 10 + }) } \ 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 d592fd0ecd..26aeb03853 100644 --- a/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee +++ b/services/web/public/coffee/app/ide/file-tree/FileTreeManager.coffee @@ -95,6 +95,10 @@ define [ parts = path.split("/") name = parts.shift() rest = parts.join("/") + + if name == "." + return @_findEntityByPathInFolder(folder, rest) + for entity in folder.children if entity.name == name if rest == "" @@ -115,6 +119,26 @@ define [ if entity.children? @_forEachEntityInFolder(entity, callback) + getEntityPath: (entity) -> + @_getEntityPathInFolder @$scope.rootFolder, entity + + _getEntityPathInFolder: (folder, entity) -> + for child in folder.children or [] + if child == entity + return entity.name + else if child.type == "folder" + path = @_getEntityPathInFolder(child, entity) + if path? + return child.name + "/" + path + return null + + getRootDocDirname: () -> + rootDoc = @findEntityById @$scope.project.rootDoc_id + return if !rootDoc? + path = @getEntityPath(rootDoc) + return if !path? + return path.split("/").slice(0, -1).join("/") + # forEachFolder: (callback) -> # @forEachEntity (entity) -> # if entity.type == "folder" diff --git a/services/web/public/coffee/app/ide/pdf/PdfManager.coffee b/services/web/public/coffee/app/ide/pdf/PdfManager.coffee index f41572aa88..47ac42a1b6 100644 --- a/services/web/public/coffee/app/ide/pdf/PdfManager.coffee +++ b/services/web/public/coffee/app/ide/pdf/PdfManager.coffee @@ -16,3 +16,5 @@ define [ rawLog: "" view: null # 'pdf' 'logs' showRawLog: false + highlights: [] + position: null 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 89683e9268..28e278d870 100644 --- a/services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/app/ide/pdf/controllers/PdfController.coffee @@ -2,7 +2,7 @@ define [ "base" "libs/latex-log-parser" ], (App, LogParser) -> - App.controller "PdfController", ["$scope", "$http", "ide", "$modal", ($scope, $http, ide, $modal) -> + App.controller "PdfController", ["$scope", "$http", "ide", "$modal", "synctex", ($scope, $http, ide, $modal, synctex) -> autoCompile = true $scope.$on "doc:opened", () -> return if !autoCompile @@ -59,8 +59,7 @@ define [ $scope.pdf.logEntryAnnotations = {} for entry in logEntries.all - entry.file = entry.file.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "") - entry.file = entry.file.replace(/^\/compile\//, "") + entry.file = normalizeFilePath(entry.file) entity = ide.fileTreeManager.findEntityByPath(entry.file) if entity? @@ -84,6 +83,16 @@ define [ return ide.editorManager.getCurrentDocId() return null + normalizeFilePath = (path) -> + path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "") + path = path.replace(/^\/compile\//, "") + + rootDocDirname = ide.fileTreeManager.getRootDocDirname() + if rootDocDirname? + path = path.replace(/^\.\//, rootDocDirname) + + return path + $scope.recompile = (options = {}) -> console.log "Recompiling", options return if $scope.pdf.compiling @@ -129,13 +138,115 @@ define [ controller: "ClearCacheModalController" scope: $scope ) + + $scope.syncToCode = (position) -> + console.log "SYNCING VIA DBL CLICK", position + synctex + .syncToCode(position) + .then (data) -> + {doc, line} = data + ide.editorManager.openDoc(doc, gotoLine: line) + + ] + + App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) -> + synctex = + syncToPdf: (cursorPosition) -> + deferred = $q.defer() + + doc_id = ide.editorManager.getCurrentDocId() + if !doc_id? + deferred.reject() + return deferred.promise() + doc = ide.fileTreeManager.findEntityById(doc_id) + if !doc? + deferred.reject() + return deferred.promise() + path = ide.fileTreeManager.getEntityPath(doc) + if !path? + deferred.reject() + return deferred.promise() + + # If the root file is folder/main.tex, then synctex sees the + # path as folder/./main.tex + rootDocDirname = ide.fileTreeManager.getRootDocDirname() + if rootDocDirname? and rootDocDirname != "" + path = path.replace(RegExp("^#{rootDocDirname}"), "#{rootDocDirname}/.") + + {row, column} = cursorPosition + + $http({ + url: "/project/#{ide.project_id}/sync/code", + method: "GET", + params: { + file: path + line: row + 1 + column: column + } + }) + .success (data) -> + console.log "SYNCTEX RESPONSE", data + deferred.resolve(data.pdf or []) + .error (error) -> + deferred.reject(error) + + return deferred.promise + + syncToCode: (position, options = {}) -> + deferred = $q.defer() + if !position? + deferred.reject() + return deferred.promise() + + # It's not clear exactly where we should sync to if it wasn't directly + # clicked on, but a little bit down from the very top seems best. + if options.includeVisualOffset + position.offset.top = position.offset.top + 80 + + $http({ + url: "/project/#{ide.project_id}/sync/pdf", + method: "GET", + params: { + page: position.page + 1 + h: position.offset.left.toFixed(2) + v: position.offset.top.toFixed(2) + } + }) + .success (data) -> + console.log "SYNCTEX RESPONSE", data + if data.code? and data.code.length > 0 + doc = ide.fileTreeManager.findEntityByPath(data.code[0].file) + return if !doc? + deferred.resolve({doc: doc, line: data.code[0].line}) + .error (error) -> + deferred.reject(error) + + return deferred.promise + + return synctex + ] + + App.controller "PdfSynctexController", ["$scope", "synctex", "ide", ($scope, synctex, ide) -> + $scope.syncToPdf = () -> + synctex + .syncToPdf($scope.editor.cursorPosition) + .then (highlights) -> + $scope.pdf.highlights = highlights + + $scope.syncToCode = () -> + synctex + .syncToCode($scope.pdf.position, includeVisualOffset: true) + .then (data) -> + {doc, line} = data + console.log "OPENING DOC", doc, line + ide.editorManager.openDoc(doc, gotoLine: line) ] 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" + return if !entity? or entity.type != "doc" if entry.line? line = entry.line ide.editorManager.openDoc(entity, gotoLine: line) diff --git a/services/web/public/coffee/app/ide/pdf/directives/pdfJs.coffee b/services/web/public/coffee/app/ide/pdf/directives/pdfJs.coffee index f614310331..b5ef69c4e3 100644 --- a/services/web/public/coffee/app/ide/pdf/directives/pdfJs.coffee +++ b/services/web/public/coffee/app/ide/pdf/directives/pdfJs.coffee @@ -28,12 +28,16 @@ define [ return { scope: { "pdfSrc": "=" + "highlights": "=" + "position": "=" + "dblClickCallback": "=" } link: (scope, element, attrs) -> pdfListView = new PDFListView element.find(".pdfjs-viewer")[0], textLayerBuilder: TextLayerBuilder annotationsLayerBuilder: AnnotationsLayerBuilder highlightsLayerBuilder: HighlightsLayerBuilder + ondblclick: (e) -> onDoubleClick(e) logLevel: PDFListView.Logger.DEBUG pdfListView.listView.pageWidthOffset = 20 pdfListView.listView.pageHeightOffset = 20 @@ -58,6 +62,8 @@ define [ if (position = $.localStorage("pdf.position.#{attrs.key}")) pdfListView.setPdfPosition(position) + scope.position = pdfListView.getPdfPosition(true) + $(window).unload () => $.localStorage "pdf.scale", { scaleMode: pdfListView.getScaleMode() @@ -69,7 +75,14 @@ define [ scope.flashControls = true $timeout () -> scope.flashControls = false - , 1000 + , 1000 + + element.find(".pdfjs-viewer").scroll () -> + console.log "UPDATING POSITION", pdfListView.getPdfPosition(true) + scope.position = pdfListView.getPdfPosition(true) + + onDoubleClick = (e) -> + scope.dblClickCallback?(page: e.page, offset: { top: e.y, left: e.x }) scope.$watch "pdfSrc", (url) -> if url @@ -85,6 +98,35 @@ define [ initializePosition() flashControls() + scope.$watch "highlights", (areas) -> + console.log "UPDATING HIGHLIGHTS", areas + return if !areas? + highlights = for area in areas or [] + { + page: area.page - 1 + highlight: + left: area.h + top: area.v + height: area.height + width: area.width + } + + if highlights.length > 0 + first = highlights[0] + pdfListView.setPdfPosition({ + page: first.page + offset: + left: first.highlight.left + top: first.highlight.top - 80 + }, true) + + pdfListView.clearHighlights() + pdfListView.setHighlights(highlights, true) + + setTimeout () => + pdfListView.clearHighlights() + , 1000 + scope.fitToHeight = () -> pdfListView.setToFitHeight() diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index cb2e1983f1..7b95eea5e9 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -112,4 +112,12 @@ position: relative; } } + +.synctex-controls { + padding: 68px 2px 0; + .btn-xs { + line-height: 1.3; + padding: 0 2px 0; + } +} \ No newline at end of file