Skip to content

Add implementations for ctags rebuildOnStart and rebuildOnFileSave #8889

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/793.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add implementations for `python.workspaceSymbols.rebuildOnStart` and `python.workspaceSymbols.rebuildOnFileSave`.
29 changes: 27 additions & 2 deletions src/client/workspaceSymbols/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CancellationToken, Disposable, languages, OutputChannel } from 'vscode';
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types';
import { CancellationToken, Disposable, languages, OutputChannel, TextDocument } from 'vscode';
import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types';
import { Commands, STANDARD_OUTPUT_CHANNEL } from '../common/constants';
import { isNotInstalledError } from '../common/helpers';
import { IFileSystem } from '../common/platform/types';
Expand All @@ -23,6 +23,7 @@ export class WorkspaceSymbols implements Disposable {
private processFactory: IProcessServiceFactory;
private appShell: IApplicationShell;
private configurationService: IConfigurationService;
private documents: IDocumentManager;

constructor(private serviceContainer: IServiceContainer) {
this.outputChannel = this.serviceContainer.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
Expand All @@ -32,12 +33,15 @@ export class WorkspaceSymbols implements Disposable {
this.processFactory = this.serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory);
this.appShell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
this.configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
this.documents = this.serviceContainer.get<IDocumentManager>(IDocumentManager);
this.disposables = [];
this.disposables.push(this.outputChannel);
this.registerCommands();
this.initializeGenerators();
languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this.fs, this.commandMgr, this.generators));
this.disposables.push(this.workspace.onDidChangeWorkspaceFolders(() => this.initializeGenerators()));
this.disposables.push(this.documents.onDidSaveTextDocument(e => this.onDocumentSaved(e)));
this.buildSymbolsOnStart();
}
public dispose() {
this.disposables.forEach(d => d.dispose());
Expand All @@ -55,6 +59,18 @@ export class WorkspaceSymbols implements Disposable {
}
}

private buildSymbolsOnStart() {
if (Array.isArray(this.workspace.workspaceFolders)) {
this.workspace.workspaceFolders.forEach(workspaceFolder => {
const pythonSettings = this.configurationService.getSettings(workspaceFolder.uri);
if (pythonSettings.workspaceSymbols.rebuildOnStart) {
const promises = this.buildWorkspaceSymbols(true);
return Promise.all(promises);
}
});
}
}

private registerCommands() {
this.disposables.push(
this.commandMgr.registerCommand(
Expand All @@ -65,6 +81,15 @@ export class WorkspaceSymbols implements Disposable {
}));
}

private onDocumentSaved(document: TextDocument) {
const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri);
const pythonSettings = this.configurationService.getSettings(workspaceFolder?.uri);
if (pythonSettings.workspaceSymbols.rebuildOnFileSave) {
const promises = this.buildWorkspaceSymbols(true);
return Promise.all(promises);
}
}

// tslint:disable-next-line:no-any
private buildWorkspaceSymbols(rebuild: boolean = true, token?: CancellationToken): Promise<any>[] {
if (token && token.isCancellationRequested) {
Expand Down
176 changes: 176 additions & 0 deletions src/test/workspaceSymbols/main.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import * as assert from 'assert';
import { use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import { anyString, anything, instance, mock, when } from 'ts-mockito';
import { EventEmitter, TextDocument, Uri, WorkspaceFolder } from 'vscode';
import { ApplicationShell } from '../../client/common/application/applicationShell';
import { CommandManager } from '../../client/common/application/commandManager';
import { DocumentManager } from '../../client/common/application/documentManager';
import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../../client/common/application/types';
import { WorkspaceService } from '../../client/common/application/workspace';
import { ConfigurationService } from '../../client/common/configuration/service';
import { STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants';
import { FileSystem } from '../../client/common/platform/fileSystem';
import { IFileSystem } from '../../client/common/platform/types';
import { ProcessService } from '../../client/common/process/proc';
import { ProcessServiceFactory } from '../../client/common/process/processFactory';
import { IProcessService, IProcessServiceFactory, Output } from '../../client/common/process/types';
import { IConfigurationService, IOutputChannel } from '../../client/common/types';
import { sleep } from '../../client/common/utils/async';
import { ServiceContainer } from '../../client/ioc/container';
import { IServiceContainer } from '../../client/ioc/types';
import { WorkspaceSymbols } from '../../client/workspaceSymbols/main';
import { MockOutputChannel } from '../mockClasses';

use(chaiAsPromised);

// tslint:disable: no-any
// tslint:disable-next-line: max-func-body-length
suite('Workspace symbols main', () => {
const mockDisposable = { dispose: () => { return; } };
const ctagsPath = 'CTAG_PATH';
const observable = {
out: {
subscribe: (cb: (out: Output<string>) => void, _errorCb: any, done: Function) => {
cb({ source: 'stdout', out: '' });
done();
}
}
};

let outputChannel: IOutputChannel;
let commandManager: ICommandManager;
let fileSystem: IFileSystem;
let workspaceService: IWorkspaceService;
let processServiceFactory: IProcessServiceFactory;
let processService: IProcessService;
let applicationShell: IApplicationShell;
let configurationService: IConfigurationService;
let documentManager: IDocumentManager;
let serviceContainer: IServiceContainer;
let workspaceFolders: WorkspaceFolder[];
let workspaceSymbols: WorkspaceSymbols;
let shellOutput: string;
let eventEmitter: EventEmitter<TextDocument>;

setup(() => {
eventEmitter = new EventEmitter<TextDocument>();
shellOutput = '';
workspaceFolders = [{ name: 'root', index: 0, uri: Uri.file('folder') }];

outputChannel = mock(MockOutputChannel);
commandManager = mock(CommandManager);
fileSystem = mock(FileSystem);
workspaceService = mock(WorkspaceService);
processServiceFactory = mock(ProcessServiceFactory);
processService = mock(ProcessService);
applicationShell = mock(ApplicationShell);
configurationService = mock(ConfigurationService);
documentManager = mock(DocumentManager);
serviceContainer = mock(ServiceContainer);

when(workspaceService.onDidChangeWorkspaceFolders).thenReturn(() => mockDisposable as any);
when(documentManager.onDidSaveTextDocument).thenReturn(eventEmitter.event);
when(commandManager.registerCommand(anything(), anything())).thenReturn(mockDisposable as any);
when(fileSystem.directoryExists(anything())).thenResolve(true);
when(fileSystem.fileExists(anything())).thenResolve(false);
when(processServiceFactory.create()).thenResolve(instance(processService));
when(processService.execObservable(ctagsPath, anything(), anything()))
.thenReturn(observable as any);
when(applicationShell.setStatusBarMessage(anyString(), anything())).thenCall((text: string) => {
shellOutput += text;
return mockDisposable;
});

when(serviceContainer.get<IOutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL)).thenReturn(instance(outputChannel));
when(serviceContainer.get<ICommandManager>(ICommandManager)).thenReturn(instance(commandManager));
when(serviceContainer.get<IFileSystem>(IFileSystem)).thenReturn(instance(fileSystem));
when(serviceContainer.get<IWorkspaceService>(IWorkspaceService)).thenReturn(instance(workspaceService));
when(serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory)).thenReturn(instance(processServiceFactory));
when(serviceContainer.get<IApplicationShell>(IApplicationShell)).thenReturn(instance(applicationShell));
when(serviceContainer.get<IConfigurationService>(IConfigurationService)).thenReturn(instance(configurationService));
when(serviceContainer.get<IDocumentManager>(IDocumentManager)).thenReturn(instance(documentManager));
});

teardown(() => {
workspaceSymbols.dispose();
});

test('Should not rebuild on start if the setting is disabled', () => {
when(workspaceService.workspaceFolders).thenReturn(workspaceFolders);
when(configurationService.getSettings(anything())).thenReturn({ workspaceSymbols: { rebuildOnStart: false } } as any);

workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));

assert.equal(shellOutput, '');
});

test('Should not rebuild on start if we don\'t have a workspace folder', () => {
when(workspaceService.workspaceFolders).thenReturn([]);
when(configurationService.getSettings(anything())).thenReturn({ workspaceSymbols: { rebuildOnStart: false } } as any);

workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));

assert.equal(shellOutput, '');
});

test('Should rebuild on start if the setting is enabled and we have a workspace folder', async () => {
when(workspaceService.workspaceFolders).thenReturn(workspaceFolders);
when(configurationService.getSettings(anything())).thenReturn({
workspaceSymbols: {
ctagsPath,
enabled: true,
exclusionPatterns: [],
rebuildOnStart: true,
tagFilePath: 'foo'
}
} as any);

workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));
await sleep(1);

assert.equal(shellOutput, 'Generating Tags');
});

test('Should rebuild on save if the setting is enabled', async () => {
when(workspaceService.workspaceFolders).thenReturn(workspaceFolders);
when(workspaceService.getWorkspaceFolder(anything())).thenReturn(workspaceFolders[0]);
when(configurationService.getSettings(anything())).thenReturn({
workspaceSymbols: {
ctagsPath,
enabled: true,
exclusionPatterns: [],
rebuildOnFileSave: true,
tagFilePath: 'foo'
}
} as any);

workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));
eventEmitter.fire({ uri: Uri.file('folder') } as any);
await sleep(1);

assert.equal(shellOutput, 'Generating Tags');
});

test('Should not rebuild on save if the setting is disabled', () => {
when(workspaceService.workspaceFolders).thenReturn(workspaceFolders);
when(configurationService.getSettings(anything())).thenReturn({
workspaceSymbols: {
ctagsPath,
enabled: true,
exclusionPatterns: [],
rebuildOnFileSave: false,
tagFilePath: 'foo'
}
} as any);

workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));

assert.equal(shellOutput, '');
});
});