Skip to content

Commit

Permalink
Merge pull request #736 from HKalbasi/master
Browse files Browse the repository at this point in the history
Rename advanced editor to Ace and add Monaco
  • Loading branch information
shepmaster committed Jan 9, 2022
2 parents d5aca97 + 1b8120b commit 60759af
Show file tree
Hide file tree
Showing 22 changed files with 536 additions and 144 deletions.
27 changes: 26 additions & 1 deletion tests/spec/features/editor_types_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
before { visit '/' }

scenario "using the simple editor" do
in_config_menu { choose("simple") }
in_config_menu { select("simple") }

fill_in('editor-simple', with: simple_editor_code)

Expand All @@ -25,4 +25,29 @@ def simple_editor_code
}
EOF
end

scenario "using the Monaco editor" do
in_config_menu { select("monaco") }

editor = page.find('.monaco-editor')

# Click on the last line as that will replace the entire content
editor.find('.view-line:last-child').click
t = editor.find('textarea', visible: false)
t.set(monaco_editor_code, clear: :backspace)

click_on("Run")

within(:output, :stdout) do
expect(page).to have_content 'Monaco editor'
end
end

# Missing indentation and closing curly braces as those are auto-inserted
def monaco_editor_code
<<~EOF
fn main() {
println!("Using the Monaco editor");
EOF
end
end
45 changes: 31 additions & 14 deletions ui/frontend/ConfigMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,24 @@ 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.keybinding);
const theme = useSelector((state: State) => state.configuration.theme);
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.pairCharacters);
const pairCharacters = useSelector((state: State) => state.configuration.ace.pairCharacters);
const assemblyFlavor = useSelector((state: State) => state.configuration.assemblyFlavor);
const demangleAssembly = useSelector((state: State) => state.configuration.demangleAssembly);
const processAssembly = useSelector((state: State) => state.configuration.processAssembly);

const dispatch = useDispatch();
const changeTheme = useCallback((t) => dispatch(actions.changeTheme(t)), [dispatch]);
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,15 +50,15 @@ const ConfigMenu: React.SFC<ConfigMenuProps> = () => {
return (
<Fragment>
<MenuGroup title="Editor">
<EitherConfig
id="editor-style"
name="Style"
a={Editor.Simple}
b={Editor.Advanced}
<SelectConfig
name="Editor"
value={editorStyle}
onChange={changeEditorStyle} />

{editorStyle === Editor.Advanced && (
onChange={changeEditorStyle}
>
{[Editor.Simple, Editor.Ace, Editor.Monaco]
.map(k => <option key={k} value={k}>{k}</option>)}
</SelectConfig>
{editorStyle === Editor.Ace && (
<Fragment>
<SelectConfig
name="Keybinding"
Expand All @@ -64,8 +70,8 @@ const ConfigMenu: React.SFC<ConfigMenuProps> = () => {

<SelectConfig
name="Theme"
value={theme}
onChange={changeTheme}
value={aceTheme}
onChange={changeAceTheme}
>
{ACE_THEMES.map(t => <option key={t} value={t}>{t}</option>)}
</SelectConfig>
Expand All @@ -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
14 changes: 0 additions & 14 deletions ui/frontend/Editor.module.css

This file was deleted.

40 changes: 10 additions & 30 deletions ui/frontend/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,29 @@ import * as selectors from './selectors';

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

const EDITION_URL = 'https://doc.rust-lang.org/edition-guide/';
const SURVEY_URL = 'https://blog.rust-lang.org/2021/12/08/survey-launch.html';
const MONACO_EDITOR_URL = 'https://microsoft.github.io/monaco-editor/';

const Notifications: React.SFC = () => {
return (
<Portal>
<div className={styles.container}>
<Rust2021IsDefaultNotification />
<RustSurvey2021Notification />
<MonacoEditorAvailableNotification />
</div>
</Portal>
);
};

const Rust2021IsDefaultNotification: React.SFC = () => {
const showRust2021IsDefault = useSelector(selectors.showRust2021IsDefaultSelector);
const MonacoEditorAvailableNotification: React.SFC = () => {
const monicoEditorAvailable = useSelector(selectors.showMonicoEditorAvailableSelector);

const dispatch = useDispatch();
const seenRust2021IsDefault = useCallback(() => dispatch(actions.seenRust2021IsDefault()), [dispatch]);
const seenMonicoEditorAvailable = useCallback(() => dispatch(actions.seenMonicoEditorAvailable()), [dispatch]);

return showRust2021IsDefault && (
<Notification onClose={seenRust2021IsDefault}>
As of Rust 1.56, the default edition of Rust is now Rust
2021. Learn more about editions in the <a href={EDITION_URL}>Edition Guide</a>.
To specify which edition to use, use the advanced compilation options menu.
</Notification>
);
};


const RustSurvey2021Notification: React.SFC = () => {
const showRustSurvey2021 = useSelector(selectors.showRustSurvey2021Selector);

const dispatch = useDispatch();
const seenRustSurvey2021 = useCallback(() => dispatch(actions.seenRustSurvey2021()), [dispatch]);

return showRustSurvey2021 && (
<Notification onClose={seenRustSurvey2021}>
Please help us take a look at who the Rust community is
composed of, how the Rust project is doing, and how we can
improve the Rust programming experience by completing the <a
href={SURVEY_URL}>2021 State of Rust Survey</a>. Whether or
not you use Rust today, we want to know your opinions.
return monicoEditorAvailable && (
<Notification onClose={seenMonicoEditorAvailable}>
The <a href={MONACO_EDITOR_URL}>Monaco Editor</a>, the code editor
that powers VS Code, is now available in the playground. Choose
your preferred editor from the Config menu.
</Notification>
);
};
Expand Down
2 changes: 1 addition & 1 deletion ui/frontend/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Split from 'split-grid';

import Editor from './Editor';
import Editor from './editor/Editor';
import Header from './Header';
import Notifications from './Notifications';
import Output from './Output';
Expand Down
16 changes: 10 additions & 6 deletions ui/frontend/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export enum ActionType {
SetPage = 'SET_PAGE',
ChangeEditor = 'CHANGE_EDITOR',
ChangeKeybinding = 'CHANGE_KEYBINDING',
ChangeTheme = 'CHANGE_THEME',
ChangeAceTheme = 'CHANGE_ACE_THEME',
ChangeMonacoTheme = 'CHANGE_MONACO_THEME',
ChangePairCharacters = 'CHANGE_PAIR_CHARACTERS',
ChangeOrientation = 'CHANGE_ORIENTATION',
ChangeAssemblyFlavor = 'CHANGE_ASSEMBLY_FLAVOR',
Expand Down Expand Up @@ -138,8 +139,11 @@ export const changeEditor = (editor: Editor) =>
export const changeKeybinding = (keybinding: string) =>
createAction(ActionType.ChangeKeybinding, { keybinding });

export const changeTheme = (theme: string) =>
createAction(ActionType.ChangeTheme, { theme });
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 @@ -701,8 +705,7 @@ export function performVersionsLoad(): ThunkAction {
const notificationSeen = (notification: Notification) =>
createAction(ActionType.NotificationSeen, { notification });

export const seenRust2021IsDefault = () => notificationSeen(Notification.Rust2021IsDefault);
export const seenRustSurvey2021 = () => notificationSeen(Notification.RustSurvey2021);
export const seenMonicoEditorAvailable = () => notificationSeen(Notification.MonacoEditorAvailable);

export const browserWidthChanged = (isSmall: boolean) =>
createAction(ActionType.BrowserWidthChanged, { isSmall });
Expand Down Expand Up @@ -816,7 +819,8 @@ export type Action =
| ReturnType<typeof changeOrientation>
| ReturnType<typeof changePrimaryAction>
| ReturnType<typeof changeProcessAssembly>
| ReturnType<typeof changeTheme>
| ReturnType<typeof changeAceTheme>
| ReturnType<typeof changeMonacoTheme>
| ReturnType<typeof requestExecute>
| ReturnType<typeof receiveExecuteSuccess>
| ReturnType<typeof receiveExecuteFailure>
Expand Down
38 changes: 19 additions & 19 deletions ui/frontend/AdvancedEditor.tsx → ui/frontend/editor/AceEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { connect } from 'react-redux';
import { aceResizeKey, offerCrateAutocompleteOnUse } from './selectors';
import { aceResizeKey, offerCrateAutocompleteOnUse } from '../selectors';

import State from './state';
import { AceResizeKey, CommonEditorProps, Crate, PairCharacters, Position, Selection } from './types';
import State from '../state';
import { AceResizeKey, CommonEditorProps, Crate, PairCharacters, Position, Selection } from '../types';

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

type Ace = typeof import('ace-builds');
type AceEditor = import('ace-builds').Ace.Editor;
type AceModule = import('ace-builds').Ace.Editor;
type AceCompleter = import('ace-builds').Ace.Completer;

const displayExternCrateAutocomplete = (editor: AceEditor, autocompleteOnUse: boolean) => {
const displayExternCrateAutocomplete = (editor: AceModule, autocompleteOnUse: boolean) => {
const { session } = editor;
const pos = editor.getCursorPosition();
const line = session.getLine(pos.row);
Expand Down Expand Up @@ -55,11 +55,11 @@ function useRafDebouncedFunction<A extends any[]>(fn: (...args: A) => void, onCa
}, [fn, onCall, timeout]);
}

interface AdvancedEditorProps extends AdvancedEditorAsyncProps {
interface AceEditorProps extends AceEditorAsyncProps {
ace: Ace;
}

interface AdvancedEditorProps {
interface AceEditorProps {
ace: Ace;
autocompleteOnUse: boolean;
code: string;
Expand All @@ -75,16 +75,16 @@ interface AdvancedEditorProps {
}

// Run an effect when the editor or prop changes
function useEditorProp<T>(editor: AceEditor, prop: T, whenPresent: (editor: AceEditor, prop: T) => void) {
function useEditorProp<T>(editor: AceModule, prop: T, whenPresent: (editor: AceModule, prop: T) => void) {
useEffect(() => {
if (editor) {
return whenPresent(editor, prop);
}
}, [editor, prop, whenPresent]);
}

const AdvancedEditor: React.SFC<AdvancedEditorProps> = props => {
const [editor, setEditor] = useState<AceEditor>(null);
const AceEditor: React.SFC<AceEditorProps> = props => {
const [editor, setEditor] = useState<AceModule>(null);
const child = useRef<HTMLDivElement>(null);

useEffect(() => {
Expand Down Expand Up @@ -292,7 +292,7 @@ const AdvancedEditor: React.SFC<AdvancedEditorProps> = props => {
}, []));

return (
<div className={styles.advanced} ref={child} />
<div className={styles.ace} ref={child} />
);
};

Expand All @@ -315,7 +315,7 @@ enum LoadState {
//
// Themes and keybindings can be changed at runtime.

interface AdvancedEditorAsyncProps {
interface AceEditorAsyncProps {
autocompleteOnUse: boolean;
code: string;
execute: () => any;
Expand All @@ -329,7 +329,7 @@ interface AdvancedEditorAsyncProps {
pairCharacters: PairCharacters;
}

class AdvancedEditorAsync extends React.Component<AdvancedEditorAsyncProps, AdvancedEditorAsyncState> {
class AceEditorAsync extends React.Component<AceEditorAsyncProps, AceEditorAsyncState> {
public constructor(props) {
super(props);
this.state = {
Expand All @@ -342,7 +342,7 @@ class AdvancedEditorAsync extends React.Component<AdvancedEditorAsyncProps, Adva
public render() {
if (this.isLoaded()) {
const { ace, theme, keybinding } = this.state;
return <AdvancedEditor {...this.props} ace={ace} theme={theme} keybinding={keybinding} />;
return <AceEditor {...this.props} ace={ace} theme={theme} keybinding={keybinding} />;
} else {
return <div>Loading the ACE editor...</div>;
}
Expand Down Expand Up @@ -447,13 +447,13 @@ class AdvancedEditorAsync extends React.Component<AdvancedEditorAsyncProps, Adva

private async requireLibraries() {
return import(
/* webpackChunkName: "advanced-editor" */
'./advanced-editor'
/* webpackChunkName: "ace-editor" */
'./ace-editor'
);
}
}

interface AdvancedEditorAsyncState {
interface AceEditorAsyncState {
theme?: string;
keybinding?: string;
themeState: LoadState;
Expand All @@ -472,7 +472,7 @@ interface PropsFromState {
}

const mapStateToProps = (state: State) => {
const { configuration: { theme, keybinding, pairCharacters } } = state;
const { configuration: { ace: { theme, keybinding, pairCharacters } } } = state;

return {
theme,
Expand All @@ -483,4 +483,4 @@ const mapStateToProps = (state: State) => {
};
};

export default connect<PropsFromState, undefined, CommonEditorProps>(mapStateToProps)(AdvancedEditorAsync);
export default connect<PropsFromState, undefined, CommonEditorProps>(mapStateToProps)(AceEditorAsync);
22 changes: 22 additions & 0 deletions ui/frontend/editor/Editor.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.container {
composes: -autoSize from '../shared.module.css';
position: relative;
}

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

.ace {
composes: -advanced;
}

.monaco {
composes: -advanced;
}

.simple {
composes: -advanced;
border: none;
}

0 comments on commit 60759af

Please sign in to comment.