|
| 1 | +/** @typedef {import('@lezer/common').SyntaxNode} SyntaxNode */ |
| 2 | + |
1 | 3 | import { |
| 4 | + defineLanguageFacet, |
| 5 | + foldNodeProp, |
| 6 | + foldService, |
2 | 7 | Language, |
3 | 8 | LanguageSupport, |
4 | | - defineLanguageFacet, |
| 9 | + syntaxTree, |
5 | 10 | } from '@codemirror/language' |
6 | | -import { parser, tags as t } from '@orgajs/lezer' |
| 11 | +import { NodeProp } from '@lezer/common' |
| 12 | +import { parser as baseParser, tags as t } from '@orgajs/lezer' |
7 | 13 |
|
8 | 14 | export const tags = t |
9 | 15 |
|
10 | 16 | const data = defineLanguageFacet({}) |
11 | 17 |
|
| 18 | +// --- folding --- |
| 19 | +/** @type {NodeProp<number>} */ |
| 20 | +const headlineProp = new NodeProp() |
| 21 | + |
| 22 | +/** |
| 23 | + * @param {import('@lezer/common').NodeType} type |
| 24 | + */ |
| 25 | +function isHeadline(type) { |
| 26 | + let match = /^headline(\d)$/.exec(type.name) |
| 27 | + return match ? +match[1] : undefined |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * @param {SyntaxNode} headerNode |
| 32 | + * @param {number} level |
| 33 | + */ |
| 34 | +function findSectionEnd(headerNode, level) { |
| 35 | + let last = headerNode |
| 36 | + for (;;) { |
| 37 | + let next = last.nextSibling |
| 38 | + if (!next) break |
| 39 | + const headline = isHeadline(next.type) |
| 40 | + if (headline != null) { |
| 41 | + if (headline <= level) { |
| 42 | + return next.from - 1 // escape the newline. TODO: is this safe? |
| 43 | + } |
| 44 | + } |
| 45 | + last = next |
| 46 | + } |
| 47 | + return last.to |
| 48 | +} |
| 49 | + |
| 50 | +const headerIndent = foldService.of((state, start, end) => { |
| 51 | + for ( |
| 52 | + let /** @type {SyntaxNode | null} */ node = syntaxTree(state).resolveInner( |
| 53 | + end, |
| 54 | + -1 |
| 55 | + ); |
| 56 | + node; |
| 57 | + node = node.parent |
| 58 | + ) { |
| 59 | + if (node.from < start) break |
| 60 | + let headline = node.type.prop(headlineProp) |
| 61 | + if (headline == null) continue |
| 62 | + let upto = findSectionEnd(node, headline) |
| 63 | + if (upto > end) return { from: end, to: upto } |
| 64 | + } |
| 65 | + return null |
| 66 | +}) |
| 67 | + |
| 68 | +const parser = baseParser.configure({ |
| 69 | + props: [ |
| 70 | + foldNodeProp.add((type) => { |
| 71 | + const m = isHeadline(type) |
| 72 | + if (!m) return undefined |
| 73 | + return (tree, state) => ({ |
| 74 | + from: state.doc.lineAt(tree.from).to, |
| 75 | + to: state.doc.lineAt(tree.from).to, |
| 76 | + }) |
| 77 | + }), |
| 78 | + headlineProp.add(isHeadline), |
| 79 | + ], |
| 80 | +}) |
| 81 | + |
12 | 82 | /** |
13 | 83 | * @param {any} parser |
14 | 84 | */ |
15 | 85 | function mkLang(parser) { |
16 | | - return new Language(data, parser, [], 'org') |
| 86 | + return new Language(data, parser, [headerIndent], 'org') |
17 | 87 | } |
18 | 88 |
|
19 | 89 | export function org() { |
|
0 commit comments