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

Add rust-analyzer-wasm #813

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions ui/frontend/ConfigElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ import MenuItem from './MenuItem';

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

interface ButtonProps extends ConfigElementProps {
id: string;
label: string;
onClick: () => any;
}

export const Button: React.SFC<ButtonProps> =
({ label, onClick, ...rest }) => (
<ConfigElement {...rest}>
<div className={styles.toggle}>
<button onClick={onClick}>{label}</button>
</div>
</ConfigElement>
);


interface EitherProps extends ConfigElementProps {
id: string;
a: string;
Expand Down
13 changes: 12 additions & 1 deletion ui/frontend/ConfigMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React, { Fragment, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { Either as EitherConfig, Select as SelectConfig } from './ConfigElement';
import { Either as EitherConfig, Select as SelectConfig, Button as ButtonConfig } from './ConfigElement';
import MenuGroup from './MenuGroup';

import * as actions from './actions';
Expand All @@ -16,6 +16,8 @@ import {
PairCharacters,
ProcessAssembly,
} from './types';
import { isEnable, enableIntellisense } from './intellisense/config';
import { configDialog } from './intellisense/ConfigMenu';

interface ConfigMenuProps {
close: () => void;
Expand Down Expand Up @@ -94,6 +96,15 @@ const ConfigMenu: React.FC<ConfigMenuProps> = () => {
>
{MONACO_THEMES.map(t => <option key={t} value={t}>{t}</option>)}
</SelectConfig>
{isEnable()
? <ButtonConfig id="intellisense"
name="Intellisense"
label="Config"
onClick={configDialog} />
: <ButtonConfig id="intellisense"
name="Intellisense"
label="Enable"
onClick={enableIntellisense} />}
</Fragment>
)}
</MenuGroup>
Expand Down
36 changes: 34 additions & 2 deletions ui/frontend/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';

import AdvancedOptionsMenu from './AdvancedOptionsMenu';
Expand All @@ -17,6 +17,9 @@ import * as selectors from './selectors';
import { useAppDispatch } from './configureStore';

import styles from './Header.module.css';
import { enableIntellisense, getIntellisenseConfig } from './intellisense/config';
import { State } from './reducers';
import { Editor } from './types';

const Header: React.FC = () => (
<div data-test-id="header" className={styles.container}>
Expand All @@ -33,6 +36,11 @@ const Header: React.FC = () => (
<AdvancedOptionsMenuButton />
</SegmentedButtonSet>
</HeaderSet>
<HeaderSet id="intellisense">
<SegmentedButtonSet>
<IntellisenseButton />
</SegmentedButtonSet>
</HeaderSet>
<HeaderSet id="share">
<SegmentedButtonSet>
<ShareButton />
Expand Down Expand Up @@ -129,7 +137,31 @@ const AdvancedOptionsMenuButton: React.FC = () => {
return <PopButton Button={Button} Menu={AdvancedOptionsMenu} />;
}

const ShareButton: React.FC = () => {
const IntellisenseButton: React.FC = () => {
const [dismissed, setDismiss] = useState(false);
const dispatch = useAppDispatch();
const editorStyle = useSelector((state: State) => state.configuration.editor);
if (dismissed || !getIntellisenseConfig().suggest) {
return <></>;
}
const enable = () => {
if (editorStyle != Editor.Monaco) {
dispatch(actions.changeEditor(Editor.Monaco));
}
enableIntellisense();
};
const dismiss: React.MouseEventHandler = (e) => {
e.stopPropagation();
setDismiss(true);
};
return (
<SegmentedButton title="Download web version of rust-analyzer to provide intellisense features" onClick={enable}>
<HeaderButton>Enable Rust-analyzer<button onClick={dismiss}>Dismiss</button></HeaderButton>
</SegmentedButton>
);
};

const ShareButton: React.SFC = () => {
const dispatch = useAppDispatch();
const gistSave = useCallback(() => dispatch(actions.performGistSave()), [dispatch]);

Expand Down
2 changes: 2 additions & 0 deletions ui/frontend/editor/MonacoEditorCore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import State from '../state';
import { config, grammar, themeVsDarkPlus } from './rust_monaco_def';

import styles from './Editor.module.css';
import { enableOnMonaco } from '../intellisense';

const MODE_ID = 'rust';

Expand All @@ -26,6 +27,7 @@ const initEditor = (execute: () => any): EditorDidMount => (editor, monaco) => {
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
execute();
});
enableOnMonaco(editor, monaco);
// Ace's Vim mode runs code with :w, so let's do the same
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
execute();
Expand Down
10 changes: 8 additions & 2 deletions ui/frontend/editor/rust_monaco_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const grammar: languages.IMonarchLanguage = {
'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',
'macro_rules', 'async', 'await',
],

controlFlowKeywords: [
Expand Down Expand Up @@ -80,6 +80,7 @@ export const grammar: languages.IMonarchLanguage = {
'@keywords': {
cases: {
'fn': { token: 'keyword', next: '@func_decl' },
'const': { token: 'keyword', next: '@const_decl' },
'@default': 'keyword',
},
},
Expand Down Expand Up @@ -157,7 +158,12 @@ export const grammar: languages.IMonarchLanguage = {

func_decl: [
[
/[a-z_$][\w$]*/, 'support.function', '@pop',
/[a-zA-Z_$][\w$]*/, 'support.function', '@pop',
],
],
const_decl: [
[
/[a-zA-Z_$][\w$]*/, 'variable.constant', '@pop',
],
],
},
Expand Down
21 changes: 21 additions & 0 deletions ui/frontend/generate-crate-src.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
rm -rf build/assets/crate-src/
mkdir build/assets/crate-src/
cd build/assets/crate-src/
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/core/src/lib.rs > core_stable_1_58.rs
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/alloc/src/lib.rs > alloc_stable_1_58.rs
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/std/src/lib.rs > std_stable_1_58.rs
# Switch to beta
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/core/src/lib.rs > core_beta_1_59.rs
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/alloc/src/lib.rs > alloc_beta_1_59.rs
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/std/src/lib.rs > std_beta_1_59.rs
# Switch to nightly
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/core/src/lib.rs > core_nightly_2022_03_07.rs
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/alloc/src/lib.rs > alloc_nightly_2022_03_07.rs
syn-file-expand-cli -c unix -c "not(test)" -c "not(no_global_oom_handling)" --loopify $(rustc --print sysroot)/lib/rustlib/src/rust/library/std/src/lib.rs > std_nightly_2022_03_07.rs
echo "{" > index.json
echo '"stdlib": [' >> index.json
echo '"stable_1_58",' >> index.json
echo '"beta_1_59",' >> index.json
echo '"nightly_2022_03_07"' >> index.json
echo ']' >> index.json
echo '}' >> index.json
23 changes: 23 additions & 0 deletions ui/frontend/intellisense/ConfigMenu.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.root {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: gray;
opacity: .7;
z-index: 5000;
}

.main {
position: fixed;
top: 10vh;
left: 10vw;
height: 80vh;
width: 80vw;
background-color: black;
color: white;
border-radius: 2rem;
padding: 2rem;
z-index: 5001;
}
69 changes: 69 additions & 0 deletions ui/frontend/intellisense/ConfigMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { getIntellisenseConfig, setConfig } from './config';
import styles from './ConfigMenu.module.css';
import { availableVersions, selectVersion } from './crates';

const ConfigMenu: React.FC<{ onDone: (result?: string | undefined) => void }> = ({ onDone }) => {
const { enable, diagnostic } = getIntellisenseConfig();
const [nextEnable, setEnable] = useState(enable);
const [nextDiag, setDiag] = useState(diagnostic);
const [nextStdlib, setNextStdlib] = useState(undefined);
const [stdlibVersions, setStdlibVersions] = useState(undefined);
useEffect(() => {
(async () => {
const x = await availableVersions('stdlib');
setStdlibVersions(x);
setNextStdlib(x.selected);
})();
}, []);
let stdlibVersionsElement = <>Loading...</>;
if (stdlibVersions) {
stdlibVersionsElement = (
<>
<span onClick={() => setNextStdlib(stdlibVersions.selected)}>
<input type="radio" checked={nextStdlib === stdlibVersions.selected} />{stdlibVersions.selected} (downloaded)
</span>
{stdlibVersions.others.map((x: string) => <span key={x} onClick={() => setNextStdlib(x)}>
{' '}<input type="radio" checked={nextStdlib === x} />{x}
</span>)}
</>
)
}
return (
<div>
<div className={styles.main}>
<div>
Enabled: <input type="checkbox" checked={nextEnable} onChange={() => setEnable(!nextEnable)} />
<br />
{nextEnable && <>
Diagnostics: <input type="checkbox" checked={nextDiag} onChange={() => setDiag(!nextDiag)} />
<br />
Standard library version: {stdlibVersionsElement}
</>}
</div>
<button onClick={() => {
selectVersion('stdlib', nextStdlib);
setConfig({
enable: nextEnable,
suggest: false,
diagnostic: nextDiag,
})
window.location.reload();
}}>Save</button>
</div>
<div className={styles.root} onClick={() => onDone()} />
</div>
);
};

export const configDialog = (): Promise<string | undefined> => {
const div = document.createElement('div');
document.body.appendChild(div);
return new Promise((res) => {
ReactDOM.render(<ConfigMenu onDone={(result) => {
document.body.removeChild(div);
res(result);
}} />, div);
});
};
38 changes: 38 additions & 0 deletions ui/frontend/intellisense/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const storageKey = 'intellisence';

type Config = {
enable: boolean,
suggest: boolean,
diagnostic: boolean,
};

export const setConfig = (x: Config) => {
window.localStorage.setItem(storageKey, JSON.stringify(x));
};

export const getIntellisenseConfig = (): Config => {
const v = window.localStorage.getItem(storageKey);
if (!v) {
setConfig({
enable: false,
suggest: true,
diagnostic: false,
});
return getIntellisenseConfig();
}
return JSON.parse(v);
};

export const isEnable = (): boolean => {
return getIntellisenseConfig().enable;
};

export const enableIntellisense = () => {
setConfig({
...getIntellisenseConfig(),
enable: true,
suggest: false,
});
window.location.reload();
};

47 changes: 47 additions & 0 deletions ui/frontend/intellisense/crates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

export const downloadCodeOfCrate = async (crateName: string, version: string) => {
return (await fetch(`/assets/crate-src/${crateName}_${version}.rs`)).text();
};

export const availableCrates = async (): Promise<{ [crate: string]: string[] }> => {
return (await fetch('/assets/crate-src/index.json')).json();
};

const storageKey = 'intellisense-crates';

const selectedCrates = (): { [crate: string]: string | undefined } => {
const x = localStorage.getItem(storageKey);
if (!x) {
localStorage.setItem(storageKey, '{}');
return {};
}
return JSON.parse(x);
};

export const availableVersions = async (crateName: string): Promise<{
selected: string | undefined,
others: string[],
}> => {
const v = (await availableCrates())[crateName];
const selected = selectedCrates()[crateName];
return {
selected,
others: v.filter((x) => x !== selected),
};
};

export const selectVersion = (crateName: string, version: string) => {
const c = selectedCrates();
c[crateName] = version;
localStorage.setItem(storageKey, JSON.stringify(c));
};

export const selectedVersion = async (crateName: string): Promise<string> => {
const current = selectedCrates()[crateName];
if (selectedCrates()[crateName]) {
return current;
}
const r = (await availableCrates())[crateName][0];
selectVersion(crateName, r);
return r;
};
Loading