latex-stickies 1.0.0 → 1.2.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/README.md +5 -1
- package/package.json +24 -6
- package/scripts/brand-electron.js +86 -0
- package/src/ai.js +171 -0
- package/src/editor/entry.js +69 -0
- package/src/main.js +292 -32
- package/src/preload.js +25 -5
- package/src/renderer/editor.js +69 -0
- package/src/renderer/ghost.js +205 -0
- package/src/renderer/live-editor.js +632 -0
- package/src/renderer/note.css +181 -139
- package/src/renderer/note.html +4 -7
- package/src/renderer/note.js +105 -129
- package/src/renderer/vendor/codemirror.js +36 -0
- package/src/store.js +25 -1
- package/src/welcome.js +45 -0
- package/src/renderer/markdown.js +0 -154
- package/src/renderer/vendor/marked.js +0 -80
- package/src/renderer/vendor/prism.js +0 -2009
- package/src/renderer/vendor/purify.js +0 -3
package/README.md
CHANGED
|
@@ -21,7 +21,8 @@ give it a minute; later launches are instant.
|
|
|
21
21
|
|
|
22
22
|
## Writing in a note
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Everything renders as you type. Click into a line to see the markdown behind
|
|
25
|
+
it; move away and it renders again.
|
|
25
26
|
|
|
26
27
|
**Math** — `$…$` inline, `$$…$$` on its own line. Delimiters have to hug their
|
|
27
28
|
content, so ordinary prose like `costs $5 and $6` stays text rather than turning
|
|
@@ -63,6 +64,9 @@ Use `Ctrl` in place of `Cmd` on Windows and Linux.
|
|
|
63
64
|
| `Cmd+E` | Toggle edit / preview |
|
|
64
65
|
| `Cmd+T` | Keep on top |
|
|
65
66
|
| `Cmd+±` | Text size |
|
|
67
|
+
| `Cmd+B` / `Cmd+I` / `Cmd+E` | Bold / italic / code |
|
|
68
|
+
| `Cmd+K` | Link |
|
|
69
|
+
| `Cmd+Shift+C` | Copy note as image |
|
|
66
70
|
| `Esc` | Leave edit mode |
|
|
67
71
|
|
|
68
72
|
## Where notes live
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latex-stickies",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Sticky notes for your desktop that render LaTeX and Markdown.",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"icon": "node scripts/make-icon.js",
|
|
11
11
|
"dist": "node scripts/build-dmg.js",
|
|
12
12
|
"install-app": "npm run dist && rm -rf \"/Applications/LaTeX Stickies.app\" && cp -R \"dist/mac-arm64/LaTeX Stickies.app\" /Applications/ && xattr -dr com.apple.quarantine \"/Applications/LaTeX Stickies.app\"",
|
|
13
|
-
"prepublishOnly": "npm test"
|
|
13
|
+
"prepublishOnly": "npm test",
|
|
14
|
+
"postinstall": "node scripts/brand-electron.js",
|
|
15
|
+
"brand": "node scripts/brand-electron.js"
|
|
14
16
|
},
|
|
15
17
|
"build": {
|
|
16
18
|
"appId": "com.kevinjusak.latexsticky",
|
|
@@ -29,12 +31,27 @@
|
|
|
29
31
|
}
|
|
30
32
|
},
|
|
31
33
|
"devDependencies": {
|
|
32
|
-
"
|
|
34
|
+
"@codemirror/commands": "^6.11.0",
|
|
35
|
+
"@codemirror/lang-cpp": "^6.0.3",
|
|
36
|
+
"@codemirror/lang-css": "^6.3.1",
|
|
37
|
+
"@codemirror/lang-go": "^6.0.1",
|
|
38
|
+
"@codemirror/lang-html": "^6.4.12",
|
|
39
|
+
"@codemirror/lang-java": "^6.0.2",
|
|
40
|
+
"@codemirror/lang-javascript": "^6.2.5",
|
|
41
|
+
"@codemirror/lang-json": "^6.0.2",
|
|
42
|
+
"@codemirror/lang-markdown": "^6.5.2",
|
|
43
|
+
"@codemirror/lang-python": "^6.2.1",
|
|
44
|
+
"@codemirror/lang-rust": "^6.0.2",
|
|
45
|
+
"@codemirror/lang-sql": "^6.10.0",
|
|
46
|
+
"@codemirror/lang-yaml": "^6.1.3",
|
|
47
|
+
"@codemirror/language": "^6.12.4",
|
|
48
|
+
"@codemirror/state": "^6.7.2",
|
|
49
|
+
"@codemirror/view": "^6.43.10",
|
|
50
|
+
"@lezer/highlight": "^1.2.3",
|
|
33
51
|
"electron-builder": "^26.15.3",
|
|
52
|
+
"esbuild": "^0.28.2",
|
|
34
53
|
"jsdom": "^30.0.1",
|
|
35
|
-
"katex": "^0.16.11"
|
|
36
|
-
"marked": "^18.0.11",
|
|
37
|
-
"prismjs": "^1.30.0"
|
|
54
|
+
"katex": "^0.16.11"
|
|
38
55
|
},
|
|
39
56
|
"dependencies": {
|
|
40
57
|
"electron": "^44.1.1"
|
|
@@ -59,6 +76,7 @@
|
|
|
59
76
|
"files": [
|
|
60
77
|
"src/",
|
|
61
78
|
"bin/",
|
|
79
|
+
"scripts/brand-electron.js",
|
|
62
80
|
"build/icon.icns",
|
|
63
81
|
"build/icon.png"
|
|
64
82
|
],
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gives the Electron shell this app's name and icon, on macOS.
|
|
4
|
+
*
|
|
5
|
+
* Run from source or from npm there is no .app bundle of our own, so macOS
|
|
6
|
+
* takes the Dock name and icon from the shell being launched -- which is called
|
|
7
|
+
* "Electron" and wears the atom logo. app.setName() cannot change that: the
|
|
8
|
+
* Dock reads it from the bundle before any of our code runs.
|
|
9
|
+
*
|
|
10
|
+
* Editing Info.plist invalidates the bundle's ad-hoc signature, so it is
|
|
11
|
+
* re-signed afterwards. That matters: an unsigned bundle will not launch on
|
|
12
|
+
* Apple Silicon at all, which is a far worse outcome than a wrong name. If
|
|
13
|
+
* anything here fails, the original Info.plist goes back and the install
|
|
14
|
+
* continues quietly -- a cosmetic nicety must never break someone's install.
|
|
15
|
+
*/
|
|
16
|
+
const { execFileSync } = require('child_process');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const NAME = 'LaTeX Stickies';
|
|
21
|
+
|
|
22
|
+
function main() {
|
|
23
|
+
if (process.platform !== 'darwin') return; // only macOS names apps this way
|
|
24
|
+
|
|
25
|
+
let appPath;
|
|
26
|
+
try {
|
|
27
|
+
const binary = require(require.resolve('electron', { paths: [path.join(__dirname, '..')] }));
|
|
28
|
+
// .../Electron.app/Contents/MacOS/Electron -> .../Electron.app
|
|
29
|
+
appPath = path.resolve(path.dirname(binary), '..', '..');
|
|
30
|
+
} catch (_) {
|
|
31
|
+
return; // no runtime installed; nothing to brand
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const plist = path.join(appPath, 'Contents', 'Info.plist');
|
|
35
|
+
if (!fs.existsSync(plist)) return;
|
|
36
|
+
|
|
37
|
+
const read = (key) => {
|
|
38
|
+
try {
|
|
39
|
+
return execFileSync('plutil', ['-extract', key, 'raw', plist]).toString().trim();
|
|
40
|
+
} catch (_) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (read('CFBundleName') === NAME) return; // already done
|
|
46
|
+
|
|
47
|
+
const backup = fs.readFileSync(plist);
|
|
48
|
+
try {
|
|
49
|
+
for (const key of ['CFBundleName', 'CFBundleDisplayName']) {
|
|
50
|
+
try {
|
|
51
|
+
execFileSync('plutil', ['-replace', key, '-string', NAME, plist]);
|
|
52
|
+
} catch (_) {
|
|
53
|
+
execFileSync('plutil', ['-insert', key, '-string', NAME, plist]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The bundle icon is what shows before the app can set its own.
|
|
58
|
+
const icon = path.join(__dirname, '..', 'build', 'icon.icns');
|
|
59
|
+
const target = path.join(appPath, 'Contents', 'Resources', 'electron.icns');
|
|
60
|
+
if (fs.existsSync(icon) && fs.existsSync(target)) fs.copyFileSync(icon, target);
|
|
61
|
+
|
|
62
|
+
// Required: the edits above invalidate the ad-hoc signature, and macOS
|
|
63
|
+
// refuses to launch a bundle whose signature does not match its contents.
|
|
64
|
+
execFileSync('codesign', ['--force', '--deep', '--sign', '-', appPath], {
|
|
65
|
+
stdio: 'ignore',
|
|
66
|
+
});
|
|
67
|
+
execFileSync('codesign', ['--verify', appPath], { stdio: 'ignore' });
|
|
68
|
+
|
|
69
|
+
console.log(`named the Electron shell "${NAME}"`);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
fs.writeFileSync(plist, backup);
|
|
72
|
+
try {
|
|
73
|
+
execFileSync('codesign', ['--force', '--deep', '--sign', '-', appPath], {
|
|
74
|
+
stdio: 'ignore',
|
|
75
|
+
});
|
|
76
|
+
} catch (_) { /* leave it as found */ }
|
|
77
|
+
console.log(`could not rename the Electron shell (${err.message.split('\n')[0]})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
main();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
// Never fail an install over the name in the Dock.
|
|
85
|
+
console.log('skipped naming the Electron shell:', err.message.split('\n')[0]);
|
|
86
|
+
}
|
package/src/ai.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Talks to Ollama, if it happens to be running.
|
|
3
|
+
*
|
|
4
|
+
* Requests are made from the main process rather than the renderer. The note
|
|
5
|
+
* page runs under `default-src 'none'` with no connect-src, so it cannot reach
|
|
6
|
+
* the network at all -- and that is worth keeping. Widening the policy so a
|
|
7
|
+
* page full of untrusted note text can open connections would trade a real
|
|
8
|
+
* guarantee for a convenience.
|
|
9
|
+
*
|
|
10
|
+
* Everything here fails quietly. Ollama is optional: if it is not installed,
|
|
11
|
+
* not running, or slow to answer, the feature simply does not appear.
|
|
12
|
+
*/
|
|
13
|
+
const HOST = process.env.OLLAMA_HOST || 'http://127.0.0.1:11434';
|
|
14
|
+
|
|
15
|
+
const STATUS_TIMEOUT_MS = 1500;
|
|
16
|
+
const COMPLETE_TIMEOUT_MS = 8000;
|
|
17
|
+
|
|
18
|
+
/** In-flight completion, so a new keystroke can cancel the last request. */
|
|
19
|
+
let pending = null;
|
|
20
|
+
|
|
21
|
+
async function withTimeout(url, options, ms) {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
24
|
+
try {
|
|
25
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
26
|
+
} finally {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Is Ollama reachable, and what has it got? */
|
|
32
|
+
async function status() {
|
|
33
|
+
try {
|
|
34
|
+
const res = await withTimeout(`${HOST}/api/tags`, {}, STATUS_TIMEOUT_MS);
|
|
35
|
+
if (!res.ok) return { available: false, models: [] };
|
|
36
|
+
const data = await res.json();
|
|
37
|
+
const models = (data.models || [])
|
|
38
|
+
// Embedding models cannot generate text; offering one would just fail.
|
|
39
|
+
.filter((m) => !/embed/i.test(m.name))
|
|
40
|
+
.map((m) => ({ name: m.name, size: m.size || 0 }))
|
|
41
|
+
.sort((a, b) => a.size - b.size);
|
|
42
|
+
return { available: true, models, host: HOST };
|
|
43
|
+
} catch (_) {
|
|
44
|
+
return { available: false, models: [] };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Which model to suggest when none has been chosen.
|
|
50
|
+
*
|
|
51
|
+
* Size matters more than quality here: this runs on every pause in typing, and
|
|
52
|
+
* a 27B model answering in four seconds is worse than a small one answering in
|
|
53
|
+
* two hundred milliseconds. Prefer a code-completion model, then the smallest.
|
|
54
|
+
*/
|
|
55
|
+
function pickDefault(models) {
|
|
56
|
+
if (!models.length) return '';
|
|
57
|
+
const coder = models.find((m) => /coder|code/i.test(m.name));
|
|
58
|
+
if (coder) return coder.name;
|
|
59
|
+
// Sort here rather than trusting the caller: relying on the list arriving
|
|
60
|
+
// in size order is the kind of assumption that quietly stops being true.
|
|
61
|
+
return [...models].sort((a, b) => (a.size || 0) - (b.size || 0))[0].name;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Drops any part of the answer that repeats what is already typed.
|
|
66
|
+
*
|
|
67
|
+
* Asked to continue "- milk\n- eggs\n- ", a model will often reply "- bread",
|
|
68
|
+
* which would insert the bullet twice. Trim the longest overlap between the end
|
|
69
|
+
* of the prefix and the start of the completion.
|
|
70
|
+
*/
|
|
71
|
+
function trimOverlap(prefix, completion) {
|
|
72
|
+
const tail = prefix.slice(-40);
|
|
73
|
+
for (let k = Math.min(tail.length, completion.length); k > 0; k -= 1) {
|
|
74
|
+
if (completion.slice(0, k) === tail.slice(-k)) return completion.slice(k);
|
|
75
|
+
}
|
|
76
|
+
return completion;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Models trained to fill a gap, which is exactly what autocomplete is.
|
|
81
|
+
*
|
|
82
|
+
* Asked with an ordinary instruction, an instruct-tuned model answers as an
|
|
83
|
+
* assistant: given "hello my name is " it replied "Qwen." -- its own name.
|
|
84
|
+
* The fill-in-the-middle format asks it to complete the text rather than
|
|
85
|
+
* respond to it.
|
|
86
|
+
*/
|
|
87
|
+
const FIM = /coder|starcoder|codellama|codegemma|codestral/i;
|
|
88
|
+
|
|
89
|
+
function buildRequest({ prefix, suffix, model }) {
|
|
90
|
+
if (FIM.test(model)) {
|
|
91
|
+
return {
|
|
92
|
+
prompt: `<|fim_prefix|>${prefix}<|fim_suffix|>${suffix || ''}<|fim_middle|>`,
|
|
93
|
+
stop: ['\n', '<|fim_pad|>', '<|endoftext|>', '<|file_sep|>'],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// No FIM support: raw mode at least skips the chat template, so the model
|
|
97
|
+
// continues the text instead of replying to it.
|
|
98
|
+
return { prompt: prefix, stop: ['\n'] };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Tidies a raw completion into something worth showing.
|
|
103
|
+
*
|
|
104
|
+
* Cuts to one line, ends at the first sentence rather than trailing off, and
|
|
105
|
+
* drops a half-finished word when the model simply ran out of budget.
|
|
106
|
+
*/
|
|
107
|
+
function tidy(text, prefix, truncated) {
|
|
108
|
+
let out = String(text || '').replace(/^[\r\n]+/, '').split('\n')[0];
|
|
109
|
+
out = trimOverlap(prefix, out);
|
|
110
|
+
|
|
111
|
+
// End at the first sentence. The terminator must be followed by a space or
|
|
112
|
+
// the end, so "3.14" is not mistaken for the end of a sentence.
|
|
113
|
+
const sentence = /^[\s\S]*?[.!?](?=\s|$)/.exec(out);
|
|
114
|
+
if (sentence) out = sentence[0];
|
|
115
|
+
|
|
116
|
+
// Nothing to cut at: cap the length, and drop a half-finished word -- but
|
|
117
|
+
// only when the model actually ran out of budget, which it tells us. Judging
|
|
118
|
+
// that from the text alone throws away perfectly complete last words.
|
|
119
|
+
if (out.length > 90) out = out.slice(0, 90).replace(/\s+\S*$/, '');
|
|
120
|
+
else if (truncated && !/[\s.!?,;:)\]}]$/.test(out)) {
|
|
121
|
+
out = out.replace(/\s+\S*$/, '');
|
|
122
|
+
}
|
|
123
|
+
return out.trimEnd();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Asks for a continuation of `prefix`.
|
|
128
|
+
*
|
|
129
|
+
* Cancels whatever was already in flight: the caller types faster than the
|
|
130
|
+
* model answers, and a stale suggestion is worse than none.
|
|
131
|
+
*/
|
|
132
|
+
async function complete({ prefix, suffix, model }) {
|
|
133
|
+
if (!prefix || !prefix.trim()) return '';
|
|
134
|
+
|
|
135
|
+
if (pending) pending.abort();
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
pending = controller;
|
|
138
|
+
|
|
139
|
+
const timer = setTimeout(() => controller.abort(), COMPLETE_TIMEOUT_MS);
|
|
140
|
+
try {
|
|
141
|
+
const { prompt, stop } = buildRequest({ prefix, suffix, model });
|
|
142
|
+
const res = await fetch(`${HOST}/api/generate`, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: { 'Content-Type': 'application/json' },
|
|
145
|
+
signal: controller.signal,
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
model,
|
|
148
|
+
prompt,
|
|
149
|
+
// Skips the chat template, so the model continues rather than replies.
|
|
150
|
+
raw: true,
|
|
151
|
+
stream: false,
|
|
152
|
+
options: {
|
|
153
|
+
// Short and fairly literal: this is a completion, not an essay.
|
|
154
|
+
num_predict: 24,
|
|
155
|
+
temperature: 0.15,
|
|
156
|
+
stop,
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
});
|
|
160
|
+
if (!res.ok) return '';
|
|
161
|
+
const data = await res.json();
|
|
162
|
+
return tidy(data.response, prefix, data.done_reason === 'length');
|
|
163
|
+
} catch (_) {
|
|
164
|
+
return ''; // aborted, unreachable, or malformed -- all mean "no suggestion"
|
|
165
|
+
} finally {
|
|
166
|
+
clearTimeout(timer);
|
|
167
|
+
if (pending === controller) pending = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = { status, complete, pickDefault, trimOverlap, tidy, HOST };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle entry for the live-preview editor.
|
|
3
|
+
*
|
|
4
|
+
* CodeMirror 6 ships as ES modules, which cannot be loaded over file:// under
|
|
5
|
+
* the page's `script-src 'self'` policy. esbuild rolls the pieces we use into
|
|
6
|
+
* one classic script that hangs off window.CM, matching how katex, purify and
|
|
7
|
+
* prism are vendored. Run `npm run vendor` after changing this.
|
|
8
|
+
*
|
|
9
|
+
* The language packages are imported statically rather than through
|
|
10
|
+
* @codemirror/language-data, which loads grammars with dynamic import -- that
|
|
11
|
+
* would leave the bundle with chunks it cannot fetch over file://.
|
|
12
|
+
*/
|
|
13
|
+
import { EditorState, StateField, StateEffect, RangeSetBuilder, Prec } from '@codemirror/state';
|
|
14
|
+
import {
|
|
15
|
+
EditorView, keymap, Decoration, WidgetType, ViewPlugin,
|
|
16
|
+
} from '@codemirror/view';
|
|
17
|
+
import {
|
|
18
|
+
defaultKeymap, history, historyKeymap, indentWithTab,
|
|
19
|
+
} from '@codemirror/commands';
|
|
20
|
+
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
|
21
|
+
import {
|
|
22
|
+
syntaxTree, HighlightStyle, syntaxHighlighting, defaultHighlightStyle,
|
|
23
|
+
LanguageDescription,
|
|
24
|
+
} from '@codemirror/language';
|
|
25
|
+
import { tags } from '@lezer/highlight';
|
|
26
|
+
|
|
27
|
+
import { python } from '@codemirror/lang-python';
|
|
28
|
+
import { javascript } from '@codemirror/lang-javascript';
|
|
29
|
+
import { json } from '@codemirror/lang-json';
|
|
30
|
+
import { yaml } from '@codemirror/lang-yaml';
|
|
31
|
+
import { html } from '@codemirror/lang-html';
|
|
32
|
+
import { css } from '@codemirror/lang-css';
|
|
33
|
+
import { cpp } from '@codemirror/lang-cpp';
|
|
34
|
+
import { rust } from '@codemirror/lang-rust';
|
|
35
|
+
import { go } from '@codemirror/lang-go';
|
|
36
|
+
import { java } from '@codemirror/lang-java';
|
|
37
|
+
import { sql } from '@codemirror/lang-sql';
|
|
38
|
+
|
|
39
|
+
/** What a fence's language name may be written as, mapped to a grammar. */
|
|
40
|
+
const codeLanguages = [
|
|
41
|
+
LanguageDescription.of({ name: 'python', alias: ['py'], load: async () => python() }),
|
|
42
|
+
LanguageDescription.of({
|
|
43
|
+
name: 'javascript',
|
|
44
|
+
alias: ['js', 'jsx', 'ts', 'typescript', 'tsx'],
|
|
45
|
+
load: async () => javascript(),
|
|
46
|
+
}),
|
|
47
|
+
LanguageDescription.of({ name: 'json', load: async () => json() }),
|
|
48
|
+
LanguageDescription.of({ name: 'yaml', alias: ['yml'], load: async () => yaml() }),
|
|
49
|
+
LanguageDescription.of({ name: 'html', alias: ['xml'], load: async () => html() }),
|
|
50
|
+
LanguageDescription.of({ name: 'css', load: async () => css() }),
|
|
51
|
+
LanguageDescription.of({
|
|
52
|
+
name: 'cpp',
|
|
53
|
+
alias: ['c', 'c++', 'h'],
|
|
54
|
+
load: async () => cpp(),
|
|
55
|
+
}),
|
|
56
|
+
LanguageDescription.of({ name: 'rust', alias: ['rs'], load: async () => rust() }),
|
|
57
|
+
LanguageDescription.of({ name: 'go', load: async () => go() }),
|
|
58
|
+
LanguageDescription.of({ name: 'java', load: async () => java() }),
|
|
59
|
+
LanguageDescription.of({ name: 'sql', load: async () => sql() }),
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
window.CM = {
|
|
63
|
+
EditorState, StateField, StateEffect, RangeSetBuilder, Prec,
|
|
64
|
+
EditorView, keymap, Decoration, WidgetType, ViewPlugin,
|
|
65
|
+
defaultKeymap, history, historyKeymap, indentWithTab,
|
|
66
|
+
markdown, markdownLanguage,
|
|
67
|
+
syntaxTree, HighlightStyle, syntaxHighlighting, defaultHighlightStyle,
|
|
68
|
+
tags, codeLanguages,
|
|
69
|
+
};
|