Skip to content

Commit 3938bea

Browse files
authored
Add implementations for ctags rebuildOnStart and rebuildOnFileSave (#8889)
* Add implementations * unit tests
1 parent 434131a commit 3938bea

File tree

3 files changed

+204
-2
lines changed

3 files changed

+204
-2
lines changed

news/2 Fixes/793.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add implementations for `python.workspaceSymbols.rebuildOnStart` and `python.workspaceSymbols.rebuildOnFileSave`.

src/client/workspaceSymbols/main.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { CancellationToken, Disposable, languages, OutputChannel } from 'vscode';
2-
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types';
1+
import { CancellationToken, Disposable, languages, OutputChannel, TextDocument } from 'vscode';
2+
import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types';
33
import { Commands, STANDARD_OUTPUT_CHANNEL } from '../common/constants';
44
import { isNotInstalledError } from '../common/helpers';
55
import { IFileSystem } from '../common/platform/types';
@@ -23,6 +23,7 @@ export class WorkspaceSymbols implements Disposable {
2323
private processFactory: IProcessServiceFactory;
2424
private appShell: IApplicationShell;
2525
private configurationService: IConfigurationService;
26+
private documents: IDocumentManager;
2627

2728
constructor(private serviceContainer: IServiceContainer) {
2829
this.outputChannel = this.serviceContainer.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
@@ -32,12 +33,15 @@ export class WorkspaceSymbols implements Disposable {
3233
this.processFactory = this.serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory);
3334
this.appShell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
3435
this.configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
36+
this.documents = this.serviceContainer.get<IDocumentManager>(IDocumentManager);
3537
this.disposables = [];
3638
this.disposables.push(this.outputChannel);
3739
this.registerCommands();
3840
this.initializeGenerators();
3941
languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this.fs, this.commandMgr, this.generators));
4042
this.disposables.push(this.workspace.onDidChangeWorkspaceFolders(() => this.initializeGenerators()));
43+
this.disposables.push(this.documents.onDidSaveTextDocument(e => this.onDocumentSaved(e)));
44+
this.buildSymbolsOnStart();
4145
}
4246
public dispose() {
4347
this.disposables.forEach(d => d.dispose());
@@ -55,6 +59,18 @@ export class WorkspaceSymbols implements Disposable {
5559
}
5660
}
5761

62+
private buildSymbolsOnStart() {
63+
if (Array.isArray(this.workspace.workspaceFolders)) {
64+
this.workspace.workspaceFolders.forEach(workspaceFolder => {
65+
const pythonSettings = this.configurationService.getSettings(workspaceFolder.uri);
66+
if (pythonSettings.workspaceSymbols.rebuildOnStart) {
67+
const promises = this.buildWorkspaceSymbols(true);
68+
return Promise.all(promises);
69+
}
70+
});
71+
}
72+
}
73+
5874
private registerCommands() {
5975
this.disposables.push(
6076
this.commandMgr.registerCommand(
@@ -65,6 +81,15 @@ export class WorkspaceSymbols implements Disposable {
6581
}));
6682
}
6783

84+
private onDocumentSaved(document: TextDocument) {
85+
const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri);
86+
const pythonSettings = this.configurationService.getSettings(workspaceFolder?.uri);
87+
if (pythonSettings.workspaceSymbols.rebuildOnFileSave) {
88+
const promises = this.buildWorkspaceSymbols(true);
89+
return Promise.all(promises);
90+
}
91+
}
92+
6893
// tslint:disable-next-line:no-any
6994
private buildWorkspaceSymbols(rebuild: boolean = true, token?: CancellationToken): Promise<any>[] {
7095
if (token && token.isCancellationRequested) {
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import * as assert from 'assert';
7+
import { use } from 'chai';
8+
import * as chaiAsPromised from 'chai-as-promised';
9+
import { anyString, anything, instance, mock, when } from 'ts-mockito';
10+
import { EventEmitter, TextDocument, Uri, WorkspaceFolder } from 'vscode';
11+
import { ApplicationShell } from '../../client/common/application/applicationShell';
12+
import { CommandManager } from '../../client/common/application/commandManager';
13+
import { DocumentManager } from '../../client/common/application/documentManager';
14+
import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../../client/common/application/types';
15+
import { WorkspaceService } from '../../client/common/application/workspace';
16+
import { ConfigurationService } from '../../client/common/configuration/service';
17+
import { STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants';
18+
import { FileSystem } from '../../client/common/platform/fileSystem';
19+
import { IFileSystem } from '../../client/common/platform/types';
20+
import { ProcessService } from '../../client/common/process/proc';
21+
import { ProcessServiceFactory } from '../../client/common/process/processFactory';
22+
import { IProcessService, IProcessServiceFactory, Output } from '../../client/common/process/types';
23+
import { IConfigurationService, IOutputChannel } from '../../client/common/types';
24+
import { sleep } from '../../client/common/utils/async';
25+
import { ServiceContainer } from '../../client/ioc/container';
26+
import { IServiceContainer } from '../../client/ioc/types';
27+
import { WorkspaceSymbols } from '../../client/workspaceSymbols/main';
28+
import { MockOutputChannel } from '../mockClasses';
29+
30+
use(chaiAsPromised);
31+
32+
// tslint:disable: no-any
33+
// tslint:disable-next-line: max-func-body-length
34+
suite('Workspace symbols main', () => {
35+
const mockDisposable = { dispose: () => { return; } };
36+
const ctagsPath = 'CTAG_PATH';
37+
const observable = {
38+
out: {
39+
subscribe: (cb: (out: Output<string>) => void, _errorCb: any, done: Function) => {
40+
cb({ source: 'stdout', out: '' });
41+
done();
42+
}
43+
}
44+
};
45+
46+
let outputChannel: IOutputChannel;
47+
let commandManager: ICommandManager;
48+
let fileSystem: IFileSystem;
49+
let workspaceService: IWorkspaceService;
50+
let processServiceFactory: IProcessServiceFactory;
51+
let processService: IProcessService;
52+
let applicationShell: IApplicationShell;
53+
let configurationService: IConfigurationService;
54+
let documentManager: IDocumentManager;
55+
let serviceContainer: IServiceContainer;
56+
let workspaceFolders: WorkspaceFolder[];
57+
let workspaceSymbols: WorkspaceSymbols;
58+
let shellOutput: string;
59+
let eventEmitter: EventEmitter<TextDocument>;
60+
61+
setup(() => {
62+
eventEmitter = new EventEmitter<TextDocument>();
63+
shellOutput = '';
64+
workspaceFolders = [{ name: 'root', index: 0, uri: Uri.file('folder') }];
65+
66+
outputChannel = mock(MockOutputChannel);
67+
commandManager = mock(CommandManager);
68+
fileSystem = mock(FileSystem);
69+
workspaceService = mock(WorkspaceService);
70+
processServiceFactory = mock(ProcessServiceFactory);
71+
processService = mock(ProcessService);
72+
applicationShell = mock(ApplicationShell);
73+
configurationService = mock(ConfigurationService);
74+
documentManager = mock(DocumentManager);
75+
serviceContainer = mock(ServiceContainer);
76+
77+
when(workspaceService.onDidChangeWorkspaceFolders).thenReturn(() => mockDisposable as any);
78+
when(documentManager.onDidSaveTextDocument).thenReturn(eventEmitter.event);
79+
when(commandManager.registerCommand(anything(), anything())).thenReturn(mockDisposable as any);
80+
when(fileSystem.directoryExists(anything())).thenResolve(true);
81+
when(fileSystem.fileExists(anything())).thenResolve(false);
82+
when(processServiceFactory.create()).thenResolve(instance(processService));
83+
when(processService.execObservable(ctagsPath, anything(), anything()))
84+
.thenReturn(observable as any);
85+
when(applicationShell.setStatusBarMessage(anyString(), anything())).thenCall((text: string) => {
86+
shellOutput += text;
87+
return mockDisposable;
88+
});
89+
90+
when(serviceContainer.get<IOutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL)).thenReturn(instance(outputChannel));
91+
when(serviceContainer.get<ICommandManager>(ICommandManager)).thenReturn(instance(commandManager));
92+
when(serviceContainer.get<IFileSystem>(IFileSystem)).thenReturn(instance(fileSystem));
93+
when(serviceContainer.get<IWorkspaceService>(IWorkspaceService)).thenReturn(instance(workspaceService));
94+
when(serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory)).thenReturn(instance(processServiceFactory));
95+
when(serviceContainer.get<IApplicationShell>(IApplicationShell)).thenReturn(instance(applicationShell));
96+
when(serviceContainer.get<IConfigurationService>(IConfigurationService)).thenReturn(instance(configurationService));
97+
when(serviceContainer.get<IDocumentManager>(IDocumentManager)).thenReturn(instance(documentManager));
98+
});
99+
100+
teardown(() => {
101+
workspaceSymbols.dispose();
102+
});
103+
104+
test('Should not rebuild on start if the setting is disabled', () => {
105+
when(workspaceService.workspaceFolders).thenReturn(workspaceFolders);
106+
when(configurationService.getSettings(anything())).thenReturn({ workspaceSymbols: { rebuildOnStart: false } } as any);
107+
108+
workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));
109+
110+
assert.equal(shellOutput, '');
111+
});
112+
113+
test('Should not rebuild on start if we don\'t have a workspace folder', () => {
114+
when(workspaceService.workspaceFolders).thenReturn([]);
115+
when(configurationService.getSettings(anything())).thenReturn({ workspaceSymbols: { rebuildOnStart: false } } as any);
116+
117+
workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));
118+
119+
assert.equal(shellOutput, '');
120+
});
121+
122+
test('Should rebuild on start if the setting is enabled and we have a workspace folder', async () => {
123+
when(workspaceService.workspaceFolders).thenReturn(workspaceFolders);
124+
when(configurationService.getSettings(anything())).thenReturn({
125+
workspaceSymbols: {
126+
ctagsPath,
127+
enabled: true,
128+
exclusionPatterns: [],
129+
rebuildOnStart: true,
130+
tagFilePath: 'foo'
131+
}
132+
} as any);
133+
134+
workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));
135+
await sleep(1);
136+
137+
assert.equal(shellOutput, 'Generating Tags');
138+
});
139+
140+
test('Should rebuild on save if the setting is enabled', async () => {
141+
when(workspaceService.workspaceFolders).thenReturn(workspaceFolders);
142+
when(workspaceService.getWorkspaceFolder(anything())).thenReturn(workspaceFolders[0]);
143+
when(configurationService.getSettings(anything())).thenReturn({
144+
workspaceSymbols: {
145+
ctagsPath,
146+
enabled: true,
147+
exclusionPatterns: [],
148+
rebuildOnFileSave: true,
149+
tagFilePath: 'foo'
150+
}
151+
} as any);
152+
153+
workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));
154+
eventEmitter.fire({ uri: Uri.file('folder') } as any);
155+
await sleep(1);
156+
157+
assert.equal(shellOutput, 'Generating Tags');
158+
});
159+
160+
test('Should not rebuild on save if the setting is disabled', () => {
161+
when(workspaceService.workspaceFolders).thenReturn(workspaceFolders);
162+
when(configurationService.getSettings(anything())).thenReturn({
163+
workspaceSymbols: {
164+
ctagsPath,
165+
enabled: true,
166+
exclusionPatterns: [],
167+
rebuildOnFileSave: false,
168+
tagFilePath: 'foo'
169+
}
170+
} as any);
171+
172+
workspaceSymbols = new WorkspaceSymbols(instance(serviceContainer));
173+
174+
assert.equal(shellOutput, '');
175+
});
176+
});

0 commit comments

Comments
 (0)