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 command for copying jack-in command to clipboard #995

Merged
merged 11 commits into from
Jan 31, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Changes to Calva.

## [Unreleased]
- [Add command for copying jack-in command to clipboard](https://github.com/BetterThanTomorrow/calva/pull/995)
- [Change default shortcuts for Paredit forward/backward sexp, expand/shrink selection, and for slurping and barfing](https://github.com/BetterThanTomorrow/calva/issues/950)
- [Add Custom Commands variables for current form and more](https://github.com/BetterThanTomorrow/calva/issues/986)

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,11 @@
}
],
"commands": [
{
"command": "calva.copyJackInCommandToClipboard",
"title": "Copy Jack-In Command to Clipboard",
"category": "Calva"
},
{
"command": "calva.openCalvaDocs",
"title": "Open Documentation (calva.io)",
Expand Down
3 changes: 2 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ async function activate(context: vscode.ExtensionContext) {

// COMMANDS
context.subscriptions.push(vscode.commands.registerCommand('calva.jackInOrConnect', jackIn.calvaJackInOrConnect));
context.subscriptions.push(vscode.commands.registerCommand('calva.jackIn', jackIn.calvaJackIn))
context.subscriptions.push(vscode.commands.registerCommand('calva.jackIn', jackIn.calvaJackIn));
context.subscriptions.push(vscode.commands.registerCommand('calva.copyJackInCommandToClipboard', jackIn.copyJackInCommandToClipboard));
context.subscriptions.push(vscode.commands.registerCommand('calva.connectNonProjectREPL', connector.connectNonProjectREPLCommand));
context.subscriptions.push(vscode.commands.registerCommand('calva.connect', connector.connectCommand));
context.subscriptions.push(vscode.commands.registerCommand('calva.disconnect', jackIn.calvaDisconnect));
Expand Down
11 changes: 8 additions & 3 deletions src/nrepl/jack-in-terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export interface JackInTerminalOptions extends vscode.TerminalOptions {
isWin: boolean
};

export function createCommandLine(executable: string, args: string[]) {
return `${executable} ${args.join(' ')}`;
}

export class JackInTerminal implements vscode.Pseudoterminal {
private writeEmitter = new vscode.EventEmitter<string>();
onDidWrite: vscode.Event<string> = this.writeEmitter.event;
Expand All @@ -25,7 +29,7 @@ export class JackInTerminal implements vscode.Pseudoterminal {
private whenError: (errorMessage: string) => void) {}

open(initialDimensions: vscode.TerminalDimensions | undefined): void {
outputWindow.append(`; Starting Jack-in Terminal: ${this.options.executable} ${this.options.args.join(' ')}`);
outputWindow.append(`; Starting Jack-in Terminal: ${createCommandLine(this.options.executable, this.options.args)}`);
this.startClojureProgram();
}

Expand Down Expand Up @@ -53,8 +57,9 @@ export class JackInTerminal implements vscode.Pseudoterminal {
}

private async startClojureProgram(): Promise<child.ChildProcess> {
return new Promise<child.ChildProcess>((resolve) => {
this.writeEmitter.fire(`${this.options.executable} ${this.options.args.join(' ')}\r\n`);
return new Promise<child.ChildProcess>(() => {
const data = `${createCommandLine(this.options.executable, this.options.args)}\r\n`;
this.writeEmitter.fire(data);
if (this.process && !this.process.killed) {
console.log("Restarting Jack-in process");
this.killProcess();
Expand Down
143 changes: 91 additions & 52 deletions src/nrepl/jack-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import statusbar from "../statusbar";
import { askForConnectSequence, ReplConnectSequence, CljsTypes } from "./connectSequence";
import * as projectTypes from './project-types';
import * as outputWindow from '../results-output/results-doc';
import { JackInTerminal, JackInTerminalOptions } from "./jack-in-terminal";
import { JackInTerminal, JackInTerminalOptions, createCommandLine } from "./jack-in-terminal";
import * as namespace from "../namespace";
import * as liveShareSupport from '../liveShareSupport';

Expand All @@ -30,21 +30,16 @@ function resolveEnvVariables(entry: any): any {
}
}

async function executeJackInTask(projectType: projectTypes.ProjectType, projectTypeSelection: any, executable: string, args: any, isWin: boolean, cljTypes: string[], connectSequence: ReplConnectSequence) {
utilities.setLaunchingState(projectTypeSelection);
statusbar.update();
const jackInEnv = _.mapValues(state.config().jackInEnv as object, resolveEnvVariables);
const env = Object.assign(process.env, jackInEnv) as {
[key: string]: any;
};
const terminalOptions: JackInTerminalOptions = {
name: `Calva Jack-in: ${connectSequence.name}`,
executable,
args,
env,
isWin,
cwd: state.getProjectRootLocal(),
function getJackInEnv(): any {
return {
...process.env,
..._.mapValues(state.config().jackInEnv as object, resolveEnvVariables)
};
}

async function executeJackInTask(terminalOptions: JackInTerminalOptions, connectSequence: ReplConnectSequence) {
utilities.setLaunchingState(connectSequence.name);
statusbar.update();

// in case we have a running task present try to end it.
calvaJackout();
Expand All @@ -53,11 +48,8 @@ async function executeJackInTask(projectType: projectTypes.ProjectType, projectT
jackInTerminal = undefined;
}

state.analytics().logEvent("REPL", "JackInExecuting", JSON.stringify(cljTypes)).send();

try {
jackInPTY = new JackInTerminal(terminalOptions, async (_p, hostname: string, port: string) => {
// Create a watcher to wait for the nREPL port file to appear with new content, and connect + open the repl window at that point.
utilities.setLaunchingState(null);
await connector.connect(connectSequence, true, hostname, port);
outputWindow.append("; Jack-in done.");
Expand Down Expand Up @@ -108,6 +100,70 @@ export function calvaJackout() {
liveShareSupport.didJackOut();
}

export async function copyJackInCommandToClipboard(): Promise<void> {
try {
await state.initProjectDir();
} catch (e) {
console.error('An error occurred while initializing project directory.', e);
return;
}
bpringe marked this conversation as resolved.
Show resolved Hide resolved
let projectConnectSequence: ReplConnectSequence;
try {
projectConnectSequence = await getProjectConnectSequence();
} catch (e) {
return;
}
if (projectConnectSequence) {
const { executable, args } = await getJackInTerminalOptions(projectConnectSequence);
if (executable && args) {
vscode.env.clipboard.writeText(createCommandLine(executable, args));
vscode.window.showInformationMessage('Jack-in command copied to the clipboard.');
}
} else {
vscode.window.showInformationMessage('No supported project types detected.');
}
}

async function getJackInTerminalOptions(projectConnectSequence: ReplConnectSequence): Promise<JackInTerminalOptions> {
if (projectConnectSequence.projectType !== 'generic') {
const projectTypeName: string = projectConnectSequence.projectType;
let selectedCljsType: CljsTypes;

if (typeof projectConnectSequence.cljsType == "string" && projectConnectSequence.cljsType != CljsTypes.none) {
selectedCljsType = projectConnectSequence.cljsType;
} else if (projectConnectSequence.cljsType && typeof projectConnectSequence.cljsType == "object") {
selectedCljsType = projectConnectSequence.cljsType.dependsOn;
}

const projectType = projectTypes.getProjectTypeForName(projectTypeName);
const executable = projectTypes.isWin ? projectType.winCmd : projectType.cmd;
// Ask the project type to build up the command line. This may prompt for further information.
const args = await projectType.commandLine(projectConnectSequence, selectedCljsType);

const terminalOptions: JackInTerminalOptions = {
name: `Calva Jack-in: ${projectConnectSequence.name}`,
executable,
args,
env: getJackInEnv(),
isWin: projectTypes.isWin,
cwd: state.getProjectRootLocal(),
};
return terminalOptions;
}
}

async function getProjectConnectSequence(): Promise<ReplConnectSequence> {
const cljTypes: string[] = await projectTypes.detectProjectTypes();
if (cljTypes.length > 1) {
const connectSequence = await askForConnectSequence(cljTypes.filter(t => t !== 'generic'), 'jack-in-type', "JackInInterrupted");
if (connectSequence) {
return connectSequence;
} else {
return Promise.reject();
}
}
}

export async function calvaJackIn() {
try {
await state.initProjectDir();
Expand All @@ -123,45 +179,28 @@ export async function calvaJackIn() {
if (state.getProjectRootUri().scheme === "vsls") {
outputWindow.append("; Aborting Jack-in, since you're the guest of a live share session.");
outputWindow.append("; Please use this command instead: Connect to a running REPL server in the project.");
return
return;
}
state.analytics().logEvent("REPL", "JackInInitiated").send();
await outputWindow.initResultsDoc();
outputWindow.append("; Jacking in...");
const outputDocument = await outputWindow.openResultsDoc();

const cljTypes: string[] = await projectTypes.detectProjectTypes();
if (cljTypes.length > 1) {
const projectConnectSequence: ReplConnectSequence = await askForConnectSequence(cljTypes.filter(t => t !== 'generic'), 'jack-in-type', "JackInInterrupted");

if (!projectConnectSequence) {
state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypeForBuildName").send();
outputWindow.append("; Aborting Jack-in, since no project type was selected.");
return;
}
if (projectConnectSequence.projectType !== 'generic') {
const projectTypeName: string = projectConnectSequence.projectType;
let selectedCljsType: CljsTypes;

if (typeof projectConnectSequence.cljsType == "string" && projectConnectSequence.cljsType != CljsTypes.none) {
selectedCljsType = projectConnectSequence.cljsType;
} else if (projectConnectSequence.cljsType && typeof projectConnectSequence.cljsType == "object") {
selectedCljsType = projectConnectSequence.cljsType.dependsOn;
}

let projectType = projectTypes.getProjectTypeForName(projectTypeName);
let executable = projectTypes.isWin ? projectType.winCmd : projectType.cmd;
// Ask the project type to build up the command line. This may prompt for further information.
let args = await projectType.commandLine(projectConnectSequence, selectedCljsType);
await outputWindow.openResultsDoc();

executeJackInTask(projectType, projectConnectSequence.name, executable, args, projectTypes.isWin, cljTypes, projectConnectSequence)
.then(() => { }, () => { });
} else {
outputWindow.append("; There is no Jack-in possible for this project type.");
let projectConnectSequence: ReplConnectSequence;
try {
projectConnectSequence = await getProjectConnectSequence();
} catch (e) {
outputWindow.append('; Aborting jack-in. No project type selected.');
return;
}
if (projectConnectSequence) {
const terminalJackInOptions = await getJackInTerminalOptions(projectConnectSequence);
if (terminalJackInOptions) {
executeJackInTask(terminalJackInOptions, projectConnectSequence);
}
} else { // Only 'generic' type left
outputWindow.append("; No Jack-in possible.");
vscode.window.showInformationMessage('No supported Jack-in project types detected. Maybe try starting your project manually and use the Connect command?')
} else {
vscode.window.showInformationMessage('No supported project types detected. Maybe try starting your project manually and use the Connect command?');
return;
}

liveShareSupport.didJackIn();
Expand Down Expand Up @@ -205,7 +244,7 @@ export async function calvaJackInOrConnect() {
commands["Connect to a running REPL server, not in your project"] = "calva.connectNonProjectREPL";
} else {
commands["Disconnect from the REPL server"] = "calva.disconnect";
if(namespace.getSession("clj")) {
if (namespace.getSession("clj")) {
commands["Open the Output Window"] = "calva.showOutputWindow";
}
}
Expand Down