Skip to content

Commit

Permalink
Merge pull request #2383 from BetterThanTomorrow/2382-sticky-start-co…
Browse files Browse the repository at this point in the history
…nnect-repl-menu

Sticky start connect repl menu
  • Loading branch information
PEZ authored Jan 10, 2024
2 parents 2270c8e + d792ea3 commit 5f509c4
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 93 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Changes to Calva.

## [Unreleased]

- [Make REPL-button menu remember last item selected](https://github.com/BetterThanTomorrow/calva/issues/2382)
- More Calva commands (especially related to connect and jack-in) are now awaitable

## [2.0.407] - 2024-01-09

- [Offer to connect when evaluating forms while disconnected](https://github.com/BetterThanTomorrow/calva/issues/1490)
Expand Down
42 changes: 26 additions & 16 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ async function activate(context: vscode.ExtensionContext) {
clearInlineResults: annotations.clearAllEvaluationDecorations,
clearReplHistory: replHistory.clearHistory,
connect: connector.connectCommand,
connectNonProjectREPL: () => void connector.connectNonProjectREPLCommand(context),
connectNonProjectREPL: () => {
return connector.connectNonProjectREPLCommand(context);
},
continueComment: edit.continueCommentCommand,
convertDart2Clj: converters.dart2clj,
convertJs2Cljs: converters.js2cljs,
Expand Down Expand Up @@ -229,9 +231,9 @@ async function activate(context: vscode.ExtensionContext) {
jackIn: jackIn.jackInCommand,
jackOut: jackIn.jackOutCommand,
loadFile: eval.loadFileCommand,
openCalvaDocs: () => {
void context.globalState.update(VIEWED_CALVA_DOCS, true);
open(CALVA_DOCS_URL)
openCalvaDocs: async () => {
await context.globalState.update(VIEWED_CALVA_DOCS, true);
return open(CALVA_DOCS_URL)
.then(() => {
state.analytics().logEvent('Calva', 'Docs opened');
})
Expand Down Expand Up @@ -265,37 +267,45 @@ async function activate(context: vscode.ExtensionContext) {
openSourceFileForFiddle: fiddleFiles.openSourceFileForFiddle,
sendCurrentTopLevelFormToOutputWindow: outputWindow.appendCurrentTopLevelForm,
setOutputWindowNamespace: outputWindow.setNamespaceFromCurrentFile,
showFileForOutputWindowNS: () => void outputWindow.revealDocForCurrentNS(false),
showFileForOutputWindowNS: () => {
return outputWindow.revealDocForCurrentNS(false);
},
showNextReplHistoryEntry: replHistory.showNextReplHistoryEntry,
showOutputWindow: () => outputWindow.revealResultsDoc(false),
showPreviousReplHistoryEntry: replHistory.showPreviousReplHistoryEntry,
startJoyrideReplAndConnect: async () => {
const projectDir: string = await joyride.prepareForJackingOrConnect();
if (projectDir !== undefined) {
void joyride.joyrideJackIn(projectDir);
return joyride.joyrideJackIn(projectDir);
}
},
startOrConnectRepl: replStart.startOrConnectRepl,
startStandaloneCljsBrowserRepl: () =>
void replStart.startStandaloneRepl(context, replStart.HELLO_CLJS_BROWSER_TEMPLATE, false),
startStandaloneCljsNodeRepl: () =>
void replStart.startStandaloneRepl(context, replStart.HELLO_CLJS_NODE_TEMPLATE, false),
startStandaloneHelloRepl: () =>
void replStart.startStandaloneRepl(context, replStart.HELLO_TEMPLATE, false),
startStandaloneRepl: () =>
void replStart.startStandaloneRepl(context, replStart.USER_TEMPLATE, true),
startStandaloneCljsBrowserRepl: () => {
return replStart.startStandaloneRepl(context, replStart.HELLO_CLJS_BROWSER_TEMPLATE, false);
},
startStandaloneCljsNodeRepl: () => {
return replStart.startStandaloneRepl(context, replStart.HELLO_CLJS_NODE_TEMPLATE, false);
},
startStandaloneHelloRepl: () => {
return replStart.startStandaloneRepl(context, replStart.HELLO_TEMPLATE, false);
},
startStandaloneRepl: () => {
return replStart.startStandaloneRepl(context, replStart.USER_TEMPLATE, true);
},
switchCljsBuild: connector.switchCljsBuild,
tapCurrentTopLevelForm: () =>
snippets.evaluateCustomCodeSnippetCommand('(tap> $top-level-form)'),
tapSelection: () => snippets.evaluateCustomCodeSnippetCommand('(tap> $current-form)'),
toggleBetweenImplAndTest: () => void fileSwitcher.toggleBetweenImplAndTest(),
toggleBetweenImplAndTest: () => {
return fileSwitcher.toggleBetweenImplAndTest();
},
toggleCLJCSession: connector.toggleCLJCSession,
toggleEvaluationSendCodeToOutputWindow: eval.toggleEvaluationSendCodeToOutputWindow,
toggleKeybindingsEnabled: () => {
const keybindingsEnabled = vscode.workspace
.getConfiguration()
.get(config.KEYBINDINGS_ENABLED_CONFIG_KEY);
void vscode.workspace
return vscode.workspace
.getConfiguration()
.update(
config.KEYBINDINGS_ENABLED_CONFIG_KEY,
Expand Down
222 changes: 146 additions & 76 deletions src/nrepl/repl-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ async function openStoredDoc(
overwrite: false,
});
} catch (e) {
console.log(e);
if (e instanceof vscode.FileSystemError && e.code === 'FileExists') {
console.info(`File ${dramFile.path} already exists in temp dir, skipping copy.`);
} else {
console.log('Unexpected error:', e);
}
}
if (dramFile['open?']) {
const doc = await vscode.workspace.openTextDocument(destUri);
Expand Down Expand Up @@ -139,11 +143,80 @@ async function extractBundledFiles(
);
}

// Connected menu items
const RE_JACK_IN_OPTION = 'Restart the Project REPL (a.k.a. Re-jack-in)';
const RE_JACK_IN_COMMAND = 'calva.jackIn';
const JACK_OUT_OPTION = 'Stop/Kill the Project REPL started by Calva (a.k.a. Jack-out)';
const JACK_OUT_COMMAND = 'calva.jackOut';
const INTERRUPT_OPTION = 'Interrupt running Evaluations';
const INTERRUPT_COMMAND = 'calva.interruptAllEvaluations';
const DISCONNECT_OPTION = 'Disconnect from the REPL';
const DISCONNECT_COMMAND = 'calva.disconnect';
const OPEN_WINDOW_OPTION = 'Open the Output Window';
const OPEN_WINDOW_COMMAND = 'calva.showOutputWindow';
const OPEN_FIDDLE_OPTION = 'Open Fiddle for Current File';
const OPEN_FIDDLE_COMMAND = 'calva.openFiddleForSourceFile';
const EVALUATE_FIDDLE_OPTION = 'Evaluate Fiddle for Current File';
const EVALUATE_FIDDLE_COMMAND = 'calva.evaluateFiddleForSourceFile';
const OPEN_SOURCE_FOR_FIDDLE_OPTION = 'Open Source File for Current Fiddle';
const OPEN_SOURCE_FOR_FIDDLE_COMMAND = 'calva.openSourceFileForFiddle';

// Disconnected menu items
const JACK_IN_OPTION = 'Start your project with a REPL and connect (a.k.a. Jack-in)';
const JACK_IN_COMMAND = 'calva.jackIn';
const START_REPL_OPTION = 'Start a standalone REPL';
const START_REPL_COMMAND = 'calva.startStandaloneRepl';
const START_JOYRIDE_REPL_OPTION = 'Start a Joyride REPL and Connect';
const START_JOYRIDE_REPL_COMMAND = 'calva.startJoyrideReplAndConnect';
const START_HELLO_REPL_OPTION = 'Fire up the ”Getting Started” REPL';
const START_HELLO_REPL_COMMAND = 'calva.startStandaloneHelloRepl';
const START_HELLO_CLJS_BROWSER_OPTION = 'Fire up the ”ClojureScript Quick Start” Browser REPL';
const START_HELLO_CLJS_BROWSER_COMMAND = 'calva.startStandaloneCljsBrowserRepl';
const START_HELLO_CLJS_NODE_OPTION = 'Fire up the ”ClojureScript Quick Start” Node REPL';
const START_HELLO_CLJS_NODE_COMMAND = 'calva.startStandaloneCljsNodeRepl';
const CONNECT_PROJECT_OPTION = 'Connect to a running REPL in your project';
const CONNECT_PROJECT_COMMAND = 'calva.connect';
const CONNECT_STANDALONE_OPTION = 'Connect to a running REPL, not in your project';
const CONNECT_STANDALONE_COMMAND = 'calva.connectNonProjectREPL';

const DRAM_TEMPLATE_TO_MENU_OPTION: { [key: string]: string } = {};

DRAM_TEMPLATE_TO_MENU_OPTION[(USER_TEMPLATE.config as DramConfig).name] = START_REPL_OPTION;
DRAM_TEMPLATE_TO_MENU_OPTION[HELLO_TEMPLATE.config as string] = START_HELLO_REPL_OPTION;
DRAM_TEMPLATE_TO_MENU_OPTION[HELLO_CLJS_BROWSER_TEMPLATE.config as string] =
START_HELLO_CLJS_BROWSER_COMMAND;
DRAM_TEMPLATE_TO_MENU_OPTION[HELLO_CLJS_NODE_TEMPLATE.config as string] =
START_HELLO_CLJS_NODE_COMMAND;

function menuSlugForProjectRoot(): MenuSlug {
const prefix = state.getProjectRootUri() ? state.getProjectRootUri().toString() : 'no-folder';
const suffix = shouldShowConnectedMenu()
? 'connect-repl-menu-connected'
: 'connect-repl-menu-not-connected';
return { prefix, suffix };
}

export async function startStandaloneRepl(
context: vscode.ExtensionContext,
dramTemplate: DramTemplate,
areBundled: boolean
) {
// This is so that we can update the REPL Menu “command palette”
// with the default reconnect option, based on dram template used
// See end of this function for the other place where we update this,
// That's because the dram content is opened in a temp dir, making
// the project root different for the dram files than for the main
// window.
// TODO: The code can probably express it better than it currently does.
const { prefix, suffix } = menuSlugForProjectRoot();
const lastMenuSlug = { prefix, suffix };
const dramTemplateName =
typeof dramTemplate.config === 'string' ? dramTemplate.config : dramTemplate.config.name;
await state.extensionContext.workspaceState.update(
`qps-${prefix}/${suffix}`,
DRAM_TEMPLATE_TO_MENU_OPTION[dramTemplateName]
);

const config =
typeof dramTemplate.config === 'string'
? await fetchConfig(dramTemplate.config)
Expand All @@ -168,6 +241,14 @@ export async function startStandaloneRepl(
for (const file of config.files.slice(1)) {
await openStoredDoc(storageUri, tempDirUri, file);
}

// We now have the proper project root for the REPL Menu “command palette”
const newMenuSlug = menuSlugForProjectRoot();
await state.extensionContext.workspaceState.update(
`qps-${newMenuSlug.prefix}/${lastMenuSlug.suffix}`,
DRAM_TEMPLATE_TO_MENU_OPTION[dramTemplateName]
);

const firstPos = mainEditor.document.positionAt(0);
mainEditor.selection = new vscode.Selection(firstPos, firstPos);
mainEditor.revealRange(new vscode.Range(firstPos, firstPos));
Expand All @@ -177,7 +258,7 @@ export async function startStandaloneRepl(
preserveFocus: false,
});

await jackIn.jackIn(dramTemplate.connectSequence, false, async () => {
return jackIn.jackIn(dramTemplate.connectSequence, false, async () => {
await vscode.window.showTextDocument(mainDoc, {
preview: false,
viewColumn: vscode.ViewColumn.One,
Expand All @@ -188,48 +269,8 @@ export async function startStandaloneRepl(
});
}

export function startOrConnectRepl() {
const JACK_IN_OPTION = 'Start your project with a REPL and connect (a.k.a. Jack-in)';
const JACK_IN_COMMAND = 'calva.jackIn';
const RE_JACK_IN_OPTION = 'Restart the Project REPL (a.k.a. Re-jack-in)';
const RE_JACK_IN_COMMAND = 'calva.jackIn';
const JACK_OUT_OPTION = 'Stop/Kill the Project REPL started by Calva (a.k.a. Jack-out)';
const JACK_OUT_COMMAND = 'calva.jackOut';
const START_REPL_OPTION = 'Start a standalone REPL';
const START_REPL_COMMAND = 'calva.startStandaloneRepl';
const START_JOYRIDE_REPL_OPTION = 'Start a Joyride REPL and Connect';
const START_JOYRIDE_REPL_COMMAND = 'calva.startJoyrideReplAndConnect';
const START_HELLO_REPL_OPTION = 'Fire up the ”Getting Started” REPL';
const START_HELLO_REPL_COMMAND = 'calva.startStandaloneHelloRepl';
const START_HELLO_CLJS_BROWSER_OPTION = 'Fire up the ”ClojureScript Quick Start” Browser REPL';
const START_HELLO_CLJS_BROWSER_COMMAND = 'calva.startStandaloneCljsBrowserRepl';
const START_HELLO_CLJS_NODE_OPTION = 'Fire up the ”ClojureScript Quick Start” Node REPL';
const START_HELLO_CLJS_NODE_COMMAND = 'calva.startStandaloneCljsNodeRepl';
const CONNECT_PROJECT_OPTION = 'Connect to a running REPL in your project';
const CONNECT_PROJECT_COMMAND = 'calva.connect';
const CONNECT_STANDALONE_OPTION = 'Connect to a running REPL, not in your project';
const CONNECT_STANDALONE_COMMAND = 'calva.connectNonProjectREPL';
const INTERRUPT_OPTION = 'Interrupt running Evaluations';
const INTERRUPT_COMMAND = 'calva.interruptAllEvaluations';
const DISCONNECT_OPTION = 'Disconnect from the REPL';
const DISCONNECT_COMMAND = 'calva.disconnect';
const OPEN_WINDOW_OPTION = 'Open the Output Window';
const OPEN_WINDOW_COMMAND = 'calva.showOutputWindow';
const OPEN_FIDDLE_OPTION = 'Open Fiddle for Current File';
const OPEN_FIDDLE_COMMAND = 'calva.openFiddleForSourceFile';
const EVALUATE_FIDDLE_OPTION = 'Evaluate Fiddle for Current File';
const EVALUATE_FIDDLE_COMMAND = 'calva.evaluateFiddleForSourceFile';
const OPEN_SOURCE_FOR_FIDDLE_OPTION = 'Open Source File for Current Fiddle';
const OPEN_SOURCE_FOR_FIDDLE_COMMAND = 'calva.openSourceFileForFiddle';
function composeConnectedMenu() {
const PREFERRED_ORDER = [
JACK_IN_OPTION,
CONNECT_PROJECT_OPTION,
START_REPL_OPTION,
START_JOYRIDE_REPL_OPTION,
CONNECT_STANDALONE_OPTION,
START_HELLO_REPL_OPTION,
START_HELLO_CLJS_BROWSER_OPTION,
START_HELLO_CLJS_NODE_OPTION,
INTERRUPT_OPTION,
OPEN_WINDOW_OPTION,
RE_JACK_IN_OPTION,
Expand All @@ -239,50 +280,79 @@ export function startOrConnectRepl() {
EVALUATE_FIDDLE_OPTION,
OPEN_SOURCE_FOR_FIDDLE_OPTION,
];

const commands = {};
if (fiddleFiles.activeEditorIsFiddle) {
commands[OPEN_SOURCE_FOR_FIDDLE_OPTION] = OPEN_SOURCE_FOR_FIDDLE_COMMAND;
} else {
commands[OPEN_FIDDLE_OPTION] = OPEN_FIDDLE_COMMAND;
}
if (
!utilities.getConnectedState() &&
!utilities.getConnectingState() &&
!utilities.getLaunchingState()
) {
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
if (vscode.workspace.workspaceFolders[0].uri.scheme != 'vsls') {
commands[JACK_IN_OPTION] = JACK_IN_COMMAND;
commands[CONNECT_STANDALONE_OPTION] = CONNECT_STANDALONE_COMMAND;
}
commands[CONNECT_PROJECT_OPTION] = CONNECT_PROJECT_COMMAND;
} else {
commands[INTERRUPT_OPTION] = INTERRUPT_COMMAND;
commands[DISCONNECT_OPTION] = DISCONNECT_COMMAND;
if (replSession.getSession('clj')) {
commands[OPEN_WINDOW_OPTION] = OPEN_WINDOW_COMMAND;
}
if (utilities.getJackedInState()) {
commands[RE_JACK_IN_OPTION] = RE_JACK_IN_COMMAND;
commands[JACK_OUT_OPTION] = JACK_OUT_COMMAND;
}
if (!fiddleFiles.activeEditorIsFiddle) {
commands[EVALUATE_FIDDLE_OPTION] = EVALUATE_FIDDLE_COMMAND;
}
return { commands, PREFERRED_ORDER };
}

function composeDisconnectedMenu() {
const PREFERRED_ORDER = [
JACK_IN_OPTION,
CONNECT_PROJECT_OPTION,
START_REPL_OPTION,
START_JOYRIDE_REPL_OPTION,
CONNECT_STANDALONE_OPTION,
START_HELLO_REPL_OPTION,
START_HELLO_CLJS_BROWSER_OPTION,
START_HELLO_CLJS_NODE_OPTION,
];

const commands = {};
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
if (vscode.workspace.workspaceFolders[0].uri.scheme != 'vsls') {
commands[JACK_IN_OPTION] = JACK_IN_COMMAND;
commands[CONNECT_STANDALONE_OPTION] = CONNECT_STANDALONE_COMMAND;
commands[START_REPL_OPTION] = START_REPL_COMMAND;
}
commands[START_JOYRIDE_REPL_OPTION] = START_JOYRIDE_REPL_COMMAND;
commands[START_HELLO_REPL_OPTION] = START_HELLO_REPL_COMMAND;
commands[START_HELLO_CLJS_BROWSER_OPTION] = START_HELLO_CLJS_BROWSER_COMMAND;
commands[START_HELLO_CLJS_NODE_OPTION] = START_HELLO_CLJS_NODE_COMMAND;
commands[CONNECT_PROJECT_OPTION] = CONNECT_PROJECT_COMMAND;
} else {
commands[INTERRUPT_OPTION] = INTERRUPT_COMMAND;
commands[DISCONNECT_OPTION] = DISCONNECT_COMMAND;
if (replSession.getSession('clj')) {
commands[OPEN_WINDOW_OPTION] = OPEN_WINDOW_COMMAND;
}
if (utilities.getJackedInState()) {
commands[RE_JACK_IN_OPTION] = RE_JACK_IN_COMMAND;
commands[JACK_OUT_OPTION] = JACK_OUT_COMMAND;
}
if (!fiddleFiles.activeEditorIsFiddle) {
commands[EVALUATE_FIDDLE_OPTION] = EVALUATE_FIDDLE_COMMAND;
}
commands[CONNECT_STANDALONE_OPTION] = CONNECT_STANDALONE_COMMAND;
commands[START_REPL_OPTION] = START_REPL_COMMAND;
}
commands[START_JOYRIDE_REPL_OPTION] = START_JOYRIDE_REPL_COMMAND;
commands[START_HELLO_REPL_OPTION] = START_HELLO_REPL_COMMAND;
commands[START_HELLO_CLJS_BROWSER_OPTION] = START_HELLO_CLJS_BROWSER_COMMAND;
commands[START_HELLO_CLJS_NODE_OPTION] = START_HELLO_CLJS_NODE_COMMAND;
return { commands, PREFERRED_ORDER };
}

type MenuSlug = { prefix: string; suffix: string };

function shouldShowConnectedMenu() {
return (
utilities.getConnectedState() || utilities.getConnectingState() || utilities.getLaunchingState()
);
}

export async function startOrConnectRepl() {
const { commands, PREFERRED_ORDER } = shouldShowConnectedMenu()
? composeConnectedMenu()
: composeDisconnectedMenu();

const { prefix, suffix } = menuSlugForProjectRoot();
const sortedCommands = utilities.sortByPresetOrder(Object.keys(commands), PREFERRED_ORDER);
void vscode.window.showQuickPick(sortedCommands).then((v) => {
if (commands[v]) {
void vscode.commands.executeCommand(commands[v]);
}
const command_key = await utilities.quickPickSingle({
values: sortedCommands.map((a) => ({ label: a })),
saveAs: `${prefix}/${suffix}`,
placeHolder: 'Start or Connect a REPL',
});
if (command_key) {
await vscode.commands.executeCommand(commands[command_key]);
}
}
6 changes: 5 additions & 1 deletion src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ export function getProjectRootUri(useCache = true): vscode.Uri | undefined {
return res;
}
}
return vscode.workspace.workspaceFolders[0]?.uri;
if (vscode.workspace.workspaceFolders) {
return vscode.workspace.workspaceFolders[0].uri;
} else {
return undefined;
}
}

const NON_PROJECT_DIR_KEY = 'calva.connect.nonProjectDir';
Expand Down

0 comments on commit 5f509c4

Please sign in to comment.