PdfSynctexController.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import App from '../../../base'
  2. import { sendMBOnce } from '../../../infrastructure/event-tracking'
  3. App.controller('PdfSynctexController', function ($scope, synctex, ide) {
  4. this.cursorPosition = null
  5. $scope.$watch(
  6. () => synctex.syncToPdfInFlight,
  7. value => ($scope.syncToPdfInFlight = value)
  8. )
  9. $scope.$watch(
  10. () => synctex.syncToCodeInFlight,
  11. value => ($scope.syncToCodeInFlight = value)
  12. )
  13. ide.$scope.$on('cursor:editor:update', (event, cursorPosition) => {
  14. this.cursorPosition = cursorPosition
  15. })
  16. $scope.syncToPdf = () => {
  17. if (this.cursorPosition == null) {
  18. return
  19. }
  20. synctex.syncToPdf(this.cursorPosition).then(highlights => {
  21. $scope.pdf.highlights = highlights
  22. })
  23. }
  24. ide.$scope.$on('cursor:editor:syncToPdf', $scope.syncToPdf)
  25. function syncToPosition(position, options) {
  26. synctex.syncToCode(position, options).then(data => {
  27. ide.editorManager.openDoc(data.doc, {
  28. gotoLine: data.line,
  29. })
  30. })
  31. }
  32. $scope.syncToCode = function () {
  33. syncToPosition($scope.pdf.position, {
  34. includeVisualOffset: true,
  35. fromPdfPosition: true,
  36. })
  37. }
  38. window.addEventListener('synctex:sync-to-position', event => {
  39. syncToPosition(event.detail, {
  40. fromPdfPosition: true,
  41. })
  42. })
  43. window.addEventListener('synctex:sync-to-entry', event => {
  44. sendMBOnce('logs-jump-to-location-once')
  45. const entry = event.detail
  46. const entity = ide.fileTreeManager.findEntityByPath(entry.file)
  47. if (entity && entity.type === 'doc') {
  48. ide.editorManager.openDoc(entity, {
  49. gotoLine: entry.line ?? undefined,
  50. gotoColumn: entry.column ?? undefined,
  51. })
  52. }
  53. })
  54. })
  55. App.factory('synctex', function (ide, $http, $q) {
  56. return {
  57. syncToPdfInFlight: false,
  58. syncToCodeInFlight: false,
  59. syncToPdf(cursorPosition) {
  60. const deferred = $q.defer()
  61. const docId = ide.editorManager.getCurrentDocId()
  62. if (docId == null) {
  63. deferred.reject()
  64. return deferred.promise
  65. }
  66. const doc = ide.fileTreeManager.findEntityById(docId)
  67. if (doc == null) {
  68. deferred.reject()
  69. return deferred.promise
  70. }
  71. let path = ide.fileTreeManager.getEntityPath(doc)
  72. if (path == null) {
  73. deferred.reject()
  74. return deferred.promise
  75. }
  76. // If the root file is folder/main.tex, then synctex sees the
  77. // path as folder/./main.tex
  78. const rootDocDirname = ide.fileTreeManager.getRootDocDirname()
  79. if (rootDocDirname != null && rootDocDirname !== '') {
  80. path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`)
  81. }
  82. const { row, column } = cursorPosition
  83. this.syncToPdfInFlight = true
  84. $http({
  85. url: `/project/${ide.project_id}/sync/code`,
  86. method: 'GET',
  87. params: {
  88. file: path,
  89. line: row + 1,
  90. column,
  91. clsiserverid: ide.$scope.pdf.clsiServerId,
  92. },
  93. })
  94. .then(response => {
  95. this.syncToPdfInFlight = false
  96. const { data } = response
  97. return deferred.resolve(data.pdf || [])
  98. })
  99. .catch(response => {
  100. this.syncToPdfInFlight = false
  101. const error = response.data
  102. return deferred.reject(error)
  103. })
  104. return deferred.promise
  105. },
  106. syncToCode(position, options) {
  107. if (options == null) {
  108. options = {}
  109. }
  110. const deferred = $q.defer()
  111. if (position == null) {
  112. deferred.reject()
  113. return deferred.promise
  114. }
  115. // FIXME: this actually works better if it's halfway across the
  116. // page (or the visible part of the page). Synctex doesn't
  117. // always find the right place in the file when the point is at
  118. // the edge of the page, it sometimes returns the start of the
  119. // next paragraph instead.
  120. const h = position.offset.left
  121. // Compute the vertical position to pass to synctex, which
  122. // works with coordinates increasing from the top of the page
  123. // down. This matches the browser's DOM coordinate of the
  124. // click point, but the pdf position is measured from the
  125. // bottom of the page so we need to invert it.
  126. let v
  127. if (
  128. options.fromPdfPosition &&
  129. (position.pageSize != null ? position.pageSize.height : undefined) !=
  130. null
  131. ) {
  132. v = position.pageSize.height - position.offset.top || 0 // measure from pdf point (inverted)
  133. } else {
  134. v = position.offset.top || 0 // measure from html click position
  135. }
  136. // It's not clear exactly where we should sync to if it wasn't directly
  137. // clicked on, but a little bit down from the very top seems best.
  138. if (options.includeVisualOffset) {
  139. v += 72 // use the same value as in pdfViewer highlighting visual offset
  140. }
  141. this.syncToCodeInFlight = true
  142. $http({
  143. url: `/project/${ide.project_id}/sync/pdf`,
  144. method: 'GET',
  145. params: {
  146. page: position.page + 1,
  147. h: h.toFixed(2),
  148. v: v.toFixed(2),
  149. clsiserverid: ide.$scope.pdf.clsiServerId,
  150. },
  151. })
  152. .then(response => {
  153. this.syncToCodeInFlight = false
  154. const { data } = response
  155. if (
  156. data.code != null &&
  157. data.code.length > 0 &&
  158. data.code[0].file !== ''
  159. ) {
  160. const doc = ide.fileTreeManager.findEntityByPath(data.code[0].file)
  161. if (doc == null) {
  162. deferred.reject()
  163. }
  164. return deferred.resolve({ doc, line: data.code[0].line })
  165. } else if (data.code[0].file === '') {
  166. ide.$scope.sync_tex_error = true
  167. setTimeout(() => (ide.$scope.sync_tex_error = false), 4000)
  168. }
  169. })
  170. .catch(response => {
  171. this.syncToCodeInFlight = false
  172. const error = response.data
  173. return deferred.reject(error)
  174. })
  175. return deferred.promise
  176. },
  177. }
  178. })