Skip to content

Commit

Permalink
Merge branch 'master' into issue-34
Browse files Browse the repository at this point in the history
  • Loading branch information
JPinkney authored Jun 13, 2017
2 parents 6940bef + ccaeb0b commit c231b2a
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 89 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,25 @@ VS Code extension that provides asssitance for authoring kubernetes
and Openshift configuration.

## Features
* YAML validation
* Kubernetes validation
* Kubernetes auto completion
![screencast](https://github.com/JPinkney/vscode-k8s/blob/master/images/demo.gif)

YAML validation:

      Detects whether the entire file is valid yaml

Kubernetes validation:

    Detects errors such as:

      Child node does not exist

      Command not found in kubernetes

      Incorrect type of value

Kubernetes auto completion:

    Auto completes on all commands and resorts to defaults for the value if found

## Developer Support

Expand Down
11 changes: 10 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"main": "./out/src/extension",
"contributes": {
"configuration": {
"properties": {
"yaml.trace.server": {
"type": "string",
"enum": [
Expand All @@ -26,9 +27,17 @@
],
"default": "off",
"description": "Traces the communication between VSCode and the languageServerExample service."
},
"k8s.glob": {
"type": [
"string"
],
"default": "",
"description": "Specifies the glob that will be used when validating yaml files as k8s"
}
}
},
}
},
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./",
Expand Down
4 changes: 2 additions & 2 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export function activate(context: ExtensionContext) {
documentSelector: ['yaml'],
synchronize: {
// Synchronize the setting section 'languageServerExample' to the server
configurationSection: 'yaml',
configurationSection: 'k8s',
// Notify the server about file changes to '.clientrc files contain in the workspace
fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
fileEvents: workspace.createFileSystemWatcher("**/*.yaml")
}
}

Expand Down
Binary file added images/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions server/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/../client/server/**/*.js" ],
"protocol": "legacy"
},
{
"type": "node",
"request": "launch",
"name": "Launch Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceRoot}/test/*.test.ts"
],
"preLaunchTask": "npm"
}
]
}
3 changes: 3 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
"node": "*"
},
"dependencies": {
"@types/mocha": "^2.2.41",
"glob": "^7.1.2",
"jsonc-parser": "^0.4.2",
"mocha": "^3.4.2",
"request-light": "^0.2.0",
"triesearch": "^1.0.2",
"vscode-languageserver": "^3.1.0",
Expand Down
180 changes: 97 additions & 83 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import Strings = require( './languageService/utils/strings');
import URI from './languageService/utils/uri';
import * as URL from 'url';
import fs = require('fs');
var glob = require('glob');

namespace VSCodeContentRequest {
export const type: RequestType<string, string, any, any> = new RequestType('vscode/content');
}

let pendingValidationRequests: { [uri: string]: NodeJS.Timer; } = {};
const validationDelayMs = 250;
let pendingValidationRequests: { [uri: string]: NodeJS.Timer; } = {};
let validDocuments: Array<String>;


// Create a connection for the server.
Expand Down Expand Up @@ -88,7 +90,9 @@ let languageService = getLanguageService(schemaRequestService, workspaceContext)
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent((change) => {
triggerValidation(change.document);
if(validDocuments.indexOf(change.document.uri) !== -1){
triggerValidation(change.document);
}
});

documents.onDidClose((event=>{
Expand All @@ -98,17 +102,50 @@ documents.onDidClose((event=>{

// The settings interface describe the server relevant settings part
interface Settings {
k8s: globSetting;
}

// hold the maxNumberOfProblems setting
// The settings have changed. Is send on server activation
// as well.
interface globSetting {
glob: string;
}

let globSetting: string;
connection.onDidChangeConfiguration((change) => {
let settings = <Settings>change.settings;
// Revalidate any open text documents
documents.all().forEach(validateTextDocument);
globSetting = settings.k8s.glob || "";
validateValidFiles();
});

function clearDiagnostics(){
//Clear all the previous diagnostics
documents.all().forEach(doc => {
connection.sendDiagnostics({ uri: doc.uri, diagnostics: [] });
});
}

function validateValidFiles(){
clearDiagnostics();

validDocuments = [];
glob(globSetting, function (er, files) {
if(er){
throw er;
}

files.forEach(file => {
documents.all().forEach(doc => {
let splitDocumentUri = doc.uri.split("/");
let strippedDocumentUri = splitDocumentUri[splitDocumentUri.length - 1];
if(strippedDocumentUri.indexOf(file) !== -1){
validDocuments.push(doc.uri);
triggerValidation(doc);
}
}
)});

})
}

function triggerValidation(textDocument: TextDocument): void {
cleanPendingValidation(textDocument);
pendingValidationRequests[textDocument.uri] = setTimeout(() => {
Expand All @@ -127,7 +164,7 @@ function cleanPendingValidation(textDocument: TextDocument): void {

function validateTextDocument(textDocument: TextDocument): void {
let yDoc= yamlLoader(textDocument.getText(),{});
if(yDoc !== undefined){
if(yDoc !== undefined){
let diagnostics = [];
if(yDoc.errors.length != 0){
diagnostics = yDoc.errors.map(error =>{
Expand All @@ -154,11 +191,60 @@ function validateTextDocument(textDocument: TextDocument): void {
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
});
}


}

connection.onDidChangeWatchedFiles((change) => {
// This handler provides the initial list of the completion items.
connection.onCompletion(textDocumentPosition => {
let document = documents.get(textDocumentPosition.textDocument.uri);
if(validDocuments.indexOf(document.uri) !== -1){
return completionHelper(document, textDocumentPosition);
}
return null;
});

function completionHelper(document: TextDocument, textDocumentPosition){

/*
* THIS IS A HACKY VERSION.
* Needed to get the parent node from the current node to support autocompletion.
*/

//Get the string we are looking at via a substring
let start = getLineOffsets(document.getText())[textDocumentPosition.position.line];
let end = document.offsetAt(textDocumentPosition.position);
let textLine = document.getText().substring(start, end);

//Check if the string we are looking at is a node
if(textLine.indexOf(":")){
//We need to add the ":" to load the nodes

let newText = "";

//This is for the empty line case
if(textLine.trim().length === 0){
//Add a temp node that is in the document but we don't use at all.
newText = document.getText().substring(0, end) + "holder:\r\n" + document.getText().substr(end+2)
//For when missing semi colon case
}else{
//Add a semicolon to the end of the current line so we can validate the node
newText = document.getText().substring(0, end) + ":\r\n" + document.getText().substr(end+2)
}

let yamlDoc:YAMLDocument = <YAMLDocument> yamlLoader(newText,{});
return languageService.doComplete(document, textDocumentPosition.position, yamlDoc);
}else{

//All the nodes are loaded
let yamlDoc:YAMLDocument = <YAMLDocument> yamlLoader(document.getText(),{});
return languageService.doComplete(document, textDocumentPosition.position, yamlDoc);
}

}

// This handler resolve additional information for the item selected in
// the completion list.
connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
return item;
});

function getLineOffsets(textDocString: String): number[] {
Expand All @@ -184,77 +270,5 @@ function getLineOffsets(textDocString: String): number[] {
return lineOffsets;
}

// This handler provides the initial list of the completion items.
connection.onCompletion(textDocumentPosition => {
let document = documents.get(textDocumentPosition.textDocument.uri);

/*
* THIS IS A HACKY VERSION.
* Needed to get the parent node from the current node to support autocompletion.
*/

//Get the string we are looking at via a substring
let start = getLineOffsets(document.getText())[textDocumentPosition.position.line];
let end = document.offsetAt(textDocumentPosition.position);
let textLine = document.getText().substring(start, end);

//Check if the string we are looking at is a node
if(textLine.indexOf(":")){
//We need to add the ":" to load the nodes

let newText = "";

//This is for the empty line case
if(textLine.trim().length === 0){
//Add a temp node that is in the document but we don't use at all.
newText = document.getText().substring(0, end) + "holder:\r\n" + document.getText().substr(end+2)
//For when missing semi colon case
}else{
//Add a semicolon to the end of the current line so we can validate the node
newText = document.getText().substring(0, end) + ":\r\n" + document.getText().substr(end+2)
}

let yamlDoc:YAMLDocument = <YAMLDocument> yamlLoader(newText,{});
return languageService.doComplete(document, textDocumentPosition.position, yamlDoc);
}else{

//All the nodes are loaded
let yamlDoc:YAMLDocument = <YAMLDocument> yamlLoader(document.getText(),{});
return languageService.doComplete(document, textDocumentPosition.position, yamlDoc);
}

});

// This handler resolve additional information for the item selected in
// the completion list.
connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
return item;
});


let t: Thenable<string>;

/*
connection.onDidOpenTextDocument((params) => {
// A text document got opened in VSCode.
// params.textDocument.uri uniquely identifies the document. For documents store on disk this is a file URI.
// params.textDocument.text the initial full content of the document.
connection.console.log(`${params.textDocument.uri} opened.`);
});
connection.onDidChangeTextDocument((params) => {
// The content of a text document did change in VSCode.
// params.textDocument.uri uniquely identifies the document.
// params.contentChanges describe the content changes to the document.
connection.console.log(`${params.textDocument.uri} changed: ${JSON.stringify(params.contentChanges)}`);
});
connection.onDidCloseTextDocument((params) => {
// A text document got closed in VSCode.
// params.textDocument.uri uniquely identifies the document.
connection.console.log(`${params.textDocument.uri} closed.`);
});
*/

// Listen on the connection
connection.listen();
18 changes: 18 additions & 0 deletions server/test/extension.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// Note: This example test is leveraging the Mocha test framework.
// Please refer to their documentation on https://mochajs.org/ for help.
//

// The module 'assert' provides assertion methods from node
var assert = require('assert');

// Defines a Mocha test suite to group tests of similar kind together
suite("Extension Tests", () => {

// Defines a Mocha unit test
test("Something 1", () => {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});

});
22 changes: 22 additions & 0 deletions server/test/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
//
// This file is providing the test runner to use when running extension tests.
// By default the test runner in use is Mocha based.
//
// You can provide your own test runner if you want to override it by exporting
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
// host can call to run the tests. The test runner is expected to use console.log
// to report the results back to the caller. When the tests are finished, return
// a possible error to the callback or null if none.

var testRunner = require('vscode/lib/testrunner');

// You can directly control Mocha options by uncommenting the following lines
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
testRunner.configure({
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
useColors: true // colored output from test results
});

module.exports = testRunner;

0 comments on commit c231b2a

Please sign in to comment.