diff --git a/services/web/frontend/js/ide/pdf/PdfManager.js b/services/web/frontend/js/ide/pdf/PdfManager.js index 78f06368f2..5ab0dbf976 100644 --- a/services/web/frontend/js/ide/pdf/PdfManager.js +++ b/services/web/frontend/js/ide/pdf/PdfManager.js @@ -9,6 +9,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ import './controllers/PdfController' +import './controllers/PdfSynctexController' import './controllers/PdfViewToggleController' import '../pdfng/directives/pdfJs' diff --git a/services/web/frontend/js/ide/pdf/controllers/PdfController.js b/services/web/frontend/js/ide/pdf/controllers/PdfController.js index d7c23faecc..3edd647404 100644 --- a/services/web/frontend/js/ide/pdf/controllers/PdfController.js +++ b/services/web/frontend/js/ide/pdf/controllers/PdfController.js @@ -974,187 +974,6 @@ App.controller( } ) -App.factory('synctex', function (ide, $http, $q) { - const synctex = { - syncToPdfInFlight: false, - syncToCodeInFlight: false, - - syncToPdf(cursorPosition) { - const deferred = $q.defer() - - const docId = ide.editorManager.getCurrentDocId() - if (docId == null) { - deferred.reject() - return deferred.promise - } - const doc = ide.fileTreeManager.findEntityById(docId) - if (doc == null) { - deferred.reject() - return deferred.promise - } - let path = ide.fileTreeManager.getEntityPath(doc) - if (path == null) { - deferred.reject() - return deferred.promise - } - - // If the root file is folder/main.tex, then synctex sees the - // path as folder/./main.tex - const rootDocDirname = ide.fileTreeManager.getRootDocDirname() - if (rootDocDirname != null && rootDocDirname !== '') { - path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`) - } - - const { row, column } = cursorPosition - - this.syncToPdfInFlight = true - - $http({ - url: `/project/${ide.project_id}/sync/code`, - method: 'GET', - params: { - file: path, - line: row + 1, - column, - clsiserverid: ide.clsiServerId, - }, - }) - .then(response => { - this.syncToPdfInFlight = false - const { data } = response - return deferred.resolve(data.pdf || []) - }) - .catch(response => { - this.syncToPdfInFlight = false - const error = response.data - return deferred.reject(error) - }) - - return deferred.promise - }, - - syncToCode(position, options) { - if (options == null) { - options = {} - } - const deferred = $q.defer() - if (position == null) { - deferred.reject() - return deferred.promise - } - - // FIXME: this actually works better if it's halfway across the - // page (or the visible part of the page). Synctex doesn't - // always find the right place in the file when the point is at - // the edge of the page, it sometimes returns the start of the - // next paragraph instead. - const h = position.offset.left - - // Compute the vertical position to pass to synctex, which - // works with coordinates increasing from the top of the page - // down. This matches the browser's DOM coordinate of the - // click point, but the pdf position is measured from the - // bottom of the page so we need to invert it. - let v - if ( - options.fromPdfPosition && - (position.pageSize != null ? position.pageSize.height : undefined) != - null - ) { - v = position.pageSize.height - position.offset.top || 0 // measure from pdf point (inverted) - } else { - v = position.offset.top || 0 // measure from html click position - } - - // 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) { - v += 72 // use the same value as in pdfViewer highlighting visual offset - } - - this.syncToCodeInFlight = true - - $http({ - url: `/project/${ide.project_id}/sync/pdf`, - method: 'GET', - params: { - page: position.page + 1, - h: h.toFixed(2), - v: v.toFixed(2), - clsiserverid: ide.clsiServerId, - }, - }) - .then(response => { - this.syncToCodeInFlight = false - const { data } = response - if ( - data.code != null && - data.code.length > 0 && - data.code[0].file !== '' - ) { - const doc = ide.fileTreeManager.findEntityByPath(data.code[0].file) - if (doc == null) { - deferred.reject() - } - return deferred.resolve({ doc, line: data.code[0].line }) - } else if (data.code[0].file === '') { - ide.$scope.sync_tex_error = true - setTimeout(() => (ide.$scope.sync_tex_error = false), 4000) - } - }) - .catch(response => { - this.syncToCodeInFlight = false - const error = response.data - return deferred.reject(error) - }) - - return deferred.promise - }, - } - - return synctex -}) - -App.controller('PdfSynctexController', function ($scope, synctex, ide) { - this.cursorPosition = null - - $scope.$watch( - () => synctex.syncToPdfInFlight, - value => ($scope.syncToPdfInFlight = value) - ) - $scope.$watch( - () => synctex.syncToCodeInFlight, - value => ($scope.syncToCodeInFlight = value) - ) - - ide.$scope.$on('cursor:editor:update', (event, cursorPosition) => { - this.cursorPosition = cursorPosition - }) - - $scope.syncToPdf = () => { - if (this.cursorPosition == null) { - return - } - synctex.syncToPdf(this.cursorPosition).then(highlights => { - $scope.pdf.highlights = highlights - }) - } - - ide.$scope.$on('cursor:editor:syncToPdf', $scope.syncToPdf) - - $scope.syncToCode = function () { - synctex - .syncToCode($scope.pdf.position, { - includeVisualOffset: true, - fromPdfPosition: true, - }) - .then(function (data) { - const { doc, line } = data - ide.editorManager.openDoc(doc, { gotoLine: line }) - }) - } -}) - App.controller('ClearCacheModalController', function ($scope, $modalInstance) { $scope.state = { error: false, inflight: false } diff --git a/services/web/frontend/js/ide/pdf/controllers/PdfSynctexController.js b/services/web/frontend/js/ide/pdf/controllers/PdfSynctexController.js new file mode 100644 index 0000000000..d574b9bc2f --- /dev/null +++ b/services/web/frontend/js/ide/pdf/controllers/PdfSynctexController.js @@ -0,0 +1,180 @@ +import App from '../../../base' + +App.controller('PdfSynctexController', function ($scope, synctex, ide) { + this.cursorPosition = null + + $scope.$watch( + () => synctex.syncToPdfInFlight, + value => ($scope.syncToPdfInFlight = value) + ) + $scope.$watch( + () => synctex.syncToCodeInFlight, + value => ($scope.syncToCodeInFlight = value) + ) + + ide.$scope.$on('cursor:editor:update', (event, cursorPosition) => { + this.cursorPosition = cursorPosition + }) + + $scope.syncToPdf = () => { + if (this.cursorPosition == null) { + return + } + synctex.syncToPdf(this.cursorPosition).then(highlights => { + $scope.pdf.highlights = highlights + }) + } + + ide.$scope.$on('cursor:editor:syncToPdf', $scope.syncToPdf) + + $scope.syncToCode = function () { + synctex + .syncToCode($scope.pdf.position, { + includeVisualOffset: true, + fromPdfPosition: true, + }) + .then(function (data) { + const { doc, line } = data + ide.editorManager.openDoc(doc, { gotoLine: line }) + }) + } +}) + +App.factory('synctex', function (ide, $http, $q) { + return { + syncToPdfInFlight: false, + syncToCodeInFlight: false, + + syncToPdf(cursorPosition) { + const deferred = $q.defer() + + const docId = ide.editorManager.getCurrentDocId() + if (docId == null) { + deferred.reject() + return deferred.promise + } + const doc = ide.fileTreeManager.findEntityById(docId) + if (doc == null) { + deferred.reject() + return deferred.promise + } + let path = ide.fileTreeManager.getEntityPath(doc) + if (path == null) { + deferred.reject() + return deferred.promise + } + + // If the root file is folder/main.tex, then synctex sees the + // path as folder/./main.tex + const rootDocDirname = ide.fileTreeManager.getRootDocDirname() + if (rootDocDirname != null && rootDocDirname !== '') { + path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`) + } + + const { row, column } = cursorPosition + + this.syncToPdfInFlight = true + + $http({ + url: `/project/${ide.project_id}/sync/code`, + method: 'GET', + params: { + file: path, + line: row + 1, + column, + clsiserverid: ide.clsiServerId, + }, + }) + .then(response => { + this.syncToPdfInFlight = false + const { data } = response + return deferred.resolve(data.pdf || []) + }) + .catch(response => { + this.syncToPdfInFlight = false + const error = response.data + return deferred.reject(error) + }) + + return deferred.promise + }, + + syncToCode(position, options) { + if (options == null) { + options = {} + } + const deferred = $q.defer() + if (position == null) { + deferred.reject() + return deferred.promise + } + + // FIXME: this actually works better if it's halfway across the + // page (or the visible part of the page). Synctex doesn't + // always find the right place in the file when the point is at + // the edge of the page, it sometimes returns the start of the + // next paragraph instead. + const h = position.offset.left + + // Compute the vertical position to pass to synctex, which + // works with coordinates increasing from the top of the page + // down. This matches the browser's DOM coordinate of the + // click point, but the pdf position is measured from the + // bottom of the page so we need to invert it. + let v + if ( + options.fromPdfPosition && + (position.pageSize != null ? position.pageSize.height : undefined) != + null + ) { + v = position.pageSize.height - position.offset.top || 0 // measure from pdf point (inverted) + } else { + v = position.offset.top || 0 // measure from html click position + } + + // 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) { + v += 72 // use the same value as in pdfViewer highlighting visual offset + } + + this.syncToCodeInFlight = true + + $http({ + url: `/project/${ide.project_id}/sync/pdf`, + method: 'GET', + params: { + page: position.page + 1, + h: h.toFixed(2), + v: v.toFixed(2), + clsiserverid: ide.clsiServerId, + }, + }) + .then(response => { + this.syncToCodeInFlight = false + const { data } = response + if ( + data.code != null && + data.code.length > 0 && + data.code[0].file !== '' + ) { + const doc = ide.fileTreeManager.findEntityByPath(data.code[0].file) + if (doc == null) { + deferred.reject() + } + return deferred.resolve({ doc, line: data.code[0].line }) + } else if (data.code[0].file === '') { + ide.$scope.sync_tex_error = true + setTimeout(() => (ide.$scope.sync_tex_error = false), 4000) + } + }) + .catch(response => { + this.syncToCodeInFlight = false + const error = response.data + return deferred.reject(error) + }) + + return deferred.promise + }, + } +})