Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename advanced editor to Ace and add Monaco #736

Merged
merged 4 commits into from
Jan 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}