* move code, switch to flat targets list, lots still TODO

* more efficient update

* update to match VS more, still need to do references

* fix getTreeItem for ReferenceNode

* fix id

* loc

* fix collapse/expand, and no backtrace

* avoid adding isGeneratorProvided targets

* update changelog

* remove unused
This commit is contained in:
Garrett Campbell 2024-04-17 08:59:17 -04:00 коммит произвёл GitHub
Родитель 548be9ae0b
Коммит 6b6c07bb90
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 234 добавлений и 60 удалений

Просмотреть файл

@ -13,6 +13,7 @@ Improvements:
- Add option to disable kit scan by default when a kit isn't selected. [#1461](https://github.com/microsoft/vscode-cmake-tools/issues/1461)
- Show cmake output when version probe fails. [#3650](https://github.com/microsoft/vscode-cmake-tools/issues/3650)
- Improve various settings scopes [#3601](https://github.com/microsoft/vscode-cmake-tools/issues/3601)
- Refactor the Project Outline view to show a flat list of targets [#491](https://github.com/microsoft/vscode-cmake-tools/issues/491), [#3684](https://github.com/microsoft/vscode-cmake-tools/issues/3684)
- Add the ability to pin CMake Commands to the sidebar [#2984](https://github.com/microsoft/vscode-cmake-tools/issues/2984) & [#3296](https://github.com/microsoft/vscode-cmake-tools/issues/3296)
Bug Fixes:

Просмотреть файл

@ -149,6 +149,15 @@ export namespace CodeModelKind {
isGenerated?: boolean;
}
export interface Dependency {
backtrace: number;
id: string;
}
export interface Folder {
name: string;
}
export interface TargetObject {
name: string;
type: string;
@ -157,6 +166,9 @@ export namespace CodeModelKind {
paths: PathInfo;
sources: TargetSourcefile[];
compileGroups?: CompileGroup[];
dependencies?: Dependency[];
folder?: Folder;
isGeneratorProvided?: boolean;
}
}
@ -490,7 +502,10 @@ async function loadCodeModelTarget(rootPaths: CodeModelKind.PathInfo, jsonFile:
a => convertToAbsolutePath(path.join(targetObject.paths.build, a.path), rootPaths.build))
: [],
fileGroups,
sysroot
sysroot,
folder: targetObject.folder,
dependencies: targetObject.dependencies,
isGeneratorProvided: targetObject.isGeneratorProvided
} as CodeModelTarget;
}

Просмотреть файл

@ -10,7 +10,8 @@ export type CodeModelContent = api.CodeModel.Content;
// TODO: Move framework definitions to the public API repo to avoid this intersection type.
export type CodeModelFileGroup = api.CodeModel.FileGroup & { frameworks?: { path: string }[] };
export type CodeModelProject = api.CodeModel.Project;
export type CodeModelTarget = api.CodeModel.Target;
// TODO: If requested, move folder, dependencies, and isGeneratorProvided definition to the public API repo to avoid this intersection type.
export type CodeModelTarget = api.CodeModel.Target & { folder?: { name: string }; dependencies?: { backtrace: number; id: string }[]; isGeneratorProvided?: boolean};
export type CodeModelToolchain = api.CodeModel.Toolchain;
export type TargetTypeString = api.CodeModel.TargetType;

Просмотреть файл

@ -31,7 +31,7 @@ import rollbar from '@cmt/rollbar';
import { StateManager } from './state';
import { cmakeTaskProvider, CMakeTaskProvider } from '@cmt/cmakeTaskProvider';
import * as telemetry from '@cmt/telemetry';
import { ProjectOutline, ProjectNode, TargetNode, SourceFileNode, WorkspaceFolderNode } from '@cmt/projectOutline';
import { ProjectOutline, ProjectNode, TargetNode, SourceFileNode, WorkspaceFolderNode } from '@cmt/projectOutline/projectOutline';
import * as util from '@cmt/util';
import { ProgressHandle, DummyDisposable, reportProgress, runCommand } from '@cmt/util';
import { DEFAULT_VARIANTS } from '@cmt/variant';

Просмотреть файл

@ -5,6 +5,9 @@ import * as codeModel from '@cmt/drivers/codeModel';
import rollbar from '@cmt/rollbar';
import { lexicographicalCompare, splitPath } from '@cmt/util';
import CMakeProject from '@cmt/cmakeProject';
import { populateViewCodeModel } from './targetsViewCodeModel';
import { fs } from '@cmt/pr';
import { CodeModelKind } from '@cmt/drivers/cmakeFileApi';
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
@ -116,23 +119,6 @@ function iconForTargetType(type: codeModel.TargetTypeString): string {
}
}
function sortStringForType(type: codeModel.TargetTypeString): string {
switch (type) {
case 'EXECUTABLE':
return 'aaa';
case 'MODULE_LIBRARY':
case 'SHARED_LIBRARY':
case 'STATIC_LIBRARY':
return 'baa';
case 'UTILITY':
return 'caa';
case 'OBJECT_LIBRARY':
return 'daa';
case 'INTERFACE_LIBRARY':
return 'eaa';
}
}
export class DirectoryNode<Node extends BaseNode> extends BaseNode {
constructor(readonly prefix: string, readonly parent: string, readonly pathPart: string) {
super(`${prefix}${path.sep}${path.normalize(pathPart)}`);
@ -247,6 +233,76 @@ export class SourceFileNode extends BaseNode {
}
}
export class ReferencesNode extends BaseNode {
constructor(targetId: string) {
super(`${targetId}-references`);
}
get name() {
return this.id;
}
private _references = new Map<string, ReferenceNode>();
getChildren(): BaseNode[] {
return [...this._references.values()];
}
getTreeItem(): vscode.TreeItem {
const item = new vscode.TreeItem(this.id);
item.id = this.id;
if (this.getChildren().length) {
item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
}
item.label = localize('references', 'References');
item.contextValue = ['nodeType=references', `compilable=${false}`, `cmakelists=${false}`].join(',');
item.iconPath = new vscode.ThemeIcon('references');
return item;
}
getOrderTuple(): string[] {
return [this.id];
}
update(dependencies: CodeModelKind.Dependency[], targetId: string) {
const new_refs = new Map<string, ReferenceNode>();
for (const ref of dependencies) {
// filter out dependecies that are found and don't have a defined backtrace
if (ref.backtrace !== undefined) {
new_refs.set(ref.id, new ReferenceNode(ref.id, targetId));
}
}
this._references = new_refs;
}
}
export class ReferenceNode extends BaseNode {
constructor(id: string = "", parentTargetId: string) {
const name = id.split("::")[0];
super(`${name}-${parentTargetId}`);
this.name = name;
}
readonly name: string;
getChildren(): BaseNode[] {
return [];
}
getTreeItem(): vscode.TreeItem {
const item = new vscode.TreeItem(this.id);
item.id = this.id;
item.label = this.name;
item.contextValue = [
"nodeType=reference",
`compilable=${false}`,
`cmakelists=${false}`
].join(",");
item.iconPath = new vscode.ThemeIcon("references");
return item;
}
getOrderTuple(): string[] {
return [this.id];
}
}
export class TargetNode extends BaseNode {
constructor(readonly prefix: string, readonly projectName: string, cm: codeModel.CodeModelTarget, readonly folder: vscode.WorkspaceFolder) {
// id: {prefix}::target_name:artifact_name:target_path
@ -265,13 +321,14 @@ export class TargetNode extends BaseNode {
private _fsPath: string = '';
getOrderTuple() {
return [sortStringForType(this._type), this.name];
return [this.name];
}
private readonly _rootDir: DirectoryNode<SourceFileNode>;
private readonly _referencesNode = new ReferencesNode(this.id);
getChildren() {
return this._rootDir.getChildren();
return [this._referencesNode, ...this._rootDir.getChildren()];
}
getTreeItem() {
try {
@ -285,16 +342,23 @@ export class TargetNode extends BaseNode {
if (this._isLaunch) {
item.label += ' 🚀';
}
if (this._fullName !== this.name && this._fullName) {
item.label += ` [${this._fullName}]`;
}
if (this._type === 'INTERFACE_LIBRARY') {
item.label += ` ${localize('interface.library', 'Interface library')}`;
} else if (this._type === 'UTILITY') {
item.label += ` ${localize('utility', 'Utility')}`;
if (this._type === "STATIC_LIBRARY") {
item.label += ` (${localize('static.library', 'Static library')})`;
} else if (this._type === "MODULE_LIBRARY") {
item.label += ` (${localize('module.library', 'Module library')})`;
} else if (this._type === "SHARED_LIBRARY") {
item.label += ` (${localize('shared.library', 'Shared library')})`;
} else if (this._type === 'OBJECT_LIBRARY') {
item.label += `${localize('object.library', 'Object library')}`;
item.label += ` (${localize('object.library', 'Object library')})`;
} else if (this._type === "EXECUTABLE") {
item.label += ` (${localize('executable', 'Executable')})`;
} else if (this._type === 'UTILITY') {
item.label += ` (${localize('utility', 'Utility')})`;
} else if (this._type === 'INTERFACE_LIBRARY') {
item.label += ` (${localize('interface.library', 'Interface library')})`;
}
item.resourceUri = vscode.Uri.file(this._fsPath);
item.tooltip = localize('target.tooltip', 'Target {0}', this.name);
if (this._isLaunch) {
@ -355,13 +419,15 @@ export class TargetNode extends BaseNode {
};
for (const grp of cm.fileGroups || []) {
for (let src of grp.sources) {
if (!path.isAbsolute(src)) {
src = path.join(this.sourceDir, src);
if (!grp.isGenerated) {
for (let src of grp.sources) {
if (!path.isAbsolute(src)) {
src = path.join(this.sourceDir, src);
}
const src_dir = path.dirname(src);
const relpath = path.relative(this.sourceDir, src_dir);
addToTree(tree, relpath, new SourceFileNode(this.id, this.folder, this.sourceDir, src, grp.language));
}
const src_dir = path.dirname(src);
const relpath = path.relative(this.sourceDir, src_dir);
addToTree(tree, relpath, new SourceFileNode(this.id, this.folder, this.sourceDir, src, grp.language));
}
}
@ -375,6 +441,8 @@ export class TargetNode extends BaseNode {
update: (_src, _cm) => {},
create: newNode => newNode
});
this._referencesNode.update(cm.dependencies || [], this.id);
}
async openInCMakeLists() {
@ -393,56 +461,103 @@ export class TargetNode extends BaseNode {
}
export class ProjectNode extends BaseNode {
constructor(readonly name: string, readonly folder: vscode.WorkspaceFolder, readonly sourceDirectory: string) {
constructor(
readonly name: string,
readonly folder: vscode.WorkspaceFolder,
readonly sourceDirectory: string
) {
// id: project_name:project_directory
super(`${name}:${sourceDirectory}`);
}
private readonly _rootDir = new DirectoryNode<TargetNode>(this.id, '', '');
private sortProjectChildren(children: BaseNode[]): BaseNode[] {
return children.sort((a, b) => {
if (a instanceof TargetNode && b instanceof TargetNode) {
return lexicographicalCompare(a.getOrderTuple(), b.getOrderTuple());
} else if (a instanceof TargetNode && b instanceof DirectoryNode) {
return -1;
} else if (a instanceof DirectoryNode && b instanceof TargetNode) {
return 1;
}
return 0;
});
}
getOrderTuple() {
return [this.sourceDirectory, this.name];
}
getChildren() {
return this._rootDir.getChildren();
const children: BaseNode[] = this.sortProjectChildren(this._rootDir.getChildren());
const cmakelists = new SourceFileNode(this.id, this.folder, this.sourceDirectory, path.join(this.sourceDirectory, 'CMakeLists.txt'));
children.push(cmakelists);
const possiblePreset = path.join(this.sourceDirectory, 'CMakePresets.json');
if (fs.existsSync(possiblePreset)) {
children.push(new SourceFileNode(this.id, this.folder, this.sourceDirectory, possiblePreset));
}
return children;
}
getTreeItem() {
const item = new vscode.TreeItem(this.name, vscode.TreeItemCollapsibleState.Expanded);
const item = new vscode.TreeItem(
this.name,
vscode.TreeItemCollapsibleState.Expanded
);
if (this.getChildren().length === 0) {
item.label += ` — (${localize('empty.project', 'Empty project')})`;
item.label += ` — (${localize("empty.project", "Empty project")})`;
}
item.tooltip = `${this.name}\n${this.sourceDirectory}`;
item.contextValue = 'nodeType=project';
item.contextValue = "nodeType=project";
return item;
}
update(pr: codeModel.CodeModelProject, ctx: TreeUpdateContext) {
// TODO: Update. We need to
if (pr.name !== this.name) {
rollbar.error(localize('update.project.with.mismatch', 'Update project with mismatching name property'), { newName: pr.name, oldName: this.name });
rollbar.error(
localize(
"update.project.with.mismatch",
"Update project with mismatching name property"
),
{ newName: pr.name, oldName: this.name }
);
}
const tree: PathedTree<codeModel.CodeModelTarget> = {
pathPart: '',
children: [],
items: []
items: [],
children: []
};
for (const target of pr.targets) {
const srcdir = target.sourceDirectory || '';
const relpath = path.relative(pr.sourceDirectory, srcdir);
addToTree(tree, relpath, target);
for (const t of pr.targets) {
const target = t as codeModel.CodeModelTarget;
// Skip targets that the generator auto-created.
if (target.isGeneratorProvided) {
continue;
}
if (target.folder) {
addToTree(tree, target.folder.name, target);
} else {
addToTree(tree, '', target);
}
}
collapseTreeInplace(tree);
this._rootDir.update({
tree,
context: ctx,
update: (tgt, cm) => tgt.update(cm, ctx),
create: cm => {
const node = new TargetNode(this.id, this.name, cm, this.folder);
node.update(cm, ctx);
create: newTgt => {
const node = new TargetNode(this.id, this.name, newTgt, this.folder);
node.update(newTgt, ctx);
return node;
}
});
@ -507,14 +622,20 @@ export class WorkspaceFolderNode extends BaseNode {
return;
}
for (const modelProj of model.configurations[0].projects) {
let item = this.getNode(cmakeProject, modelProj.name);
if (!item) {
item = new ProjectNode(modelProj.name, this.wsFolder, cmakeProject.folderPath);
this.setNode(cmakeProject, modelProj.name, item);
}
item.update(modelProj, ctx);
if (model.configurations[0].projects.length === 0) {
this.removeNodes(cmakeProject);
ctx.nodesToUpdate.push(this);
return;
}
const projectOutlineModel = populateViewCodeModel(model);
const rootProject = projectOutlineModel.project;
let item = this.getNode(cmakeProject, rootProject.name);
if (!item) {
item = new ProjectNode(rootProject.name, this.wsFolder, cmakeProject.folderPath);
this.setNode(cmakeProject, rootProject.name, item);
}
item.update(rootProject, ctx);
}
getChildren() {

Просмотреть файл

@ -0,0 +1,35 @@
import { CodeModelContent } from "@cmt/drivers/codeModel";
import { CodeModel } from "vscode-cmake-tools";
interface ProjectOutlineCodeModel {
project: CodeModel.Project;
}
/**
* Construct and populate the view model for the Project Outline view.
* We are constructing a flat list of all of the targets in the project.
* @param model The code model from the CMake FileAPI.
*/
export function populateViewCodeModel(model: CodeModelContent): ProjectOutlineCodeModel {
const configuration = model.configurations[0];
// The first project in the list is the root project.
const originalProject = configuration.projects[0];
// Flatten the list of targets into a single list.
const targets: CodeModel.Target[] = [];
for (const projects of configuration.projects) {
for (const t of projects.targets) {
targets.push(t);
}
}
// Construct the new project object. Everything will be the same except for the newly constructed flat list of targets.
const project: CodeModel.Project = {
name: originalProject.name,
targets: targets,
sourceDirectory: originalProject.sourceDirectory,
hasInstallRule: originalProject.hasInstallRule
};
return { project };
}

Просмотреть файл

@ -17,7 +17,8 @@
"experimentalDecorators": true,
"noUnusedParameters": true,
"noImplicitThis": true,
"skipLibCheck": true
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": [
"node_modules",