Git
38 files changed +7297 −684
Review
Insert▾
react-native/components/CodeEditor.jsx (+1235 −115) modified
2
  EDITOR_OPTIONS,
2
  EDITOR_OPTIONS,
3
  EXTRA_LANGUAGES,
3
  EXTRA_LANGUAGES,
4
  MONACO_CDN,
4
  MONACO_CDN,
5
  SHIKI_CDN,


  
6
  SHIKI_LANGS,
5
  SHIKI_LANGS,
7
  SHIKI_LANG_ALIAS,
6
  SHIKI_LANG_ALIAS,
8
  SHIKI_MONACO_CDN,


  
9
  THEMES,
7
  THEMES,
10
  monacoTheme,
8
  monacoTheme,
11
} from './monacoTheme';
9
} from './monacoTheme';
12
import React, {useCallback, useEffect, useRef, useState} from 'react';


  


  
10
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
13
import {StyleSheet, useColorScheme} from 'react-native';
11
import {StyleSheet, useColorScheme} from 'react-native';
14


      
12


      


  
13
import {CDN_SOURCES} from './code/vendor';
15
import {WebView} from 'react-native-webview';
14
import {WebView} from 'react-native-webview';


  
15
import {fileSystem} from './fileSystem';
16
import {useTheme} from './theme';
16
import {useTheme} from './theme';
17
import {useThemedStyles} from './theme';
17
import {useThemedStyles} from './theme';
18


      
18


      
24
 * build resolves `CodeEditor.web.jsx` and uses Monaco directly, the native
24
 * build resolves `CodeEditor.web.jsx` and uses Monaco directly, the native
25
 * build gets this file and runs Monaco in the only DOM the app has.
25
 * build gets this file and runs Monaco in the only DOM the app has.
26
 *
26
 *
27
 * The page is served as an HTML string with the CDN as its base URL, so the


  


  
27
 * The page is served as an HTML string whose base URL is wherever Monaco is


  
28
 * loaded from — the file server when it is up, the CDN otherwise — so the
28
 * loader script counts as same-origin and WebKit doesn't refuse it.
29
 * loader script counts as same-origin and WebKit doesn't refuse it.


  
30
 *


  
31
 * Everything that crosses the bridge is a message. Native -> page is


  
32
 * `injectJavaScript` of a `window.*` call; page -> native is `postMessage`


  
33
 * with a `type`. The ref exposes the calls the page wants as methods.
29
 */
34
 */
30
export function CodeEditor({


  


  
35
export const CodeEditor = forwardRef(function CodeEditor(


  
36
  {
31
  value = '',
37
    value = '',
32
  language,
38
    language,
33
  path,
39
    path,


  
40
    projectRoot,


  
41
    sources = CDN_SOURCES,


  
42
    appKeys,


  
43
    gitChanges,


  
44
    diffOriginal = null,
34
  onChange,
45
    onChange,
35
  onSave,
46
    onSave,
36
  onSearch,


  


  
47
    onCommand,


  
48
    onOpenFile,


  
49
    onCursor,


  
50
    onDiagnostics,


  
51
    onActions,


  
52
    onLoadError,


  
53
    onFocus,
37
  style,
54
    style,
38
}) {


  


  
55
  },


  
56
  ref,


  
57
) {
39
  const webView = useRef(null);
58
  const webView = useRef(null);
40
  const [loaded, setLoaded] = useState(false);
59
  const [loaded, setLoaded] = useState(false);
41
  const colors = useTheme();
60
  const colors = useTheme();
42
  const scheme = useColorScheme() ?? 'light';
61
  const scheme = useColorScheme() ?? 'light';
43
  const styles = useThemedStyles(createStyles);
62
  const styles = useThemedStyles(createStyles);
44


      
63


      


  
64
  const inject = useCallback(script => {


  
65
    webView.current?.injectJavaScript(`${script} true;`);


  
66
  }, []);


  
67


      


  
68
  const call = useCallback(


  
69
    (name, ...args) => {


  
70
      inject(`window.${name}(${args.map(argument => jsString(argument)).join(', ')});`);


  
71
    },


  
72
    [inject],


  
73
  );


  
74


      
45
  // Monaco owns the buffers once it is up, so the document is built exactly
75
  // Monaco owns the buffers once it is up, so the document is built exactly
46
  // once. A `source` that changed identity on re-render would reload the frame
76
  // once. A `source` that changed identity on re-render would reload the frame
47
  // and throw away the user's edits along with the undo stacks.
77
  // and throw away the user's edits along with the undo stacks.
48
  const source = useRef(null);
78
  const source = useRef(null);
49
  source.current ??= {
79
  source.current ??= {
50
    html: editorHtml({path, value, language}),


  
51
    baseUrl: `${MONACO_CDN}/`,


  


  
80
    html: editorHtml({path, value, language, projectRoot}, sources),


  
81
    baseUrl: `${sources.monaco}/`,
52
  };
82
  };
53


      
83


      
54
  // Monaco owns the buffers, so a new `value` is pushed in as an edit rather
84
  // Monaco owns the buffers, so a new `value` is pushed in as an edit rather
59
  // alone can't tell "the user typed" from "we are on a different file now".
89
  // alone can't tell "the user typed" from "we are on a different file now".
60
  const applied = useRef({path, value, language});
90
  const applied = useRef({path, value, language});
61


      
91


      


  
92
  useEffect(() => {


  
93
    if (!loaded) {


  
94
      return;


  
95
    }


  
96


      


  
97
    call('setProject', projectRoot ?? null);


  
98
  }, [loaded, projectRoot, call]);


  
99


      
62
  // Path and value go over together, in one message, for the same reason.
100
  // Path and value go over together, in one message, for the same reason.
63
  // Pushing the value first would write the incoming file's text into the
101
  // Pushing the value first would write the incoming file's text into the
64
  // outgoing file's buffer before the editor ever swapped models. `language`
102
  // outgoing file's buffer before the editor ever swapped models.
65
  // rides along; without it the web side resolves the mode from the path


  
66
  // against Monaco's own extension registry — `.md` is only `markdown`


  
67
  // because Monaco says so.


  
68
  useEffect(() => {
103
  useEffect(() => {
69
    if (
104
    if (
70
      !loaded ||
105
      !loaded ||
76
    }
111
    }
77


      
112


      
78
    applied.current = {path, value, language};
113
    applied.current = {path, value, language};
79
    webView.current?.injectJavaScript(


  
80
      `window.setFile(${JSON.stringify({path, value, language})}); true;`,


  
81
    );


  


  
114
    call('setFile', {path, value, language});
82
  }, [loaded, path, value, language]);
115
  }, [loaded, path, value, language, call]);
83


      
116


      
84
  // The palette arrives from a fetch, so the theme is pushed in rather than
117
  // The palette arrives from a fetch, so the theme is pushed in rather than
85
  // baked into the document.
118
  // baked into the document.
88
      return;
121
      return;
89
    }
122
    }
90


      
123


      
91
    webView.current?.injectJavaScript(


  
92
      `window.setTheme(${JSON.stringify(monacoTheme(colors, scheme))}); true;`,


  


  
124
    call('setTheme', monacoTheme(colors, scheme));


  
125
  }, [loaded, colors, scheme, call]);


  
126


      


  
127
  useEffect(() => {


  
128
    if (!loaded || !appKeys) {


  
129
      return;


  
130
    }


  
131


      


  
132
    call('setAppKeys', appKeys);


  
133
  }, [loaded, appKeys, call]);


  
134


      


  
135
  useEffect(() => {


  
136
    if (!loaded || !path) {


  
137
      return;


  
138
    }


  
139


      


  
140
    call('setGitChanges', path, gitChanges ?? []);


  
141
  }, [loaded, path, gitChanges, call]);


  
142


      


  
143
  useEffect(() => {


  
144
    if (!loaded) {


  
145
      return;


  
146
    }


  
147


      


  
148
    if (diffOriginal === null || diffOriginal === undefined) {


  
149
      call('hideDiff');


  
150
    } else {


  
151
      call('showDiff', diffOriginal);


  
152
    }


  
153
  }, [loaded, diffOriginal, call]);


  
154


      


  
155
  // Language servers. The page asks for one per language it meets; the socket


  
156
  // is dialled from here because the page is a secure context and may not open


  
157
  // a plain `ws://` itself. Messages are relayed verbatim in both directions.


  
158
  const servers = useRef(new Map());


  
159


      


  
160
  const closeServers = useCallback(() => {


  
161
    for (const socket of servers.current.values()) {


  
162
      socket.onmessage = null;


  
163
      socket.onclose = null;


  
164
      socket.onerror = null;


  
165
      socket.close();


  
166
    }


  
167
    servers.current.clear();


  
168
  }, []);


  
169


      


  
170
  const openServer = useCallback(


  
171
    lang => {


  
172
      if (servers.current.has(lang) || !projectRoot) {


  
173
        return;


  
174
      }


  
175


      


  
176
      let socket;


  
177
      try {


  
178
        socket = new WebSocket(fileSystem.lspUrl(projectRoot, lang));


  
179
      } catch (error) {


  
180
        call('lspUnavailable', lang);


  
181
        return;


  
182
      }


  
183


      


  
184
      servers.current.set(lang, socket);


  
185


      


  
186
      socket.onmessage = event => {


  
187
        const message = JSON.parse(event.data);


  
188


      


  
189
        if (message.type === 'ready') {


  
190
          call('lspReady', lang, message.server);


  
191
        } else if (message.type === 'message') {


  
192
          call('lspReceive', lang, message.body);


  
193
        } else if (message.type === 'unavailable' || message.type === 'error' || message.type === 'exit') {


  
194
          call('lspUnavailable', lang);


  
195
        }


  
196
      };


  
197


      


  
198
      socket.onerror = () => call('lspUnavailable', lang);


  
199
      socket.onclose = () => {


  
200
        if (servers.current.get(lang) === socket) {


  
201
          servers.current.delete(lang);


  
202
        }


  
203
      };


  
204
    },


  
205
    [projectRoot, call],
93
    );
206
  );
94
  }, [loaded, colors, scheme]);


  


  
207


      


  
208
  // A new project means new servers: their roots are fixed when spawned.


  
209
  useEffect(() => {


  
210
    closeServers();


  
211
    if (loaded) {


  
212
      call('resetLanguageServers');


  
213
    }


  
214
  }, [projectRoot, loaded, closeServers, call]);


  
215


      


  
216
  useEffect(() => () => closeServers(), [closeServers]);


  
217


      


  
218
  const pendingActions = useRef([]);


  
219


      


  
220
  useImperativeHandle(


  
221
    ref,


  
222
    () => ({


  
223
      revealPosition: (line, column = 1) => call('revealPosition', line, column),


  
224
      requestSave: (format = false) => call('requestSave', format),


  
225
      runAction: id => call('runAction', id),


  
226
      focus: () => call('focusEditor'),


  
227
      find: term => call('openFind', term ?? ''),


  
228
      listActions: () =>


  
229
        new Promise(resolve => {


  
230
          pendingActions.current.push(resolve);


  
231
          call('listActions');


  
232
        }),


  
233
      reload: () => webView.current?.reload(),


  
234
    }),


  
235
    [call],


  
236
  );
95


      
237


      
96
  const onMessage = useCallback(
238
  const onMessage = useCallback(
97
    event => {
239
    event => {
98
      const message = JSON.parse(event.nativeEvent.data);
240
      const message = JSON.parse(event.nativeEvent.data);
99


      
241


      
100
      if (message.type === 'change') {


  


  
242
      switch (message.type) {


  
243
        case 'ready':


  
244
          setLoaded(true);


  
245
          break;


  
246


      


  
247
        case 'loadError':


  
248
          onLoadError?.(message);


  
249
          break;


  
250


      


  
251
        case 'change':
101
        applied.current = {path: message.path, value: message.value, language};
252
          applied.current = {path: message.path, value: message.value, language};
102
        onChange?.(message.value);


  


  
253
          onChange?.(message.value, message.path);


  
254
          break;


  
255


      


  
256
        // The buffer the editor is asking us to write is the one it just


  
257
        // handed over, so it counts as agreed on — otherwise a save


  
258
        // mid-keystroke would come back as a `value` prop and get injected


  
259
        // straight back at it.


  
260
        case 'save':


  
261
          applied.current = {path: message.path, value: message.value, language};


  
262
          onSave?.(message.value, message.path, Boolean(message.blur));


  
263
          break;


  
264


      


  
265
        // A shortcut that belongs to the app rather than to the editor —


  
266
        // Monaco is the only thing that sees the keystroke while the WebView


  
267
        // holds focus, so it hands it back.


  
268
        case 'key':


  
269
          onCommand?.(message);


  
270
          break;


  
271


      


  
272
        case 'focus':


  
273
          onFocus?.();


  
274
          break;


  
275


      


  
276
        case 'openFile':


  
277
          onOpenFile?.(message);


  
278
          break;


  
279


      


  
280
        case 'cursor':


  
281
          onCursor?.(message);


  
282
          break;


  
283


      


  
284
        case 'diagnostics':


  
285
          onDiagnostics?.(message);


  
286
          break;


  
287


      


  
288
        case 'actions': {


  
289
          const waiting = pendingActions.current.splice(0);


  
290
          waiting.forEach(resolve => resolve(message.actions));


  
291
          onActions?.(message.actions);


  
292
          break;
103
      }
293
        }
104


      
294


      
105
      // The buffer the editor is asking us to write is the one it just handed


  
106
      // over, so it counts as agreed on — otherwise a save mid-keystroke would


  
107
      // come back as a `value` prop and get injected straight back at it.


  
108
      if (message.type === 'save') {


  
109
        applied.current = {path: message.path, value: message.value, language};


  
110
        onSave?.(message.value);


  


  
295
        case 'lspOpen':


  
296
          openServer(message.language);


  
297
          break;


  
298


      


  
299
        case 'lsp': {


  
300
          const socket = servers.current.get(message.language);


  
301
          if (socket?.readyState === 1) {


  
302
            socket.send(JSON.stringify({type: 'message', body: message.body}));


  
303
          }


  
304
          break;
111
      }
305
        }
112


      
306


      
113
      // Cmd+P is the app's, not the editor's — Monaco is the only thing that


  
114
      // sees the keystroke while the WebView holds focus, so it hands it back.


  
115
      if (message.type === 'search') {


  
116
        onSearch?.();


  


  
307
        default:


  
308
          break;
117
      }
309
      }
118
    },
310
    },
119
    [language, onChange, onSave, onSearch],


  


  
311
    [language, onChange, onSave, onCommand, onOpenFile, onCursor, onDiagnostics, onActions, onLoadError, onFocus, openServer],
120
  );
312
  );
121


      
313


      
122
  return (
314
  return (
131
      // are needed: the style covers the old renderer, `opaque` the new one.
323
      // are needed: the style covers the old renderer, `opaque` the new one.
132
      opaque={false}
324
      opaque={false}
133
      backgroundColor="transparent"
325
      backgroundColor="transparent"
134
      onLoadEnd={() => setLoaded(true)}


  
135
      onMessage={onMessage}
326
      onMessage={onMessage}
136
      // Nothing in the editor should ever navigate the app away from itself.
327
      // Nothing in the editor should ever navigate the app away from itself.
137
      onShouldStartLoadWithRequest={request =>
328
      onShouldStartLoadWithRequest={request =>
138
        request.navigationType !== 'click'
329
        request.navigationType !== 'click'
139
      }
330
      }
140
    />
331
    />
141
  );
332
  );


  
333
});


  
334


      


  
335
/**


  
336
 * `injectJavaScript` takes source, not data. JSON is very nearly a subset of


  
337
 * JS, and the two line separators are the whole of the difference: JSON allows


  
338
 * them raw inside a string, JS ends the line on them.


  
339
 */


  
340
function jsString(value) {


  
341
  return JSON.stringify(value === undefined ? null : value)


  
342
    .replace(/\u2028/g, '\\u2028')


  
343
    .replace(/\u2029/g, '\\u2029');
142
}
344
}
143


      
345


      
144
function editorHtml(file) {


  


  
346
export function editorHtml(file, sources) {


  
347
  const importMap = sources.importMap


  
348
    ? `<script type="importmap">${JSON.stringify(sources.importMap)}</script>`


  
349
    : '';


  
350


      
145
  return `<!doctype html>
351
  return `<!doctype html>
146
<html>
352
<html>
147
  <head>
353
  <head>
148
    <meta charset="utf-8" />
354
    <meta charset="utf-8" />
149
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
355
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />


  
356
    ${importMap}
150
    <style>
357
    <style>
151
      /* A transparent WKWebView still paints whatever the document paints,
358
      /* A transparent WKWebView still paints whatever the document paints,
152
         and the default UA background is white. */
359
         and the default UA background is white. */
153
      html, body, #container {
360
      html, body, #container, #diff {
154
        margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden;
361
        margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden;
155
        background: transparent;
362
        background: transparent;
156
      }
363
      }


  
364
      #diff { display: none; }
157
      .monaco-editor, .monaco-editor-background { background: transparent; }
365
      .monaco-editor, .monaco-editor-background, .monaco-diff-editor { background: transparent; }
158


      
366


      
159
      /* WebKit rings the focused element in the system accent colour. Monaco
367
      /* WebKit rings the focused element in the system accent colour. Monaco
160
         keeps focus on a hidden textarea, so the ring only ever traces the
368
         keeps focus on a hidden textarea, so the ring only ever traces the
161
         editor's outer edge — it reads as a stray blue border. */
369
         editor's outer edge — it reads as a stray blue border. */
162
      :focus, :focus-visible { outline: none; }
370
      :focus, :focus-visible { outline: none; }
163
      .monaco-editor .overflow-guard { outline: none; }
371
      .monaco-editor .overflow-guard { outline: none; }


  
372


      


  
373
      /* Git changes in the gutter, the way VS Code draws them. */


  
374
      .git-added, .git-modified, .git-deleted {


  
375
        width: 3px !important; margin-left: 6px; border-radius: 1px;


  
376
      }


  
377
      .git-added { background: #2ea043; }


  
378
      .git-modified { background: #0078d4; }


  
379
      .git-deleted {


  
380
        background: transparent;


  
381
        border-bottom: 3px solid #f85149; height: 0 !important;


  
382
        top: -1px;


  
383
      }


  
384


      


  
385
      #status {


  
386
        position: absolute; right: 10px; bottom: 6px; font: 11px -apple-system, sans-serif;


  
387
        opacity: 0.6; pointer-events: none;


  
388
      }
164
    </style>
389
    </style>
165
  </head>
390
  </head>
166
  <body>
391
  <body>
167
    <div id="container"></div>
392
    <div id="container"></div>
168
    <script src="${MONACO_CDN}/vs/loader.js"></script>


  


  
393
    <div id="diff"></div>
169
    <script>
394
    <script>


  
395
      var SOURCES = ${JSON.stringify(sources)};


  
396
      var CDN = ${JSON.stringify(MONACO_CDN)};
170
      var editor = null;
397
      var editor = null;


  
398
      var diffEditor = null;
171
      var theme = null;
399
      var theme = null;
172
      var pendingFile = null;
400
      var pendingFile = null;


  
401
      var pendingOptions = null;


  
402
      var projectRoot = ${JSON.stringify(file.projectRoot ?? null)};


  
403
      var monacoBase = SOURCES.monaco;


  
404


      


  
405
      function post(message) {


  
406
        if (window.ReactNativeWebView) {


  
407
          window.ReactNativeWebView.postMessage(JSON.stringify(message));


  
408
        }


  
409
      }
173


      
410


      
174
      // One model per file, keyed by path. Sharing a model across files shares
411
      // One model per file, keyed by path. Sharing a model across files shares
175
      // its undo stack with them: every switch pushed the new text onto the one
412
      // its undo stack with them: every switch pushed the new text onto the one
195
        return currentPath === UNTITLED ? null : currentPath;
432
        return currentPath === UNTITLED ? null : currentPath;
196
      }
433
      }
197


      
434


      


  
435
      // Absolute on disk, so a language server and the editor agree on what a


  
436
      // file is called, and so a definition in another file resolves to a path


  
437
      // the native side can open.


  
438
      function absolutePath(path) {


  
439
        if (!projectRoot) {


  
440
          return '/' + path;


  
441
        }


  
442
        return projectRoot.replace(/\\/$/, '') + '/' + path;


  
443
      }


  
444


      


  
445
      function fileUri(path) {


  
446
        return monaco.Uri.file(absolutePath(path));


  
447
      }


  
448


      


  
449
      function relativePath(uri) {


  
450
        var fsPath = uri.path || uri.fsPath || String(uri);


  
451
        if (projectRoot && fsPath.indexOf(projectRoot) === 0) {


  
452
          return fsPath.slice(projectRoot.length).replace(/^\\//, '');


  
453
        }


  
454
        return fsPath.replace(/^\\//, '');


  
455
      }


  
456


      
198
      // The two VS Code theme files, whole. Shiki reads its grammars off a CDN,
457
      // The two VS Code theme files, whole. Shiki reads its grammars off a CDN
199
      // but a theme is the user's own file — it travels with the document.


  


  
458
      // or the file server, but a theme is the user's own file — it travels


  
459
      // with the document.
200
      var THEMES = ${JSON.stringify(THEMES)};
460
      var THEMES = ${JSON.stringify(THEMES)};
201


      
461


      
202
      // Each theme run through textmateThemeToMonacoTheme, once Shiki is up.
462
      // Each theme run through textmateThemeToMonacoTheme, once Shiki is up.
203
      // Until then Monaco is still tokenizing with Monarch, whose token names
463
      // Until then Monaco is still tokenizing with Monarch, whose token names
204
      // no TextMate rule would have matched anyway.
464
      // no TextMate rule would have matched anyway.
205
      var converted = null;
465
      var converted = null;
206


      
466


      
207
      // Monaco's language services run in workers. Pulled straight off a CDN
467
      // Monaco's language services run in workers. Pulled straight off another
208
      // they would be cross-origin, so each worker is booted from a data: URL
468
      // origin they would be cross-origin, so each worker is booted from a
209
      // that imports the real script instead.
469
      // data: URL that imports the real script instead.
210
      window.MonacoEnvironment = {
470
      window.MonacoEnvironment = {
211
        getWorkerUrl: function () {
471
        getWorkerUrl: function () {
212
          return 'data:text/javascript;charset=utf-8,' + encodeURIComponent(
472
          return 'data:text/javascript;charset=utf-8,' + encodeURIComponent(
213
            "self.MonacoEnvironment = { baseUrl: '${MONACO_CDN}/' };" +
473
            "self.MonacoEnvironment = { baseUrl: '" + monacoBase + "/' };" +
214
            "importScripts('${MONACO_CDN}/vs/base/worker/workerMain.js');"
474
            "importScripts('" + monacoBase + "/vs/base/worker/workerMain.js');"
215
          );
475
          );
216
        },
476
        },
217
      };
477
      };
225


      
485


      
226
      // The theme is defined twice over its life: once from the file alone, so
486
      // The theme is defined twice over its life: once from the file alone, so
227
      // the editor is transparent and the right shade from the first frame, and
487
      // the editor is transparent and the right shade from the first frame, and
228
      // again once Shiki has converted the 211 TextMate rules the file carries.
488
      // again once Shiki has converted the TextMate rules the file carries.
229
      // Either way the app's own colours go on last.
489
      // Either way the app's own colours go on last.
230
      function applyTheme() {
490
      function applyTheme() {
231
        if (!window.monaco || !theme) {
491
        if (!window.monaco || !theme) {
236
        var next = converted && converted[theme.name];
496
        var next = converted && converted[theme.name];
237


      
497


      
238
        if (!next) {
498
        if (!next) {
239
          // The file's colours without its scopes: Monarch still doing the


  
240
          // tokenizing, but against the right surface.


  
241
          next = {
499
          next = {
242
            base: file.type === 'dark' ? 'vs-dark' : 'vs',
500
            base: file.type === 'dark' ? 'vs-dark' : 'vs',
243
            inherit: true,
501
            inherit: true,
254
      }
512
      }
255


      
513


      
256
      // Shiki hands Monaco VS Code's own tokenizer, so the theme's scopes
514
      // Shiki hands Monaco VS Code's own tokenizer, so the theme's scopes
257
      // finally have scopes to match. It is a CDN import over WASM: slower to


  


  
515
      // finally have scopes to match. It is a WASM-backed ESM import: slower
258
      // arrive than the editor is to boot, and allowed to fail — a miss leaves
516
      // to arrive than the editor is to boot, and allowed to fail — a miss
259
      // Monarch in place, which is what the editor came with.
517
      // leaves Monarch in place, which is what the editor came with.
260
      async function startShiki() {
518
      async function startShiki() {
261
        try {
519
        try {
262
          var shiki = await import('${SHIKI_CDN}');


  
263
          var bridge = await import('${SHIKI_MONACO_CDN}');


  


  
520
          var shiki = await import(SOURCES.shiki);


  
521
          var bridge = await import(SOURCES.shikiMonaco);
264
          var names = Object.keys(THEMES);
522
          var names = Object.keys(THEMES);
265


      
523


      
266
          var highlighter = await shiki.createHighlighter({
524
          var highlighter = await shiki.createHighlighter({
269
            langAlias: ${JSON.stringify(SHIKI_LANG_ALIAS)},
527
            langAlias: ${JSON.stringify(SHIKI_LANG_ALIAS)},
270
          });
528
          });
271


      
529


      
272
          // shikiToMonaco colours a token by looking its scope up by the


  
273
          // colour it resolved, so the rules Monaco holds have to be the ones


  
274
          // it derived — same function over the same themes. applyTheme then


  
275
          // layers the app's colours back over the result.


  
276
          converted = {};
530
          converted = {};
277
          names.forEach(function (name) {
531
          names.forEach(function (name) {
278
            converted[name] = bridge.textmateThemeToMonacoTheme(
532
            converted[name] = bridge.textmateThemeToMonacoTheme(
283
          bridge.shikiToMonaco(highlighter, monaco);
537
          bridge.shikiToMonaco(highlighter, monaco);
284
          applyTheme();
538
          applyTheme();
285
        } catch (error) {
539
        } catch (error) {


  
540
          post({type: 'log', text: 'shiki unavailable: ' + (error && error.message)});
286
        }
541
        }
287
      }
542
      }
288


      
543


      
327
        var model = models[path];
582
        var model = models[path];
328


      
583


      
329
        if (!model) {
584
        if (!model) {
330
          var uri = path === UNTITLED ? undefined : monaco.Uri.file(path);
585
          var uri = path === UNTITLED ? undefined : fileUri(path);
331


      
586


      
332
          model =
587
          model =
333
            (uri && monaco.editor.getModel(uri)) ||
588
            (uri && monaco.editor.getModel(uri)) ||
337
              uri,
592
              uri,
338
            );
593
            );
339


      
594


      


  
595
          if (pendingOptions) {


  
596
            model.updateOptions({tabSize: pendingOptions.tabSize, insertSpaces: pendingOptions.insertSpaces});


  
597
          }


  
598


      
340
          models[path] = model;
599
          models[path] = model;


  
600


      


  
601
          if (path !== UNTITLED) {


  
602
            lspDidOpen(model);


  
603
          }
341
        }
604
        }
342


      
605


      
343
        return model;
606
        return model;
366
        // A file that has moved on disk since it was last open. Anything else
629
        // A file that has moved on disk since it was last open. Anything else
367
        // is the editor's own text coming back around as a prop.
630
        // is the editor's own text coming back around as a prop.
368
        if (model.getValue() !== next.value) {
631
        if (model.getValue() !== next.value) {
369
          model.setValue(next.value);


  


  
632
          // pushEditOperations rather than setValue: the undo stack survives,


  
633
          // and the cursor stays roughly where it was.


  
634
          var full = model.getFullModelRange();


  
635
          model.pushEditOperations([], [{range: full, text: next.value}], function () { return null; });
370
        }
636
        }
371


      
637


      
372
        var id = next.language || languageForPath(path);
638
        var id = next.language || languageForPath(path);
377


      
643


      
378
        if (switching) {
644
        if (switching) {
379
          currentPath = path;
645
          currentPath = path;


  
646
          if (diffEditor && diffVisible) {


  
647
            hideDiff();


  
648
          }
380
          editor.setModel(model);
649
          editor.setModel(model);
381


      
650


      
382
          if (viewStates[path]) {
651
          if (viewStates[path]) {
383
            editor.restoreViewState(viewStates[path]);
652
            editor.restoreViewState(viewStates[path]);
384
          }
653
          }


  
654


      


  
655
          applyGitDecorations(path);


  
656
          postDiagnosticsFor(model);
385
        }
657
        }
386
      };
658
      };
387


      
659


      
388
      function post(message) {


  
389
        if (window.ReactNativeWebView) {


  
390
          window.ReactNativeWebView.postMessage(JSON.stringify(message));


  


  
660
      // All buffers belong to the project; a new project means new URIs, new


  
661
      // servers and no leftover models.


  
662
      window.setProject = function (root) {


  
663
        if (root === projectRoot) {


  
664
          return;
391
        }
665
        }
392
      }


  
393


      
666


      
394
      require.config({paths: {vs: '${MONACO_CDN}/vs'}});


  
395
      require(['vs/editor/editor.main'], function () {


  
396
        // Registered before Shiki, which pairs its grammars against whichever


  
397
        // modes exist at the moment it runs.


  
398
        ${JSON.stringify(EXTRA_LANGUAGES)}.forEach(function (language) {


  
399
          monaco.languages.register(language);


  


  
667
        projectRoot = root;


  
668


      


  
669
        if (!window.monaco) {


  
670
          return;


  
671
        }


  
672


      


  
673
        Object.keys(models).forEach(function (path) {


  
674
          if (models[path] !== editor.getModel()) {


  
675
            models[path].dispose();


  
676
          }
400
        });
677
        });
401


      
678


      
402
        // No model of its own: every buffer this editor shows is one setFile


  
403
        // created against a URI, and an implicit one would only be orphaned by


  
404
        // the first switch.


  
405
        editor = monaco.editor.create(document.getElementById('container'), Object.assign(


  
406
          ${JSON.stringify(EDITOR_OPTIONS)},


  
407
          {model: null}


  
408
        ));


  


  
679
        var current = editor && editor.getModel();


  
680
        models = {};


  
681
        viewStates = {};


  
682
        gitChanges = {};
409


      
683


      
410
        applyTheme();


  


  
684
        if (current) {


  
685
          current.dispose();


  
686
          currentPath = null;


  
687
        }


  
688
      };


  
689


      


  
690
      window.setOptions = function (options) {


  
691
        pendingOptions = options;


  
692


      


  
693
        if (!editor) {


  
694
          return;


  
695
        }
411


      
696


      
412
        // Whatever the native side asked for while the loader was still


  
413
        // running, or the file the document was built around if it asked for


  
414
        // nothing.


  
415
        window.setFile(pendingFile || ${JSON.stringify(file)});


  
416
        pendingFile = null;


  
417


      


  
418
        // Bound after the first file, so opening one is not itself a change.


  
419
        // The listener is the editor's rather than the model's, so it follows


  
420
        // whichever model is attached.


  
421
        editor.onDidChangeModelContent(function () {


  
422
          post({type: 'change', path: filePath(), value: editor.getValue()});


  


  
697
        editor.updateOptions(options);


  
698
        if (diffEditor) {


  
699
          diffEditor.updateOptions(options);


  
700
        }


  
701
        monaco.editor.getModels().forEach(function (model) {


  
702
          model.updateOptions({tabSize: options.tabSize, insertSpaces: options.insertSpaces});
423
        });
703
        });


  
704
      };


  
705


      


  
706
      window.revealPosition = function (line, column) {


  
707
        if (!editor) {


  
708
          return;


  
709
        }


  
710


      


  
711
        var position = {lineNumber: Math.max(1, line || 1), column: Math.max(1, column || 1)};


  
712
        editor.setPosition(position);


  
713
        editor.revealPositionInCenter(position, 0);


  
714
        editor.focus();


  
715
      };


  
716


      


  
717
      window.focusEditor = function () {


  
718
        if (editor) {


  
719
          editor.focus();


  
720
        }


  
721
      };


  
722


      


  
723
      window.openFind = function (term) {


  
724
        if (!editor) {


  
725
          return;


  
726
        }


  
727


      


  
728
        editor.focus();


  
729
        var action = editor.getAction('actions.find');


  
730
        if (action) {


  
731
          action.run();


  
732
        }


  
733
      };


  
734


      


  
735
      window.runAction = function (id) {


  
736
        if (!editor) {


  
737
          return;


  
738
        }


  
739


      


  
740
        editor.focus();


  
741
        var action = editor.getAction(id);


  
742
        if (action) {


  
743
          action.run();


  
744
        } else {


  
745
          editor.trigger('app', id, null);


  
746
        }


  
747
      };


  
748


      


  
749
      window.listActions = function () {


  
750
        if (!editor) {


  
751
          post({type: 'actions', actions: []});


  
752
          return;


  
753
        }


  
754


      


  
755
        post({


  
756
          type: 'actions',


  
757
          actions: editor.getSupportedActions().map(function (action) {


  
758
            return {id: action.id, label: action.label, alias: action.alias};


  
759
          }),


  
760
        });


  
761
      };


  
762


      


  
763
      // Format first when asked, then hand the buffer over. A language with no


  
764
      // formatter is common, so the format is allowed to do nothing — and it


  
765
      // is given a deadline, because a hung language server must not swallow


  
766
      // the save.


  
767
      window.requestSave = function (format) {


  
768
        if (!editor) {


  
769
          return;


  
770
        }
424


      
771


      
425
        // CtrlCmd is Cmd on macOS. Registering the binding with Monaco rather


  
426
        // than on the document is also what keeps WebKit from opening its own


  
427
        // save sheet — a handled keybinding never reaches the browser default.


  
428
        editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {


  


  
772
        var done = false;


  
773
        function finish() {


  
774
          if (done) {


  
775
            return;


  
776
          }


  
777
          done = true;
429
          post({type: 'save', path: filePath(), value: editor.getValue()});
778
          post({type: 'save', path: filePath(), value: editor.getValue()});


  
779
        }


  
780


      


  
781
        if (!format) {


  
782
          finish();


  
783
          return;


  
784
        }


  
785


      


  
786
        var action = editor.getAction('editor.action.formatDocument');


  
787
        if (!action) {


  
788
          finish();


  
789
          return;


  
790
        }


  
791


      


  
792
        var timer = setTimeout(finish, 1500);


  
793
        Promise.resolve()


  
794
          .then(function () { return action.run(); })


  
795
          .catch(function () {})


  
796
          .then(function () { clearTimeout(timer); finish(); });


  
797
      };


  
798


      


  
799
      // ---- Git gutter --------------------------------------------------------


  
800


      


  
801
      var gitChanges = {};


  
802
      var gitDecorationIds = {};


  
803


      


  
804
      window.setGitChanges = function (path, changes) {


  
805
        gitChanges[path] = changes || [];


  
806
        if (path === currentPath) {


  
807
          applyGitDecorations(path);


  
808
        }


  
809
      };


  
810


      


  
811
      function applyGitDecorations(path) {


  
812
        var model = models[path];


  
813
        if (!model || model.isDisposed()) {


  
814
          return;


  
815
        }


  
816


      


  
817
        var decorations = (gitChanges[path] || []).map(function (change) {


  
818
          var start = Math.max(1, change.startLine);


  
819
          var end = Math.max(start, change.endLine);


  
820
          return {


  
821
            range: new monaco.Range(start, 1, end, 1),


  
822
            options: {


  
823
              isWholeLine: false,


  
824
              linesDecorationsClassName: 'git-' + change.kind,


  
825
              overviewRuler: {


  
826
                color: change.kind === 'added' ? '#2ea043' : change.kind === 'deleted' ? '#f85149' : '#0078d4',


  
827
                position: monaco.editor.OverviewRulerLane.Left,


  
828
              },


  
829
            },


  
830
          };
430
        });
831
        });
431


      
832


      
432
        // Same reasoning as save: bound here so the keystroke never reaches


  
433
        // WebKit, which would otherwise open its own print dialog.


  
434
        editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyP, function () {


  
435
          post({type: 'search'});


  


  
833
        gitDecorationIds[path] = model.deltaDecorations(gitDecorationIds[path] || [], decorations);


  
834
      }


  
835


      


  
836
      // ---- Diff view ---------------------------------------------------------


  
837


      


  
838
      var diffVisible = false;


  
839
      var diffOriginalModel = null;


  
840


      


  
841
      window.showDiff = function (original) {


  
842
        if (!editor) {


  
843
          return;


  
844
        }


  
845


      


  
846
        var model = editor.getModel();


  
847
        if (!model) {


  
848
          return;


  
849
        }


  
850


      


  
851
        if (!diffEditor) {


  
852
          diffEditor = monaco.editor.createDiffEditor(document.getElementById('diff'), Object.assign(


  
853
            {},


  
854
            ${JSON.stringify(EDITOR_OPTIONS)},


  
855
            pendingOptions || {},


  
856
            {automaticLayout: true, renderSideBySide: true, originalEditable: false, readOnly: false, renderOverviewRuler: false}


  
857
          ));


  
858
        }


  
859


      


  
860
        if (diffOriginalModel) {


  
861
          diffOriginalModel.dispose();


  
862
        }


  
863


      


  
864
        diffOriginalModel = monaco.editor.createModel(original || '', model.getLanguageId());


  
865
        diffEditor.setModel({original: diffOriginalModel, modified: model});


  
866
        document.getElementById('container').style.display = 'none';


  
867
        document.getElementById('diff').style.display = 'block';


  
868
        diffVisible = true;


  
869
      };


  
870


      


  
871
      function hideDiff() {


  
872
        if (!diffVisible) {


  
873
          return;


  
874
        }


  
875


      


  
876
        document.getElementById('diff').style.display = 'none';


  
877
        document.getElementById('container').style.display = 'block';


  
878
        diffVisible = false;


  
879


      


  
880
        if (diffEditor) {


  
881
          diffEditor.setModel(null);


  
882
        }


  
883
        if (diffOriginalModel) {


  
884
          diffOriginalModel.dispose();


  
885
          diffOriginalModel = null;


  
886
        }


  
887
        if (editor) {


  
888
          editor.layout();


  
889
          editor.focus();


  
890
        }


  
891
      }


  
892


      


  
893
      window.hideDiff = hideDiff;


  
894


      


  
895
      // ---- App shortcuts -----------------------------------------------------


  
896


      


  
897
      // The app's own bindings, as [{key, metaKey, ctrlKey, altKey, shiftKey}].


  
898
      // Any of them pressed while the editor has focus is handed back rather


  
899
      // than swallowed. Only combinations with a modifier are taken on the


  
900
      // way down — a bare Escape belongs to whatever Monaco widget is open,


  
901
      // and is only handed back when nothing here wanted it.


  
902
      var appKeys = [];


  
903


      


  
904
      window.setAppKeys = function (keys) {


  
905
        appKeys = keys || [];


  
906
      };


  
907


      


  
908
      function keyMatches(binding, event) {


  
909
        return String(binding.key).toLowerCase() === String(event.key).toLowerCase() &&


  
910
          Boolean(binding.metaKey) === event.metaKey &&


  
911
          Boolean(binding.ctrlKey) === event.ctrlKey &&


  
912
          Boolean(binding.altKey) === event.altKey &&


  
913
          Boolean(binding.shiftKey) === event.shiftKey;


  
914
      }


  
915


      


  
916
      function keyMessage(event) {


  
917
        return {


  
918
          type: 'key',


  
919
          key: event.key,


  
920
          code: event.code,


  
921
          metaKey: event.metaKey,


  
922
          ctrlKey: event.ctrlKey,


  
923
          altKey: event.altKey,


  
924
          shiftKey: event.shiftKey,


  
925
        };


  
926
      }


  
927


      


  
928
      document.addEventListener('keydown', function (event) {


  
929
        var hasModifier = event.metaKey || event.ctrlKey || event.altKey;


  
930
        if (!hasModifier) {


  
931
          return;


  
932
        }


  
933


      


  
934
        for (var index = 0; index < appKeys.length; index++) {


  
935
          if (keyMatches(appKeys[index], event)) {


  
936
            event.preventDefault();


  
937
            event.stopPropagation();


  
938
            post(keyMessage(event));


  
939
            return;


  
940
          }


  
941
        }


  
942
      }, true);


  
943


      


  
944
      document.addEventListener('keydown', function (event) {


  
945
        if (event.metaKey || event.ctrlKey || event.altKey || event.defaultPrevented) {


  
946
          return;


  
947
        }


  
948


      


  
949
        for (var index = 0; index < appKeys.length; index++) {


  
950
          if (keyMatches(appKeys[index], event)) {


  
951
            post(keyMessage(event));


  
952
            return;


  
953
          }


  
954
        }


  
955
      }, false);


  
956


      


  
957
      // ---- Diagnostics -------------------------------------------------------


  
958


      


  
959
      // Monaco's own workers (TypeScript, JSON, CSS) and the language servers


  
960
      // both end up as markers; whichever wrote them, the native side gets


  
961
      // the list for the Problems panel.


  
962
      function postDiagnosticsFor(model) {


  
963
        if (!model || model.isDisposed() || !model.uri || model.uri.scheme !== 'file') {


  
964
          return;


  
965
        }


  
966


      


  
967
        var markers = monaco.editor.getModelMarkers({resource: model.uri});


  
968
        post({


  
969
          type: 'diagnostics',


  
970
          path: relativePath(model.uri),


  
971
          diagnostics: markers.map(function (marker) {


  
972
            return {


  
973
              line: marker.startLineNumber,


  
974
              column: marker.startColumn,


  
975
              endLine: marker.endLineNumber,


  
976
              endColumn: marker.endColumn,


  
977
              message: marker.message,


  
978
              severity: marker.severity === monaco.MarkerSeverity.Error ? 'error'


  
979
                : marker.severity === monaco.MarkerSeverity.Warning ? 'warning'


  
980
                : marker.severity === monaco.MarkerSeverity.Info ? 'info' : 'hint',


  
981
              source: marker.source || marker.owner,


  
982
            };


  
983
          }),
436
        });
984
        });


  
985
      }
437


      
986


      
438
        // Clicking away — into the file tree, or out of the window — is a save.


  
439
        // The native side drops the write if nothing has changed, so this is


  
440
        // free when the editor is only being tabbed through.


  
441
        editor.onDidBlurEditorText(function () {


  
442
          post({type: 'save', path: filePath(), value: editor.getValue()});


  


  
987
      // ---- Language servers --------------------------------------------------


  
988


      


  
989
      // Monaco ships its own intelligence for these; a server is only asked


  
990
      // for when it has none.


  
991
      var BUILTIN = {typescript: 1, javascript: 1, css: 1, scss: 1, less: 1, html: 1, json: 1, plaintext: 1};


  
992
      var clients = {};


  
993


      


  
994
      function ensureClient(language) {


  
995
        if (!language || BUILTIN[language] || clients[language] || !projectRoot) {


  
996
          return;


  
997
        }


  
998


      


  
999
        clients[language] = {state: 'opening', nextId: 1, pending: {}, versions: {}, disposables: []};


  
1000
        post({type: 'lspOpen', language: language});


  
1001
      }


  
1002


      


  
1003
      window.resetLanguageServers = function () {


  
1004
        Object.keys(clients).forEach(function (language) {


  
1005
          clients[language].disposables.forEach(function (disposable) { disposable.dispose(); });


  
1006
        });


  
1007
        clients = {};


  
1008
        if (window.monaco) {


  
1009
          monaco.editor.getModels().forEach(function (model) {


  
1010
            monaco.editor.setModelMarkers(model, 'lsp', []);


  
1011
          });


  
1012
        }


  
1013
      };


  
1014


      


  
1015
      function lspSend(language, message) {


  
1016
        post({type: 'lsp', language: language, body: JSON.stringify(message)});


  
1017
      }


  
1018


      


  
1019
      function lspRequest(language, method, params) {


  
1020
        var client = clients[language];


  
1021
        if (!client) {


  
1022
          return Promise.reject(new Error('no client'));


  
1023
        }


  
1024


      


  
1025
        var id = client.nextId++;


  
1026
        return new Promise(function (resolve, reject) {


  
1027
          client.pending[id] = {resolve: resolve, reject: reject};


  
1028
          lspSend(language, {jsonrpc: '2.0', id: id, method: method, params: params});


  
1029
        });


  
1030
      }


  
1031


      


  
1032
      function lspNotify(language, method, params) {


  
1033
        lspSend(language, {jsonrpc: '2.0', method: method, params: params});


  
1034
      }


  
1035


      


  
1036
      window.lspReady = function (language, server) {


  
1037
        var client = clients[language];


  
1038
        if (!client) {


  
1039
          return;


  
1040
        }


  
1041


      


  
1042
        client.state = 'initializing';


  
1043
        client.server = server;


  
1044


      


  
1045
        lspRequest(language, 'initialize', {


  
1046
          processId: null,


  
1047
          clientInfo: {name: 'all-in-one', version: '1.0'},


  
1048
          rootUri: fileUri('').toString(),


  
1049
          rootPath: projectRoot,


  
1050
          workspaceFolders: [{uri: fileUri('').toString(), name: projectRoot.split('/').pop()}],


  
1051
          capabilities: {


  
1052
            workspace: {workspaceFolders: true, configuration: false, applyEdit: false},


  
1053
            textDocument: {


  
1054
              synchronization: {didSave: true, willSave: false},


  
1055
              hover: {contentFormat: ['markdown', 'plaintext']},


  
1056
              completion: {completionItem: {snippetSupport: true, documentationFormat: ['markdown', 'plaintext']}, contextSupport: true},


  
1057
              signatureHelp: {signatureInformation: {documentationFormat: ['markdown', 'plaintext']}},


  
1058
              definition: {},


  
1059
              references: {},


  
1060
              documentSymbol: {hierarchicalDocumentSymbolSupport: true},


  
1061
              formatting: {},


  
1062
              rangeFormatting: {},


  
1063
              rename: {prepareSupport: false},


  
1064
              publishDiagnostics: {relatedInformation: false},


  
1065
              codeAction: {},


  
1066
            },


  
1067
          },


  
1068
          initializationOptions: {},


  
1069
        }).then(function (result) {


  
1070
          client.capabilities = (result && result.capabilities) || {};


  
1071
          client.state = 'ready';


  
1072
          lspNotify(language, 'initialized', {});


  
1073
          registerProviders(language);


  
1074


      


  
1075
          monaco.editor.getModels().forEach(function (model) {


  
1076
            if (model.getLanguageId() === language && model.uri.scheme === 'file') {


  
1077
              lspDidOpen(model);


  
1078
            }


  
1079
          });


  
1080
        }).catch(function () {


  
1081
          client.state = 'unavailable';


  
1082
        });


  
1083
      };


  
1084


      


  
1085
      window.lspUnavailable = function (language) {


  
1086
        if (clients[language]) {


  
1087
          clients[language].state = 'unavailable';


  
1088
        } else {


  
1089
          clients[language] = {state: 'unavailable', nextId: 1, pending: {}, versions: {}, disposables: []};


  
1090
        }


  
1091
      };


  
1092


      


  
1093
      window.lspReceive = function (language, body) {


  
1094
        var client = clients[language];


  
1095
        if (!client) {


  
1096
          return;


  
1097
        }


  
1098


      


  
1099
        var message;


  
1100
        try {


  
1101
          message = JSON.parse(body);


  
1102
        } catch (error) {


  
1103
          return;


  
1104
        }


  
1105


      


  
1106
        if (message.id != null && client.pending[message.id]) {


  
1107
          var waiting = client.pending[message.id];


  
1108
          delete client.pending[message.id];


  
1109
          if (message.error) {


  
1110
            waiting.reject(message.error);


  
1111
          } else {


  
1112
            waiting.resolve(message.result);


  
1113
          }


  
1114
          return;


  
1115
        }


  
1116


      


  
1117
        if (message.method === 'textDocument/publishDiagnostics') {


  
1118
          applyLspDiagnostics(message.params);


  
1119
          return;


  
1120
        }


  
1121


      


  
1122
        // Requests from the server we have no answer to get an empty one, so


  
1123
        // it does not sit waiting.


  
1124
        if (message.method && message.id != null) {


  
1125
          var result = null;


  
1126
          if (message.method === 'workspace/configuration') {


  
1127
            result = (message.params.items || []).map(function () { return null; });


  
1128
          }


  
1129
          if (message.method === 'client/registerCapability' || message.method === 'client/unregisterCapability' || message.method === 'window/workDoneProgress/create') {


  
1130
            result = null;


  
1131
          }


  
1132
          if (message.method === 'workspace/workspaceFolders') {


  
1133
            result = [{uri: fileUri('').toString(), name: 'project'}];


  
1134
          }


  
1135
          lspSend(language, {jsonrpc: '2.0', id: message.id, result: result});


  
1136
        }


  
1137
      };


  
1138


      


  
1139
      function lspDidOpen(model) {


  
1140
        var language = model.getLanguageId();


  
1141
        ensureClient(language);


  
1142
        var client = clients[language];


  
1143


      


  
1144
        if (!client || client.state !== 'ready') {


  
1145
          return;


  
1146
        }


  
1147


      


  
1148
        var uri = model.uri.toString();


  
1149
        if (client.versions[uri] != null) {


  
1150
          return;


  
1151
        }


  
1152


      


  
1153
        client.versions[uri] = 1;


  
1154
        lspNotify(language, 'textDocument/didOpen', {


  
1155
          textDocument: {uri: uri, languageId: language, version: 1, text: model.getValue()},


  
1156
        });


  
1157
      }


  
1158


      


  
1159
      function lspDidChange(model) {


  
1160
        var language = model.getLanguageId();


  
1161
        var client = clients[language];


  
1162
        if (!client || client.state !== 'ready') {


  
1163
          return;


  
1164
        }


  
1165


      


  
1166
        var uri = model.uri.toString();


  
1167
        if (client.versions[uri] == null) {


  
1168
          lspDidOpen(model);


  
1169
          return;


  
1170
        }


  
1171


      


  
1172
        client.versions[uri]++;


  
1173
        lspNotify(language, 'textDocument/didChange', {


  
1174
          textDocument: {uri: uri, version: client.versions[uri]},


  
1175
          contentChanges: [{text: model.getValue()}],
443
        });
1176
        });


  
1177
      }


  
1178


      


  
1179
      function lspDidSave(model) {


  
1180
        var language = model.getLanguageId();


  
1181
        var client = clients[language];


  
1182
        if (!client || client.state !== 'ready' || client.versions[model.uri.toString()] == null) {


  
1183
          return;


  
1184
        }


  
1185
        lspNotify(language, 'textDocument/didSave', {textDocument: {uri: model.uri.toString()}, text: model.getValue()});


  
1186
      }


  
1187


      


  
1188
      var LSP_SEVERITY = {1: 8, 2: 4, 3: 2, 4: 1};


  
1189


      


  
1190
      function applyLspDiagnostics(params) {


  
1191
        var model = monaco.editor.getModel(monaco.Uri.parse(params.uri));


  
1192
        if (!model) {


  
1193
          return;


  
1194
        }


  
1195


      


  
1196
        monaco.editor.setModelMarkers(model, 'lsp', (params.diagnostics || []).map(function (diagnostic) {


  
1197
          return {


  
1198
            startLineNumber: diagnostic.range.start.line + 1,


  
1199
            startColumn: diagnostic.range.start.character + 1,


  
1200
            endLineNumber: diagnostic.range.end.line + 1,


  
1201
            endColumn: diagnostic.range.end.character + 1,


  
1202
            message: diagnostic.message,


  
1203
            severity: LSP_SEVERITY[diagnostic.severity] || 8,


  
1204
            source: diagnostic.source,


  
1205
            code: diagnostic.code != null ? String(diagnostic.code) : undefined,


  
1206
          };


  
1207
        }));


  
1208
      }


  
1209


      


  
1210
      function toLspPosition(position) {


  
1211
        return {line: position.lineNumber - 1, character: position.column - 1};


  
1212
      }


  
1213


      


  
1214
      function toRange(range) {


  
1215
        return new monaco.Range(range.start.line + 1, range.start.character + 1, range.end.line + 1, range.end.character + 1);


  
1216
      }


  
1217


      


  
1218
      function toLocation(location) {


  
1219
        var uri = location.targetUri || location.uri;


  
1220
        var range = location.targetSelectionRange || location.targetRange || location.range;


  
1221
        return {uri: monaco.Uri.parse(uri), range: toRange(range)};


  
1222
      }


  
1223


      


  
1224
      function textDocument(model) {


  
1225
        return {uri: model.uri.toString()};


  
1226
      }


  
1227


      


  
1228
      function markdown(contents) {


  
1229
        if (!contents) {


  
1230
          return [];


  
1231
        }


  
1232
        var list = Array.isArray(contents) ? contents : [contents];


  
1233
        return list.map(function (item) {


  
1234
          if (typeof item === 'string') {


  
1235
            return {value: item};


  
1236
          }


  
1237
          if (item.language) {


  
1238
            return {value: '\`\`\`' + item.language + '\\n' + item.value + '\\n\`\`\`'};


  
1239
          }


  
1240
          return {value: item.value || ''};


  
1241
        });


  
1242
      }


  
1243


      


  
1244
      var COMPLETION_KIND = {


  
1245
        1: 18, 2: 0, 3: 1, 4: 2, 5: 3, 6: 4, 7: 5, 8: 7, 9: 8, 10: 9, 11: 12, 12: 13, 13: 15, 14: 17,


  
1246
        15: 27, 16: 19, 17: 20, 18: 21, 19: 23, 20: 16, 21: 14, 22: 6, 23: 10, 24: 11, 25: 24,


  
1247
      };


  
1248


      


  
1249
      function registerProviders(language) {


  
1250
        var client = clients[language];


  
1251
        var capabilities = client.capabilities || {};


  
1252
        var disposables = client.disposables;


  
1253


      


  
1254
        if (capabilities.hoverProvider) {


  
1255
          disposables.push(monaco.languages.registerHoverProvider(language, {


  
1256
            provideHover: function (model, position) {


  
1257
              return lspRequest(language, 'textDocument/hover', {


  
1258
                textDocument: textDocument(model), position: toLspPosition(position),


  
1259
              }).then(function (result) {


  
1260
                if (!result || !result.contents) {


  
1261
                  return null;


  
1262
                }


  
1263
                return {contents: markdown(result.contents), range: result.range ? toRange(result.range) : undefined};


  
1264
              }).catch(function () { return null; });


  
1265
            },


  
1266
          }));


  
1267
        }


  
1268


      


  
1269
        if (capabilities.completionProvider) {


  
1270
          disposables.push(monaco.languages.registerCompletionItemProvider(language, {


  
1271
            triggerCharacters: capabilities.completionProvider.triggerCharacters || [],


  
1272
            provideCompletionItems: function (model, position, context) {


  
1273
              var word = model.getWordUntilPosition(position);


  
1274
              var defaultRange = new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);


  
1275


      


  
1276
              return lspRequest(language, 'textDocument/completion', {


  
1277
                textDocument: textDocument(model),


  
1278
                position: toLspPosition(position),


  
1279
                context: {triggerKind: context.triggerKind === 1 ? 2 : 1, triggerCharacter: context.triggerCharacter},


  
1280
              }).then(function (result) {


  
1281
                var items = Array.isArray(result) ? result : (result && result.items) || [];


  
1282
                return {


  
1283
                  incomplete: Boolean(result && result.isIncomplete),


  
1284
                  suggestions: items.map(function (item) {


  
1285
                    var edit = item.textEdit;


  
1286
                    var range = defaultRange;


  
1287
                    var insertText = item.insertText || item.label;


  
1288


      


  
1289
                    if (edit) {


  
1290
                      insertText = edit.newText;


  
1291
                      range = toRange(edit.range || edit.insert);


  
1292
                    }


  
1293


      


  
1294
                    return {


  
1295
                      label: typeof item.label === 'string' ? item.label : item.label.label,


  
1296
                      kind: COMPLETION_KIND[item.kind] || 18,


  
1297
                      detail: item.detail,


  
1298
                      documentation: item.documentation ? markdown(item.documentation)[0] : undefined,


  
1299
                      insertText: insertText,


  
1300
                      insertTextRules: item.insertTextFormat === 2 ? 4 : 0,


  
1301
                      range: range,


  
1302
                      sortText: item.sortText,


  
1303
                      filterText: item.filterText,


  
1304
                      additionalTextEdits: (item.additionalTextEdits || []).map(function (extra) {


  
1305
                        return {range: toRange(extra.range), text: extra.newText};


  
1306
                      }),


  
1307
                    };


  
1308
                  }),


  
1309
                };


  
1310
              }).catch(function () { return {suggestions: []}; });


  
1311
            },


  
1312
          }));


  
1313
        }


  
1314


      


  
1315
        if (capabilities.definitionProvider) {


  
1316
          disposables.push(monaco.languages.registerDefinitionProvider(language, {


  
1317
            provideDefinition: function (model, position) {


  
1318
              return lspRequest(language, 'textDocument/definition', {


  
1319
                textDocument: textDocument(model), position: toLspPosition(position),


  
1320
              }).then(function (result) {


  
1321
                if (!result) {


  
1322
                  return [];


  
1323
                }


  
1324
                return (Array.isArray(result) ? result : [result]).map(toLocation);


  
1325
              }).catch(function () { return []; });


  
1326
            },


  
1327
          }));


  
1328
        }


  
1329


      


  
1330
        if (capabilities.referencesProvider) {


  
1331
          disposables.push(monaco.languages.registerReferenceProvider(language, {


  
1332
            provideReferences: function (model, position) {


  
1333
              return lspRequest(language, 'textDocument/references', {


  
1334
                textDocument: textDocument(model), position: toLspPosition(position), context: {includeDeclaration: true},


  
1335
              }).then(function (result) {


  
1336
                return (result || []).map(toLocation);


  
1337
              }).catch(function () { return []; });


  
1338
            },


  
1339
          }));


  
1340
        }


  
1341


      


  
1342
        if (capabilities.documentSymbolProvider) {


  
1343
          disposables.push(monaco.languages.registerDocumentSymbolProvider(language, {


  
1344
            provideDocumentSymbols: function (model) {


  
1345
              return lspRequest(language, 'textDocument/documentSymbol', {


  
1346
                textDocument: textDocument(model),


  
1347
              }).then(function (result) {


  
1348
                function convert(symbol) {


  
1349
                  var range = symbol.location ? symbol.location.range : symbol.range;


  
1350
                  var selection = symbol.selectionRange || range;


  
1351
                  return {


  
1352
                    name: symbol.name,


  
1353
                    detail: symbol.detail || '',


  
1354
                    kind: Math.max(0, (symbol.kind || 1) - 1),


  
1355
                    tags: [],


  
1356
                    range: toRange(range),


  
1357
                    selectionRange: toRange(selection),


  
1358
                    children: (symbol.children || []).map(convert),


  
1359
                  };


  
1360
                }


  
1361
                return (result || []).map(convert);


  
1362
              }).catch(function () { return []; });


  
1363
            },


  
1364
          }));


  
1365
        }


  
1366


      


  
1367
        if (capabilities.documentFormattingProvider) {


  
1368
          disposables.push(monaco.languages.registerDocumentFormattingEditProvider(language, {


  
1369
            provideDocumentFormattingEdits: function (model, options) {


  
1370
              return lspRequest(language, 'textDocument/formatting', {


  
1371
                textDocument: textDocument(model),


  
1372
                options: {tabSize: options.tabSize, insertSpaces: options.insertSpaces},


  
1373
              }).then(function (result) {


  
1374
                return (result || []).map(function (edit) {


  
1375
                  return {range: toRange(edit.range), text: edit.newText};


  
1376
                });


  
1377
              }).catch(function () { return []; });


  
1378
            },


  
1379
          }));


  
1380
        }
444


      
1381


      
445
        post({type: 'ready'});


  


  
1382
        if (capabilities.renameProvider) {


  
1383
          disposables.push(monaco.languages.registerRenameProvider(language, {


  
1384
            provideRenameEdits: function (model, position, newName) {


  
1385
              return lspRequest(language, 'textDocument/rename', {


  
1386
                textDocument: textDocument(model), position: toLspPosition(position), newName: newName,


  
1387
              }).then(function (result) {


  
1388
                var edits = [];


  
1389
                var changes = (result && result.changes) || {};


  
1390
                Object.keys(changes).forEach(function (uri) {


  
1391
                  changes[uri].forEach(function (edit) {


  
1392
                    edits.push({resource: monaco.Uri.parse(uri), versionId: undefined, textEdit: {range: toRange(edit.range), text: edit.newText}});


  
1393
                  });


  
1394
                });


  
1395
                ((result && result.documentChanges) || []).forEach(function (change) {


  
1396
                  if (!change.textDocument) {


  
1397
                    return;


  
1398
                  }


  
1399
                  change.edits.forEach(function (edit) {


  
1400
                    edits.push({resource: monaco.Uri.parse(change.textDocument.uri), versionId: undefined, textEdit: {range: toRange(edit.range), text: edit.newText}});


  
1401
                  });


  
1402
                });


  
1403
                return {edits: edits};


  
1404
              }).catch(function (error) {


  
1405
                return {edits: [], rejectReason: error && error.message};


  
1406
              });


  
1407
            },


  
1408
          }));


  
1409
        }


  
1410


      


  
1411
        if (capabilities.signatureHelpProvider) {


  
1412
          disposables.push(monaco.languages.registerSignatureHelpProvider(language, {


  
1413
            signatureHelpTriggerCharacters: capabilities.signatureHelpProvider.triggerCharacters || ['(', ','],


  
1414
            provideSignatureHelp: function (model, position) {


  
1415
              return lspRequest(language, 'textDocument/signatureHelp', {


  
1416
                textDocument: textDocument(model), position: toLspPosition(position),


  
1417
              }).then(function (result) {


  
1418
                if (!result) {


  
1419
                  return null;


  
1420
                }


  
1421
                return {


  
1422
                  value: {


  
1423
                    signatures: (result.signatures || []).map(function (signature) {


  
1424
                      return {


  
1425
                        label: signature.label,


  
1426
                        documentation: signature.documentation ? markdown(signature.documentation)[0] : undefined,


  
1427
                        parameters: (signature.parameters || []).map(function (parameter) {


  
1428
                          return {label: parameter.label, documentation: parameter.documentation ? markdown(parameter.documentation)[0] : undefined};


  
1429
                        }),


  
1430
                      };


  
1431
                    }),


  
1432
                    activeSignature: result.activeSignature || 0,


  
1433
                    activeParameter: result.activeParameter || 0,


  
1434
                  },


  
1435
                  dispose: function () {},


  
1436
                };


  
1437
              }).catch(function () { return null; });


  
1438
            },


  
1439
          }));


  
1440
        }


  
1441
      }


  
1442


      


  
1443
      // ---- Boot --------------------------------------------------------------


  
1444


      


  
1445
      function loadScript(src, onload, onerror) {


  
1446
        var script = document.createElement('script');


  
1447
        script.src = src;


  
1448
        script.onload = onload;


  
1449
        script.onerror = onerror;


  
1450
        document.head.appendChild(script);


  
1451
      }


  
1452


      


  
1453
      function boot() {


  
1454
        require.config({paths: {vs: monacoBase + '/vs'}});


  
1455
        require(['vs/editor/editor.main'], function () {


  
1456
          // Registered before Shiki, which pairs its grammars against whichever


  
1457
          // modes exist at the moment it runs.


  
1458
          ${JSON.stringify(EXTRA_LANGUAGES)}.forEach(function (language) {


  
1459
            monaco.languages.register(language);


  
1460
          });


  
1461


      


  
1462
          // No model of its own: every buffer this editor shows is one setFile


  
1463
          // created against a URI, and an implicit one would only be orphaned


  
1464
          // by the first switch.


  
1465
          editor = monaco.editor.create(document.getElementById('container'), Object.assign(


  
1466
            ${JSON.stringify(EDITOR_OPTIONS)},


  
1467
            pendingOptions || {},


  
1468
            {model: null}


  
1469
          ));


  
1470


      


  
1471
          applyTheme();


  
1472


      


  
1473
          // A definition that lands in another file: Monaco asks its editor


  
1474
          // service to open it, and that is the native side's job.


  
1475
          monaco.editor.registerEditorOpener({


  
1476
            openCodeEditor: function (source, resource, selectionOrPosition) {


  
1477
              if (resource.scheme !== 'file') {


  
1478
                return false;


  
1479
              }


  
1480
              var line = 1;


  
1481
              var column = 1;


  
1482
              if (selectionOrPosition) {


  
1483
                line = selectionOrPosition.startLineNumber || selectionOrPosition.lineNumber || 1;


  
1484
                column = selectionOrPosition.startColumn || selectionOrPosition.column || 1;


  
1485
              }


  
1486
              post({type: 'openFile', path: relativePath(resource), line: line, column: column});


  
1487
              return true;


  
1488
            },


  
1489
          });
446


      
1490


      


  
1491
          // Whatever the native side asked for while the loader was still


  
1492
          // running, or the file the document was built around if it asked


  
1493
          // for nothing.


  
1494
          window.setFile(pendingFile || ${JSON.stringify({path: file.path, value: file.value, language: file.language})});


  
1495
          pendingFile = null;


  
1496


      


  
1497
          // Bound after the first file, so opening one is not itself a change.


  
1498
          // The listener is the editor's rather than the model's, so it


  
1499
          // follows whichever model is attached.


  
1500
          editor.onDidChangeModelContent(function () {


  
1501
            post({type: 'change', path: filePath(), value: editor.getValue()});


  
1502
            var model = editor.getModel();


  
1503
            if (model && model.uri.scheme === 'file') {


  
1504
              lspDidChange(model);


  
1505
            }


  
1506
          });


  
1507


      


  
1508
          editor.onDidFocusEditorText(function () {


  
1509
            post({type: 'focus'});


  
1510
          });


  
1511


      


  
1512
          var cursorTimer = null;


  
1513
          editor.onDidChangeCursorPosition(function (event) {


  
1514
            clearTimeout(cursorTimer);


  
1515
            cursorTimer = setTimeout(function () {


  
1516
              var model = editor.getModel();


  
1517
              post({


  
1518
                type: 'cursor',


  
1519
                line: event.position.lineNumber,


  
1520
                column: event.position.column,


  
1521
                path: filePath(),


  
1522
                language: model ? model.getLanguageId() : null,


  
1523
              });


  
1524
            }, 50);


  
1525
          });


  
1526


      


  
1527
          // Clicking away — into the file tree, or out of the window — is a


  
1528
          // save. The native side drops the write if nothing has changed, so


  
1529
          // this is free when the editor is only being tabbed through.


  
1530
          editor.onDidBlurEditorText(function () {


  
1531
            post({type: 'save', path: filePath(), value: editor.getValue(), blur: true});


  
1532
            var model = editor.getModel();


  
1533
            if (model && model.uri.scheme === 'file') {


  
1534
              lspDidSave(model);


  
1535
            }


  
1536
          });


  
1537


      


  
1538
          monaco.editor.onDidChangeMarkers(function (uris) {


  
1539
            uris.forEach(function (uri) {


  
1540
              postDiagnosticsFor(monaco.editor.getModel(uri));


  
1541
            });


  
1542
          });


  
1543


      


  
1544
          monaco.editor.onWillDisposeModel(function (model) {


  
1545
            var language = model.getLanguageId();


  
1546
            var client = clients[language];


  
1547
            if (client && client.state === 'ready' && client.versions[model.uri.toString()] != null) {


  
1548
              delete client.versions[model.uri.toString()];


  
1549
              lspNotify(language, 'textDocument/didClose', {textDocument: {uri: model.uri.toString()}});


  
1550
            }


  
1551
          });


  
1552


      


  
1553
          post({type: 'ready'});


  
1554


      
447
        startShiki();
1555
          startShiki();


  
1556
        });


  
1557
      }


  
1558


      


  
1559
      loadScript(monacoBase + '/vs/loader.js', boot, function () {


  
1560
        if (monacoBase === CDN) {


  
1561
          post({type: 'loadError', source: monacoBase});


  
1562
          return;


  
1563
        }


  
1564
        monacoBase = CDN;


  
1565
        loadScript(CDN + '/vs/loader.js', boot, function () {


  
1566
          post({type: 'loadError', source: CDN});


  
1567
        });
448
      });
1568
      });
449
    </script>
1569
    </script>
450
  </body>
1570
  </body>
453


      
1573


      
454
const createStyles = colors => StyleSheet.create({
1574
const createStyles = colors => StyleSheet.create({
455
  editor: {
1575
  editor: {
456
    flex: 2,
1576
    flex: 1,
457
    backgroundColor: colors.surface,


  


  
1577
    backgroundColor: 'transparent',
458
    borderRadius: 16,
1578
    borderRadius: 16,
459
  },
1579
  },
460
});
1580
});