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

FEAT: add code navigation to script editor #1660

Open
wants to merge 2 commits into
base: dev
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
3 changes: 3 additions & 0 deletions src/ScriptEditor/ScriptEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { NetscriptExtra } from "../NetscriptFunctions/Extra";
import * as enums from "../Enums";
import { ns } from "../NetscriptFunctions";
import { isLegacyScript } from "../Paths/ScriptFilePath";
import { openCodeEditor } from "./ui/ScriptEditorRoot";

/** Event emitter used for tracking when changes have been made to a content file. */
export const fileEditEvents = new EventEmitter<[hostname: string, filename: ContentFilePath]>();
Expand Down Expand Up @@ -126,6 +127,8 @@ export class ScriptEditor {
});
}

monaco.editor.registerEditorOpener({openCodeEditor});

monaco.languages.json.jsonDefaults.setModeConfiguration({
...monaco.languages.json.jsonDefaults.modeConfiguration,
//completion should be disabled because the
Expand Down
102 changes: 94 additions & 8 deletions src/ScriptEditor/ui/ScriptEditorRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { useVimEditor } from "./useVimEditor";
import { useCallback } from "react";
import { type AST, getFileType, parseAST } from "../../utils/ScriptTransformer";
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { hasScriptExtension, isLegacyScript } from "../../Paths/ScriptFilePath";
import { hasScriptExtension, isLegacyScript, resolveScriptFilePath, ScriptFilePath } from "../../Paths/ScriptFilePath";

interface IProps {
// Map of filename -> code
Expand All @@ -42,6 +42,72 @@ interface IProps {
}
const openScripts: OpenScript[] = [];
let currentScript: OpenScript | null = null;
let vim: boolean = false; // this will be set on mounting the component

export function openCodeEditor(
source: monaco.editor.ICodeEditor,
resource: monaco.Uri,
selectionOrPosition?: monaco.IRange | monaco.IPosition,
): boolean {
const resolvedPath = resolveScriptFilePath(resource.path);
if (resolvedPath === null) {
return false;
}

const [hostname, path] = resolvedPath.split("/", 2);

// check if we have an open file already; if so, show it at the requested position
const openScript = openScripts.find((s) => s.path === path && s.hostname === hostname);
if (openScript) {
if (openScript.model === undefined || openScript.model === null || openScript.model.isDisposed()) {
openScript.regenerateModel();
}
currentScript = openScript;
source.setModel(openScript.model);
} else {
const server = GetServer(hostname);
if (server === null) {
return false;
}

const script = server.scripts.get(path as ScriptFilePath);
if (script === undefined) {
return false;
}

// Open script
const newScript = new OpenScript(
path as ScriptFilePath,
script.code,
hostname,
new monaco.Position(0, 0),
makeModel(hostname, path, script.code),
vim,
);
openScripts.push(newScript);
currentScript = newScript;
source.setModel(newScript.model);
}

if (selectionOrPosition === undefined) {
return true;
}

if ((selectionOrPosition as monaco.IRange) !== undefined) {
const range = selectionOrPosition as monaco.IRange;

source.setSelection(range);
source.revealLineInCenter(range.startLineNumber);
} else if ((selectionOrPosition as monaco.IPosition) !== undefined) {
const pos = selectionOrPosition as monaco.IPosition;

source.setPosition(pos);
source.revealLineInCenter(pos.lineNumber);
}

source.focus();
return true;
}

function Root(props: IProps): React.ReactElement {
const rerender = useRerender();
Expand Down Expand Up @@ -187,19 +253,40 @@ function Root(props: IProps): React.ReactElement {
debouncedCodeParsing(newCode);
};

let changeModelCallback: monaco.IDisposable | null = null;

// When the editor is mounted
function onMount(editor: IStandaloneCodeEditor): void {
// Required when switching between site navigation (e.g. from Script Editor -> Terminal and back)
// the `useEffect()` for vim mode is called before editor is mounted.
editorRef.current = editor;
changeModelCallback = editorRef.current.onDidChangeModel((e) => {
if (e.newModelUrl === null) {
return;
}

const resolvedPath = resolveScriptFilePath(e.newModelUrl.path);
if (resolvedPath === null) {
return false;
}
const [hostname, path] = resolvedPath.split("/", 2);

const script = openScripts.find((s) => s.hostname === hostname && s.path === path);
if (script) {
parseCode(script.code);
}

rerender();
removeOutlineOfEditor();
});
vim = props.vim;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this changing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using code navigation, monaco might open a file that has not been loaded yet. Originally, props.vim is set by the parent ReactNode when calling OnMount; unfortunately, the CodeEditorOpener doesn't know the props and hence I save the most recent value so that new files are opened with the latest props.vim setting instead of falling back to a fixed true or false.


if (props.files.size === 0 && currentScript !== null) {
// Open currentscript
currentScript.regenerateModel();
editorRef.current.setModel(currentScript.model);
editorRef.current.setPosition(currentScript.lastPosition);
editorRef.current.revealLineInCenter(currentScript.lastPosition.lineNumber);
parseCode(currentScript.code);
editorRef.current.focus();
return;
}
Expand All @@ -218,7 +305,6 @@ function Root(props: IProps): React.ReactElement {
editorRef.current.setModel(openScript.model);
editorRef.current.setPosition(openScript.lastPosition);
editorRef.current.revealLineInCenter(openScript.lastPosition.lineNumber);
parseCode(openScript.code);
} else {
// Open script
const newScript = new OpenScript(
Expand All @@ -232,7 +318,6 @@ function Root(props: IProps): React.ReactElement {
openScripts.push(newScript);
currentScript = newScript;
editorRef.current.setModel(newScript.model);
parseCode(newScript.code);
}
}

Expand Down Expand Up @@ -289,7 +374,6 @@ function Root(props: IProps): React.ReactElement {
editorRef.current.setModel(currentScript.model);
editorRef.current.setPosition(currentScript.lastPosition);
editorRef.current.revealLineInCenter(currentScript.lastPosition.lineNumber);
parseCode(currentScript.code);
editorRef.current.focus();
}
removeOutlineOfEditor();
Expand Down Expand Up @@ -334,7 +418,6 @@ function Root(props: IProps): React.ReactElement {
editorRef.current.setModel(currentScript.model);
editorRef.current.setPosition(currentScript.lastPosition);
editorRef.current.revealLineInCenter(currentScript.lastPosition.lineNumber);
parseCode(currentScript.code);
editorRef.current.focus();
}
}
Expand Down Expand Up @@ -366,9 +449,7 @@ function Root(props: IProps): React.ReactElement {
openScript.regenerateModel();
}
editorRef.current.setModel(openScript.model);

editorRef.current.setValue(openScript.code);
parseCode(openScript.code);
editorRef.current.focus();
}
}
Expand Down Expand Up @@ -399,6 +480,11 @@ function Root(props: IProps): React.ReactElement {
}

function onUnmountEditor() {
if (changeModelCallback) {
changeModelCallback.dispose();
changeModelCallback = null;
}

if (!currentScript) {
return;
}
Expand Down
Loading