@bhsd/codemirror-mediawiki 2.30.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fold.d.ts CHANGED
@@ -1,11 +1,14 @@
1
- import { StateField } from '@codemirror/state';
2
- import type { EditorView, Tooltip, Command } from '@codemirror/view';
3
- import type { EditorState, Extension } from '@codemirror/state';
1
+ import { GutterMarker, ViewPlugin } from '@codemirror/view';
2
+ import { RangeSet } from '@codemirror/state';
3
+ import type { EditorView, Tooltip, ViewUpdate, BlockInfo, Command } from '@codemirror/view';
4
+ import type { EditorState, StateEffect, Extension } from '@codemirror/state';
4
5
  import type { SyntaxNode, Tree } from '@lezer/common';
5
6
  export interface DocRange {
6
7
  from: number;
7
8
  to: number;
8
9
  }
10
+ declare type AnchorUpdate = (pos: number, range: DocRange) => number;
11
+ export declare const updateSelection: AnchorUpdate;
9
12
  /**
10
13
  * Update the stack of opening (+) or closing (-) brackets
11
14
  * @param state
@@ -20,12 +23,56 @@ export declare const braceStackUpdate: (state: EditorState, node: SyntaxNode) =>
20
23
  * @param refOnly 是否仅检查`<ref>`标签
21
24
  */
22
25
  export declare const foldable: (state: EditorState, posOrNode: number | SyntaxNode, tree?: Tree | null, refOnly?: boolean) => DocRange | false;
26
+ /**
27
+ * 创建折叠提示
28
+ * @param state
29
+ */
30
+ export declare const create: (state: EditorState) => Tooltip | null;
31
+ /**
32
+ * 执行折叠
33
+ * @param view
34
+ * @param effects 折叠
35
+ * @param anchor 光标位置
36
+ */
37
+ export declare const execute: (view: EditorView, effects: StateEffect<DocRange>[], anchor: number) => boolean;
38
+ /**
39
+ * The rightmost position of all selections, to be updated with folding
40
+ * @param state
41
+ */
42
+ export declare const getAnchor: (state: EditorState) => number;
43
+ /**
44
+ * 折叠所有模板
45
+ * @param state
46
+ * @param tree 语法树
47
+ * @param effects 折叠
48
+ * @param node 语法树节点
49
+ * @param end 终止位置
50
+ * @param anchor 光标位置
51
+ * @param update 更新光标位置
52
+ * @param refOnly 是否仅检查`<ref>`标签
53
+ */
54
+ export declare const traverse: (state: EditorState, tree: Tree, effects: StateEffect<DocRange>[], node: SyntaxNode | null, end: number, anchor: number, update: AnchorUpdate, refOnly?: boolean) => number;
55
+ export declare class FoldMarker extends GutterMarker {
56
+ readonly open: boolean;
57
+ constructor(open: boolean);
58
+ eq(other: this): boolean;
59
+ toDOM({ state }: EditorView): HTMLSpanElement;
60
+ }
61
+ export declare const findFold: ({ state }: EditorView, line: BlockInfo) => DocRange | undefined;
23
62
  export declare const foldableLine: ({ state, viewport: { to: end }, viewportLineBlocks }: EditorView, { from: f, to: t }: DocRange) => DocRange | false;
63
+ export declare const markers: ViewPlugin<{
64
+ markers: RangeSet<FoldMarker>;
65
+ update({ docChanged, viewportChanged, startState, state, view }: ViewUpdate): void;
66
+ }, undefined>;
67
+ /**
68
+ * 生成折叠命令
69
+ * @param refOnly 是否仅检查`<ref>`标签
70
+ */
71
+ export declare const foldCommand: (refOnly?: boolean) => Command;
24
72
  export declare const foldRef: Command;
25
- declare const _default: [(e?: Extension | undefined) => Extension, {
26
- mediawiki: (Extension | StateField<Tooltip | null>)[];
27
- }];
73
+ declare const _default: [(e?: Extension | undefined) => Extension];
28
74
  export default _default;
75
+ export declare const mediaWikiFold: Extension;
29
76
  /**
30
77
  * 点击提示折叠模板参数
31
78
  * @param view
package/dist/fold.js CHANGED
@@ -4,24 +4,32 @@ import { syntaxTree, ensureSyntaxTree, foldEffect, unfoldEffect, foldedRanges, u
4
4
  import { getRegex } from '@bhsd/common';
5
5
  import { tokens } from './config';
6
6
  import { matchTag, getTag } from './matchTag';
7
- const getExtRegex = getRegex(tag => new RegExp(`mw-tag-${tag}(?![a-z])`, 'u'));
8
- const updateSelection = (pos, { to }) => Math.max(pos, to), updateAll = (pos, { from, to }) => from <= pos && to > pos ? to : pos;
7
+ const getExtRegex = /* @__PURE__ */ getRegex(tag => new RegExp(`mw-tag-${tag}(?![a-z])`, 'u'));
8
+ export const updateSelection = (pos, { to }) => Math.max(pos, to);
9
+ const updateAll = (pos, { from, to }) => from <= pos && to > pos ? to : pos;
9
10
  /**
10
11
  * Check if a SyntaxNode is among the specified components
11
12
  * @param keys The keys of the tokens to check
12
13
  */
13
- const isComponent = (keys) => ({ name }) => keys.some(key => name.includes(tokens[key])),
14
+ const isComponent = (keys) => (node) => keys.some(key => node?.name.includes(tokens[key])),
14
15
  /** Check if a SyntaxNode is a template bracket (`{{` or `}}`) */
15
- isTemplateBracket = isComponent(['templateBracket', 'parserFunctionBracket']),
16
+ isTemplateBracket = /* @__PURE__ */ isComponent(['templateBracket', 'parserFunctionBracket']),
17
+ /** Check if a SyntaxNode is a template name */
18
+ isTemplateName = /* @__PURE__ */ isComponent(['templateName', 'parserFunctionName']),
16
19
  /** Check if a SyntaxNode is a template delimiter (`|` or `:`) */
17
- isDelimiter = isComponent(['templateDelimiter', 'parserFunctionDelimiter']),
20
+ isDelimiter = /* @__PURE__ */ isComponent(['templateDelimiter', 'parserFunctionDelimiter']),
21
+ /**
22
+ * Check if a SyntaxNode is a template delimiter (`|` or `:`), excluding `subst:` and `safesubst:`
23
+ * @param node SyntaxNode
24
+ */
25
+ isTemplateDelimiter = (node) => isDelimiter(node) && !isTemplateName(node.nextSibling),
18
26
  /**
19
27
  * Check if a SyntaxNode is part of a template, except for the brackets
20
28
  * @param node 语法树节点
21
29
  */
22
30
  isTemplate = (node) => /-(?:template|ext)[a-z\d-]+ground/u.test(node.name) && !isTemplateBracket(node),
23
31
  /** Check if a SyntaxNode is an extension tag bracket (`<` or `>`) */
24
- isExtBracket = isComponent(['extTagBracket']),
32
+ isExtBracket = /* @__PURE__ */ isComponent(['extTagBracket']),
25
33
  /**
26
34
  * Check if a SyntaxNode is part of a extension tag
27
35
  * @param node 语法树节点
@@ -88,7 +96,7 @@ export const foldable = (state, posOrNode, tree, refOnly = false) => {
88
96
  }
89
97
  let { prevSibling, nextSibling } = node,
90
98
  /** The stack of opening (+) or closing (-) brackets */ stack = 1,
91
- /** The first delimiter */ delimiter = isDelimiter(node) ? node : null,
99
+ /** The first delimiter */ delimiter = isTemplateDelimiter(node) ? node : null,
92
100
  /** The start of the closing bracket */ to = 0;
93
101
  while (nextSibling) {
94
102
  if (isTemplateBracket(nextSibling)) {
@@ -103,7 +111,7 @@ export const foldable = (state, posOrNode, tree, refOnly = false) => {
103
111
  }
104
112
  stack += lbrace;
105
113
  }
106
- else if (!delimiter && stack === 1 && isDelimiter(nextSibling)) {
114
+ else if (!delimiter && stack === 1 && isTemplateDelimiter(nextSibling)) {
107
115
  // The first delimiter of the current template so far
108
116
  delimiter = nextSibling;
109
117
  }
@@ -124,7 +132,7 @@ export const foldable = (state, posOrNode, tree, refOnly = false) => {
124
132
  }
125
133
  stack += rbrace;
126
134
  }
127
- else if (stack === -1 && isDelimiter(prevSibling)) {
135
+ else if (stack === -1 && isTemplateDelimiter(prevSibling)) {
128
136
  // The first delimiter of the current template so far
129
137
  delimiter = prevSibling;
130
138
  }
@@ -137,7 +145,7 @@ export const foldable = (state, posOrNode, tree, refOnly = false) => {
137
145
  * 创建折叠提示
138
146
  * @param state
139
147
  */
140
- const create = (state) => {
148
+ export const create = (state) => {
141
149
  const { selection: { main: { head } } } = state, range = foldable(state, head);
142
150
  if (range) {
143
151
  const { from, to } = range;
@@ -171,7 +179,7 @@ const create = (state) => {
171
179
  * @param effects 折叠
172
180
  * @param anchor 光标位置
173
181
  */
174
- const execute = (view, effects, anchor) => {
182
+ export const execute = (view, effects, anchor) => {
175
183
  if (effects.length > 0) {
176
184
  view.dom.querySelector('.cm-tooltip-fold')?.remove();
177
185
  // Fold the template(s) and update the cursor position
@@ -184,7 +192,7 @@ const execute = (view, effects, anchor) => {
184
192
  * The rightmost position of all selections, to be updated with folding
185
193
  * @param state
186
194
  */
187
- const getAnchor = (state) => Math.max(...state.selection.ranges.map(({ to }) => to));
195
+ export const getAnchor = (state) => Math.max(...state.selection.ranges.map(({ to }) => to));
188
196
  /**
189
197
  * 折叠所有模板
190
198
  * @param state
@@ -196,7 +204,7 @@ const getAnchor = (state) => Math.max(...state.selection.ranges.map(({ to }) =>
196
204
  * @param update 更新光标位置
197
205
  * @param refOnly 是否仅检查`<ref>`标签
198
206
  */
199
- const traverse = (state, tree, effects, node, end, anchor, update, refOnly) => {
207
+ export const traverse = (state, tree, effects, node, end, anchor, update, refOnly) => {
200
208
  while (node && node.from <= end) {
201
209
  /* eslint-disable no-param-reassign */
202
210
  const range = foldable(state, node, tree, refOnly);
@@ -212,7 +220,7 @@ const traverse = (state, tree, effects, node, end, anchor, update, refOnly) => {
212
220
  }
213
221
  return anchor;
214
222
  };
215
- class FoldMarker extends GutterMarker {
223
+ export class FoldMarker extends GutterMarker {
216
224
  constructor(open) {
217
225
  super();
218
226
  this.open = open;
@@ -227,8 +235,8 @@ class FoldMarker extends GutterMarker {
227
235
  return span;
228
236
  }
229
237
  }
230
- const canFold = new FoldMarker(true), canUnfold = new FoldMarker(false);
231
- const findFold = ({ state }, line) => {
238
+ const canFold = /* @__PURE__ */ new FoldMarker(true), canUnfold = /* @__PURE__ */ new FoldMarker(false);
239
+ export const findFold = ({ state }, line) => {
232
240
  let found;
233
241
  state.field(foldState, false)?.between(line.from, line.to, (from, to) => {
234
242
  if (!found && to === line.to) {
@@ -302,7 +310,7 @@ const buildMarkers = (view) => {
302
310
  }
303
311
  return builder.finish();
304
312
  };
305
- const markers = ViewPlugin.fromClass(class {
313
+ export const markers = /* @__PURE__ */ ViewPlugin.fromClass(class {
306
314
  constructor(view) {
307
315
  this.markers = buildMarkers(view);
308
316
  }
@@ -321,129 +329,125 @@ const defaultFoldExtension = [foldGutter(), keymap.of(foldKeymap)];
321
329
  * 生成折叠命令
322
330
  * @param refOnly 是否仅检查`<ref>`标签
323
331
  */
324
- const foldCommand = (refOnly) => view => {
332
+ export const foldCommand = (refOnly) => view => {
325
333
  const { state } = view, tree = syntaxTree(state), effects = [], anchor = traverse(state, tree, effects, tree.topNode.firstChild, Infinity, getAnchor(state), updateAll, refOnly);
326
334
  return execute(view, effects, anchor);
327
335
  };
328
- export const foldRef = foldCommand(true);
329
- export default [
330
- (e = defaultFoldExtension) => e,
331
- {
332
- mediawiki: [
333
- codeFolding({
334
- placeholderDOM(view) {
335
- const element = document.createElement('span');
336
- element.textContent = '';
337
- element.setAttribute('aria-label', 'folded code');
338
- element.title = view.state.phrase('unfold');
339
- element.className = 'cm-foldPlaceholder';
340
- element.addEventListener('click', ({ target }) => {
341
- const pos = view.posAtDOM(target), { state } = view, { selection } = state;
342
- foldedRanges(state).between(pos, pos, (from, to) => {
343
- if (from === pos) {
344
- // Unfold the template and redraw the selections
345
- view.dispatch({ effects: unfoldEffect.of({ from, to }), selection });
346
- }
347
- });
348
- });
349
- return element;
350
- },
351
- }),
352
- /** @see https://codemirror.net/examples/tooltip/ */
353
- StateField.define({
354
- create,
355
- update(tooltip, { state, docChanged, selection }) {
356
- if (docChanged) {
357
- return null;
336
+ export const foldRef = /* @__PURE__ */ foldCommand(true);
337
+ export default [(e = defaultFoldExtension) => e];
338
+ export const mediaWikiFold = /* @__PURE__ */ (() => [
339
+ codeFolding({
340
+ placeholderDOM(view) {
341
+ const element = document.createElement('span');
342
+ element.textContent = '…';
343
+ element.setAttribute('aria-label', 'folded code');
344
+ element.title = view.state.phrase('unfold');
345
+ element.className = 'cm-foldPlaceholder';
346
+ element.addEventListener('click', ({ target }) => {
347
+ const pos = view.posAtDOM(target), { state } = view, { selection } = state;
348
+ foldedRanges(state).between(pos, pos, (from, to) => {
349
+ if (from === pos) {
350
+ // Unfold the template and redraw the selections
351
+ view.dispatch({ effects: unfoldEffect.of({ from, to }), selection });
358
352
  }
359
- return selection ? create(state) : tooltip;
360
- },
361
- provide(f) {
362
- return showTooltip.from(f);
363
- },
364
- }),
365
- keymap.of([
366
- {
367
- // Fold the template at the selection/cursor
368
- key: 'Ctrl-Shift-[',
369
- mac: 'Cmd-Alt-[',
370
- run(view) {
371
- const { state } = view, tree = syntaxTree(state), effects = [];
372
- let anchor = getAnchor(state);
373
- for (const { from, to, empty } of state.selection.ranges) {
374
- let node;
375
- if (empty) {
376
- // No selection, try both sides of the cursor position
377
- node = tree.resolve(from, -1);
378
- }
379
- if (!node || node.name === 'Document') {
380
- node = tree.resolve(from, 1);
381
- }
382
- anchor = traverse(state, tree, effects, node, to, anchor, updateSelection);
383
- }
384
- return execute(view, effects, anchor);
385
- },
386
- },
387
- {
388
- // Fold all templates in the document
389
- key: 'Ctrl-Alt-[',
390
- run: foldCommand(),
391
- },
392
- {
393
- // Fold all `<ref>` tags in the document
394
- key: 'Mod-Alt-,',
395
- run: foldRef,
396
- },
397
- {
398
- // Unfold the template at the selection/cursor
399
- key: 'Ctrl-Shift-]',
400
- mac: 'Cmd-Alt-]',
401
- run(view) {
402
- const { state } = view, { selection } = state, effects = [], folded = foldedRanges(state);
403
- for (const { from, to } of selection.ranges) {
404
- // Unfold any folded range at the selection
405
- folded.between(from, to, (i, j) => {
406
- effects.push(unfoldEffect.of({ from: i, to: j }));
407
- });
408
- }
409
- if (effects.length > 0) {
410
- // Unfold the template(s) and redraw the selections
411
- view.dispatch({ effects, selection });
412
- return true;
413
- }
414
- return false;
415
- },
416
- },
417
- { key: 'Ctrl-Alt-]', run: unfoldAll },
418
- ]),
419
- markers,
420
- gutter({
421
- class: 'cm-foldGutter',
422
- markers(view) {
423
- return view.plugin(markers)?.markers ?? RangeSet.empty;
424
- },
425
- initialSpacer() {
426
- return new FoldMarker(false);
427
- },
428
- domEventHandlers: {
429
- click(view, line) {
430
- const folded = findFold(view, line);
431
- if (folded) {
432
- view.dispatch({ effects: unfoldEffect.of(folded) });
433
- return true;
434
- }
435
- const range = foldableLine(view, line);
436
- if (range) {
437
- view.dispatch({ effects: foldEffect.of(range) });
438
- return true;
439
- }
440
- return false;
441
- },
442
- },
443
- }),
444
- ],
445
- },
446
- ];
353
+ });
354
+ });
355
+ return element;
356
+ },
357
+ }),
358
+ /** @see https://codemirror.net/examples/tooltip/ */
359
+ StateField.define({
360
+ create,
361
+ update(tooltip, { state, docChanged, selection }) {
362
+ if (docChanged) {
363
+ return null;
364
+ }
365
+ return selection ? create(state) : tooltip;
366
+ },
367
+ provide(f) {
368
+ return showTooltip.from(f);
369
+ },
370
+ }),
371
+ keymap.of([
372
+ {
373
+ // Fold the template at the selection/cursor
374
+ key: 'Ctrl-Shift-[',
375
+ mac: 'Cmd-Alt-[',
376
+ run(view) {
377
+ const { state } = view, tree = syntaxTree(state), effects = [];
378
+ let anchor = getAnchor(state);
379
+ for (const { from, to, empty } of state.selection.ranges) {
380
+ let node;
381
+ if (empty) {
382
+ // No selection, try both sides of the cursor position
383
+ node = tree.resolve(from, -1);
384
+ }
385
+ if (!node || node.name === 'Document') {
386
+ node = tree.resolve(from, 1);
387
+ }
388
+ anchor = traverse(state, tree, effects, node, to, anchor, updateSelection);
389
+ }
390
+ return execute(view, effects, anchor);
391
+ },
392
+ },
393
+ {
394
+ // Fold all templates in the document
395
+ key: 'Ctrl-Alt-[',
396
+ run: foldCommand(),
397
+ },
398
+ {
399
+ // Fold all `<ref>` tags in the document
400
+ key: 'Mod-Alt-,',
401
+ run: foldRef,
402
+ },
403
+ {
404
+ // Unfold the template at the selection/cursor
405
+ key: 'Ctrl-Shift-]',
406
+ mac: 'Cmd-Alt-]',
407
+ run(view) {
408
+ const { state } = view, { selection } = state, effects = [], folded = foldedRanges(state);
409
+ for (const { from, to } of selection.ranges) {
410
+ // Unfold any folded range at the selection
411
+ folded.between(from, to, (i, j) => {
412
+ effects.push(unfoldEffect.of({ from: i, to: j }));
413
+ });
414
+ }
415
+ if (effects.length > 0) {
416
+ // Unfold the template(s) and redraw the selections
417
+ view.dispatch({ effects, selection });
418
+ return true;
419
+ }
420
+ return false;
421
+ },
422
+ },
423
+ { key: 'Ctrl-Alt-]', run: unfoldAll },
424
+ ]),
425
+ markers,
426
+ gutter({
427
+ class: 'cm-foldGutter',
428
+ markers(view) {
429
+ return view.plugin(markers)?.markers ?? RangeSet.empty;
430
+ },
431
+ initialSpacer() {
432
+ return new FoldMarker(false);
433
+ },
434
+ domEventHandlers: {
435
+ click(view, line) {
436
+ const folded = findFold(view, line);
437
+ if (folded) {
438
+ view.dispatch({ effects: unfoldEffect.of(folded) });
439
+ return true;
440
+ }
441
+ const range = foldableLine(view, line);
442
+ if (range) {
443
+ view.dispatch({ effects: foldEffect.of(range) });
444
+ return true;
445
+ }
446
+ return false;
447
+ },
448
+ },
449
+ }),
450
+ ])();
447
451
  /**
448
452
  * 点击提示折叠模板参数
449
453
  * @param view
package/dist/hover.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { hoverTooltip } from '@codemirror/view';
2
- import { loadScript, getLSP } from '@bhsd/common';
2
+ import { loadScript, getLSP } from '@bhsd/browser';
3
3
  /**
4
4
  * 将索引转换为位置
5
5
  * @param doc Text 实例
package/dist/indent.d.ts CHANGED
@@ -1,4 +1,8 @@
1
- import type { Text } from '@codemirror/state';
1
+ import type { Text as TextBase } from '@codemirror/state';
2
+ export interface Text extends TextBase {
3
+ children: readonly Text[] | null;
4
+ text?: string[];
5
+ }
2
6
  /**
3
7
  * 检测文本的缩进方式
4
8
  * @param text 文本内容
package/dist/indent.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const noDetectionLangs = new Set(['plain', 'mediawiki', 'html']);
2
+ const getLines = (text) => text.children?.flatMap(getLines) ?? text.text;
2
3
  /**
3
4
  * 检测文本的缩进方式
4
5
  * @param text 文本内容
@@ -9,7 +10,7 @@ export const detectIndent = (text, defaultIndent, lang) => {
9
10
  if (noDetectionLangs.has(lang)) {
10
11
  return defaultIndent;
11
12
  }
12
- const lineSpaces = [], lines = typeof text === 'string' ? text.split('\n') : text.text;
13
+ const lineSpaces = [], lines = typeof text === 'string' ? text.split('\n') : getLines(text);
13
14
  let tabLines = 0;
14
15
  for (const line of lines) {
15
16
  if (!line.trim()) {
package/dist/inlay.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { StateField, StateEffect } from '@codemirror/state';
2
2
  import { Decoration, EditorView, WidgetType, ViewPlugin } from '@codemirror/view';
3
- import { getLSP } from '@bhsd/common';
3
+ import { getLSP } from '@bhsd/browser';
4
4
  import { posToIndex } from './hover';
5
5
  class InlayHintWidget extends WidgetType {
6
6
  constructor(label) {
@@ -1,3 +1,4 @@
1
1
  import type { Extension } from '@codemirror/state';
2
+ export declare const jsCompletion: Extension;
2
3
  declare const _default: () => Extension;
3
4
  export default _default;
@@ -1,5 +1,3 @@
1
1
  import { javascript as js, javascriptLanguage, scopeCompletionSource } from '@codemirror/lang-javascript';
2
- export default () => [
3
- js(),
4
- javascriptLanguage.data.of({ autocomplete: scopeCompletionSource(globalThis) }),
5
- ];
2
+ export const jsCompletion = javascriptLanguage.data.of({ autocomplete: scopeCompletionSource(globalThis) });
3
+ export default () => [js(), jsCompletion];
@@ -2,6 +2,7 @@ export const keybindings = [
2
2
  { key: 'Ctrl-8', pre: '<blockquote>', post: '</blockquote>', desc: 'blockquote' },
3
3
  { key: 'Mod-.', pre: '<sup>', post: '</sup>', desc: 'sup' },
4
4
  { key: 'Mod-,', pre: '<sub>', post: '</sub>', desc: 'sub' },
5
+ { key: 'Ctrl-,', pre: '<sub>', post: '</sub>', desc: 'sub' },
5
6
  { key: 'Mod-Shift-6', pre: '<code>', post: '</code>', desc: 'code' },
6
7
  { key: 'Ctrl-Shift-5', pre: '<s>', post: '</s>', desc: 's' },
7
8
  { key: 'Mod-u', pre: '<u>', post: '</u>', desc: 'u' },
package/dist/linter.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { Linter } from 'eslint';
3
3
  import type { Warning } from 'stylelint';
4
4
  import type { Diagnostic } from 'luacheck-browserify';
5
5
  export type Option = Record<string, unknown> | null | undefined;
6
- export type LiveOption = (runtime?: boolean) => Option;
6
+ export type LiveOption = (runtime?: boolean) => Option | Promise<Option>;
7
7
  declare type getLinter<T> = () => (text: string) => T;
8
8
  declare type asyncLinter<T, S = Record<string, unknown>> = ((text: string, config?: Option) => T) & {
9
9
  config?: S;
package/dist/linter.js CHANGED
@@ -1,17 +1,38 @@
1
1
  /* eslint-disable unicorn/no-unreadable-iife */
2
- import { loadScript, getWikiparse, getLSP, sanitizeInlineStyle } from '@bhsd/common';
3
- import { styleLint } from '@bhsd/common/dist/stylelint';
2
+ import { sanitizeInlineStyle } from '@bhsd/common';
3
+ import { loadScript, getWikiparse, getLSP } from '@bhsd/browser';
4
+ import { styleLint } from '@bhsd/stylelint-util';
4
5
  /**
5
6
  * 计算位置
6
7
  * @param range 范围
7
- * @param line 行号
8
- * @param column 列号
8
+ * @param lineOrOffset 行号或相对位置
9
+ * @param columnOrPrefix 列号或前缀
9
10
  */
10
- const offsetAt = (range, line, column) => {
11
- if (line === -2) {
11
+ const offsetAt = (range, lineOrOffset, columnOrPrefix) => {
12
+ if (typeof columnOrPrefix === 'string') {
13
+ return Math.min(range[1], range[0] + Math.max(0, lineOrOffset - columnOrPrefix.length));
14
+ }
15
+ else if (lineOrOffset === -2) {
12
16
  return range[0];
13
17
  }
14
- return line === 0 ? range[1] : range[0] + column;
18
+ return lineOrOffset === 0 ? range[1] : range[0] + columnOrPrefix;
19
+ };
20
+ /**
21
+ * 获取伪CSS代码块的前缀
22
+ * @param token AST 节点
23
+ * @param token.type 节点类型
24
+ * @param token.tag 节点标签
25
+ * @param i 节点序号
26
+ */
27
+ const getPrefix = ({ type, tag }, i) => `${type === 'ext-attr' ? 'div' : tag}#${i}{\n`;
28
+ /**
29
+ * 将偏移量转换为位置
30
+ * @param code 代码字符串
31
+ * @param index 偏移量
32
+ */
33
+ const indexToPos = (code, index) => {
34
+ const lines = code.slice(0, index).split('\n');
35
+ return { line: lines.length - 1, character: lines[lines.length - 1].length };
15
36
  };
16
37
  /**
17
38
  * 获取 Wikitext LSP
@@ -22,16 +43,16 @@ export const getWikiLinter = async (opt, obj) => {
22
43
  await getWikiparse(opt?.['getConfig'], opt?.['i18n']);
23
44
  const lsp = getLSP(obj, opt?.['include']);
24
45
  return async (text, config) => {
25
- const diagnostics = (await lsp.provideDiagnostics(text)).filter(({ code, severity }) => Number(config?.[code] ?? 2) > Number(severity === 2)), tokens = 'findStyleTokens' in lsp && config?.['invalid-css'] !== '0' ? await lsp.findStyleTokens() : [];
46
+ const defaultSeverity = config?.['defaultSeverity'] ?? 2, diagnostics = (await lsp.provideDiagnostics(text)).filter(({ code, severity }) => Number(config?.[code] ?? defaultSeverity) > Number(severity === 2)), tokens = 'findStyleTokens' in lsp && config?.['invalid-css'] !== '0' ? await lsp.findStyleTokens() : [];
26
47
  if (tokens.length === 0) {
27
48
  return diagnostics;
28
49
  }
29
50
  const cssLint = await getCssLinter();
30
51
  return [
31
52
  ...diagnostics,
32
- ...(await cssLint(tokens.map(({ childNodes, type, tag }, i) => `${type === 'ext-attr' ? 'div' : tag}#${i}{\n${sanitizeInlineStyle(childNodes[1].childNodes[0].data)
33
- .replace(/\n/gu, ' ')}\n}`).join('\n'))).map(({ line, column, endLine, endColumn, rule, severity, text: message }) => {
34
- const i = Math.ceil(line / 3), { range } = tokens[i - 1].childNodes[1].childNodes[0], from = offsetAt(range, line - 3 * i, column - 1);
53
+ ...(await cssLint(tokens.map((token, i) => `${getPrefix(token, i)}${sanitizeInlineStyle(token.childNodes[1].childNodes[0].data)
54
+ .replace(/\n/gu, ' ')}\n}`).join('\n'), config?.['css'])).map(({ line, column, endLine, endColumn, rule, severity, text: message, fix }) => {
55
+ const i = Math.ceil(line / 3), prefix = getPrefix(tokens[i - 1], i), { range } = tokens[i - 1].childNodes[1].childNodes[0], from = offsetAt(range, line - 3 * i, column - 1);
35
56
  return {
36
57
  from,
37
58
  to: endLine === undefined ? from : offsetAt(range, endLine - 3 * i, endColumn - 1),
@@ -39,6 +60,21 @@ export const getWikiLinter = async (opt, obj) => {
39
60
  source: 'Stylelint',
40
61
  code: rule,
41
62
  message,
63
+ ...fix
64
+ ? {
65
+ data: [
66
+ {
67
+ range: {
68
+ start: indexToPos(text, offsetAt(range, fix.range[0], prefix)),
69
+ end: indexToPos(text, offsetAt(range, fix.range[1], prefix)),
70
+ },
71
+ newText: fix.text,
72
+ title: 'Fix: Stylelint',
73
+ fix: true,
74
+ },
75
+ ],
76
+ }
77
+ : {},
42
78
  };
43
79
  }),
44
80
  ];
@@ -0,0 +1,14 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ import type { EditorState, Text } from '@codemirror/state';
3
+ import type { Diagnostic } from '@codemirror/lint';
4
+ import type { Option, LiveOption } from './linter';
5
+ export type LintSource = ((state: EditorState) => Diagnostic[] | Promise<Diagnostic[]>) & {
6
+ fixer?: (doc: Text, rule?: string) => string | Promise<string>;
7
+ };
8
+ export type LintSourceGetter = (opt?: Option | LiveOption, view?: EditorView) => LintSource | Promise<LintSource>;
9
+ export declare const getWikiLintSource: LintSourceGetter;
10
+ export declare const getJsLintSource: LintSourceGetter;
11
+ export declare const getCssLintSource: LintSourceGetter;
12
+ export declare const getVueLintSource: LintSourceGetter;
13
+ export declare const getJsonLintSource: LintSourceGetter;
14
+ export declare const getLuaLintSource: LintSourceGetter;