Skip to content

Commit

Permalink
WIP #162 Implement signature help for instructions
Browse files Browse the repository at this point in the history
Signature help for instruction arguments have been added for:
- ARG
- SHELL
- STOPSIGNAL
- USER
- WORKDIR

Signed-off-by: Remy Suen <[email protected]>
  • Loading branch information
rcjsuen committed Aug 23, 2017
1 parent ceeb7ba commit cad55bc
Show file tree
Hide file tree
Showing 8 changed files with 954 additions and 12 deletions.
15 changes: 11 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ All notable changes to this project will be documented in this file.
- warn if ENV or LABEL is missing closing quote ([#143](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/143))
- warn if FROM's build stage name is invalid ([#132](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/132))
- warn if an invalid unit of time is used in a duration flag ([#152](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/152))
- textDocument/signatureHelp ([#147](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/147))
- escape parser directive
- COPY's --from flag
- HEALTHCHECK CMD flags
- textDocument/signatureHelp
- escape parser directive ([#147](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/147))
- instruction flags ([#147](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/147))
- COPY's --from
- HEALTHCHECK CMD flags
- instructions ([#162](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/162))
- ARG
- SHELL
- STOPSIGNAL
- USER
- WORKDIR

### Fixed
- correct handling of escaped quotes in ENV variables ([#144](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/144))
Expand Down
44 changes: 43 additions & 1 deletion src/dockerPlainText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,28 @@ export class PlainTextDocumentation {
"signatureEscape": "Sets this Dockerfile's escape character. If unspecified, the default escape character is `\\`.",
"signatureEscape_Param": "The character to use to escape characters and newlines in this Dockerfile.",

"signatureArg_Signature0": "Define a variable that users can pass a value to at build-time with `docker build`.",
"signatureArg_Signature0_Param": "The name of the variable.",
"signatureArg_Signature1": "Define a variable with an optional default value that users can override at build-time with `docker build`.",
"signatureArg_Signature1_Param1": "The default value of the variable.",
"signatureCopyFlagFrom": "Set the build stage to use as the source location of this copy instruction instead of the build's context.",
"signatureCopyFlagFrom_Param": "The build stage or image name to use as the source. Also may be a numeric index.",
"signatureHealthcheck": "Define how Docker should test the container to check that it is still working.",
"signatureShell": "Override default shell used for the shell form of commands.",
"signatureShell_Param1": "The shell executable to use.",
"signatureShell_Param2": "The parameters to the shell executable.",
"signatureStopsignal": "Set the system call signal to use to send to the container to exit.",
"signatureStopsignal_Param": "The signal to send to the container to exit. This may be an valid unsigned number or a signal name in the SIGNAME format such as SIGKILL.",
"signatureUser_Signature0": "Set the user name to use for running any RUN, CMD, and ENTRYPOINT instructions that follow.",
"signatureUser_Signature0_Param": "The user name to use.",
"signatureUser_Signature1": "Set the user name and user group to use for running any RUN, CMD, and ENTRYPOINT instructions that follow.",
"signatureUser_Signature1_Param1": "The group name to use.",
"signatureUser_Signature2": "Set the UID to use for running any RUN, CMD, and ENTRYPOINT instructions that follow.",
"signatureUser_Signature2_Param": "The UID to use.",
"signatureUser_Signature3": "Set the UID and GID to use for running any RUN, CMD, and ENTRYPOINT instructions that follow.",
"signatureUser_Signature3_Param1": "The GID to use.",
"signatureWorkdir": "Set the working directory for any ADD, COPY, CMD, ENTRYPOINT, or RUN instructions that follow.",
"signatureWorkdir_Param": "The absolute or relative path to use as the working directory. Will be created if it does not exist.",

"proposalArgNameOnly": "Define a variable that users can set at build-time when using `docker build`.\n\n",
"proposalArgDefaultValue": "Define a variable with the given default value that users can override at build-time when using `docker build`.\n\n",
Expand Down Expand Up @@ -165,13 +184,36 @@ export class PlainTextDocumentation {
signatureEscape: this.dockerMessages["signatureEscape"],
signatureEscape_Param: this.dockerMessages["signatureEscape_Param"],

signatureArg_Signature0: this.dockerMessages["signatureArg_Signature0"],
signatureArg_Signature0_Param: this.dockerMessages["signatureArg_Signature0_Param"],
signatureArg_Signature1: this.dockerMessages["signatureArg_Signature1"],
signatureArg_Signature1_Param0: this.dockerMessages["signatureArg_Signature0_Param"],
signatureArg_Signature1_Param1: this.dockerMessages["signatureArg_Signature1_Param1"],
signatureCopyFlagFrom: this.dockerMessages["signatureCopyFlagFrom"],
signatureCopyFlagFrom_Param: this.dockerMessages["signatureCopyFlagFrom_Param"],
signatureHealthcheck: this.dockerMessages["signatureHealthcheck"],
signatureHealthcheckFlagInterval_Param: this.dockerMessages["hoverHealthcheckFlagInterval"],
signatureHealthcheckFlagRetries_Param: this.dockerMessages["hoverHealthcheckFlagRetries"],
signatureHealthcheckFlagStartPeriod_Param: this.dockerMessages["hoverHealthcheckFlagStartPeriod"],
signatureHealthcheckFlagTimeout_Param: this.dockerMessages["hoverHealthcheckFlagTimeout"]
signatureHealthcheckFlagTimeout_Param: this.dockerMessages["hoverHealthcheckFlagTimeout"],
signatureShell: this.dockerMessages["signatureShell"],
signatureShell_Param1: this.dockerMessages["signatureShell_Param1"],
signatureShell_Param2: this.dockerMessages["signatureShell_Param2"],
signatureShell_Param3: this.dockerMessages["signatureShell_Param2"],
signatureStopsignal: this.dockerMessages["signatureStopsignal"],
signatureStopsignal_Param: this.dockerMessages["signatureStopsignal_Param"],
signatureUser_Signature0: this.dockerMessages["signatureUser_Signature0"],
signatureUser_Signature0_Param: this.dockerMessages["signatureUser_Signature0_Param"],
signatureUser_Signature1: this.dockerMessages["signatureUser_Signature1"],
signatureUser_Signature1_Param0: this.dockerMessages["signatureUser_Signature0"],
signatureUser_Signature1_Param1: this.dockerMessages["signatureUser_Signature1_Param1"],
signatureUser_Signature2: this.dockerMessages["signatureUser_Signature2"],
signatureUser_Signature2_Param: this.dockerMessages["signatureUser_Signature2_Param"],
signatureUser_Signature3: this.dockerMessages["signatureUser_Signature3"],
signatureUser_Signature3_Param0: this.dockerMessages["signatureUser_Signature2_Param"],
signatureUser_Signature3_Param1: this.dockerMessages["signatureUser_Signature3_Param1"],
signatureWorkdir: this.dockerMessages["signatureWorkdir"],
signatureWorkdir_Param: this.dockerMessages["signatureWorkdir_Param"]
};
}

Expand Down
275 changes: 271 additions & 4 deletions src/dockerSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {
TextDocument, Position, SignatureHelp
TextDocument, Range, Position, SignatureHelp
} from 'vscode-languageserver';
import { Dockerfile } from './parser/dockerfile';
import { Instruction } from './parser/instruction';
import { Arg } from './parser/instructions/arg';
import { Copy } from './parser/instructions/copy';
import { Healthcheck } from './parser/instructions/healthcheck';
import { Shell } from './parser/instructions/shell';
import { PlainTextDocumentation } from './dockerPlainText';
import { DockerfileParser } from './parser/dockerfileParser';
import { Util, DIRECTIVE_ESCAPE } from './docker';
Expand Down Expand Up @@ -43,9 +45,9 @@ export class DockerSignatures {
}
}

let signatureHelp = this.getInstructionSignatures(dockerfile.getInstructions(), position);
let signatureHelp = this.getInstructionSignatures(document, dockerfile.getInstructions(), position);
if (!signatureHelp) {
signatureHelp = this.getInstructionSignatures(dockerfile.getOnbuildTriggers(), position);
signatureHelp = this.getInstructionSignatures(document, dockerfile.getOnbuildTriggers(), position);
if (!signatureHelp) {
signatureHelp = {
signatures: [],
Expand All @@ -58,9 +60,76 @@ export class DockerSignatures {
return signatureHelp;
}

private getInstructionSignatures(instructions: Instruction[], position: Position): SignatureHelp {
private getInstructionSignatures(document: TextDocument, instructions: Instruction[], position: Position): SignatureHelp {
for (let instruction of instructions) {
if (!Util.isInsideRange(position, instruction.getRange())) {
continue;
} else if (Util.isInsideRange(position, instruction.getInstructionRange())) {
return null;
}

switch (instruction.getKeyword()) {
case "ARG":
let argSignatureHelp: SignatureHelp = {
signatures: [
{
label: "ARG name",
documentation: this.documentation.getDocumentation("signatureArg_Signature0"),
parameters: [
{
label: "name",
documentation: this.documentation.getDocumentation("signatureArg_Signature0_Param")
}
]
},
{
label: "ARG name=defaultValue",
documentation: this.documentation.getDocumentation("signatureArg_Signature1"),
parameters: [
{
label: "name",
documentation: this.documentation.getDocumentation("signatureArg_Signature1_Param0")
},
{
label: "defaultValue",
documentation: this.documentation.getDocumentation("signatureArg_Signature1_Param1")
}
]
}
],
activeSignature: 0,
activeParameter: 0
};

let content = instruction.getTextContent();
let index = content.indexOf('=');
if (index !== -1) {
argSignatureHelp = {
signatures: [
{
label: "ARG name=defaultValue",
documentation: this.documentation.getDocumentation("signatureArg_Signature1"),
parameters: [
{
label: "name",
documentation: this.documentation.getDocumentation("signatureArg_Signature1_Param0")
},
{
label: "defaultValue",
documentation: this.documentation.getDocumentation("signatureArg_Signature1_Param1")
}
]
}
],
activeSignature: 0,
activeParameter: 0
};

if (document.offsetAt(position) > document.offsetAt(instruction.getRange().start) + index) {
argSignatureHelp.activeParameter = 1;
}
}
return argSignatureHelp;
case "COPY":
let flag = (instruction as Copy).getFromFlag();
if (flag !== null) {
Expand Down Expand Up @@ -163,6 +232,204 @@ export class DockerSignatures {
}
}
break;
case "SHELL":
let shell = instruction as Shell;
let shellSignatureHelp: SignatureHelp = {
signatures: [
{
label: "SHELL [ \"executable\", \"parameter\", ... ]",
documentation: this.documentation.getDocumentation("signatureShell"),
parameters: [
{
label: "["
},
{
label: "\"executable\"",
documentation: this.documentation.getDocumentation("signatureShell_Param1")
},
{
label: "\"parameter\"",
documentation: this.documentation.getDocumentation("signatureShell_Param2")
},
{
label: "...",
documentation: this.documentation.getDocumentation("signatureShell_Param3")
},
{
label: "]"
}
]
}
],
activeSignature: 0,
activeParameter: null
};

const closingBracket = shell.getClosingBracket();
if (closingBracket) {
let range = closingBracket.getRange();
if (range.end.line === position.line && range.end.character === position.character) {
shellSignatureHelp.activeParameter = 4;
return shellSignatureHelp;
} else if (closingBracket.isBefore(position)) {
return null;
}
}

const parameter = shell.getParameter();
if (parameter && parameter.isBefore(position)) {
shellSignatureHelp.activeParameter = 3;
return shellSignatureHelp;
}

const executable = shell.getExecutable();
if (executable && executable.isBefore(position)) {
shellSignatureHelp.activeParameter = 2;
return shellSignatureHelp;
}

const openingBracket = shell.getOpeningBracket();
if (openingBracket) {
let range = openingBracket.getRange();
if ((range.end.line === position.line && range.end.character === position.character) || openingBracket.isBefore(position)) {
shellSignatureHelp.activeParameter = 1;
return shellSignatureHelp;
}
}

shellSignatureHelp.activeParameter = 0;
return shellSignatureHelp;
case "STOPSIGNAL":
return {
signatures: [
{
label: "STOPSIGNAL signal",
documentation: this.documentation.getDocumentation("signatureStopsignal"),
parameters: [
{
label: "signal",
documentation: this.documentation.getDocumentation("signatureStopsignal_Param")
}
]
}
],
activeSignature: 0,
activeParameter: 0
};
case "USER":
let userSignatureHelp = {
signatures: [
{
label: "USER user",
documentation: this.documentation.getDocumentation("signatureUser_Signature0"),
parameters: [
{
label: "user",
documentation: this.documentation.getDocumentation("signatureUser_Signature0_Param")
}
]
},
{
label: "USER user:group",
documentation: this.documentation.getDocumentation("signatureUser_Signature1"),
parameters: [
{
label: "user",
documentation: this.documentation.getDocumentation("signatureUser_Signature1_Param0")
},
{
label: "group",
documentation: this.documentation.getDocumentation("signatureUser_Signature1_Param1")
}
]
},
{
label: "USER uid",
documentation: this.documentation.getDocumentation("signatureUser_Signature2"),
parameters: [
{
label: "uid",
documentation: this.documentation.getDocumentation("signatureUser_Signature2_Param")
}
]
},
{
label: "USER uid:gid",
documentation: this.documentation.getDocumentation("signatureUser_Signature3"),
parameters: [
{
label: "uid",
documentation: this.documentation.getDocumentation("signatureUser_Signature3_Param0")
},
{
label: "gid",
documentation: this.documentation.getDocumentation("signatureUser_Signature3_Param1")
}
]
}
],
activeSignature: 0,
activeParameter: 0
};
let userSeparatorIndex = instruction.getTextContent().indexOf(":");
if (userSeparatorIndex !== -1) {
userSignatureHelp = {
signatures: [
{
label: "USER user:group",
documentation: this.documentation.getDocumentation("signatureUser_Signature1"),
parameters: [
{
label: "user",
documentation: this.documentation.getDocumentation("signatureUser_Signature1_Param0")
},
{
label: "group",
documentation: this.documentation.getDocumentation("signatureUser_Signature1_Param1")
}
]
},
{
label: "USER uid:gid",
documentation: this.documentation.getDocumentation("signatureUser_Signature3"),
parameters: [
{
label: "uid",
documentation: this.documentation.getDocumentation("signatureUser_Signature3_Param0")
},
{
label: "gid",
documentation: this.documentation.getDocumentation("signatureUser_Signature3_Param1")
}
]
}
],
activeSignature: 0,
activeParameter: 0
};

if (document.offsetAt(position) > document.offsetAt(instruction.getRange().start) + userSeparatorIndex) {
userSignatureHelp.activeParameter = 1;
}
}
return userSignatureHelp;
case "WORKDIR":
return {
signatures: [
{
label: "WORKDIR /the/workdir/path",
documentation: this.documentation.getDocumentation("signatureWorkdir"),
parameters: [
{
label: "/the/workdir/path",
documentation: this.documentation.getDocumentation("signatureWorkdir_Param")
}
]
}
],
activeSignature: 0,
activeParameter: 0
};
}
}
return null;
Expand Down
Loading

0 comments on commit cad55bc

Please sign in to comment.