Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d1da48b
update to use latest api
DonJayamanne Oct 3, 2017
15d8d35
Merge branch 'master' into 1228Multiroot
DonJayamanne Oct 4, 2017
b305a72
config changes for multiroot workspace
DonJayamanne Oct 5, 2017
674da9e
linting support with multi roots
DonJayamanne Oct 5, 2017
a549fc6
multi root support for formatters
DonJayamanne Oct 6, 2017
657e20f
determine workspace root path
DonJayamanne Oct 6, 2017
d492b5a
revert change
DonJayamanne Oct 6, 2017
c0c70fb
support multiple configs per workspace folder
DonJayamanne Oct 6, 2017
86e36c1
modify formatters to use resource specific settings
DonJayamanne Oct 6, 2017
c5cd8a9
modified installer to pass resource for workspace resolution
DonJayamanne Oct 9, 2017
663b345
null test in installer
DonJayamanne Oct 9, 2017
bcca381
canges to config settings to support multiroot workspace
DonJayamanne Oct 9, 2017
e3279cb
changes to code refactoring to support workspace symbols
DonJayamanne Oct 9, 2017
965eae9
oops
DonJayamanne Oct 9, 2017
234ff78
modified to settings are resolved using document uri
DonJayamanne Oct 9, 2017
e15e627
merged 1228Multiroot
DonJayamanne Oct 9, 2017
da4e2ab
unit tests for multi root support
DonJayamanne Oct 11, 2017
b575dcc
fix unittests for multiroot
DonJayamanne Oct 11, 2017
7457c89
exclude files
DonJayamanne Oct 11, 2017
54741c1
add new line
DonJayamanne Oct 11, 2017
ef306b0
config changes for multiroot workspace
DonJayamanne Oct 5, 2017
af113a4
installer, config changes with unit tests
DonJayamanne Oct 11, 2017
23ab751
new lines and enabled multi root linter tests
DonJayamanne Oct 11, 2017
b4295ef
fix sys variables
DonJayamanne Oct 12, 2017
360e919
added unit test to resolve ${workspaceRoot} in settings.json
DonJayamanne Oct 12, 2017
52e93c5
#1228 workspace symbols with multiroot support
DonJayamanne Oct 12, 2017
c2ba665
fix test
DonJayamanne Oct 12, 2017
e0f57ce
added some data for workspace symbol tests
DonJayamanne Oct 12, 2017
9e79d26
data for unit tests
DonJayamanne Oct 12, 2017
bbf2403
fixed to add support for multit roots with unit tests
DonJayamanne Oct 12, 2017
57ce355
account for mutiroot files in sub directory
DonJayamanne Oct 12, 2017
f044e27
disable all but multiroot tests
DonJayamanne Oct 12, 2017
99bf348
fixed tests
DonJayamanne Oct 12, 2017
223b6dc
include files for tests
DonJayamanne Oct 13, 2017
67ec41e
Fixed travis tests for multi root workspace symbols (#1306)
DonJayamanne Oct 13, 2017
3da7274
merged
DonJayamanne Oct 13, 2017
158d837
merged fixes
DonJayamanne Oct 13, 2017
ee0003c
Added brackets around print statements (for p3)
DonJayamanne Oct 13, 2017
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
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
out
node_modules
*.pyc
.vscode/.ropeproject/**
src/test/.vscode/**
**/.vscode/tags
**/.vscode/.ropeproject/**
**/testFiles/**/.cache/**
*.noseids
.vscode-test
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1593,7 +1593,7 @@
"vscode:prepublish": "tsc -p ./ && webpack",
"compile": "webpack && tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "node ./node_modules/vscode/bin/test && node ./out/test/multiRootTest.js",
"test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js",
"precommit": "node gulpfile.js",
"lint": "tslint src/**/*.ts -t verbose"
},
Expand Down
38 changes: 24 additions & 14 deletions src/client/workspaceSymbols/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as child_process from 'child_process';
import { PythonSettings } from '../common/configSettings';

const pythonSettings = PythonSettings.getInstance();
import { IPythonSettings, PythonSettings } from '../common/configSettings';

export class Generator implements vscode.Disposable {
private optionsFile: string;
private disposables: vscode.Disposable[];

constructor(private output: vscode.OutputChannel) {
private pythonSettings: IPythonSettings;
public get tagFilePath(): string {
return this.pythonSettings.workspaceSymbols.tagFilePath;
}
public get enabled(): boolean {
return this.pythonSettings.workspaceSymbols.enabled;
}
constructor(public readonly workspaceFolder: vscode.Uri, private output: vscode.OutputChannel) {
this.disposables = [];
this.optionsFile = path.join(__dirname, '..', '..', '..', 'resources', 'ctagOptions');
this.pythonSettings = PythonSettings.getInstance(workspaceFolder);
}

dispose() {
Expand All @@ -21,20 +26,25 @@ export class Generator implements vscode.Disposable {

private buildCmdArgs(): string[] {
const optionsFile = this.optionsFile.indexOf(' ') > 0 ? `"${this.optionsFile}"` : this.optionsFile;
const exclusions = pythonSettings.workspaceSymbols.exclusionPatterns;
const exclusions = this.pythonSettings.workspaceSymbols.exclusionPatterns;
const excludes = exclusions.length === 0 ? [] : exclusions.map(pattern => `--exclude=${pattern}`);

return [`--options=${optionsFile}`, '--languages=Python'].concat(excludes);
}

generateWorkspaceTags(): Promise<any> {
const tagFile = path.normalize(pythonSettings.workspaceSymbols.tagFilePath);
return this.generateTags(tagFile, { directory: vscode.workspace.rootPath });
async generateWorkspaceTags(): Promise<void> {
if (!this.pythonSettings.workspaceSymbols.enabled) {
return;
}
return await this.generateTags({ directory: this.workspaceFolder.fsPath });
}

private generateTags(outputFile: string, source: { directory?: string, file?: string }): Promise<any> {
const cmd = pythonSettings.workspaceSymbols.ctagsPath;
private generateTags(source: { directory?: string, file?: string }): Promise<void> {
const tagFile = path.normalize(this.pythonSettings.workspaceSymbols.tagFilePath);
const cmd = this.pythonSettings.workspaceSymbols.ctagsPath;
const args = this.buildCmdArgs();

let outputFile = tagFile;
if (source.file && source.file.length > 0) {
source.directory = path.dirname(source.file);
}
Expand All @@ -43,14 +53,14 @@ export class Generator implements vscode.Disposable {
outputFile = path.basename(outputFile);
}
const outputDir = path.dirname(outputFile);
if (!fs.existsSync(outputDir)){
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
outputFile = outputFile.indexOf(' ') > 0 ? `"${outputFile}"` : outputFile;
args.push(`-o ${outputFile}`, '.');
this.output.appendLine('-'.repeat(10) + 'Generating Tags' + '-'.repeat(10));
this.output.appendLine(`${cmd} ${args.join(' ')}`);
const promise = new Promise<any>((resolve, reject) => {
const promise = new Promise<void>((resolve, reject) => {
let options: child_process.SpawnOptions = {
cwd: source.directory
};
Expand All @@ -75,7 +85,7 @@ export class Generator implements vscode.Disposable {
reject(errorMsg);
}
else {
resolve(outputFile);
resolve();
}
});
});
Expand Down
82 changes: 52 additions & 30 deletions src/client/workspaceSymbols/main.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
import * as vscode from 'vscode';
import { Generator } from './generator';
import { Installer, InstallerResponse, Product } from '../common/installer';
import { PythonSettings } from '../common/configSettings';
import { fsExistsAsync } from '../common/utils';
import { isNotInstalledError } from '../common/helpers';
import { PythonLanguage, Commands } from '../common/constants';
import { WorkspaceSymbolProvider } from './provider';

const pythonSettings = PythonSettings.getInstance();
const MAX_NUMBER_OF_ATTEMPTS_TO_INSTALL_AND_BUILD = 2;

export class WorkspaceSymbols implements vscode.Disposable {
private disposables: vscode.Disposable[];
private generator: Generator;
private generators: Generator[] = [];
private installer: Installer;
constructor(private outputChannel: vscode.OutputChannel) {
this.disposables = [];
this.disposables.push(this.outputChannel);
this.generator = new Generator(this.outputChannel);
this.disposables.push(this.generator);
this.installer = new Installer();
this.disposables.push(this.installer);
this.registerCommands();

// The extension has just loaded, so lets rebuild the tags
vscode.languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this.generator, this.outputChannel));
this.initializeGenerators();
vscode.languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this.generators, this.outputChannel));
this.buildWorkspaceSymbols(true);
this.disposables.push(vscode.workspace.onDidChangeWorkspaceFolders(() => this.initializeGenerators()));
}
private initializeGenerators() {
while (this.generators.length > 0) {
const generator = this.generators.shift();
generator.dispose();
}

if (Array.isArray(vscode.workspace.workspaceFolders)) {
vscode.workspace.workspaceFolders.forEach(wkSpc => {
this.generators.push(new Generator(wkSpc.uri, this.outputChannel));
});
}
}
registerCommands() {
this.disposables.push(vscode.commands.registerCommand(Commands.Build_Workspace_Symbols, (rebuild: boolean = true, token?: vscode.CancellationToken) => {
Expand Down Expand Up @@ -52,40 +61,53 @@ export class WorkspaceSymbols implements vscode.Disposable {
dispose() {
this.disposables.forEach(d => d.dispose());
}
buildWorkspaceSymbols(rebuild: boolean = true, token?: vscode.CancellationToken): Promise<any> {
if (!pythonSettings.workspaceSymbols.enabled || (token && token.isCancellationRequested)) {
async buildWorkspaceSymbols(rebuild: boolean = true, token?: vscode.CancellationToken): Promise<any> {
if (token && token.isCancellationRequested) {
return Promise.resolve([]);
}
if (!vscode.workspace || typeof vscode.workspace.rootPath !== 'string' || vscode.workspace.rootPath.length === 0) {
if (this.generators.length === 0) {
return Promise.resolve([]);
}
return fsExistsAsync(pythonSettings.workspaceSymbols.tagFilePath).then(exits => {
let promise = Promise.resolve();

let promptPromise: Promise<InstallerResponse>;
let promptResponse: InstallerResponse;
return this.generators.map(async generator => {
if (!generator.enabled) {
return;
}
const exists = await fsExistsAsync(generator.tagFilePath);
// if file doesn't exist, then run the ctag generator
// Or check if required to rebuild
if (rebuild || !exits) {
promise = this.generator.generateWorkspaceTags();
if (!rebuild && exists) {
return;
}

return promise.catch(reason => {
if (!isNotInstalledError(reason)) {
this.outputChannel.show();
return Promise.reject(reason);
for (let counter = 0; counter < MAX_NUMBER_OF_ATTEMPTS_TO_INSTALL_AND_BUILD; counter++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just screaming for ... in ..., but I know there's no range() equivalent in TS without going to npm.

try {
await generator.generateWorkspaceTags();
return;
}
catch (error) {
if (!isNotInstalledError(error)) {
this.outputChannel.show();
return;
}
}
if (!token || token.isCancellationRequested) {
return;
}
return this.installer.promptToInstall(Product.ctags)
.then(result => {
if (!token || token.isCancellationRequested) {
return;
}
if (result === InstallerResponse.Installed) {
return this.buildWorkspaceSymbols(rebuild, token);
}
});
});
// Display prompt once for all workspaces
if (promptPromise) {
promptResponse = await promptPromise;
continue;
}
else {
promptPromise = this.installer.promptToInstall(Product.ctags);
promptResponse = await promptPromise;
}
if (promptResponse !== InstallerResponse.Installed || (!token || token.isCancellationRequested)) {
return;
}
}
});
}
}
Expand Down
15 changes: 6 additions & 9 deletions src/client/workspaceSymbols/parser.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import * as vscode from 'vscode';
import { Tag } from './contracts';
import * as path from 'path';
import { PythonSettings } from '../common/configSettings';
import { fsExistsAsync } from '../common/utils';

const LineByLineReader = require("line-by-line");
const NamedRegexp = require('named-js-regexp');
const fuzzy = require('fuzzy');

const pythonSettings = PythonSettings.getInstance();
const IsFileRegEx = /\tkind:file\tline:\d+$/g;
const LINE_REGEX = '(?<name>\\w+)\\t(?<file>.*)\\t\\/\\^(?<code>.*)\\$\\/;"\\tkind:(?<type>\\w+)\\tline:(?<line>\\d+)$';

Expand Down Expand Up @@ -102,15 +100,14 @@ Object.keys(newValuesAndKeys).forEach(key => {
CTagKinMapping.set(key, newValuesAndKeys[key]);
});

export function parseTags(query: string, token: vscode.CancellationToken, maxItems: number = 200): Promise<Tag[]> {
const file = pythonSettings.workspaceSymbols.tagFilePath;
return fsExistsAsync(file).then(exists => {
export function parseTags(workspaceFolder: string, tagFile: string, query: string, token: vscode.CancellationToken, maxItems: number = 200): Promise<Tag[]> {
return fsExistsAsync(tagFile).then(exists => {
if (!exists) {
return null;
}

return new Promise<Tag[]>((resolve, reject) => {
let lr = new LineByLineReader(file);
let lr = new LineByLineReader(tagFile);
let lineNumber = 0;
let tags: Tag[] = [];

Expand All @@ -124,7 +121,7 @@ export function parseTags(query: string, token: vscode.CancellationToken, maxIte
lr.close();
return;
}
const tag = parseTagsLine(line, query);
const tag = parseTagsLine(workspaceFolder, line, query);
if (tag) {
tags.push(tag);
}
Expand All @@ -139,7 +136,7 @@ export function parseTags(query: string, token: vscode.CancellationToken, maxIte
});
});
}
function parseTagsLine(line: string, searchPattern: string): Tag {
function parseTagsLine(workspaceFolder: string, line: string, searchPattern: string): Tag {
if (IsFileRegEx.test(line)) {
return;
}
Expand All @@ -152,7 +149,7 @@ function parseTagsLine(line: string, searchPattern: string): Tag {
}
let file = match.file;
if (!path.isAbsolute(file)) {
file = path.resolve(vscode.workspace.rootPath, '.vscode', file);
file = path.resolve(workspaceFolder, '.vscode', file);
}

const symbolKind = CTagKinMapping.has(match.type) ? CTagKinMapping.get(match.type) : vscode.SymbolKind.Null;
Expand Down
47 changes: 27 additions & 20 deletions src/client/workspaceSymbols/provider.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
import * as vscode from 'vscode';
import * as _ from 'lodash';
import { Generator } from './generator';
import { PythonSettings } from '../common/configSettings';
import { parseTags } from './parser';
import { fsExistsAsync } from '../common/utils';
import { createDeferred } from '../common/helpers';
import { Commands } from '../common/constants';
const pythonSettings = PythonSettings.getInstance();

export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
public constructor(private tagGenerator: Generator, private outputChannel: vscode.OutputChannel) {
public constructor(private tagGenerators: Generator[], private outputChannel: vscode.OutputChannel) {
}

async provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): Promise<vscode.SymbolInformation[]> {
if (!pythonSettings.workspaceSymbols.enabled) {
if (this.tagGenerators.length === 0) {
return [];
}
if (!vscode.workspace || typeof vscode.workspace.rootPath !== 'string' || vscode.workspace.rootPath.length === 0) {
return Promise.resolve([]);
}
// check whether tag file needs to be built
const tagFileExists = await fsExistsAsync(pythonSettings.workspaceSymbols.tagFilePath);
if (!tagFileExists) {
const generatorsWithTagFiles = await Promise.all(this.tagGenerators.map(generator => fsExistsAsync(generator.tagFilePath)));
if (generatorsWithTagFiles.filter(exists => exists).length !== this.tagGenerators.length) {
await vscode.commands.executeCommand(Commands.Build_Workspace_Symbols, true, token);
}
// load tags
const items = await parseTags(query, token);
if (!Array.isArray(items)) {
return [];
}
return items.map(item => new vscode.SymbolInformation(
item.symbolName, item.symbolKind, '',
new vscode.Location(vscode.Uri.file(item.fileName), item.position)
));

const generators = await Promise.all(this.tagGenerators.map(async generator => {
const tagFileExists = await fsExistsAsync(generator.tagFilePath);
return tagFileExists ? generator : undefined;
}));

const promises = generators
.filter(generator => generator !== undefined && generator.enabled)
.map(async generator => {
// load tags
const items = await parseTags(generator.workspaceFolder.fsPath, generator.tagFilePath, query, token);
if (!Array.isArray(items)) {
return [];
}
return items.map(item => new vscode.SymbolInformation(
item.symbolName, item.symbolKind, '',
new vscode.Location(vscode.Uri.file(item.fileName), item.position)
));
});

const symbols = await Promise.all(promises);
return _.flatten(symbols);
}
}
12 changes: 12 additions & 0 deletions src/test/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"python.pythonPath": "python",
"python.linting.pylintEnabled": true,
"python.linting.flake8Enabled": true,
"python.workspaceSymbols.enabled": true,
"python.unitTest.nosetestArgs": [],
"python.unitTest.pyTestArgs": [],
"python.unitTest.unittestArgs": [
"-s=./tests",
"-p=test_*.py"
]
}
Loading