Skip to content

Commit

Permalink
(GH-666) Puppetfile view
Browse files Browse the repository at this point in the history
Adds a new view to the Puppet toolbar view container that shows the puppet modules listed in the Puppetfile in the current workspace. This also adds a new command and context menu that will open the Puppetfile at the line of the selected module in the view.

This uses the `openTextDocument` VS Code API to open the Puppetfile without showing the file to the user. This initiates a `didOpen` notification which sends the content to the Language Server. If we opened the document and sent the content ourselves, a `didOpen` notitication is still sent, so it would result in sending the content twice.
  • Loading branch information
jpogran committed Jun 5, 2020
1 parent 8e6c8b7 commit 1247c80
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how

## [Unreleased]

### Added

- ([GH-666](https://github.com/puppetlabs/puppet-vscode/issues/666)) Add Puppetfile view to Puppet ToolBar

### Changed

- ([GH-649](https://github.com/puppetlabs/puppet-vscode/issues/649)) Reduce activation events for extension
Expand Down
36 changes: 35 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "0.26.1",
"editorComponents": {
"editorServices": {
"release": "0.26.0"
"release": "0.26.1"
},
"editorSyntax": {
"release": "1.3.6"
Expand Down Expand Up @@ -194,6 +194,24 @@
"light": "assets/icons/light/sync.svg",
"dark": "assets/icons/dark/sync.svg"
}
},
{
"command": "puppet.goToPuppetfileDefinition",
"title": "Go to definition",
"enablement": "puppet:puppetfileEnabled",
"icon": {
"light": "assets/icons/light/sync.svg",
"dark": "assets/icons/dark/sync.svg"
}
},
{
"command": "puppet.refreshPuppetfileDependencies",
"title": "Refresh Puppetfile View",
"enablement": "puppet:puppetfileEnabled",
"icon": {
"light": "assets/icons/light/sync.svg",
"dark": "assets/icons/dark/sync.svg"
}
}
],
"viewsContainers": {
Expand All @@ -209,13 +227,23 @@
{
"view": "puppetFacts",
"contents": "No facts found\n[Refresh](command:puppet.refreshFacts)"
},
{
"view": "puppetfile",
"contents": "No Puppetfile found\n[Refresh](command:puppet.refreshPuppetfileDependencies)",
"when": "puppet:puppetfileEnabled"
}
],
"views": {
"puppet-toolbar": [
{
"id": "puppetFacts",
"name": "Facts"
},
{
"id": "puppetfile",
"name": "Puppetfile",
"when": "puppet:puppetfileEnabled"
}
]
},
Expand Down Expand Up @@ -323,6 +351,12 @@
"when": "view == puppetFacts",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "puppet.goToPuppetfileDefinition",
"when": "view == puppetfile"
}
]
},
"configurationDefaults": {
Expand Down
11 changes: 11 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
'use strict';

import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { CreateAggregrateConfiguration, IAggregateConfiguration } from './configuration';
import { IFeature } from './feature';
Expand All @@ -22,6 +23,7 @@ import { OutputChannelLogger } from './logging/outputchannel';
import { ConnectionType, legacySettings, ProtocolType, PuppetInstallType, SettingsFromWorkspace } from './settings';
import { reporter } from './telemetry';
import { PuppetFactsProvider } from './views/facts';
import { PuppetfileProvider } from './views/puppetfile';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const axios = require('axios');
Expand Down Expand Up @@ -68,6 +70,12 @@ export function activate(context: vscode.ExtensionContext) {
pdkVersion: configSettings.ruby.pdkVersion,
});

const puppetfile = path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, 'Puppetfile');
const exists = fs.existsSync(puppetfile);
if (exists && configSettings.workspace.editorService.enable) {
vscode.commands.executeCommand('setContext', 'puppet:puppetfileEnabled', true);
}

const statusBar = new PuppetStatusBarFeature([puppetLangID, puppetFileLangID], configSettings, logger, context);

extensionFeatures = [
Expand Down Expand Up @@ -120,6 +128,9 @@ export function activate(context: vscode.ExtensionContext) {

const facts = new PuppetFactsProvider(connectionHandler);
vscode.window.registerTreeDataProvider('puppetFacts', facts);

const puppetfileView = new PuppetfileProvider(connectionHandler);
vscode.window.registerTreeDataProvider('puppetfile', puppetfileView);
}

export function deactivate() {
Expand Down
142 changes: 142 additions & 0 deletions src/views/puppetfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import * as path from 'path';
import {
commands,
Event,
EventEmitter,
ProviderResult,
ThemeIcon,
TreeDataProvider,
TreeItem,
TreeItemCollapsibleState,
Uri,
ViewColumn,
window,
workspace,
} from 'vscode';
import { RequestType } from 'vscode-languageclient';
import { ConnectionHandler } from '../handler';
import { reporter } from '../telemetry';

class PuppetfileDependencyItem extends TreeItem {
constructor(
public readonly name: string,
public readonly version: string,
public readonly start_line: number,
public readonly end_line: number,
public readonly collapsibleState: TreeItemCollapsibleState,
public readonly children?: Array<[string, PuppetfileDependencyItem]>,
) {
super(name, collapsibleState);
if (children) {
this.iconPath = ThemeIcon.Folder;
} else {
this.iconPath = new ThemeIcon('package');
}
}

get tooltip(): string {
return `${this.name}-${this.version}`;
}

get description(): string {
return this.version;
}
}

class PuppetfileDependency {
constructor(
public readonly name: string,
public readonly version: string,
public readonly start_line: number,
public readonly end_line: number,
) {
//
}
}

interface PuppetfileDependencyResponse {
dependencies: PuppetfileDependency[];
error: string[];
}

export class PuppetfileProvider implements TreeDataProvider<PuppetfileDependencyItem> {
private _onDidChangeTreeData: EventEmitter<PuppetfileDependencyItem | undefined> = new EventEmitter<
PuppetfileDependencyItem | undefined
>();
readonly onDidChangeTreeData: Event<PuppetfileDependencyItem | undefined>;

constructor(protected handler: ConnectionHandler) {
this.onDidChangeTreeData = this._onDidChangeTreeData.event;
commands.registerCommand('puppet.refreshPuppetfileDependencies', () => {
reporter.sendTelemetryEvent('puppet.refreshPuppetfileDependencies');
this.refresh();
});
commands.registerCommand('puppet.goToPuppetfileDefinition', (puppetModule: PuppetfileDependencyItem) => {
reporter.sendTelemetryEvent('puppet.goToPuppetfileDefinition');

const workspaceFolder = workspace.workspaceFolders[0].uri;
const puppetfile = path.join(workspaceFolder.fsPath, 'Puppetfile');
workspace.openTextDocument(puppetfile).then((doc) => {
const line = doc.lineAt(+puppetModule.start_line);
window.showTextDocument(doc, {
preserveFocus: true,
preview: false,
selection: line.range,
viewColumn: ViewColumn.Active,
});
});
});
}

refresh(): void {
this._onDidChangeTreeData.fire(null);
}

getTreeItem(element: PuppetfileDependencyItem): TreeItem | Thenable<PuppetfileDependencyItem> {
return element;
}

getChildren(element?: PuppetfileDependencyItem): Promise<PuppetfileDependencyItem[]> {
if (element) {
return Promise.resolve(element.children.map((e) => e[1]));
} else {
return this.getPuppetfileDependenciesFromLanguageServer();
}
}

private async getPuppetfileDependenciesFromLanguageServer(): Promise<PuppetfileDependencyItem[]> {
await this.handler.languageClient.onReady();

const fileUri = Uri.file(path.join(workspace.workspaceFolders[0].uri.fsPath, 'Puppetfile'));
/*
We use openTextDocument here because we need to parse whether or not a user has opened a
Puppetfile or not. This triggers onDidOpen notification which sends the content of the Puppetfile
to the Puppet Language Server which caches the content for puppetfile-resolver to parse
*/
return workspace.openTextDocument(fileUri).then(async () => {
const results = await this.handler.languageClient.sendRequest(
new RequestType<never, PuppetfileDependencyResponse, void, void>('puppetfile/getDependencies'),
{
uri: fileUri.toString(),
},
);

reporter.sendTelemetryEvent('puppetfileView');

if (results.error) {
window.showErrorMessage(`${results.error}`);
}

const list = results.dependencies.map((d) => {
return new PuppetfileDependencyItem(d.name, d.version, d.start_line, d.end_line, TreeItemCollapsibleState.None);
});

return list;
});
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
getParent?(element: PuppetfileDependencyItem): ProviderResult<PuppetfileDependencyItem> {
throw new Error('Method not implemented.');
}
}

0 comments on commit 1247c80

Please sign in to comment.