Skip to content

Commit a8de2ea

Browse files
committed
Support reconfiguration of an LRLanguage's language data
FEATURE: `LRLanguage.reconfigure` now supports changing the language data associated with the language. See https://discuss.codemirror.net/t/dash-separated-kebab-case-selection-too-inclusive/9619
1 parent 51cdc78 commit a8de2ea

File tree

3 files changed

+59
-9
lines changed

3 files changed

+59
-9
lines changed

src/language.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,11 @@ function topNodeAt(state: EditorState, pos: number, side: -1 | 0 | 1) {
166166
/// [LR parsers](https://lezer.codemirror.net/docs/ref#lr.LRParser)
167167
/// parsers.
168168
export class LRLanguage extends Language {
169-
private constructor(data: Facet<{[name: string]: any}>, readonly parser: LRParser, name?: string) {
170-
super(data, parser, [], name)
169+
private constructor(data: Facet<{[name: string]: any}>,
170+
readonly parser: LRParser,
171+
private languageData: {[name: string]: any} | null,
172+
name?: string) {
173+
super(data, parser, languageData ? [data.of(languageData)] : [], name)
171174
}
172175

173176
/// Define a language from a parser.
@@ -182,16 +185,29 @@ export class LRLanguage extends Language {
182185
/// to register for this language.
183186
languageData?: {[name: string]: any}
184187
}) {
185-
let data = defineLanguageFacet(spec.languageData)
188+
let data = defineLanguageFacet()
186189
return new LRLanguage(data, spec.parser.configure({
187190
props: [languageDataProp.add(type => type.isTop ? data : undefined)]
188-
}), spec.name)
191+
}), spec.languageData || null, spec.name)
189192
}
190193

191194
/// Create a new instance of this language with a reconfigured
192-
/// version of its parser and optionally a new name.
193-
configure(options: ParserConfig, name?: string): LRLanguage {
194-
return new LRLanguage(this.data, this.parser.configure(options), name || this.name)
195+
/// version of its parser, language data, and name.
196+
///
197+
/// When `languageData` is given, any property set to `undefined`
198+
/// in it will be removed from the language's
199+
/// [data](#state.EditorState.languageDataAt), any other property
200+
/// is added.
201+
configure(options: ParserConfig & {languageData?: {[name: string]: any}}, name?: string): LRLanguage {
202+
let {languageData} = this
203+
if (options.languageData) {
204+
languageData = {}
205+
for (let prop in options.languageData)
206+
if (options.languageData[prop] !== undefined) languageData[prop] = options.languageData[prop]
207+
for (let prop in this.languageData)
208+
if (!(prop in options.languageData)) languageData[prop] = this.languageData[prop]
209+
}
210+
return new LRLanguage(this.data, this.parser.configure(options), languageData, name || this.name)
195211
}
196212

197213
get allowsNesting() { return this.parser.hasWrappers() }

src/stream-parser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,14 @@ export class StreamLanguage<State> extends Language {
9191
topNode: NodeType
9292

9393
private constructor(parser: StreamParser<State>) {
94-
let data = defineLanguageFacet(parser.languageData)
94+
let data = defineLanguageFacet()
9595
let p = fullParser(parser), self: StreamLanguage<State>
9696
let impl = new class extends Parser {
9797
createParse(input: Input, fragments: readonly TreeFragment[], ranges: readonly {from: number, to: number}[]) {
9898
return new Parse(self, input, fragments, ranges)
9999
}
100100
}
101-
super(data, impl, [], parser.name)
101+
super(data, impl, parser.languageData ? [data.of(parser.languageData)] : [], parser.name)
102102
this.topNode = docID(data, this)
103103
self = this
104104
this.streamParser = p

test/test-language.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import ist from "ist"
2+
import {LRLanguage, syntaxTree} from "@codemirror/language"
3+
import {EditorState} from "@codemirror/state"
4+
import {parser} from "@lezer/javascript"
5+
6+
describe("LRLanguage", () => {
7+
it("parses the document", () => {
8+
let lang = LRLanguage.define({name: "js", parser})
9+
let state = EditorState.create({doc: "x = 2", extensions: lang})
10+
ist(syntaxTree(state).topNode.name, "Script")
11+
})
12+
13+
it("can be reconfigured", () => {
14+
let lang = LRLanguage.define({name: "js", parser})
15+
let lang2 = lang.configure({dialect: "jsx"})
16+
let state = EditorState.create({doc: "x = <foo/>", extensions: lang2})
17+
ist(syntaxTree(state).resolve(6).name, "JSXIdentifier")
18+
})
19+
20+
it("associates language data", () => {
21+
let lang = LRLanguage.define({name: "js", parser, languageData: {foo: 22}})
22+
let state = EditorState.create({extensions: lang})
23+
ist(state.languageDataAt("foo", 0).join(","), "22")
24+
})
25+
26+
it("can reconfigure associated language data", () => {
27+
let lang = LRLanguage.define({name: "js", parser, languageData: {foo: 22, bar: 10}})
28+
let lang2 = lang.configure({languageData: {foo: undefined, baz: 11}})
29+
let state = EditorState.create({extensions: lang2})
30+
ist(state.languageDataAt("foo", 0).length, 0)
31+
ist(state.languageDataAt("bar", 0).join(","), "10")
32+
ist(state.languageDataAt("baz", 0).join(","), "11")
33+
})
34+
})

0 commit comments

Comments
 (0)