Skip to content

Commit

Permalink
feat(aztec): search for aztec-nargo on top of nargo bin (#67)
Browse files Browse the repository at this point in the history
Co-authored-by: Tom French <[email protected]>
Co-authored-by: Tom French <[email protected]>
  • Loading branch information
3 people authored Mar 4, 2024
1 parent 47e5b3c commit 0be02a5
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 62 deletions.
4 changes: 4 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ export default class Client extends LanguageClient {
});
}

get command(): string {
return this.#command;
}

async refreshProfileInfo() {
const response = await this.sendRequest<NargoProfileRunResult>('nargo/profile/run', { package: '' });

Expand Down
134 changes: 78 additions & 56 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ import {
window,
ProgressLocation,
} from 'vscode';
import os from 'os';

import { languageId } from './constants';
import Client from './client';
import findNargo from './find-nargo';
import findNargo, { findNargoBinaries } from './find-nargo';
import { lspClients, editorLineDecorationManager } from './noir';
import { getNoirStatusBarItem, handleClientStartError } from './noir';

const activeCommands: Map<string, Disposable> = new Map();

Expand Down Expand Up @@ -168,6 +170,15 @@ function registerCommands(uri: Uri) {
});
commands$.push(hideProfileInformationCommand$);

const selectNargoPathCommand$ = commands.registerCommand('nargo.config.path.select', async (..._args) => {
const homeDir = os.homedir();
const foundNargoBinaries = findNargoBinaries(homeDir);
const result = await window.showQuickPick(foundNargoBinaries, { placeHolder: 'Select the Nargo binary to use' });
const config = workspace.getConfiguration('noir', uri);
config.update('nargoPath', result);
});
commands$.push(selectNargoPathCommand$);

activeCommands.set(file, Disposable.from(...commands$));
}

Expand Down Expand Up @@ -246,67 +257,78 @@ async function didOpenTextDocument(document: TextDocument): Promise<Disposable>
const uri = document.uri;
let folder = workspace.getWorkspaceFolder(uri);
let configHandler;
if (folder) {
// If we have nested workspace folders we only start a server on the outer most workspace folder.
folder = getOuterMostWorkspaceFolder(folder);

await addWorkspaceClient(folder);
registerWorkspaceCommands(folder);

configHandler = mutex(folder.uri.toString(), async (e: ConfigurationChangeEvent) => {
if (e.affectsConfiguration('noir.nargoFlags', folder.uri)) {
disposeWorkspaceCommands(folder);
await removeWorkspaceClient(folder);
await addWorkspaceClient(folder);
registerWorkspaceCommands(folder);
}

if (e.affectsConfiguration('noir.nargoPath', folder.uri)) {
disposeWorkspaceCommands(folder);
await removeWorkspaceClient(folder);
await addWorkspaceClient(folder);
registerWorkspaceCommands(folder);
try {
if (folder) {
// If we have nested workspace folders we only start a server on the outer most workspace folder.
folder = getOuterMostWorkspaceFolder(folder);

await addWorkspaceClient(folder);

const currentLspClient = lspClients.get(folder.uri.toString());
const statusBarItem = getNoirStatusBarItem();
statusBarItem.tooltip = currentLspClient.command;
statusBarItem.show();

registerWorkspaceCommands(folder);

configHandler = mutex(folder.uri.toString(), async (e: ConfigurationChangeEvent) => {
if (e.affectsConfiguration('noir.nargoFlags', folder.uri)) {
disposeWorkspaceCommands(folder);
await removeWorkspaceClient(folder);
await addWorkspaceClient(folder);
registerWorkspaceCommands(folder);
}

if (e.affectsConfiguration('noir.nargoPath', folder.uri)) {
disposeWorkspaceCommands(folder);
await removeWorkspaceClient(folder);
await addWorkspaceClient(folder);
registerWorkspaceCommands(folder);
}

if (e.affectsConfiguration('noir.enableLSP', folder.uri)) {
await removeWorkspaceClient(folder);
await addWorkspaceClient(folder);
}
});
} else {
// We only want to handle `file:` and `untitled:` schemes because
// vscode sends `output:` schemes for markdown responses from our LSP
if (uri.scheme !== 'file' && uri.scheme !== 'untitled') {
return Disposable.from();
}

if (e.affectsConfiguration('noir.enableLSP', folder.uri)) {
await removeWorkspaceClient(folder);
await addWorkspaceClient(folder);
}
});
} else {
// We only want to handle `file:` and `untitled:` schemes because
// vscode sends `output:` schemes for markdown responses from our LSP
if (uri.scheme !== 'file' && uri.scheme !== 'untitled') {
return Disposable.from();
// Each file outside of a workspace gets it's own client
await addFileClient(uri);
registerFileCommands(uri);

configHandler = mutex(uri.toString(), async (e: ConfigurationChangeEvent) => {
if (e.affectsConfiguration('noir.nargoFlags', uri)) {
disposeFileCommands(uri);
await removeFileClient(uri);
await addFileClient(uri);
registerFileCommands(uri);
}

if (e.affectsConfiguration('noir.nargoPath', uri)) {
disposeFileCommands(uri);
await removeFileClient(uri);
await addFileClient(uri);
registerFileCommands(uri);
}

if (e.affectsConfiguration('noir.enableLSP', uri)) {
await removeFileClient(uri);
await addFileClient(uri);
}
});
}

// Each file outside of a workspace gets it's own client
await addFileClient(uri);
registerFileCommands(uri);

configHandler = mutex(uri.toString(), async (e: ConfigurationChangeEvent) => {
if (e.affectsConfiguration('noir.nargoFlags', uri)) {
disposeFileCommands(uri);
await removeFileClient(uri);
await addFileClient(uri);
registerFileCommands(uri);
}

if (e.affectsConfiguration('noir.nargoPath', uri)) {
disposeFileCommands(uri);
await removeFileClient(uri);
await addFileClient(uri);
registerFileCommands(uri);
}

if (e.affectsConfiguration('noir.enableLSP', uri)) {
await removeFileClient(uri);
await addFileClient(uri);
}
});
return workspace.onDidChangeConfiguration(configHandler);
} catch (e) {
handleClientStartError(e);
}

return workspace.onDidChangeConfiguration(configHandler);
}

async function didChangeWorkspaceFolders(event: WorkspaceFoldersChangeEvent) {
Expand Down
62 changes: 56 additions & 6 deletions src/find-nargo.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,66 @@
import os from 'os';
import path from 'path';
import fs from 'fs';
import which from 'which';
import { NargoNotFoundError } from './noir';
import { MarkdownString } from 'vscode';

const nargoBinaries = ['nargo'];
// List of possible nargo binaries to find on Path
// We prioritize 'nargo' as the more standard version.
const NARGO_BINARIES = ['nargo', 'aztec-nargo'];
// List of possible default installations in users folder
const NARGO_INSTALL_LOCATION_POSTFIXES = ['.nargo/bin/nargo', '.aztec/bin/aztec-nargo'];

export default function findNargo() {
for (const bin of nargoBinaries) {
function absoluteInstallLocationPaths(homeDir: string): string[] {
return NARGO_INSTALL_LOCATION_POSTFIXES.map((postfix) => path.join(homeDir, postfix)).filter((filePath) =>
fs.existsSync(filePath),
);
}

export function findNargoBinaries(homeDir: string): string[] {
// Note that JS sets maintain insertion order.
const nargoBinaryPaths: Set<string> = new Set();

for (const bin of NARGO_BINARIES) {
try {
const nargo = which.sync(bin);
const path = which.sync(bin);
// If it didn't throw, we found a nargo binary
return nargo;
nargoBinaryPaths.add(path);
} catch (err) {
// Not found
}
}
throw new Error('Unable to locate any nargo binary. Did you install it?');

// So far we have not found installations on path
// Let's check default installation locations
for (const filePath of absoluteInstallLocationPaths(homeDir)) {
nargoBinaryPaths.add(filePath);
}

return [...nargoBinaryPaths];
}

export default function findNargo() {
const homeDir = os.homedir();
const nargoBinaryPaths = findNargoBinaries(homeDir);

if (nargoBinaryPaths.length > 0) {
return nargoBinaryPaths[0];
} else {
const message = new MarkdownString();
message.appendText(`Could not locate any of\n`);
for (const nargoBinary of NARGO_BINARIES) {
message.appendMarkdown(`\`${nargoBinary}\``);
message.appendText(`\n`);
}

message.appendText(`on \`$PATH\`, or one of default installation locations\n`);
for (const postfix of NARGO_INSTALL_LOCATION_POSTFIXES) {
const filePath = path.join(homeDir, postfix);
message.appendMarkdown(`\`${filePath}\``);
message.appendText(`\n`);
}

throw new NargoNotFoundError(message);
}
}
41 changes: 41 additions & 0 deletions src/noir.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
import { MarkdownString, StatusBarAlignment, StatusBarItem, ThemeColor, window } from 'vscode';
import { EditorLineDecorationManager } from './EditorLineDecorationManager';
import Client from './client';

export const lspClients: Map<string, Client> = new Map();

export const editorLineDecorationManager = new EditorLineDecorationManager(lspClients);

let noirStatusBarItem: StatusBarItem;

export function getNoirStatusBarItem() {
if (noirStatusBarItem) {
return noirStatusBarItem;
}
// Create Nargo Status bar item, we only need one for lifetime of plugin
// we will show/update it depending on file user is working with
noirStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 100);
noirStatusBarItem.text = 'Nargo';
noirStatusBarItem.command = 'nargo.config.path.select';

return noirStatusBarItem;
}

export class NargoNotFoundError extends Error {
constructor(markdownMessage?: MarkdownString) {
super(markdownMessage.value);

this.markdownMessage = markdownMessage;
}

markdownMessage: MarkdownString;
}

export function handleClientStartError(err: Error) {
if (err instanceof NargoNotFoundError) {
const backgroundColor = new ThemeColor('statusBarItem.errorBackground');
const foregroundColor = new ThemeColor('statusBarItem.errorForeground');

const statusBarItem = getNoirStatusBarItem();
statusBarItem.backgroundColor = backgroundColor;
statusBarItem.color = foregroundColor;
statusBarItem.tooltip = err.markdownMessage;
statusBarItem.show();
} else {
throw err;
}
}

0 comments on commit 0be02a5

Please sign in to comment.