latex-folding.test.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import { expect } from 'chai'
  2. import { EditorState, Text } from '@codemirror/state'
  3. import { LaTeXLanguage } from '../../../../../../frontend/js/features/source-editor/languages/latex/latex-language'
  4. import {
  5. ensureSyntaxTree,
  6. foldNodeProp,
  7. LanguageSupport,
  8. } from '@codemirror/language'
  9. import { EditorView } from '@codemirror/view'
  10. const latex = new LanguageSupport(LaTeXLanguage)
  11. const makeView = (lines: string[]): EditorView => {
  12. const text = Text.of(lines)
  13. const view = new EditorView({
  14. state: EditorState.create({
  15. doc: text,
  16. extensions: [latex],
  17. }),
  18. })
  19. return view
  20. }
  21. type Fold = { from: number; to: number }
  22. const _getFolds = (view: EditorView) => {
  23. const ranges: Fold[] = []
  24. const tree = ensureSyntaxTree(view.state, view.state.doc.length)
  25. if (!tree) {
  26. throw new Error("Couldn't get Syntax Tree")
  27. }
  28. tree.iterate({
  29. enter: nodeRef => {
  30. const prop = nodeRef.type.prop(foldNodeProp)
  31. if (prop) {
  32. const hasFold = prop(nodeRef.node, view.state)
  33. if (hasFold) {
  34. ranges.push({ from: hasFold.from, to: hasFold.to })
  35. }
  36. }
  37. },
  38. })
  39. return ranges
  40. }
  41. describe('CodeMirror LaTeX-folding', function () {
  42. describe('With empty document', function () {
  43. let view: EditorView, content: string[]
  44. beforeEach(function () {
  45. content = ['']
  46. view = makeView(content)
  47. })
  48. it('should not produce any folds', function () {
  49. const folds = _getFolds(view)
  50. expect(folds).to.be.empty
  51. })
  52. })
  53. describe('Sectioning command folding', function () {
  54. describe('with no foldable sections', function () {
  55. let view: EditorView, content: string[]
  56. beforeEach(function () {
  57. content = ['hello', 'test']
  58. view = makeView(content)
  59. })
  60. it('should not produce any folds', function () {
  61. const folds = _getFolds(view)
  62. expect(folds).to.be.empty
  63. })
  64. })
  65. describe('with one foldable section', function () {
  66. let view: EditorView, content: string[]
  67. beforeEach(function () {
  68. content = ['hello', '\\section{one}', 'a', 'b', 'c']
  69. view = makeView(content)
  70. })
  71. it('should produce one fold', function () {
  72. const folds = _getFolds(view)
  73. expect(folds.length).to.equal(1)
  74. })
  75. it('should fold from the section line to last line', function () {
  76. const folds = _getFolds(view)
  77. const fold = folds[0]
  78. expect(view.state.doc.lineAt(fold.from).number).to.equal(2)
  79. expect(view.state.doc.lineAt(fold.to).number).to.equal(
  80. view.state.doc.lines
  81. )
  82. })
  83. })
  84. describe('with two foldable sections', function () {
  85. let view: EditorView, content: string[]
  86. beforeEach(function () {
  87. content = [
  88. 'hello',
  89. '\\section{one}',
  90. 'a',
  91. 'b',
  92. '\\section{two}',
  93. 'c',
  94. 'd',
  95. ]
  96. view = makeView(content)
  97. })
  98. it('should produce two folds', function () {
  99. const folds = _getFolds(view)
  100. expect(folds.length).to.equal(2)
  101. expect(view.state.doc.lineAt(folds[0].from).number).to.equal(2)
  102. expect(view.state.doc.lineAt(folds[0].to).number).to.equal(4)
  103. expect(view.state.doc.lineAt(folds[1].from).number).to.equal(5)
  104. expect(view.state.doc.lineAt(folds[1].to).number).to.equal(
  105. view.state.doc.lines
  106. )
  107. })
  108. })
  109. describe('with realistic nesting', function () {
  110. let view: EditorView, content: string[]
  111. beforeEach(function () {
  112. content = [
  113. 'hello',
  114. '\\chapter{1}',
  115. ' a',
  116. ' \\section{1.1}',
  117. ' a',
  118. ' \\subsection{1.1.1}',
  119. ' a',
  120. ' \\section{1.2}',
  121. ' a',
  122. ' \\subsection{1.2.1}',
  123. ' a',
  124. '\\chapter{2}',
  125. ' a',
  126. ' \\section{2.1}',
  127. ' a',
  128. ' \\section{2.2}',
  129. ' a',
  130. ]
  131. view = makeView(content)
  132. })
  133. it('should produce many folds', function () {
  134. const folds = _getFolds(view)
  135. expect(folds.length).to.equal(8)
  136. const foldDescriptions = folds.map(fold => {
  137. const fromLine = view.state.doc.lineAt(fold.from).number
  138. const toLine = view.state.doc.lineAt(fold.to).number
  139. return { fromLine, toLine }
  140. })
  141. expect(foldDescriptions).to.deep.equal([
  142. { fromLine: 2, toLine: 11 },
  143. { fromLine: 4, toLine: 7 },
  144. { fromLine: 6, toLine: 7 },
  145. { fromLine: 8, toLine: 11 },
  146. { fromLine: 10, toLine: 11 },
  147. { fromLine: 12, toLine: 17 },
  148. { fromLine: 14, toLine: 15 },
  149. { fromLine: 16, toLine: 17 },
  150. ])
  151. })
  152. })
  153. })
  154. describe('Environment folding', function () {
  155. describe('with single environment', function () {
  156. let view: EditorView, content: string[]
  157. beforeEach(function () {
  158. content = ['\\begin{foo}', 'content', '\\end{foo}']
  159. view = makeView(content)
  160. })
  161. it('should fold the environment', function () {
  162. const folds = _getFolds(view)
  163. expect(folds.length).to.equal(1)
  164. expect(folds).to.deep.equal([{ from: 11, to: 20 }])
  165. })
  166. })
  167. describe('with nested environment', function () {
  168. let view: EditorView, content: string[]
  169. beforeEach(function () {
  170. content = [
  171. '\\begin{foo}',
  172. '\\begin{bar}',
  173. 'content',
  174. '\\end{bar}',
  175. '\\end{foo}',
  176. ]
  177. view = makeView(content)
  178. })
  179. it('should fold the environment', function () {
  180. const folds = _getFolds(view)
  181. expect(folds.length).to.equal(2)
  182. expect(folds).to.deep.equal([
  183. { from: 11, to: 42 },
  184. { from: 23, to: 32 },
  185. ])
  186. })
  187. })
  188. })
  189. describe('Comment folding', function () {
  190. describe('with a single set of comments', function () {
  191. let view: EditorView, content: string[]
  192. beforeEach(function () {
  193. content = ['Hello', '% {', 'this is folded', '% }', 'End']
  194. view = makeView(content)
  195. })
  196. it('should fold the region marked by comments', function () {
  197. const folds = _getFolds(view)
  198. expect(folds.length).to.equal(1)
  199. expect(folds).to.deep.equal([{ from: 9, to: 27 }])
  200. })
  201. })
  202. describe('with several sets of comments', function () {
  203. let view: EditorView, content: string[]
  204. beforeEach(function () {
  205. content = [
  206. 'Hello',
  207. '% {',
  208. 'this is folded',
  209. '% }',
  210. '',
  211. '% {',
  212. 'and this also',
  213. '% }',
  214. 'End',
  215. ]
  216. view = makeView(content)
  217. })
  218. it('should fold both regions marked by comments', function () {
  219. const folds = _getFolds(view)
  220. expect(folds.length).to.equal(2)
  221. expect(folds).to.deep.equal([
  222. { from: 9, to: 27 },
  223. { from: 33, to: 50 },
  224. ])
  225. })
  226. })
  227. describe('with nested sets of comments', function () {
  228. let view: EditorView, content: string[]
  229. beforeEach(function () {
  230. content = [
  231. 'Hello',
  232. '% {',
  233. 'one',
  234. '% {',
  235. 'two',
  236. '% {',
  237. 'three',
  238. '% }',
  239. 'two',
  240. '% }',
  241. 'one',
  242. '% }',
  243. 'End',
  244. ]
  245. view = makeView(content)
  246. })
  247. it('should fold all the regions marked by comments, with nesting', function () {
  248. const folds = _getFolds(view)
  249. expect(folds.length).to.equal(3)
  250. expect(folds).to.deep.equal([
  251. { from: 9, to: 50 },
  252. { from: 17, to: 42 },
  253. { from: 25, to: 34 },
  254. ])
  255. })
  256. })
  257. describe('with fold comment spanning entire document', function () {
  258. let view: EditorView, content: string[]
  259. beforeEach(function () {
  260. content = ['% {', 'Hello', '% }']
  261. view = makeView(content)
  262. })
  263. it('should fold', function () {
  264. const folds = _getFolds(view)
  265. expect(folds.length).to.equal(1)
  266. expect(folds).to.deep.equal([{ from: 3, to: 12 }])
  267. })
  268. })
  269. describe('with fold comment at start of document', function () {
  270. let view: EditorView, content: string[]
  271. beforeEach(function () {
  272. content = ['% {', 'Hello', '% }', 'Test']
  273. view = makeView(content)
  274. })
  275. it('should fold', function () {
  276. const folds = _getFolds(view)
  277. expect(folds.length).to.equal(1)
  278. expect(folds).to.deep.equal([{ from: 3, to: 12 }])
  279. })
  280. })
  281. describe('with fold comment at end of document', function () {
  282. let view: EditorView, content: string[]
  283. beforeEach(function () {
  284. content = ['Test', '% {', 'Hello', '% }']
  285. view = makeView(content)
  286. })
  287. it('should fold', function () {
  288. const folds = _getFolds(view)
  289. expect(folds.length).to.equal(1)
  290. expect(folds).to.deep.equal([{ from: 8, to: 17 }])
  291. })
  292. })
  293. })
  294. })