Skip to content

Commit

Permalink
Merge pull request #10 from Microsoft/feature/apiupdates
Browse files Browse the repository at this point in the history
New API updates
  • Loading branch information
Peter Jausovec authored and Peter Jausovec committed Nov 12, 2015
2 parents c7308e2 + 0f72f46 commit 31fb1d1
Show file tree
Hide file tree
Showing 21 changed files with 823 additions and 1,141 deletions.
67 changes: 67 additions & 0 deletions dockerCompose/dockerComposeCompletionItemProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

'use strict';

import {TextDocument, Position, CancellationToken, CompletionItem, CompletionItemProvider, CompletionItemKind, Uri} from 'vscode';
import helper = require('../helpers/suggestSupportHelper');
import {DOCKER_COMPOSE_KEY_INFO} from './dockerComposeKeyInfo';
import hub = require('../dockerHubApi');

export class DockerComposeCompletionItemProvider implements CompletionItemProvider {

public triggerCharacters: string[] = [];
public excludeTokens: string[] = [];

public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise<CompletionItem[]> {
var yamlSuggestSupport = new helper.SuggestSupportHelper();

// Get the line where intellisense was invoked on (e.g. 'image: u').
var line = document.lineAt(position.line).text;

if (line.length === 0) {
// empty line
return Promise.resolve(this.suggestKeys(''));
}

let range = document.getWordRangeAtPosition(position);

// Get the text where intellisense was invoked on (e.g. 'u').
let word = range && document.getText(range) || '';

var textBefore = line.substring(0, position.character);
if (/^\s*[\w_]*$/.test(textBefore)) {
// on the first token
return Promise.resolve(this.suggestKeys(word));
}

// Matches strings like: 'image: "ubuntu'
var imageTextWithQuoteMatchYaml = textBefore.match(/^\s*image\s*\:\s*"([^"]*)$/);

if (imageTextWithQuoteMatchYaml) {
var imageText = imageTextWithQuoteMatchYaml[1];
return yamlSuggestSupport.suggestImages(imageText);
}

// Matches strings like: 'image: ubuntu'
var imageTextWithoutQuoteMatch = textBefore.match(/^\s*image\s*\:\s*([\w\:\/]*)/);

if (imageTextWithoutQuoteMatch) {
var imageText = imageTextWithoutQuoteMatch[1];
return yamlSuggestSupport.suggestImages(imageText);
}

return Promise.resolve([]);
}

private suggestKeys(word: string): CompletionItem[] {
return Object.keys(DOCKER_COMPOSE_KEY_INFO).map(ruleName => {
var completionItem = new CompletionItem(ruleName);
completionItem.kind = CompletionItemKind.Keyword;
completionItem.insertText = ruleName + ': ';
completionItem.documentation = DOCKER_COMPOSE_KEY_INFO[ruleName];
return completionItem;
});
}
}
106 changes: 106 additions & 0 deletions dockerCompose/dockerComposeKeyInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

// https://docs.docker.com/compose/yml/
export var DOCKER_COMPOSE_KEY_INFO: { [keyName: string]: string; } = {
'image': (
"Tag or partial image ID. Can be local or remote - Compose will attempt to pull if it doesn't exist locally."
),
'build': (
"Path to a directory containing a Dockerfile. When the value supplied is a relative path, it is interpreted as relative to the " +
"location of the yml file itself. This directory is also the build context that is sent to the Docker daemon.\n\n" +
"Compose will build and tag it with a generated name, and use that image thereafter."
),
'command': (
"Override the default command."
),
'links': (
"Link to containers in another service. Either specify both the service name and the link alias (`CONTAINER:ALIAS`), or " +
"just the service name (which will also be used for the alias)."
),
'external_links': (
"Link to containers started outside this `docker-compose.yml` or even outside of Compose, especially for containers that " +
"provide shared or common services. `external_links` follow " +
"semantics similar to `links` when specifying both the container name and the link alias (`CONTAINER:ALIAS`)."
),
'ports': (
"Expose ports. Either specify both ports (`HOST:CONTAINER`), or just the container port (a random host port will be chosen).\n\n" +
"**Note**: When mapping ports in the `HOST:CONTAINER` format, you may experience erroneous results when using a container port " +
"lower than 60, because YAML will parse numbers in the format `xx:yy` as sexagesimal (base 60). For this reason, we recommend " +
"always explicitly specifying your port mappings as strings."
),
'expose': (
"Expose ports without publishing them to the host machine - they'll only be accessible to linked services. \n" +
"Only the internal port can be specified."
),
'volumes': (
"Mount paths as volumes, optionally specifying a path on the host machine (`HOST:CONTAINER`), or an access mode (`HOST:CONTAINER:ro`)."
),
'volumes_from': (
"Mount all of the volumes from another service or container."
),
'environment': (
"Add environment variables. You can use either an array or a dictionary.\n\n" +
"Environment variables with only a key are resolved to their values on the machine Compose is running on, which can be helpful for secret or host-specific values."
),
'env_file': (
"Add environment variables from a file. Can be a single value or a list.\n\n" +
"If you have specified a Compose file with `docker-compose -f FILE`, paths in `env_file` are relative to the directory that file is in.\n\n" +
"Environment variables specified in `environment` override these values."
),
'net': (
"Networking mode. Use the same values as the docker client `--net` parameter."
),
'pid': (
"Sets the PID mode to the host PID mode. This turns on sharing between container and the host operating system the PID address space. " +
"Containers launched with this flag will be able to access and manipulate other containers in the bare-metal machine's namespace and vise-versa."
),
'dns': (
"Custom DNS servers. Can be a single value or a list."
),
'cap_add': (
"Add or drop container capabilities. See `man 7 capabilities` for a full list."
),
'cap_drop': (
"Add or drop container capabilities. See `man 7 capabilities` for a full list."
),
'dns_search': (
"Custom DNS search domains. Can be a single value or a list."
),
'cgroup_parent': (
"Specify an optional parent cgroup for the container."
),
'container_name': (
"Specify custom container name, rather than a generated default name."
),
'devices': (
"List of device mappings. Uses the same format as the `--device` docker client create option."
),
'dockerfile': (
"Alternate dockerfile. Compose will use an alternate file to build with. Using `dockerfile` together with `image` is not allowed. Attempting to do so results in an error."
),
'extends': (
"Extend another service, in the current file or another, optionally overriding configuration.\nYou can use `extends` on any service together with other configuration keys. " +
"The `extends` value must be a dictionary defined with a required `service` and an optional `file` key."
),
'extra_hosts': (
"Add hostname mappings. Use the same values as the docker client `--add-host` parameter."
),
'labels': (
"Add metadata to containers using Docker labels. You can either use an array or a dictionary.\n" +
"It's recommended that you use reverse-DNS notation to prevent your labels from conflicting with those used by other software."
),
'log_driver': (
"Specify a logging driver for the service's containers, as with the `--log-driver` option for docker run. The default value is json-file."
),
'log_opt': (
"Specify logging options with `log_opt` for the logging driver, as with the `--log-opt` option for docker run."
),
'security_opt': (
"Override the default labeling scheme for each container."
),
'volume_driver': (
"If you use a volume name (instead of a volume path), you may also specify a `volume_driver`."
)
}
84 changes: 84 additions & 0 deletions dockerCompose/dockerComposeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

'use strict';

import vscode = require('vscode');
import {Parser, TokenType, IToken} from '../parser';

export class DockerComposeParser extends Parser {
constructor() {
var parseRegex = /\:+$/g;
super(parseRegex, /docker\-compose\.yml$/);
}

parseLine(textLine: vscode.TextLine): IToken[] {
var r: IToken[] = [];
var lastTokenEndIndex = 0, lastPushedToken: IToken = null;

var emit = (end: number, type: TokenType) => {
if (end <= lastTokenEndIndex) {
return;
}

if (lastPushedToken && lastPushedToken.type === type) {
// merge with last pushed token
lastPushedToken.endIndex = end;
lastTokenEndIndex = end;
return;
}

lastPushedToken = {
startIndex: lastTokenEndIndex,
endIndex: end,
type: type
};

r.push(lastPushedToken);
lastTokenEndIndex = end;
};

var inString = false;
var idx = textLine.firstNonWhitespaceCharacterIndex;
var line = textLine.text;

for (var i = idx, len = line.length; i < len; i++) {
var ch = line.charAt(i);

if (inString) {
if (ch === '"' && line.charAt(i - 1) !== '\\') {
inString = false;
emit(i + 1, TokenType.String);
}

continue;
}

if (ch === '"') {
emit(i, TokenType.Text);
inString = true;
continue;
}

if (ch === '#') {
// Comment the rest of the line
emit(i, TokenType.Text);
emit(line.length, TokenType.Comment);
break;
}

if (ch === ':') {
emit(i + 1, TokenType.Key);
}

if (ch === ' ' || ch === '\t') {
emit(i, TokenType.Text);
emit(i + 1, TokenType.Whitespace);
}
}

emit(line.length, TokenType.Text);
return r;
}
}
30 changes: 16 additions & 14 deletions dockerExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import dockerfileDef = require('./dockerfile/dockerfileDef');
import dockerExtraInfoSupport = require('./dockerfile/dockerfileExtraInfo');
import dockerSuggestSupport = require('./dockerfile/dockerfileSuggestSupport');
import yamlExtraInfoSupport = require('./yaml/yamlExtraInfo');
import yamlSuggestSupport = require('./yaml/yamlSuggestSupport');
import {DockerHoverProvider} from './dockerHoverProvider';
import {DockerfileCompletionItemProvider} from './dockerfile/dockerfileCompletionItemProvider';
import {DockerComposeCompletionItemProvider} from './dockerCompose/dockerComposeCompletionItemProvider';
import {DOCKERFILE_KEY_INFO} from './dockerfile/dockerfileKeyInfo';
import {DOCKER_COMPOSE_KEY_INFO} from './dockerCompose/dockerComposeKeyInfo';
import {DockerComposeParser} from './dockerCompose/dockerComposeParser';
import {DockerfileParser} from './dockerfile/dockerfileParser';
import vscode = require('vscode');

export function activate(subscriptions: vscode.Disposable[]) {

var DOCKERFILE_MODE_ID = 'dockerfile';
subscriptions.push(vscode.Modes.registerMonarchDefinition(DOCKERFILE_MODE_ID, dockerfileDef.language));
subscriptions.push(vscode.Modes.ExtraInfoSupport.register(DOCKERFILE_MODE_ID, new dockerExtraInfoSupport.ExtraInfoSupport()));
subscriptions.push(vscode.Modes.SuggestSupport.register(DOCKERFILE_MODE_ID, new dockerSuggestSupport.SuggestSupport()));
export function activate(ctx: vscode.ExtensionContext): void {
const DOCKERFILE_MODE_ID: vscode.DocumentFilter = { language: 'dockerfile', scheme: 'file' };
var dockerHoverProvider = new DockerHoverProvider(new DockerfileParser(), DOCKERFILE_KEY_INFO);
ctx.subscriptions.push(vscode.languages.registerHoverProvider(DOCKERFILE_MODE_ID, dockerHoverProvider));
ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(DOCKERFILE_MODE_ID, new DockerfileCompletionItemProvider(), '.'));

var YAML_MODE_ID = 'yaml';
subscriptions.push(vscode.Modes.ExtraInfoSupport.register(YAML_MODE_ID, new yamlExtraInfoSupport.ExtraInfoSupport()));
subscriptions.push(vscode.Modes.SuggestSupport.register(YAML_MODE_ID, new yamlSuggestSupport.SuggestSupport()));
const YAML_MODE_ID: vscode.DocumentFilter = { language: 'yaml', scheme: 'file'};
var yamlHoverProvider = new DockerHoverProvider(new DockerComposeParser(), DOCKER_COMPOSE_KEY_INFO);
ctx.subscriptions.push(vscode.languages.registerHoverProvider(YAML_MODE_ID, yamlHoverProvider));
ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(YAML_MODE_ID, new DockerComposeCompletionItemProvider(), '.'))
}
82 changes: 82 additions & 0 deletions dockerHoverProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

'use strict';

import { Range, TextDocument, Position, CancellationToken, HoverProvider, Hover, MarkedString } from 'vscode';
import parser = require('./parser');
import hub = require('./dockerHubApi');
import suggestHelper = require('./helpers/suggestSupportHelper');

export class DockerHoverProvider implements HoverProvider {
_parser: parser.Parser;
_keyInfo: { [keyName: string]: string; };

// Provide the parser you want to use as well as keyinfo dictionary.
constructor(wordParser: parser.Parser, keyInfo: { [keyName: string]: string; }) {
this._parser = wordParser;
this._keyInfo = keyInfo;
}

public provideHover(document: TextDocument, position: Position, token: CancellationToken): Thenable<Hover> {
if (!this._parser.isFileSupported(document.fileName)) {
return Promise.resolve(null);
}

var line = document.lineAt(position.line);

if (line.text.length === 0) {
return Promise.resolve(null);
}

var tokens = this._parser.parseLine(line);
return this._computeInfoForLineWithTokens(line.text, tokens, position);
}

private _computeInfoForLineWithTokens(line: string, tokens: parser.IToken[], position: Position): Promise<Hover> {
var possibleTokens = this._parser.tokensAtColumn(tokens, position.character);

return Promise.all(possibleTokens.map(tokenIndex => this._computeInfoForToken(line, tokens, tokenIndex))).then((results) => {
return possibleTokens.map((tokenIndex, arrayIndex) => {
return {
startIndex: tokens[tokenIndex].startIndex,
endIndex: tokens[tokenIndex].endIndex,
result: results[arrayIndex]
};
});
}).then((results) => {
var r = results.filter(r => !!r.result);
if (r.length === 0) {
return null;
}

let range = new Range(position.line, r[0].startIndex, position.line, r[0].endIndex);
let hover = new Hover(r[0].result, range);
return hover;
});
}

private _computeInfoForToken(line: string, tokens: parser.IToken[], tokenIndex: number): Promise<MarkedString[]> {
// -------------
// Detect hovering on a key
if (tokens[tokenIndex].type === parser.TokenType.Key) {
var keyName = this._parser.keyNameFromKeyToken(this._parser.tokenValue(line, tokens[tokenIndex])).trim();
var r = this._keyInfo[keyName];
if (r) {
return Promise.resolve([r]);
}
}

// -------------
// Detect <<image: [["something"]]>>
// Detect <<image: [[something]]>>
var helper = new suggestHelper.SuggestSupportHelper();
var r2 = helper.getImageNameHover(line, this._parser, tokens, tokenIndex);
if (r2) {
return r2;
}

return null;
}
}
Loading

0 comments on commit 31fb1d1

Please sign in to comment.