Skip to content

Commit

Permalink
feat: User is prompted to choose a directory for Explain command
Browse files Browse the repository at this point in the history
  • Loading branch information
kgilpin committed Jan 23, 2024
1 parent 79b4dd9 commit 62f0007
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 52 deletions.
90 changes: 90 additions & 0 deletions src/lib/selectIndexProcess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as vscode from 'vscode';
import IndexProcessWatcher from '../services/indexProcessWatcher';
import { NodeProcessService } from '../services/nodeProcessService';
import { workspaceServices } from '../services/workspaceServices';

export type IndexProcess = {
configFolder: string;
rpcPort: number;
};

export enum ReasonCode {
NoIndexProcessWatchers = 1,
NoReadyIndexProcessWatchers = 2,
NoSelectionMade = 3,
}

export function readyProcessWatchers(
workspace?: vscode.WorkspaceFolder
): IndexProcessWatcher[] | undefined {
const nodeProcessService = workspaceServices().getService(NodeProcessService);
if (!nodeProcessService) {
console.warn('No node process service found');
return;
}

const nodeProcessServices = workspaceServices().getServiceInstances(nodeProcessService);
if (nodeProcessServices.length === 0) {
console.log('No node process services found');
return;
}

const indexProcessWatchers = new Array<IndexProcessWatcher>();
for (const nodeProcessServiceInstance of nodeProcessServices) {
if (workspace && nodeProcessServiceInstance.folder !== workspace) continue;

for (const processWatcher of nodeProcessServiceInstance.processes) {
if (processWatcher instanceof IndexProcessWatcher) indexProcessWatchers.push(processWatcher);
}
}

return indexProcessWatchers;
}

export default async function selectIndexProcess(
workspace?: vscode.WorkspaceFolder
): Promise<IndexProcess | ReasonCode | undefined> {
const indexProcessWatchers = readyProcessWatchers(workspace);
if (!indexProcessWatchers) return;

if (indexProcessWatchers.length === 0) {
console.log('No index process watchers found');
return ReasonCode.NoIndexProcessWatchers;
}

const readyIndexProcessWatchers = indexProcessWatchers.filter((watcher) =>
watcher.isRpcAvailable()
);
if (readyIndexProcessWatchers.length === 0) {
console.log('No ready index process watchers found');
return ReasonCode.NoReadyIndexProcessWatchers;
}

let selectedWatcher: IndexProcessWatcher | undefined;
if (readyIndexProcessWatchers.length === 1) {
selectedWatcher = readyIndexProcessWatchers[0];
} else {
const pickResult = await vscode.window.showQuickPick(
readyIndexProcessWatchers.map((watcher) => ({
label: watcher.configFolder,
watcher,
})),
{
placeHolder: 'Select a project directory for your question',
}
);
if (!pickResult) return ReasonCode.NoSelectionMade;

selectedWatcher = pickResult.watcher;
}

if (!selectedWatcher.rpcPort) {
console.warn(`No RPC port available on index process watcher ${selectedWatcher.configFolder}`);
return;
}

return {
configFolder: selectedWatcher.configFolder,
rpcPort: selectedWatcher.rpcPort,
};
}
1 change: 0 additions & 1 deletion src/services/indexProcessWatcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as vscode from 'vscode';
import ExtensionSettings from '../configuration/extensionSettings';
import { NodeProcessService } from './nodeProcessService';
import { ProcessId, ProcessWatcher, ProcessWatcherOptions } from './processWatcher';
Expand Down
73 changes: 23 additions & 50 deletions src/webviews/chatSearchWebview.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import * as vscode from 'vscode';
import getWebviewContent from './getWebviewContent';
import { workspaceServices } from '../services/workspaceServices';
import { NodeProcessService } from '../services/nodeProcessService';
import { warn } from 'console';
import IndexProcessWatcher from '../services/indexProcessWatcher';
import { ProcessId } from '../services/processWatcher';
import appmapMessageHandler from './appmapMessageHandler';
import FilterStore, { SavedFilter } from './filterStore';
import WebviewList from './WebviewList';
import selectIndexProcess, { IndexProcess, ReasonCode } from '../lib/selectIndexProcess';

export default class ChatSearchWebview {
private webviewList = new WebviewList();
Expand All @@ -27,54 +23,31 @@ export default class ChatSearchWebview {
return this.webviewList.currentWebview;
}

readyIndexProcess(workspace: vscode.WorkspaceFolder): IndexProcessWatcher | undefined {
const processServiceInstance = workspaceServices().getServiceInstanceFromClass(
NodeProcessService,
workspace
);
if (!processServiceInstance) return;

const indexProcess = processServiceInstance.processes.find(
(proc) => proc.id === ProcessId.Index
) as IndexProcessWatcher;
if (!indexProcess) {
warn(`No ${ProcessId.Index} helper process found for workspace: ${workspace.name}`);
return;
}

if (!indexProcess.isRpcAvailable()) return;

return indexProcess;
}

isReady(workspace: vscode.WorkspaceFolder): boolean {
return !!this.readyIndexProcess(workspace);
}

async explain(workspace?: vscode.WorkspaceFolder, question?: string) {
if (!workspace) {
const workspaces = vscode.workspace.workspaceFolders;
if (!workspaces) return;

if (workspaces.length === 1) {
workspace = workspaces[0];
} else {
workspace = await vscode.window.showWorkspaceFolderPick({
placeHolder: 'Select a workspace folder',
});
}
if (!workspace) return;
const selectIndexProcessResult = await selectIndexProcess(workspace);
if (!selectIndexProcessResult) return;

let selectedWatcher: IndexProcess | undefined;
switch (selectIndexProcessResult) {
case ReasonCode.NoIndexProcessWatchers:
vscode.window.showInformationMessage(
`${workspace?.name || 'Your workspace'} does not have AppMaps`
);
break;
case ReasonCode.NoReadyIndexProcessWatchers:
vscode.window.showInformationMessage(
`AppMap AI is not ready yet. Please try again in a few seconds.`
);
break;
case ReasonCode.NoSelectionMade:
break;
default:
selectedWatcher = selectIndexProcessResult;
break;
}
if (!selectedWatcher) return;

const showError = async (message: string): Promise<string | undefined> => {
return vscode.window.showErrorMessage(message);
};

const indexProcess = this.readyIndexProcess(workspace);
if (!indexProcess)
return showError('AppMap Explain is not ready yet. Please try again in a few seconds.');

const { rpcPort: appmapRpcPort } = indexProcess;
const { rpcPort: appmapRpcPort } = selectedWatcher;

const panel = vscode.window.createWebviewPanel(
'chatSearch',
Expand Down
9 changes: 8 additions & 1 deletion test/integration/explain/explain.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as vscode from 'vscode';
import { initializeWorkspace, waitFor, waitForExtension, withAuthenticatedUser } from '../util';
import assert from 'assert';
import { readyProcessWatchers } from '../../../src/lib/selectIndexProcess';
import { initializeWorkspaceServices } from '../../../src/services/workspaceServices';

describe('appmap.explain', () => {
withAuthenticatedUser();
Expand All @@ -10,12 +12,17 @@ describe('appmap.explain', () => {
afterEach(initializeWorkspace);

it('opens a Chat + Search view', async () => {
initializeWorkspaceServices();

const chatSearchWebview = (await waitForExtension()).chatSearchWebview;

const workspace = vscode.workspace.workspaceFolders?.[0];
assert(workspace);

await waitFor('Explain service is not ready', () => chatSearchWebview.isReady(workspace));
await waitFor(
'Explain service is not ready',
() => readyProcessWatchers(workspace)?.length !== 0
);

await waitFor('Invoking appmap.explain opens a new text document', async () => {
await vscode.commands.executeCommand('appmap.explain');
Expand Down

0 comments on commit 62f0007

Please sign in to comment.