diff --git a/package.json b/package.json index c06abe95d10..3d4454ffec2 100644 --- a/package.json +++ b/package.json @@ -104,9 +104,6 @@ "url": "https://github.com/Microsoft/vscode/issues" }, "config": { - "ghooks": { - "pre-commit": "node build/gulpfile.hygiene.js" - } }, "optionalDependencies": { "windows-foreground-love": "0.1.0", diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 3bf3adbe4ef..adaa36fa7af 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2788,20 +2788,6 @@ declare module monaco.editor { * An array of keybindings for the action. */ keybindings?: number[]; - /** - * Control if the action should show up in the context menu and where. - * The context menu of the editor has these default: - * navigation - The navigation group comes first in all cases. - * 1_modification - This group comes next and contains commands that modify your code. - * 9_cutcopypaste - The last default group with the basic editing commands. - * You can also create your own group. - * Defaults to null (don't show in context menu). - */ - contextMenuGroupId?: string; - /** - * Control the order in the context menu group. - */ - contextMenuOrder?: number; /** * The keybinding rule. */ diff --git a/src/vs/platform/actions/browser/menusExtensionPoint.ts b/src/vs/platform/actions/browser/menusExtensionPoint.ts index 42e12353fdf..8ffafaec1d0 100644 --- a/src/vs/platform/actions/browser/menusExtensionPoint.ts +++ b/src/vs/platform/actions/browser/menusExtensionPoint.ts @@ -14,6 +14,8 @@ import { forEach } from 'vs/base/common/collections'; import { IExtensionPointUser, IExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { Registry } from 'vs/platform/platform'; +import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ToggleViewletAction } from 'vs/workbench/browser/viewlet'; namespace schema { @@ -206,6 +208,29 @@ namespace schema { } ] }; + + // --- treeExplorers contribution point + + export interface IExplorer { + treeContentProviderId: string; + icon: IUserFriendlyIcon; + } + + export const explorerContribtion: IJSONSchema = { + description: localize('vscode.extension.contributes.explorer', "Contributes explorer viewlet to the sidebar"), + type: 'object', + properties: { + treeContentProviderId: { + description: localize('vscode.extension.contributes.explorer.treeContentProviderId', 'Unique id used to identify provider registered through vscode.workspace.registerTreeContentProvider'), + type: 'string' + }, + icon: { + description: localize('vscode.extension.contributes.explorer.icon', 'Icon to put on activity bar'), + type: 'string' + } + } + }; + } ExtensionsRegistry.registerExtensionPoint('commands', schema.commandsContribution).setHandler(extensions => { @@ -307,3 +332,18 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM }); } }); + +ExtensionsRegistry.registerExtensionPoint('explorer', schema.explorerContribtion).setHandler(extensions => { + for (let extension of extensions) { + const { treeContentProviderId, icon } = extension.value; + + Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( + 'vs/workbench/parts/explorers/browser/treeExplorerViewlet', + 'TreeExplorerViewlet', + 'workbench.view.treeExplorer', // Later change this to make it unique + localize('treeExplorer', 'treeExplorer'), + 'treeExplorer', + 125 + )); + } +}); \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 96bbe6beb85..f3aef501a95 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -74,6 +74,11 @@ export interface ITheme { label: string; } +export interface ITreeExplorer { + treeContentProviderId: string; + icon: string; +} + export interface IExtensionContributions { commands?: ICommand[]; configuration?: IConfiguration; @@ -85,6 +90,7 @@ export interface IExtensionContributions { menus?: { [context: string]: IMenu[] }; snippets?: ISnippet[]; themes?: ITheme[]; + explorer?: ITreeExplorer; } export interface IExtensionManifest { diff --git a/src/vs/workbench/parts/explorers/browser/media/files-dark.svg b/src/vs/workbench/parts/explorers/browser/media/files-dark.svg new file mode 100644 index 00000000000..7e3e59b370b --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/media/files-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/media/treeExplorerViewlet.contribution.css b/src/vs/workbench/parts/explorers/browser/media/treeExplorerViewlet.contribution.css new file mode 100644 index 00000000000..fe7d7722e73 --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/media/treeExplorerViewlet.contribution.css @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Activity Bar */ +.monaco-workbench > .activitybar .monaco-action-bar .action-label.treeExplorer { + background-image: url('files-dark.svg'); +} \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.contribution.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.contribution.ts new file mode 100644 index 00000000000..5721dc1b88f --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.contribution.ts @@ -0,0 +1,3 @@ +'use strict'; + +import 'vs/css!./media/treeExplorerViewlet.contribution'; \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts new file mode 100644 index 00000000000..e27974dc1ab --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts @@ -0,0 +1,74 @@ +import 'vs/css!./media/customViewlet'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { Builder, Dimension } from 'vs/base/browser/builder'; +import { SplitView, Orientation } from 'vs/base/browser/ui/splitview/splitview'; + +import { IViewletView, Viewlet } from 'vs/workbench/browser/viewlet'; + +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +import { TreeView } from 'vs/workbench/parts/explorers/browser/views/treeView'; + +export const CUSTOM_VIEWLET_ID_ROOT = 'workbench.view.customViewlet.'; + +export class TreeExplorerViewlet extends Viewlet { + private static _idCounter = 1; + + private viewletContainer: Builder; + private splitView: SplitView; + private views: IViewletView[]; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super(CUSTOM_VIEWLET_ID_ROOT + TreeExplorerViewlet._idCounter, telemetryService); + TreeExplorerViewlet._idCounter++; + + this.views = []; + } + + create(parent: Builder): TPromise { + super.create(parent); + + this.viewletContainer = parent.div().addClass('custom-viewlet'); + this.splitView = new SplitView(this.viewletContainer.getHTMLElement()); + this.addTreeView('Tree ' + (this.views.length + 1)); + + const settings = this.configurationService.getConfiguration(); + + return this.onConfigurationUpdated(settings); + } + + layout(dimension: Dimension): void { + this.splitView.layout(dimension.height); + } + + private onConfigurationUpdated(config: ICustomViewletConfiguration): TPromise { + return TPromise.as(null); + } + + private addTreeView(treeName: string): void { + const treeView = this.instantiationService.createInstance(TreeView, treeName, this.getActionRunner()); + this.views.push(treeView); + this.splitView.addView(treeView); + } + + dispose(): void { + this.views.forEach(view => { + view.dispose(); + }); + this.views = null; + } +} + + +export interface ICustomViewletConfiguration { + viewlet: { + + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/views/treeView.ts b/src/vs/workbench/parts/explorers/browser/views/treeView.ts new file mode 100644 index 00000000000..7a010364668 --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/views/treeView.ts @@ -0,0 +1,125 @@ +import nls = require('vs/nls'); +import labels = require('vs/base/common/labels'); + +import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; +import * as DOM from 'vs/base/browser/dom'; +import { Builder, $ } from 'vs/base/browser/builder'; +import { IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { CollapsibleViewletView } from 'vs/workbench/browser/viewlet'; + +import { IActionRunner } from 'vs/base/common/actions'; +import { IMessageService } from 'vs/platform/message/common/message'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; + +import { ITree, IDataSource, IRenderer } from 'vs/base/parts/tree/browser/tree'; +import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; +import { DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; +import { TreeDataSource, TreeRenderer, TreeController } from 'vs/workbench/parts/explorers/browser/views/treeViewer'; + +import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; + +import { documentSymbols } from '../../common/goOutline'; + +export class TreeView extends CollapsibleViewletView { + private workspace: IWorkspace; + + private _treeName: string = "TreeExplorer"; + + get treeName(): string { return this._treeName; } + + constructor( + treeName: string, + actionRunner: IActionRunner, + @IMessageService messageService: IMessageService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IInstantiationService private instantiationService: IInstantiationService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IEditorGroupService private editorGroupService: IEditorGroupService + ) { + super(actionRunner, false, nls.localize('treeExplorerViewletTree', "Tree Explorer Tree Section"), messageService, keybindingService, contextMenuService); + + this.workspace = contextService.getWorkspace(); + this.create(); + } + + renderHeader(container: HTMLElement): void { + const titleDiv = $('div.title').appendTo(container); + $('span') + .text(this._treeName) + .title(this._treeName) + .appendTo(titleDiv); + + super.renderHeader(container); + } + + renderBody(container: HTMLElement): void { + this.treeContainer = super.renderViewTree(container); + DOM.addClass(this.treeContainer, 'tree-explorer-viewlet-tree-view'); + + this.tree = this.createViewer($(this.treeContainer)); + } + + createViewer(container: Builder): ITree { + const dataSource = this.instantiationService.createInstance(TreeDataSource); + const renderer = this.instantiationService.createInstance(TreeRenderer, this.actionRunner, container.getHTMLElement()); + const controller = null; + const sorter = null; + const filter = null; + const dnd = null; + const accessibilityProvider = null; + + return new Tree(container.getHTMLElement(), { + dataSource, + renderer, + controller, + sorter, + filter, + dnd, + accessibilityProvider + }); + } + + create(): TPromise { + this.toDispose.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())) + return TPromise.as(null); + } + + private onEditorsChanged() { + const activeInput = this.editorService.getActiveEditorInput(); + + if (activeInput instanceof FileEditorInput) { + const fileResource = activeInput.getResource(); + + this.updateOutlineViewlet(fileResource); + } + + } + + private doRefresh(): TPromise { + return TPromise.as(null); + } + + private updateOutlineViewlet(fileURI: URI): TPromise { + documentSymbols(fileURI.path).then(nodes => { + const root = nodes[0]; + this.tree.setInput(root); + }); + + return TPromise.as(null); + } + + public getOptimalWidth(): number { + const parentNode = this.tree.getHTMLElement(); + const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a')); + + return DOM.getLargestChildWidth(parentNode, childNodes); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/views/treeViewer.ts b/src/vs/workbench/parts/explorers/browser/views/treeViewer.ts new file mode 100644 index 00000000000..992beebc61c --- /dev/null +++ b/src/vs/workbench/parts/explorers/browser/views/treeViewer.ts @@ -0,0 +1,75 @@ +import { TPromise } from 'vs/base/common/winjs.base'; +import { $, Builder } from 'vs/base/browser/builder'; + +import { ITree, IDataSource, IRenderer, IElementCallback } from 'vs/base/parts/tree/browser/tree'; +import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; +import { TreeViewNode } from 'vs/workbench/parts/explorers/common/treeViewModel'; +import { DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; + +import { IActionRunner } from 'vs/base/common/actions'; +import { ActionsRenderer } from 'vs/base/parts/tree/browser/actionsRenderer'; + +import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/platform/extensions/common/extensions'; +import { IModeService } from 'vs/editor/common/services/modeService'; + +export class TreeDataSource implements IDataSource { + + constructor() { + + } + + public getId(tree: ITree, node: TreeViewNode): string { + return node.label; + } + + public hasChildren(tree: ITree, node: TreeViewNode): boolean { + return node.children.length > 0; + } + + public getChildren(tree: ITree, node: TreeViewNode): TPromise { + return TPromise.as(node.children); + } + + public getParent(tree: ITree, node: TreeViewNode): TPromise { + return TPromise.as(null); + } +} + +export class TreeRenderer extends ActionsRenderer implements IRenderer { + + constructor( + actionRunner: IActionRunner, + private container: HTMLElement, + @IContextViewService private contextViewService: IContextViewService, + @IExtensionService private extensionService: IExtensionService, + @IModeService private modeService: IModeService + ) { + super({ + actionProvider: null, + actionRunner: actionRunner + }); + } + + public getContentHeight(tree: ITree, element: any): number { + return 22; + } + + public renderContents(tree: ITree, node: TreeViewNode, domElement: HTMLElement, previousCleanupFn: IElementCallback): IElementCallback { + const el = $(domElement).clearChildren(); + const item = $('.custom-viewlet-tree-node-item'); + item.appendTo(el); + return this.renderFileFolderLabel(item, node); + } + + private renderFileFolderLabel(container: Builder, node: TreeViewNode): IElementCallback { + const label = $('.custom-viewlet-tree-node-item-label').appendTo(container); + $('a.plain').text(node.label).title(node.label).appendTo(label); + + return null; + } +} + +export class TreeController extends DefaultController { + +} \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/common/goOutline.ts b/src/vs/workbench/parts/explorers/common/goOutline.ts new file mode 100644 index 00000000000..b81eed7fd4c --- /dev/null +++ b/src/vs/workbench/parts/explorers/common/goOutline.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import cp = require('child_process'); +import path = require('path'); +import { getBinPath } from './goPath'; + +import { TreeViewNode } from './treeViewModel'; + +// Keep in sync with https://github.com/lukehoban/go-outline +export interface GoOutlineRange { + start: number; + end: number; +} + +export interface GoOutlineDeclaration { + label: string; + type: string; + receiverType?: string; + icon?: string; // icon class or null to use the default images based on the type + start: number; + end: number; + children?: GoOutlineDeclaration[]; + signature?: GoOutlineRange; + comment?: GoOutlineRange; +} + +function documentSymbolToSymbolStat(decl: GoOutlineDeclaration): TreeViewNode { + const children = decl.children && decl.children.length > 0 + ? decl.children.map(documentSymbolToSymbolStat) + : []; + + return new TreeViewNode(decl.label, decl.type, decl.start, decl.end, children); +} + +export function documentSymbols(filename: string): Promise { + return new Promise((resolve, reject) => { + let gooutline = getBinPath('go-outline'); + // Spawn `go-outline` process + let p = cp.execFile(gooutline, ['-f', filename], {}, (err, stdout, stderr) => { + try { + if (err && (err).code === 'ENOENT') { + console.log('Go-outline not installed'); + // promptForMissingTool('go-outline'); + } + if (err) return resolve(null); + let result = stdout.toString(); + let decls = JSON.parse(result); + let symbols = decls.map(documentSymbolToSymbolStat); + return resolve(symbols); + } catch (e) { + reject(e); + } + }); + }); +} \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/common/goPath.ts b/src/vs/workbench/parts/explorers/common/goPath.ts new file mode 100644 index 00000000000..07e51376aa0 --- /dev/null +++ b/src/vs/workbench/parts/explorers/common/goPath.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import fs = require('fs'); +import path = require('path'); +import os = require('os'); + +let binPathCache: { [bin: string]: string; } = {}; +let runtimePathCache: string = null; + +export function getBinPath(binname: string) { + binname = correctBinname(binname); + if (binPathCache[binname]) return binPathCache[binname]; + + // First search each GOPATH workspace's bin folder + if (process.env['GOPATH']) { + let workspaces = process.env['GOPATH'].split(path.delimiter); + for (let i = 0; i < workspaces.length; i++) { + let binpath = path.join(workspaces[i], 'bin', binname); + if (fs.existsSync(binpath)) { + binPathCache[binname] = binpath; + return binpath; + } + } + } + + // Then search PATH parts + if (process.env['PATH']) { + let pathparts = process.env['PATH'].split(path.delimiter); + for (let i = 0; i < pathparts.length; i++) { + let binpath = path.join(pathparts[i], binname); + if (fs.existsSync(binpath)) { + binPathCache[binname] = binpath; + return binpath; + } + } + } + + // Finally check GOROOT just in case + if (process.env['GOROOT']) { + let binpath = path.join(process.env['GOROOT'], 'bin', binname); + if (fs.existsSync(binpath)) { + binPathCache[binname] = binpath; + return binpath; + } + } + + // Else return the binary name directly (this will likely always fail downstream) + binPathCache[binname] = binname; + return binname; +} + +function correctBinname(binname: string) { + if (process.platform === 'win32') + return binname + '.exe'; + else + return binname; +} + +/** + * Returns Go runtime binary path. + * + * @return the path to the Go binary. + */ +export function getGoRuntimePath(): string { + if (runtimePathCache) return runtimePathCache; + if (process.env['GOROOT']) { + runtimePathCache = path.join(process.env['GOROOT'], 'bin', correctBinname('go')); + } else if (process.env['PATH']) { + let pathparts = (process.env.PATH).split(path.delimiter); + runtimePathCache = pathparts.map(dir => path.join(dir, correctBinname('go'))).filter(candidate => fs.existsSync(candidate))[0]; + } + return runtimePathCache; +} \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/common/treeViewModel.ts b/src/vs/workbench/parts/explorers/common/treeViewModel.ts new file mode 100644 index 00000000000..fae5b6d90e0 --- /dev/null +++ b/src/vs/workbench/parts/explorers/common/treeViewModel.ts @@ -0,0 +1,15 @@ +export class TreeViewNode { + public label: string; + public type: string; + public start: number; + public end: number; + public children: TreeViewNode[]; + + constructor(label: string, type: string, start: number, end: number, children: TreeViewNode[]) { + this.label = label; + this.type = type; + this.start = start; + this.end = end; + this.children = children; + } +} diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 44e08cbac2e..ea66c2ef039 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -57,6 +57,8 @@ import 'vs/workbench/parts/extensions/electron-browser/extensions.contribution'; import 'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen'; import 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; // can be packaged separately +import 'vs/workbench/parts/explorers/browser/treeExplorerViewlet.contribution'; + import 'vs/workbench/parts/output/browser/output.contribution'; import 'vs/workbench/parts/output/browser/outputPanel'; // can be packaged separately