PdfController.coffee 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. define [
  2. "base"
  3. "libs/latex-log-parser"
  4. ], (App, LogParser) ->
  5. App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking) ->
  6. autoCompile = true
  7. $scope.$on "project:joined", () ->
  8. return if !autoCompile
  9. autoCompile = false
  10. $scope.recompile(isAutoCompile: true)
  11. $scope.hasPremiumCompile = $scope.project.features.compileGroup == "priority"
  12. sendCompileRequest = (options = {}) ->
  13. url = "/project/#{$scope.project_id}/compile"
  14. if options.isAutoCompile
  15. url += "?auto_compile=true"
  16. return $http.post url, {
  17. settingsOverride:
  18. rootDoc_id: options.rootDocOverride_id or null
  19. _csrf: window.csrfToken
  20. }
  21. parseCompileResponse = (response) ->
  22. # Reset everything
  23. $scope.pdf.error = false
  24. $scope.pdf.timedout = false
  25. $scope.pdf.failure = false
  26. $scope.pdf.uncompiled = false
  27. $scope.pdf.url = null
  28. if response.status == "timedout"
  29. $scope.pdf.timedout = true
  30. else if response.status == "autocompile-backoff"
  31. $scope.pdf.uncompiled = true
  32. else if response.status == "failure"
  33. $scope.pdf.failure = true
  34. fetchLogs()
  35. else if response.status == "success"
  36. $scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf?cache_bust=#{Date.now()}"
  37. fetchLogs()
  38. IGNORE_FILES = ["output.fls", "output.fdb_latexmk"]
  39. $scope.pdf.outputFiles = []
  40. for file in response.outputFiles
  41. if IGNORE_FILES.indexOf(file.path) == -1
  42. # Turn 'output.blg' into 'blg file'.
  43. if file.path.match(/^output\./)
  44. file.name = "#{file.path.replace(/^output\./, "")} file"
  45. else
  46. file.name = file.path
  47. $scope.pdf.outputFiles.push file
  48. fetchLogs = () ->
  49. $http.get "/project/#{$scope.project_id}/output/output.log"
  50. .success (log) ->
  51. $scope.pdf.rawLog = log
  52. logEntries = LogParser.parse(log, ignoreDuplicates: true)
  53. $scope.pdf.logEntries = logEntries
  54. $scope.pdf.logEntries.all = logEntries.errors.concat(logEntries.warnings).concat(logEntries.typesetting)
  55. $scope.pdf.logEntryAnnotations = {}
  56. for entry in logEntries.all
  57. entry.file = normalizeFilePath(entry.file)
  58. entity = ide.fileTreeManager.findEntityByPath(entry.file)
  59. if entity?
  60. $scope.pdf.logEntryAnnotations[entity.id] ||= []
  61. $scope.pdf.logEntryAnnotations[entity.id].push {
  62. row: entry.line - 1
  63. type: if entry.level == "error" then "error" else "warning"
  64. text: entry.message
  65. }
  66. .error () ->
  67. $scope.pdf.logEntries = []
  68. $scope.pdf.rawLog = ""
  69. getRootDocOverride_id = () ->
  70. doc = ide.editorManager.getCurrentDocValue()
  71. return null if !doc?
  72. for line in doc.split("\n")
  73. match = line.match /(.*)\\documentclass/
  74. if match and !match[1].match /%/
  75. return ide.editorManager.getCurrentDocId()
  76. return null
  77. normalizeFilePath = (path) ->
  78. path = path.replace(/^(.*)\/compiles\/[0-9a-f]{24}\/(\.\/)?/, "")
  79. path = path.replace(/^\/compile\//, "")
  80. rootDocDirname = ide.fileTreeManager.getRootDocDirname()
  81. if rootDocDirname?
  82. path = path.replace(/^\.\//, rootDocDirname + "/")
  83. return path
  84. compileCount = 0
  85. $scope.recompile = (options = {}) ->
  86. return if $scope.pdf.compiling
  87. $scope.pdf.compiling = true
  88. if !options.isAutoCompile
  89. compileCount++
  90. if compileCount == 1
  91. event_tracking.send('editor-interaction', 'single-compile')
  92. else if compileCount == 3
  93. event_tracking.send('editor-interaction', 'multi-compile')
  94. options.rootDocOverride_id = getRootDocOverride_id()
  95. sendCompileRequest(options)
  96. .success (data) ->
  97. $scope.pdf.view = "pdf"
  98. $scope.pdf.compiling = false
  99. parseCompileResponse(data)
  100. .error () ->
  101. $scope.pdf.compiling = false
  102. $scope.pdf.error = true
  103. # This needs to be public.
  104. ide.$scope.recompile = $scope.recompile
  105. $scope.clearCache = () ->
  106. $http {
  107. url: "/project/#{$scope.project_id}/output"
  108. method: "DELETE"
  109. headers:
  110. "X-Csrf-Token": window.csrfToken
  111. }
  112. $scope.toggleLogs = () ->
  113. if !$scope.pdf.view? or $scope.pdf.view == "pdf"
  114. $scope.pdf.view = "logs"
  115. else
  116. $scope.pdf.view = "pdf"
  117. $scope.showPdf = () ->
  118. $scope.pdf.view = "pdf"
  119. $scope.toggleRawLog = () ->
  120. $scope.pdf.showRawLog = !$scope.pdf.showRawLog
  121. $scope.openOutputFile = (file) ->
  122. window.open("/project/#{$scope.project_id}/output/#{file.path}")
  123. $scope.openClearCacheModal = () ->
  124. modalInstance = $modal.open(
  125. templateUrl: "clearCacheModalTemplate"
  126. controller: "ClearCacheModalController"
  127. scope: $scope
  128. )
  129. $scope.syncToCode = (position) ->
  130. synctex
  131. .syncToCode(position)
  132. .then (data) ->
  133. {doc, line} = data
  134. ide.editorManager.openDoc(doc, gotoLine: line)
  135. $scope.switchToFlatLayout = () ->
  136. $scope.ui.pdfLayout = 'flat'
  137. $scope.ui.view = 'pdf'
  138. $.localStorage "pdf.layout", "flat"
  139. $scope.switchToSideBySideLayout = () ->
  140. $scope.ui.pdfLayout = 'sideBySide'
  141. $scope.ui.view = 'editor'
  142. $.localStorage "pdf.layout", "split"
  143. if pdfLayout = $.localStorage("pdf.layout")
  144. $scope.switchToSideBySideLayout() if pdfLayout == "split"
  145. $scope.switchToFlatLayout() if pdfLayout == "flat"
  146. else
  147. $scope.switchToSideBySideLayout()
  148. App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
  149. synctex =
  150. syncToPdf: (cursorPosition) ->
  151. deferred = $q.defer()
  152. doc_id = ide.editorManager.getCurrentDocId()
  153. if !doc_id?
  154. deferred.reject()
  155. return deferred.promise
  156. doc = ide.fileTreeManager.findEntityById(doc_id)
  157. if !doc?
  158. deferred.reject()
  159. return deferred.promise
  160. path = ide.fileTreeManager.getEntityPath(doc)
  161. if !path?
  162. deferred.reject()
  163. return deferred.promise
  164. # If the root file is folder/main.tex, then synctex sees the
  165. # path as folder/./main.tex
  166. rootDocDirname = ide.fileTreeManager.getRootDocDirname()
  167. if rootDocDirname? and rootDocDirname != ""
  168. path = path.replace(RegExp("^#{rootDocDirname}"), "#{rootDocDirname}/.")
  169. {row, column} = cursorPosition
  170. $http({
  171. url: "/project/#{ide.project_id}/sync/code",
  172. method: "GET",
  173. params: {
  174. file: path
  175. line: row + 1
  176. column: column
  177. }
  178. })
  179. .success (data) ->
  180. deferred.resolve(data.pdf or [])
  181. .error (error) ->
  182. deferred.reject(error)
  183. return deferred.promise
  184. syncToCode: (position, options = {}) ->
  185. deferred = $q.defer()
  186. if !position?
  187. deferred.reject()
  188. return deferred.promise
  189. # It's not clear exactly where we should sync to if it wasn't directly
  190. # clicked on, but a little bit down from the very top seems best.
  191. if options.includeVisualOffset
  192. position.offset.top = position.offset.top + 80
  193. $http({
  194. url: "/project/#{ide.project_id}/sync/pdf",
  195. method: "GET",
  196. params: {
  197. page: position.page + 1
  198. h: position.offset.left.toFixed(2)
  199. v: position.offset.top.toFixed(2)
  200. }
  201. })
  202. .success (data) ->
  203. if data.code? and data.code.length > 0
  204. doc = ide.fileTreeManager.findEntityByPath(data.code[0].file)
  205. return if !doc?
  206. deferred.resolve({doc: doc, line: data.code[0].line})
  207. .error (error) ->
  208. deferred.reject(error)
  209. return deferred.promise
  210. return synctex
  211. ]
  212. App.controller "PdfSynctexController", ["$scope", "synctex", "ide", ($scope, synctex, ide) ->
  213. @cursorPosition = null
  214. ide.$scope.$on "cursor:editor:update", (event, @cursorPosition) =>
  215. $scope.syncToPdf = () =>
  216. return if !@cursorPosition?
  217. synctex
  218. .syncToPdf(@cursorPosition)
  219. .then (highlights) ->
  220. $scope.pdf.highlights = highlights
  221. $scope.syncToCode = () ->
  222. synctex
  223. .syncToCode($scope.pdf.position, includeVisualOffset: true)
  224. .then (data) ->
  225. {doc, line} = data
  226. ide.editorManager.openDoc(doc, gotoLine: line)
  227. ]
  228. App.controller "PdfLogEntryController", ["$scope", "ide", ($scope, ide) ->
  229. $scope.openInEditor = (entry) ->
  230. entity = ide.fileTreeManager.findEntityByPath(entry.file)
  231. return if !entity? or entity.type != "doc"
  232. if entry.line?
  233. line = entry.line
  234. ide.editorManager.openDoc(entity, gotoLine: line)
  235. ]
  236. App.controller 'ClearCacheModalController', ["$scope", "$modalInstance", ($scope, $modalInstance) ->
  237. $scope.state =
  238. inflight: false
  239. $scope.clear = () ->
  240. $scope.state.inflight = true
  241. $scope
  242. .clearCache()
  243. .then () ->
  244. $scope.state.inflight = false
  245. $modalInstance.close()
  246. $scope.cancel = () ->
  247. $modalInstance.dismiss('cancel')
  248. ]