Skip to content

Commit

Permalink
Add the Monaco editor
Browse files Browse the repository at this point in the history
  • Loading branch information
HKalbasi authored and shepmaster committed Oct 18, 2021
1 parent f0a76ec commit 5a8c95e
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 10 deletions.
31 changes: 24 additions & 7 deletions ui/frontend/ConfigMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ interface ConfigMenuProps {
close: () => void;
}

const MONACO_THEMES = [
'vs', 'vs-dark', 'vscode-dark-plus',
];

const ConfigMenu: React.SFC<ConfigMenuProps> = () => {
const keybinding = useSelector((state: State) => state.configuration.ace.keybinding);
const aceTheme = useSelector((state: State) => state.configuration.ace.theme);
const monacoTheme = useSelector((state: State) => state.configuration.monaco.theme);
const orientation = useSelector((state: State) => state.configuration.orientation);
const editorStyle = useSelector((state: State) => state.configuration.editor);
const pairCharacters = useSelector((state: State) => state.configuration.ace.pairCharacters);
Expand All @@ -33,6 +38,7 @@ const ConfigMenu: React.SFC<ConfigMenuProps> = () => {

const dispatch = useDispatch();
const changeAceTheme = useCallback((t) => dispatch(actions.changeAceTheme(t)), [dispatch]);
const changeMonacoTheme = useCallback((t) => dispatch(actions.changeMonacoTheme(t)), [dispatch]);
const changeKeybinding = useCallback((k) => dispatch(actions.changeKeybinding(k)), [dispatch]);
const changeOrientation = useCallback((o) => dispatch(actions.changeOrientation(o)), [dispatch]);
const changeEditorStyle = useCallback((e) => dispatch(actions.changeEditor(e)), [dispatch]);
Expand All @@ -44,14 +50,14 @@ const ConfigMenu: React.SFC<ConfigMenuProps> = () => {
return (
<Fragment>
<MenuGroup title="Editor">
<EitherConfig
id="editor-style"
name="Style"
a={Editor.Simple}
b={Editor.Ace}
<SelectConfig
name="Editor"
value={editorStyle}
onChange={changeEditorStyle} />

onChange={changeEditorStyle}
>
{[Editor.Simple, Editor.Ace, Editor.Monaco]
.map(k => <option key={k} value={k}>{k}</option>)}
</SelectConfig>
{editorStyle === Editor.Ace && (
<Fragment>
<SelectConfig
Expand Down Expand Up @@ -79,6 +85,17 @@ const ConfigMenu: React.SFC<ConfigMenuProps> = () => {
onChange={changePairCharacters} />
</Fragment>
)}
{editorStyle === Editor.Monaco && (
<Fragment>
<SelectConfig
name="Theme"
value={monacoTheme}
onChange={changeMonacoTheme}
>
{MONACO_THEMES.map(t => <option key={t} value={t}>{t}</option>)}
</SelectConfig>
</Fragment>
)}
</MenuGroup>

<MenuGroup title="UI">
Expand Down
5 changes: 5 additions & 0 deletions ui/frontend/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export enum ActionType {
ChangeEditor = 'CHANGE_EDITOR',
ChangeKeybinding = 'CHANGE_KEYBINDING',
ChangeAceTheme = 'CHANGE_ACE_THEME',
ChangeMonacoTheme = 'CHANGE_MONACO_THEME',
ChangePairCharacters = 'CHANGE_PAIR_CHARACTERS',
ChangeOrientation = 'CHANGE_ORIENTATION',
ChangeAssemblyFlavor = 'CHANGE_ASSEMBLY_FLAVOR',
Expand Down Expand Up @@ -141,6 +142,9 @@ export const changeKeybinding = (keybinding: string) =>
export const changeAceTheme = (theme: string) =>
createAction(ActionType.ChangeAceTheme, { theme });

export const changeMonacoTheme = (theme: string) =>
createAction(ActionType.ChangeMonacoTheme, { theme });

export const changePairCharacters = (pairCharacters: PairCharacters) =>
createAction(ActionType.ChangePairCharacters, { pairCharacters });

Expand Down Expand Up @@ -823,6 +827,7 @@ export type Action =
| ReturnType<typeof changePrimaryAction>
| ReturnType<typeof changeProcessAssembly>
| ReturnType<typeof changeAceTheme>
| ReturnType<typeof changeMonacoTheme>
| ReturnType<typeof requestExecute>
| ReturnType<typeof receiveExecuteSuccess>
| ReturnType<typeof receiveExecuteFailure>
Expand Down
12 changes: 10 additions & 2 deletions ui/frontend/editor/Editor.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
position: relative;
}

.ace {
.-advanced {
composes: -bodyMonospace -autoSize from '../shared.module.css';
position: absolute;
}

.ace {
composes: -advanced;
}

.monaco {
composes: -advanced;
}

.simple {
composes: ace;
composes: -advanced;
border: none;
}
9 changes: 8 additions & 1 deletion ui/frontend/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CommonEditorProps, Editor as EditorType, Position, Selection } from '..
import { State } from '../reducers';

import styles from './Editor.module.css';
import MonacoEditor from './MonacoEditor';

class CodeByteOffsets {
readonly code: string;
Expand Down Expand Up @@ -107,6 +108,12 @@ class SimpleEditor extends React.PureComponent<CommonEditorProps> {
}
}

const editorMap = {
[EditorType.Simple]: SimpleEditor,
[EditorType.Ace]: AceEditor,
[EditorType.Monaco]: MonacoEditor,
};

const Editor: React.SFC = () => {
const code = useSelector((state: State) => state.code);
const editor = useSelector((state: State) => state.configuration.editor);
Expand All @@ -118,7 +125,7 @@ const Editor: React.SFC = () => {
const execute = useCallback(() => dispatch(actions.performPrimaryAction()), [dispatch]);
const onEditCode = useCallback((c) => dispatch(actions.editCode(c)), [dispatch]);

const SelectedEditor = editor === EditorType.Simple ? SimpleEditor : AceEditor;
const SelectedEditor = editorMap[editor];

return (
<div className={styles.container}>
Expand Down
13 changes: 13 additions & 0 deletions ui/frontend/editor/MonacoEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { Suspense } from 'react';

import { CommonEditorProps } from '../types';

const MonacoEditorLazy = React.lazy(() => import('./MonacoEditorCore'));

const MonacoEditor: React.SFC<CommonEditorProps> = props => (
<Suspense fallback={'Loading'}>
<MonacoEditorLazy {...props} />
</Suspense>
)

export default MonacoEditor;
41 changes: 41 additions & 0 deletions ui/frontend/editor/MonacoEditorCore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { CommonEditorProps } from '../types';
import MonacoEditor, { EditorWillMount } from 'react-monaco-editor';
import { useSelector } from 'react-redux';
import State from '../state';
import { config, grammar, themeVsDarkPlus } from './rust_monaco_def';

import styles from './Editor.module.css';

const MODE_ID = 'my-rust';

const initMonaco: EditorWillMount = (monaco) => {
monaco.editor.defineTheme('vscode-dark-plus', themeVsDarkPlus);

monaco.languages.register({
id: MODE_ID,
});

monaco.languages.onLanguage(MODE_ID, async () => {
monaco.languages.setLanguageConfiguration(MODE_ID, config);
monaco.languages.setMonarchTokensProvider(MODE_ID, grammar);
});
};

const MonacoEditorCore: React.SFC<CommonEditorProps> = props => {
const theme = useSelector((s: State) => s.configuration.monaco.theme);

return (
<MonacoEditor
language={MODE_ID}
theme={theme}
className={styles.monaco}
value={props.code}
onChange={props.onEditCode}
editorWillMount={initMonaco}
options={{ automaticLayout: true }}
/>
);
}

export default MonacoEditorCore;
163 changes: 163 additions & 0 deletions ui/frontend/editor/rust_monaco_def.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { languages, editor } from 'monaco-editor';

export const config: languages.LanguageConfiguration = {
comments: {
lineComment: '//',
blockComment: ['/*', '*/'],
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')'],
],
autoClosingPairs: [
{ open: '[', close: ']' },
{ open: '{', close: '}' },
{ open: '(', close: ')' },
{ open: '"', close: '"', notIn: ['string'] },
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: '\'', close: '\'' },
],
folding: {
markers: {
start: new RegExp('^\\s*#pragma\\s+region\\b'),
end: new RegExp('^\\s*#pragma\\s+endregion\\b'),
},
},
};

export const grammar: languages.IMonarchLanguage = {
// Set defaultToken to invalid to see what you do not tokenize yet
// defaultToken: 'invalid',

keywords: [
'as', 'break', 'const', 'crate', 'enum', 'extern', 'false', 'fn', 'impl', 'in',
'let', 'mod', 'move', 'mut', 'pub', 'ref', 'return', 'self', 'Self', 'static',
'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where',
'macro_rules',
],

controlFlowKeywords: [
'continue', 'else', 'for', 'if', 'while', 'loop', 'match',
],

typeKeywords: [
'Self', 'm32', 'm64', 'm128', 'f80', 'f16', 'f128', 'int', 'uint', 'float', 'char',
'bool', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'i8', 'i16', 'i32', 'i64', 'str',
'Option', 'Either', 'c_float', 'c_double', 'c_void', 'FILE', 'fpos_t', 'DIR', 'dirent',
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort', 'c_int', 'c_uint', 'c_long', 'c_ulong',
'size_t', 'ptrdiff_t', 'clock_t', 'time_t', 'c_longlong', 'c_ulonglong', 'intptr_t',
'uintptr_t', 'off_t', 'dev_t', 'ino_t', 'pid_t', 'mode_t', 'ssize_t',
],

operators: [
'=', '>', '<', '!', '~', '?', ':', '==', '<=', '>=', '!=',
'&&', '||', '++', '--', '+', '-', '*', '/', '&', '|', '^', '%',
'<<', '>>', '>>>', '+=', '-=', '*=', '/=', '&=', '|=', '^=',
'%=', '<<=', '>>=', '>>>=',
],

// we include these common regular expressions
symbols: /[=><!~?:&|+\-*\/\^%]+/,

// for strings
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,

// The main tokenizer for our languages
tokenizer: {
root: [
// identifiers and keywords
[/[a-z_$][\w$]*/, {
cases: {
'@typeKeywords': 'type.identifier',
'@keywords': {
cases: {
'fn': { token: 'keyword', next: '@func_decl' },
'@default': 'keyword',
},
},
'@controlFlowKeywords': 'keyword.control',
'@default': 'variable',
},
}],
[/[A-Z][\w\$]*/, 'type.identifier'], // to show class names nicely

// whitespace
{ include: '@whitespace' },

// delimiters and operators
[/[{}()\[\]]/, '@brackets'],
[/[<>](?!@symbols)/, '@brackets'],
[/@symbols/, {
cases: {
'@operators': 'operator',
'@default': '',
},
}],

// @ annotations.
// As an example, we emit a debugging log message on these tokens.
// Note: message are supressed during the first load -- change some lines to see them.
[/@\s*[a-zA-Z_\$][\w\$]*/, { token: 'annotation', log: 'annotation token: $0' }],

// numbers
[/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],
[/0[xX][0-9a-fA-F]+/, 'number.hex'],
[/\d+/, 'number'],

// delimiter: after number because of .\d floats
[/[;,.]/, 'delimiter'],

// strings
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
[/"/, { token: 'string.quote', bracket: '@open', next: '@string' }],

// characters
[/'[^\\']'/, 'string'],
[/(')(@escapes)(')/, ['string', 'string.escape', 'string']],
[/'/, 'string.invalid'],
],

comment: [
[/[^\/*]+/, 'comment'],
[/\/\*/, 'comment', '@push'], // nested comment
['\\*/', 'comment', '@pop'],
[/[\/*]/, 'comment'],
],

string: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }],
],

whitespace: [
[/[ \t\r\n]+/, 'white'],
[/\/\*/, 'comment', '@comment'],
[/\/\/.*$/, 'comment'],
],

func_decl: [
[
/[a-z_$][\w$]*/, 'support.function', '@pop',
],
],
},
};

export const themeVsDarkPlus: editor.IStandaloneThemeData = {
base: 'vs-dark',
inherit: true,
colors: {},
rules: [
{ token: 'keyword.control', foreground: 'C586C0' },
{ token: 'variable', foreground: '9CDCFE' },
{ token: 'support.function', foreground: 'DCDCAA' },
],
};
3 changes: 3 additions & 0 deletions ui/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
"history": "^4.6.0",
"isomorphic-fetch": "^3.0.0",
"lodash": "^4.17.0",
"monaco-editor": "^0.27.0",
"prismjs": "^1.6.0",
"qs": "^6.4.0",
"react": "^17.0.1",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^17.0.1",
"react-monaco-editor": "^0.45.0",
"react-popper": "^2.0.0",
"react-portal": "^4.1.4",
"react-prism": "^4.0.0",
Expand Down Expand Up @@ -58,6 +60,7 @@
"jest": "^27.0.0",
"json-loader": "^0.5.4",
"mini-css-extract-plugin": "^2.0.0",
"monaco-editor-webpack-plugin": "^4.0.0",
"normalize.css": "^8.0.0",
"postcss": "^8.2.7",
"postcss-loader": "^6.1.0",
Expand Down

0 comments on commit 5a8c95e

Please sign in to comment.