import { NxWorkspace } from '@nx-console/shared-types';
import { GlobalConfigurationStore } from '@nx-console/vscode-configuration';
import { onWorkspaceRefreshed } from '@nx-console/vscode-lsp-client';
import {
getNxWorkspace,
getProjectFolderTree,
} from '@nx-console/vscode-nx-workspace';
import {
CliTaskProvider,
selectRunInformationAndRun,
} from '@nx-console/vscode-tasks';
import { getTelemetry } from '@nx-console/vscode-telemetry';
import {
AbstractTreeProvider,
createProjectTargetString,
surroundWithQuotesIfNeeded,
} from '@nx-console/vscode-utils';
import { commands, env, ExtensionContext } from 'vscode';
import { NxTreeItem } from './nx-tree-item';
import { TargetViewItem } from './views/nx-project-base-view';
import { ListView, ListViewItem } from './views/nx-project-list-view';
import { TreeView, TreeViewItem } from './views/nx-project-tree-view';
import {
NxConsolePluginsDefinition,
ProjectViewTreeItem,
loadPlugins,
} from '@nx-console/shared-nx-console-plugins';
import { getNxWorkspacePath } from '@nx-console/vscode-configuration';
export type ViewItem = ListViewItem | TreeViewItem;
interface NxOptionalFlags {
skipNxCache: boolean;
}
/**
* Provides data for the "Projects" tree view
*/
export class NxProjectTreeProvider extends AbstractTreeProvider<NxTreeItem> {
private readonly listView: ListView = new ListView();
private readonly treeView: TreeView = new TreeView();
private workspaceData: NxWorkspace | undefined = undefined;
private plugins: NxConsolePluginsDefinition | undefined = undefined;
constructor(context: ExtensionContext) {
super();
(
[
['revealInExplorer', this.revealInExplorer],
['run-task-projects-view', this.runTask],
['run-task-projects-view-skip-cache', this.runTaskSkipNxCache],
['copyTaskToClipboard', this.copyTaskToClipboard],
['run-task-projects-view-options', this.runTaskWithOptions],
] as const
).forEach(([commandSuffix, callback]) => {
context.subscriptions.push(
commands.registerCommand(`nxConsole.${commandSuffix}`, callback, this),
);
});
GlobalConfigurationStore.instance.onConfigurationChange(() =>
this.refresh(),
);
onWorkspaceRefreshed(() => this.refresh());
}
getParent() {
// not implemented, because the reveal API is not needed for the projects view
return null;
}
async getChildren(element?: NxTreeItem): Promise<NxTreeItem[] | undefined> {
if (!element) {
this.workspaceData = await getNxWorkspace();
this.plugins = await loadPlugins(getNxWorkspacePath());
this.treeView.workspaceData = this.workspaceData;
this.listView.workspaceData = this.workspaceData;
}
let items: (TreeViewItem | ListViewItem)[] | undefined;
if (this.shouldUseTreeView()) {
if (!element) {
const projectFolderTree = await getProjectFolderTree();
if (!projectFolderTree) {
return;
}
const { treeMap, roots } = projectFolderTree;
this.treeView.treeMap = treeMap;
this.treeView.roots = roots;
}
items = await this.treeView.getChildren(element?.item as TreeViewItem);
} else {
items = await this.listView.getChildren(element?.item as ListViewItem);
}
if (!items) return;
if (this.plugins.projectViewItemProcessors) {
this.plugins.projectViewItemProcessors.forEach((processor) => {
items = items.map((item) => {
const processed = processor(
item as ProjectViewTreeItem,
this.workspaceData,
);
item.label = processed.label ?? item.label;
item.description = processed.description ?? item.description;
item.tooltip = processed.tooltip ?? item.tooltip;
item.iconPath = processed.iconPath ?? item.iconPath;
item.collapsible = processed.collapsible ?? item.collapsible;
return item;
});
});
}
return items.map((item) => new NxTreeItem(item));
}
private shouldUseTreeView() {
const config = GlobalConfigurationStore.instance.get('projectViewingStyle');
if (config === 'tree') {
return true;
}
if (config === 'list') {
return false;
}
if (!this.workspaceData) {
return true;
}
return Object.keys(this.workspaceData.projectGraph.nodes).length > 10;
}
private async runTask(
selection: NxTreeItem | undefined,
optionalFlags?: NxOptionalFlags,
) {
getTelemetry().logUsage('tasks.run', {
source: 'projects-view',
});
const viewItem = selection?.item;
if (
!viewItem ||
viewItem.contextValue === 'project' ||
viewItem.contextValue === 'folder' ||
viewItem.contextValue === 'targetGroup' ||
viewItem.contextValue === 'projectGraphError'
) {
// can not run a task on a project, folder or target group
return;
}
const { project } = viewItem.nxProject;
const target = viewItem.nxTarget;
const flags = [];
if (target.configuration) {
flags.push(`--configuration=${target.configuration}`);
}
if (optionalFlags?.skipNxCache) {
flags.push('--skip-nx-cache');
}
CliTaskProvider.instance.executeTask({
command: 'run',
positional: createProjectTargetString(project, target.name),
flags,
});
}
private async runTaskSkipNxCache(selection: NxTreeItem | undefined) {
this.runTask(selection, { skipNxCache: true });
}
private async runTaskWithOptions(selection: NxTreeItem | undefined) {
getTelemetry().logUsage('tasks.run', {
source: 'projects-view',
});
const item = selection?.item as TargetViewItem;
const project = item?.nxProject.project;
const target = item?.nxTarget.name;
const configuration = item?.nxTarget.configuration;
selectRunInformationAndRun(project, target, configuration, true);
}
private parseTreeItemId(
id: string,
): { project: string; target: string; configuration?: string } | null {
if (id.startsWith('target$$')) {
const [, project, target] = id.split('$$');
return { project, target };
}
if (id.startsWith('config$$')) {
const [, project, target, configuration] = id.split('$$');
return { project, target, configuration };
}
return null;
}
private async copyTaskToClipboard(selection: NxTreeItem | undefined) {
getTelemetry().logUsage('tasks.copy-to-clipboard');
if (!selection) {
return;
}
const parsed = this.parseTreeItemId(selection.id);
if (!parsed) {
env.clipboard.writeText(
`nx run ${surroundWithQuotesIfNeeded(selection.id)}`,
);
return;
}
let command = `${parsed.project}:${parsed.target}`;
if (parsed.configuration) {
command += `:${parsed.configuration}`;
}
env.clipboard.writeText(`nx run ${surroundWithQuotesIfNeeded(command)}`);
}
private async revealInExplorer(selection: NxTreeItem | undefined) {
if (!selection) {
return;
}
if (selection.resourceUri) {
getTelemetry().logUsage('misc.show-project-configuration');
commands.executeCommand('revealInExplorer', selection.resourceUri);
}
}
}