@difizen/libro-codemirror 0.0.2-alpha.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/LICENSE +21 -0
- package/README.md +0 -0
- package/es/auto-complete/closebrackets.d.ts +12 -0
- package/es/auto-complete/closebrackets.d.ts.map +1 -0
- package/es/auto-complete/closebrackets.js +408 -0
- package/es/auto-complete/completion.d.ts +57 -0
- package/es/auto-complete/completion.d.ts.map +1 -0
- package/es/auto-complete/completion.js +265 -0
- package/es/auto-complete/config.d.ts +22 -0
- package/es/auto-complete/config.d.ts.map +1 -0
- package/es/auto-complete/config.js +44 -0
- package/es/auto-complete/filter.d.ts +13 -0
- package/es/auto-complete/filter.d.ts.map +1 -0
- package/es/auto-complete/filter.js +191 -0
- package/es/auto-complete/index.d.ts +17 -0
- package/es/auto-complete/index.d.ts.map +1 -0
- package/es/auto-complete/index.js +107 -0
- package/es/auto-complete/snippet.d.ts +14 -0
- package/es/auto-complete/snippet.d.ts.map +1 -0
- package/es/auto-complete/snippet.js +447 -0
- package/es/auto-complete/state.d.ts +63 -0
- package/es/auto-complete/state.d.ts.map +1 -0
- package/es/auto-complete/state.js +452 -0
- package/es/auto-complete/theme.d.ts +6 -0
- package/es/auto-complete/theme.d.ts.map +1 -0
- package/es/auto-complete/theme.js +151 -0
- package/es/auto-complete/tooltip.d.ts +5 -0
- package/es/auto-complete/tooltip.d.ts.map +1 -0
- package/es/auto-complete/tooltip.js +365 -0
- package/es/auto-complete/view.d.ts +43 -0
- package/es/auto-complete/view.d.ts.map +1 -0
- package/es/auto-complete/view.js +372 -0
- package/es/auto-complete/word.d.ts +3 -0
- package/es/auto-complete/word.d.ts.map +1 -0
- package/es/auto-complete/word.js +119 -0
- package/es/completion.d.ts +6 -0
- package/es/completion.d.ts.map +1 -0
- package/es/completion.js +84 -0
- package/es/config.d.ts +184 -0
- package/es/config.d.ts.map +1 -0
- package/es/config.js +473 -0
- package/es/editor.d.ts +361 -0
- package/es/editor.d.ts.map +1 -0
- package/es/editor.js +1126 -0
- package/es/factory.d.ts +3 -0
- package/es/factory.d.ts.map +1 -0
- package/es/factory.js +12 -0
- package/es/hyperlink.d.ts +15 -0
- package/es/hyperlink.d.ts.map +1 -0
- package/es/hyperlink.js +120 -0
- package/es/indent.d.ts +8 -0
- package/es/indent.d.ts.map +1 -0
- package/es/indent.js +58 -0
- package/es/indentation-markers/config.d.ts +17 -0
- package/es/indentation-markers/config.d.ts.map +1 -0
- package/es/indentation-markers/config.js +10 -0
- package/es/indentation-markers/index.d.ts +3 -0
- package/es/indentation-markers/index.d.ts.map +1 -0
- package/es/indentation-markers/index.js +160 -0
- package/es/indentation-markers/map.d.ts +77 -0
- package/es/indentation-markers/map.d.ts.map +1 -0
- package/es/indentation-markers/map.js +265 -0
- package/es/indentation-markers/utils.d.ts +27 -0
- package/es/indentation-markers/utils.d.ts.map +1 -0
- package/es/indentation-markers/utils.js +91 -0
- package/es/index.d.ts +11 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +10 -0
- package/es/libro-icon.d.ts +3 -0
- package/es/libro-icon.d.ts.map +1 -0
- package/es/libro-icon.js +2 -0
- package/es/mimetype.d.ts +22 -0
- package/es/mimetype.d.ts.map +1 -0
- package/es/mimetype.js +59 -0
- package/es/mode.d.ts +86 -0
- package/es/mode.d.ts.map +1 -0
- package/es/mode.js +284 -0
- package/es/monitor.d.ts +32 -0
- package/es/monitor.d.ts.map +1 -0
- package/es/monitor.js +129 -0
- package/es/python-lang.d.ts +3 -0
- package/es/python-lang.d.ts.map +1 -0
- package/es/python-lang.js +7 -0
- package/es/style/base.css +131 -0
- package/es/style/theme.css +12 -0
- package/es/style/variables.css +403 -0
- package/es/theme.d.ts +35 -0
- package/es/theme.d.ts.map +1 -0
- package/es/theme.js +225 -0
- package/es/tooltip.d.ts +10 -0
- package/es/tooltip.d.ts.map +1 -0
- package/es/tooltip.js +170 -0
- package/package.json +74 -0
- package/src/auto-complete/README.md +71 -0
- package/src/auto-complete/closebrackets.ts +423 -0
- package/src/auto-complete/completion.ts +345 -0
- package/src/auto-complete/config.ts +101 -0
- package/src/auto-complete/filter.ts +215 -0
- package/src/auto-complete/index.ts +112 -0
- package/src/auto-complete/snippet.ts +394 -0
- package/src/auto-complete/state.ts +472 -0
- package/src/auto-complete/theme.ts +126 -0
- package/src/auto-complete/tooltip.ts +386 -0
- package/src/auto-complete/view.ts +343 -0
- package/src/auto-complete/word.ts +118 -0
- package/src/completion.ts +61 -0
- package/src/config.ts +689 -0
- package/src/editor.ts +1078 -0
- package/src/factory.ts +10 -0
- package/src/hyperlink.ts +95 -0
- package/src/indent.ts +69 -0
- package/src/indentation-markers/config.ts +31 -0
- package/src/indentation-markers/index.ts +192 -0
- package/src/indentation-markers/map.ts +273 -0
- package/src/indentation-markers/utils.ts +84 -0
- package/src/index.ts +11 -0
- package/src/libro-icon.ts +4 -0
- package/src/mimetype.ts +49 -0
- package/src/mode.ts +269 -0
- package/src/monitor.ts +105 -0
- package/src/python-lang.ts +7 -0
- package/src/style/base.css +129 -0
- package/src/style/theme.css +12 -0
- package/src/style/variables.css +405 -0
- package/src/theme.ts +231 -0
- package/src/tooltip.ts +145 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { EditorState, Line } from '@codemirror/state';
|
|
2
|
+
import type { EditorView } from '@codemirror/view';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Gets the visible lines in the editor. Lines will not be repeated.
|
|
6
|
+
*
|
|
7
|
+
* @param view - The editor view to get the visible lines from.
|
|
8
|
+
* @param state - The editor state. Defaults to the view's current one.
|
|
9
|
+
*/
|
|
10
|
+
export function getVisibleLines(view: EditorView, state = view.state) {
|
|
11
|
+
const lines = new Set<Line>();
|
|
12
|
+
|
|
13
|
+
for (const { from, to } of view.visibleRanges) {
|
|
14
|
+
let pos = from;
|
|
15
|
+
|
|
16
|
+
while (pos <= to) {
|
|
17
|
+
const line = state.doc.lineAt(pos);
|
|
18
|
+
|
|
19
|
+
if (!lines.has(line)) {
|
|
20
|
+
lines.add(line);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pos = line.to + 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Gets the line at the position of the primary cursor.
|
|
32
|
+
*
|
|
33
|
+
* @param state - The editor state from which to extract the line.
|
|
34
|
+
*/
|
|
35
|
+
export function getCurrentLine(state: EditorState) {
|
|
36
|
+
const currentPos = state.selection.main.head;
|
|
37
|
+
return state.doc.lineAt(currentPos);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns the number of columns that a string is indented, controlling for
|
|
42
|
+
* tabs. This is useful for determining the indentation level of a line.
|
|
43
|
+
*
|
|
44
|
+
* Note that this only returns the number of _visible_ columns, not the number
|
|
45
|
+
* of whitespace characters at the start of the string.
|
|
46
|
+
*
|
|
47
|
+
* @param str - The string to check.
|
|
48
|
+
* @param tabSize - The size of a tab character. Usually 2 or 4.
|
|
49
|
+
*/
|
|
50
|
+
export function numColumns(str: string, tabSize: number) {
|
|
51
|
+
// as far as I can tell, this is pretty much the fastest way to do this,
|
|
52
|
+
// at least involving iteration. `str.length - str.trimStart().length` is
|
|
53
|
+
// much faster, but it has some edge cases that are hard to deal with.
|
|
54
|
+
|
|
55
|
+
let col = 0;
|
|
56
|
+
|
|
57
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
58
|
+
loop: for (let i = 0; i < str.length; i++) {
|
|
59
|
+
switch (str[i]) {
|
|
60
|
+
case ' ': {
|
|
61
|
+
col += 1;
|
|
62
|
+
continue loop;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
case '\t': {
|
|
66
|
+
// if the current column is a multiple of the tab size, we can just
|
|
67
|
+
// add the tab size to the column. otherwise, we need to add the
|
|
68
|
+
// difference between the tab size and the current column.
|
|
69
|
+
col += tabSize - (col % tabSize);
|
|
70
|
+
continue loop;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case '\r': {
|
|
74
|
+
continue loop;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
default: {
|
|
78
|
+
break loop;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return col;
|
|
84
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import './style/base.css';
|
|
2
|
+
import './style/theme.css';
|
|
3
|
+
import './style/variables.css';
|
|
4
|
+
|
|
5
|
+
export * from './config.js';
|
|
6
|
+
export * from './editor.js';
|
|
7
|
+
export * from './mode.js';
|
|
8
|
+
export * from './theme.js';
|
|
9
|
+
export * from './factory.js';
|
|
10
|
+
export * from './monitor.js';
|
|
11
|
+
export * from './auto-complete/index.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const FoldIcon =
|
|
2
|
+
'<svg width="8px" height="6px" viewBox="0 0 8 6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>1.通用/2.Icon图标/Line/Down</title><g id="0424" stroke="none" stroke-width="1" fill="none" fillRule="evenodd"><g id="Notebook-cell-色阶" transform="translate(-2015.000000, -434.000000)" fill="#A4AECB"><g id="编组-15" transform="translate(2004.000000, 407.000000)"><g id="1.通用/2.Icon图标/Line/Down" transform="translate(11.250000, 27.500000)"><path d="M7.34387369,0 L6.61145181,0 C6.56164712,0 6.51477212,0.0244140625 6.48547525,0.064453125 L3.71106119,3.88867188 L0.936647123,0.064453125 C0.907350248,0.0244140625 0.860475248,0 0.81067056,0 L0.0782486852,0 C0.0147721227,0 -0.0223372523,0.072265625 0.0147721227,0.124023438 L3.4581315,4.87109375 C3.5831315,5.04296875 3.83899087,5.04296875 3.96301431,4.87109375 L7.40637369,0.124023437 C7.44445962,0.072265625 7.40735025,0 7.34387369,0 Z" id="Down"></path></g></g></g></g></svg>';
|
|
3
|
+
export const UnFoldIcon =
|
|
4
|
+
'<svg width="6px" height="8px" viewBox="0 0 6 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>1.通用/2.Icon图标/Line/Down收起</title><g id="0424" stroke="none" stroke-width="1" fill="none" fillRule="evenodd"><g id="Notebook-cell-色阶" transform="translate(-2094.000000, -433.000000)" fill="#A4AECB"><g id="编组-15备份" transform="translate(2082.000000, 407.000000)"><g id="1.通用/2.Icon图标/Line/Down" transform="translate(15.000000, 30.039124) rotate(270.000000) translate(-15.000000, -30.039124) translate(11.289124, 27.539124)"><path d="M7.34387369,1.77635684e-15 L6.61145181,1.77635684e-15 C6.56164712,1.77635684e-15 6.51477212,0.0244140625 6.48547525,0.064453125 L3.71106119,3.88867188 L0.936647123,0.064453125 C0.907350248,0.0244140625 0.860475248,1.77635684e-15 0.81067056,1.77635684e-15 L0.0782486852,1.77635684e-15 C0.0147721227,1.77635684e-15 -0.0223372523,0.072265625 0.0147721227,0.124023438 L3.4581315,4.87109375 C3.5831315,5.04296875 3.83899087,5.04296875 3.96301431,4.87109375 L7.40637369,0.124023438 C7.44445962,0.072265625 7.40735025,1.77635684e-15 7.34387369,1.77635684e-15 Z" id="Down"></path></g></g></g></g></svg>';
|
package/src/mimetype.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { IEditorMimeTypeService } from '@difizen/libro-code-editor';
|
|
2
|
+
import { defaultMimeType } from '@difizen/libro-code-editor';
|
|
3
|
+
import type { ILanguageInfoMetadata } from '@difizen/libro-common';
|
|
4
|
+
|
|
5
|
+
import { findBest, findByFileName } from './mode.js';
|
|
6
|
+
|
|
7
|
+
const extname = (path: string) => {
|
|
8
|
+
return path.split('.').pop();
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The mime type service for CodeMirror.
|
|
13
|
+
*/
|
|
14
|
+
export class CodeMirrorMimeTypeService implements IEditorMimeTypeService {
|
|
15
|
+
/**
|
|
16
|
+
* Returns a mime type for the given language info.
|
|
17
|
+
*
|
|
18
|
+
* #### Notes
|
|
19
|
+
* If a mime type cannot be found returns the default mime type `text/plain`, never `null`.
|
|
20
|
+
*/
|
|
21
|
+
getMimeTypeByLanguage(info: ILanguageInfoMetadata): string {
|
|
22
|
+
const ext = info.file_extension || '';
|
|
23
|
+
const mode = findBest(
|
|
24
|
+
(info.codemirror_mode as any) || {
|
|
25
|
+
mimetype: info.mimetype,
|
|
26
|
+
name: info.name,
|
|
27
|
+
ext: [ext.split('.').slice(-1)[0]],
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
return mode ? (mode.mime as string) : defaultMimeType;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns a mime type for the given file path.
|
|
35
|
+
*
|
|
36
|
+
* #### Notes
|
|
37
|
+
* If a mime type cannot be found returns the default mime type `text/plain`, never `null`.
|
|
38
|
+
*/
|
|
39
|
+
getMimeTypeByFilePath(path: string): string {
|
|
40
|
+
const ext = extname(path);
|
|
41
|
+
if (ext === '.ipy') {
|
|
42
|
+
return 'text/x-python';
|
|
43
|
+
} else if (ext === '.md') {
|
|
44
|
+
return 'text/x-ipythongfm';
|
|
45
|
+
}
|
|
46
|
+
const mode = findByFileName(path) || findBest('');
|
|
47
|
+
return mode ? (mode.mime as string) : defaultMimeType;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/mode.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { markdown } from '@codemirror/lang-markdown';
|
|
2
|
+
import type { LanguageSupport } from '@codemirror/language';
|
|
3
|
+
import { LanguageDescription } from '@codemirror/language';
|
|
4
|
+
import { defaultMimeType } from '@difizen/libro-code-editor';
|
|
5
|
+
|
|
6
|
+
// This ensures the language spec for python will be loaded when
|
|
7
|
+
// we instantiate a new editor instance, which is required since
|
|
8
|
+
// python is the default language and we don't want to split
|
|
9
|
+
// the editor constructor because of asynchronous loading.
|
|
10
|
+
import { PathExt } from '@difizen/libro-common';
|
|
11
|
+
import { highlightTree } from '@lezer/highlight';
|
|
12
|
+
|
|
13
|
+
import { python } from './python-lang.js';
|
|
14
|
+
import { jupyterHighlightStyle } from './theme.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The interface of a codemirror language spec.
|
|
18
|
+
*/
|
|
19
|
+
export interface ISpec {
|
|
20
|
+
name: string;
|
|
21
|
+
alias?: readonly string[];
|
|
22
|
+
mime: string | readonly string[];
|
|
23
|
+
load?: () => Promise<LanguageSupport>;
|
|
24
|
+
extensions?: readonly string[];
|
|
25
|
+
filename?: RegExp;
|
|
26
|
+
support?: LanguageSupport;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Code mirror uses two similar structures, a plain object with optional fields,
|
|
30
|
+
// and a class with the same fields but all mandatory. Maybe adopting the same
|
|
31
|
+
// pattern would be less confusing (although far more verbose)
|
|
32
|
+
function makeSpec(spec: ISpec): ISpec {
|
|
33
|
+
const res = LanguageDescription.of(spec) as unknown as ISpec;
|
|
34
|
+
res.mime = spec.mime;
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const modeList: ISpec[] = [
|
|
39
|
+
makeSpec({
|
|
40
|
+
name: 'Python',
|
|
41
|
+
mime: 'text/x-python',
|
|
42
|
+
extensions: ['BUILD', 'bzl', 'py', 'pyw'],
|
|
43
|
+
filename: /^(BUCK|BUILD)$/,
|
|
44
|
+
load() {
|
|
45
|
+
return Promise.resolve(python());
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
makeSpec({
|
|
49
|
+
name: 'Markdown',
|
|
50
|
+
mime: 'text/x-markdown',
|
|
51
|
+
extensions: ['md', 'markdown', 'mkd'],
|
|
52
|
+
async load() {
|
|
53
|
+
return Promise.resolve(markdown());
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the raw list of available modes specs.
|
|
60
|
+
*
|
|
61
|
+
* @alpha
|
|
62
|
+
* @returns The available modes
|
|
63
|
+
*/
|
|
64
|
+
export function getModeInfo(): ISpec[] {
|
|
65
|
+
return modeList;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Find a codemirror mode by MIME.
|
|
70
|
+
*
|
|
71
|
+
* @alpha
|
|
72
|
+
* @param mime Mime type to look for
|
|
73
|
+
* @returns The mode or null
|
|
74
|
+
*/
|
|
75
|
+
export function findByMIME(mime: string | readonly string[]): ISpec | null {
|
|
76
|
+
if (Array.isArray(mime)) {
|
|
77
|
+
for (let i = 0; i < mime.length; i++) {
|
|
78
|
+
const spec = findByMIME(mime[i]);
|
|
79
|
+
if (spec) {
|
|
80
|
+
return spec;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const _mime = (mime as string).toLowerCase();
|
|
86
|
+
for (let i = 0; i < modeList.length; i++) {
|
|
87
|
+
const info = modeList[i];
|
|
88
|
+
if (Array.isArray(info.mime)) {
|
|
89
|
+
for (let j = 0; j < info.mime.length; j++) {
|
|
90
|
+
if (info.mime[j] === _mime) {
|
|
91
|
+
return info;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else if (info.mime === _mime) {
|
|
95
|
+
return info;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (/\+xml$/.test(_mime)) {
|
|
99
|
+
return findByMIME('application/xml');
|
|
100
|
+
}
|
|
101
|
+
if (/\+json$/.test(_mime)) {
|
|
102
|
+
return findByMIME('application/json');
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Find a codemirror mode by name.
|
|
109
|
+
*
|
|
110
|
+
* @alpha
|
|
111
|
+
* @param name The mode name
|
|
112
|
+
* @returns The mode or null
|
|
113
|
+
*/
|
|
114
|
+
export function findByName(name: string): ISpec | null {
|
|
115
|
+
const _name = name.toLowerCase();
|
|
116
|
+
for (let i = 0; i < modeList.length; i++) {
|
|
117
|
+
const info = modeList[i];
|
|
118
|
+
if (info.name.toLowerCase() === _name) {
|
|
119
|
+
return info;
|
|
120
|
+
}
|
|
121
|
+
if (info.alias) {
|
|
122
|
+
for (let j = 0; j < info.alias.length; j++) {
|
|
123
|
+
if (info.alias[j].toLowerCase() === _name) {
|
|
124
|
+
return info;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Find a codemirror mode by extension.
|
|
134
|
+
*
|
|
135
|
+
* @alpha
|
|
136
|
+
* @param ext The extension name
|
|
137
|
+
* @returns The mode or null
|
|
138
|
+
*/
|
|
139
|
+
export function findByExtension(ext: string | readonly string[]): ISpec | null {
|
|
140
|
+
if (Array.isArray(ext)) {
|
|
141
|
+
for (let i = 0; i < ext.length; i++) {
|
|
142
|
+
const spec = findByExtension(ext[i]);
|
|
143
|
+
if (spec) {
|
|
144
|
+
return spec;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const _ext = (ext as string).toLowerCase();
|
|
150
|
+
for (let i = 0; i < modeList.length; i++) {
|
|
151
|
+
const info = modeList[i];
|
|
152
|
+
for (let j = 0; j < info.extensions!.length; j++) {
|
|
153
|
+
if (info.extensions![j].toLowerCase() === _ext) {
|
|
154
|
+
return info;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Find a codemirror mode by filename.
|
|
163
|
+
*
|
|
164
|
+
* @param name File name
|
|
165
|
+
* @returns The mode or null
|
|
166
|
+
*/
|
|
167
|
+
export function findByFileName(name: string): ISpec | null {
|
|
168
|
+
const basename = PathExt.basename(name);
|
|
169
|
+
for (let i = 0; i < modeList.length; i++) {
|
|
170
|
+
const info = modeList[i];
|
|
171
|
+
if (info.filename && info.filename.test(basename)) {
|
|
172
|
+
return info;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const dot = basename.lastIndexOf('.');
|
|
176
|
+
const ext = dot > -1 && basename.substring(dot + 1, basename.length);
|
|
177
|
+
if (ext) {
|
|
178
|
+
return findByExtension(ext);
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Find a codemirror mode by name or CodeMirror spec.
|
|
185
|
+
*
|
|
186
|
+
* @alpha
|
|
187
|
+
* @param mode The CodeMirror mode
|
|
188
|
+
* @param fallback Whether to fallback to default mimetype spec or not
|
|
189
|
+
* @returns The mode or null
|
|
190
|
+
*/
|
|
191
|
+
export function findBest(mode: string | ISpec, fallback = true): ISpec | null {
|
|
192
|
+
const modename = typeof mode === 'string' ? mode : mode.name;
|
|
193
|
+
const mimetype = typeof mode !== 'string' ? mode.mime : modename;
|
|
194
|
+
const ext = typeof mode !== 'string' ? mode.extensions ?? [] : [];
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
(modename ? findByName(modename) : null) ??
|
|
198
|
+
(mimetype ? findByMIME(mimetype) : null) ??
|
|
199
|
+
findByExtension(ext) ??
|
|
200
|
+
(fallback ? findByMIME(defaultMimeType) : null)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Ensure a codemirror mode is available by name or Codemirror spec.
|
|
206
|
+
*
|
|
207
|
+
* @param mode - The mode to ensure. If it is a string, uses [findBest]
|
|
208
|
+
* to get the appropriate spec.
|
|
209
|
+
*
|
|
210
|
+
* @returns A promise that resolves when the mode is available.
|
|
211
|
+
*/
|
|
212
|
+
export async function ensure(mode: string | ISpec): Promise<ISpec | null> {
|
|
213
|
+
const spec = findBest(mode);
|
|
214
|
+
if (spec) {
|
|
215
|
+
spec.support = await spec.load!();
|
|
216
|
+
return spec;
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Register a new mode for CodeMirror
|
|
223
|
+
*
|
|
224
|
+
* @alpha
|
|
225
|
+
* @param mode Mode to register
|
|
226
|
+
*/
|
|
227
|
+
export function registerModeInfo(mode: ISpec): void {
|
|
228
|
+
const info = findBest(mode, false);
|
|
229
|
+
if (info) {
|
|
230
|
+
throw new Error(`${mode.mime} already registered`);
|
|
231
|
+
}
|
|
232
|
+
modeList.push(makeSpec(mode));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Parse and style a string.
|
|
237
|
+
*
|
|
238
|
+
* @alpha
|
|
239
|
+
* @param code Code to highlight
|
|
240
|
+
* @param mode Code mode
|
|
241
|
+
* @param el HTML element into which the highlighted code will be inserted
|
|
242
|
+
*/
|
|
243
|
+
export function run(code: string, mode: ISpec, el: HTMLElement): void {
|
|
244
|
+
const language = mode.support?.language;
|
|
245
|
+
if (!language) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const tree = language.parser.parse(code);
|
|
250
|
+
// position state required because unstyled tokens are not emitted
|
|
251
|
+
// in highlightTree
|
|
252
|
+
let pos = 0;
|
|
253
|
+
highlightTree(tree, jupyterHighlightStyle, (from, to, classes) => {
|
|
254
|
+
if (from > pos) {
|
|
255
|
+
// No style applied to the token between pos and from
|
|
256
|
+
el.appendChild(document.createTextNode(code.slice(pos, from)));
|
|
257
|
+
}
|
|
258
|
+
const sp = el.appendChild(document.createElement('span'));
|
|
259
|
+
sp.className = classes;
|
|
260
|
+
sp.appendChild(document.createTextNode(code.slice(from, to)));
|
|
261
|
+
pos = to;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (pos < tree.length - 1) {
|
|
265
|
+
// No style applied on the trailing text
|
|
266
|
+
el.appendChild(document.createTextNode(code.slice(pos, tree.length)));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// }
|
package/src/monitor.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { EditorView, hasHoverTooltips } from '@codemirror/view';
|
|
2
|
+
import { Emitter } from '@difizen/mana-app';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
closeCompletionEffect,
|
|
6
|
+
setSelectedEffect,
|
|
7
|
+
startCompletionEffect,
|
|
8
|
+
} from './auto-complete/state.js';
|
|
9
|
+
|
|
10
|
+
interface DocStatus {
|
|
11
|
+
source: string[];
|
|
12
|
+
cursor: number;
|
|
13
|
+
changes?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface CompletionChange {
|
|
17
|
+
start?: DocStatus;
|
|
18
|
+
accept?: DocStatus;
|
|
19
|
+
close?: DocStatus;
|
|
20
|
+
selectIndex?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class CompletionMonitor {
|
|
24
|
+
protected static instance: CompletionMonitor;
|
|
25
|
+
|
|
26
|
+
static getInstance() {
|
|
27
|
+
if (!CompletionMonitor.instance) {
|
|
28
|
+
CompletionMonitor.instance = new CompletionMonitor();
|
|
29
|
+
}
|
|
30
|
+
return CompletionMonitor.instance;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected completionChangeEmitter: Emitter<CompletionChange> = new Emitter();
|
|
34
|
+
|
|
35
|
+
get compeltionChange() {
|
|
36
|
+
return this.completionChangeEmitter.event;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected tooltipChangeEmitter: Emitter<boolean> = new Emitter();
|
|
40
|
+
|
|
41
|
+
get onTooltipChange() {
|
|
42
|
+
return this.tooltipChangeEmitter.event;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected currentChange: CompletionChange | undefined;
|
|
46
|
+
|
|
47
|
+
start(doc: DocStatus) {
|
|
48
|
+
this.currentChange = { start: doc, selectIndex: 0 };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
accept(doc: DocStatus) {
|
|
52
|
+
this.currentChange = { ...this.currentChange, ...{ accept: doc } };
|
|
53
|
+
this.emitChange(this.currentChange);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
close(doc: DocStatus) {
|
|
57
|
+
this.currentChange = { ...this.currentChange, ...{ close: doc, selectIndex: -1 } };
|
|
58
|
+
this.emitChange(this.currentChange);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
updateIndex(index: number) {
|
|
62
|
+
this.currentChange = { ...this.currentChange, ...{ selectIndex: index } };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
emitChange(change: CompletionChange) {
|
|
66
|
+
this.completionChangeEmitter.fire(change);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface MonitorPluginOptions {
|
|
71
|
+
onTooltipChange?: (visible: boolean) => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const monitorPlugin = (options: MonitorPluginOptions) =>
|
|
75
|
+
EditorView.updateListener.of((update) => {
|
|
76
|
+
for (const trans of update.transactions) {
|
|
77
|
+
const { effects } = trans;
|
|
78
|
+
|
|
79
|
+
options?.onTooltipChange?.(hasHoverTooltips(update.state));
|
|
80
|
+
|
|
81
|
+
if (trans.isUserEvent('input.complete')) {
|
|
82
|
+
CompletionMonitor.getInstance().accept({
|
|
83
|
+
changes: JSON.stringify(trans.changes.toJSON()),
|
|
84
|
+
cursor: update.state.selection.main.head,
|
|
85
|
+
source: update.state.doc.toJSON(),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
for (const effect of effects) {
|
|
89
|
+
if (effect.is(startCompletionEffect)) {
|
|
90
|
+
CompletionMonitor.getInstance().start({
|
|
91
|
+
cursor: update.state.selection.main.head,
|
|
92
|
+
source: update.state.doc.toJSON(),
|
|
93
|
+
});
|
|
94
|
+
} else if (effect.is(closeCompletionEffect)) {
|
|
95
|
+
CompletionMonitor.getInstance().close({
|
|
96
|
+
cursor: update.state.selection.main.head,
|
|
97
|
+
source: update.state.doc.toJSON(),
|
|
98
|
+
});
|
|
99
|
+
} else if (effect.is(setSelectedEffect)) {
|
|
100
|
+
CompletionMonitor.getInstance().updateIndex(effect.value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* stylelint-disable function-url-quotes */
|
|
2
|
+
.cm-editor {
|
|
3
|
+
line-height: var(--jp-code-line-height);
|
|
4
|
+
font-size: var(--jp-code-font-size);
|
|
5
|
+
font-family: var(--jp-code-font-family);
|
|
6
|
+
border: 0;
|
|
7
|
+
border-radius: 4px;
|
|
8
|
+
height: auto;
|
|
9
|
+
background: var(--mana-libro-input-background);
|
|
10
|
+
padding: 12px 0 18px;
|
|
11
|
+
|
|
12
|
+
/* Changed to auto to autogrow */
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.cm-editor pre {
|
|
16
|
+
padding: 0 var(--jp-code-padding);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.cm-selectionMatch {
|
|
20
|
+
background-color: #e3ebff !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.jp-CodeMirrorEditor[data-type='inline'] .cm-dialog {
|
|
24
|
+
background-color: var(--jp-layout-color0);
|
|
25
|
+
color: var(--jp-content-font-color1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.jp-CodeMirrorEditor {
|
|
29
|
+
cursor: text;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* When zoomed out 67% and 33% on a screen of 1440 width x 900 height */
|
|
33
|
+
@media screen and (min-width: 2138px) and (max-width: 4319px) {
|
|
34
|
+
.jp-CodeMirrorEditor[data-type='inline'] .cm-cursor {
|
|
35
|
+
border-left: var(--jp-code-cursor-width1) solid var(--jp-editor-cursor-color);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* When zoomed out less than 33% */
|
|
40
|
+
@media screen and (min-width: 4320px) {
|
|
41
|
+
.jp-CodeMirrorEditor[data-type='inline'] .cm-cursor {
|
|
42
|
+
border-left: var(--jp-code-cursor-width2) solid var(--jp-editor-cursor-color);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.cm-editor.jp-mod-readOnly .cm-cursor {
|
|
47
|
+
display: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.jp-CollaboratorCursor {
|
|
51
|
+
border-left: 5px solid transparent;
|
|
52
|
+
border-right: 5px solid transparent;
|
|
53
|
+
border-top: none;
|
|
54
|
+
border-bottom: 3px solid;
|
|
55
|
+
background-clip: content-box;
|
|
56
|
+
margin-left: -5px;
|
|
57
|
+
margin-right: -5px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.cm-searching,
|
|
61
|
+
.cm-searching span {
|
|
62
|
+
background-color: var(--jp-search-unselected-match-background-color);
|
|
63
|
+
color: var(--jp-search-unselected-match-color);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.cm-searching span::selection {
|
|
67
|
+
background-color: var(--jp-search-selected-match-background-color) !important;
|
|
68
|
+
color: var(--jp-search-selected-match-color) !important;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.cm-trailingspace {
|
|
72
|
+
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAsElEQVQIHQGlAFr/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7+r3zKmT0/+pk9P/7+r3zAAAAAAAAAAABAAAAAAAAAAA6OPzM+/q9wAAAAAA6OPzMwAAAAAAAAAAAgAAAAAAAAAAGR8NiRQaCgAZIA0AGR8NiQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQyoYJ/SY80UAAAAASUVORK5CYII=);
|
|
73
|
+
background-position: center left;
|
|
74
|
+
background-repeat: repeat-x;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.jp-CollaboratorCursor-hover {
|
|
78
|
+
position: absolute;
|
|
79
|
+
z-index: 1;
|
|
80
|
+
transform: translateX(-50%);
|
|
81
|
+
color: white;
|
|
82
|
+
border-radius: 3px;
|
|
83
|
+
padding: 1px 4px;
|
|
84
|
+
text-align: center;
|
|
85
|
+
font-size: var(--jp-ui-font-size1);
|
|
86
|
+
white-space: nowrap;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.jp-CodeMirror-ruler {
|
|
90
|
+
border-left: 1px dashed var(--jp-border-color2);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Styles for shared cursors (remote cursor locations and selected ranges) */
|
|
94
|
+
.jp-CodeMirrorEditor .remote-caret {
|
|
95
|
+
position: relative;
|
|
96
|
+
border-left: 2px solid black;
|
|
97
|
+
margin-left: -1px;
|
|
98
|
+
margin-right: -1px;
|
|
99
|
+
box-sizing: border-box;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.jp-CodeMirrorEditor .remote-caret > div {
|
|
103
|
+
white-space: nowrap;
|
|
104
|
+
position: absolute;
|
|
105
|
+
top: -1.15em;
|
|
106
|
+
padding-bottom: 0.05em;
|
|
107
|
+
left: -2px;
|
|
108
|
+
font-size: 0.95em;
|
|
109
|
+
background-color: rgb(250, 129, 0);
|
|
110
|
+
font-family: var(--jp-ui-font-family);
|
|
111
|
+
font-weight: bold;
|
|
112
|
+
line-height: normal;
|
|
113
|
+
user-select: none;
|
|
114
|
+
color: white;
|
|
115
|
+
padding-left: 2px;
|
|
116
|
+
padding-right: 2px;
|
|
117
|
+
z-index: 3;
|
|
118
|
+
transition: opacity 0.3s ease-in-out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.jp-CodeMirrorEditor .remote-caret.hide-name > div {
|
|
122
|
+
transition-delay: 0.7s;
|
|
123
|
+
opacity: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.jp-CodeMirrorEditor .remote-caret:hover > div {
|
|
127
|
+
opacity: 1;
|
|
128
|
+
transition-delay: 0s;
|
|
129
|
+
}
|