Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Bug 517230 - Provide content assist for the escape directive in Docke…
…rfiles

If the user's text caret is currently inside the first comment of the
Dockerfile and there are no instructions preceding this first
comment, we should suggest the escape directive as a content assist
proposal if the user invokes content assist.

The user should also be provided with documentation assistance if the
user hovers over a comment that looks like an escape directive.

Signed-off-by: Remy Suen <remy.suen@gmail.com>
  • Loading branch information
rcjsuen committed May 25, 2017
1 parent 44e8aca commit ecb6c46
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 1 deletion.
Expand Up @@ -280,6 +280,19 @@ define([
assert.equal(proposal.escapePosition, offset - prefix.length + 13);
}

function assertDirectiveEscape(proposals, offset, prefix) {
assert.equal(proposals.length, 1);
assert.equal(proposals[0].proposal, "escape=`");
assert.equal(proposals[0].name, "escape");
assert.equal(proposals[0].description, "=`");
assert.equal(proposals[0].prefix, prefix);
assert.equal(proposals[0].overwrite, true);
assert.equal(proposals[0].positions.length, 1);
assert.equal(proposals[0].positions[0].offset, offset - prefix.length + 7);
assert.equal(proposals[0].positions[0].length, 1);
assert.equal(proposals[0].escapePosition, offset - prefix.length + 8);
}

function assertProposals(proposals, offset, prefix) {
for (var i = 0; i < proposals.length; i++) {
switch (proposals[i].name) {
Expand Down Expand Up @@ -1097,6 +1110,106 @@ define([
});
});

describe("directives", function() {
describe("escape", function() {
it("#", function() {
var proposals = compute("#", 1, {
prefix: "",
line: "#"
});
assertDirectiveEscape(proposals, 1, "");
});

it("# ", function() {
var proposals = compute("# ", 2, {
prefix: "",
line: "# "
});
assertDirectiveEscape(proposals, 2, "");
});

it("##", function() {
var proposals = compute("##", 1, {
prefix: "",
line: "##"
});
assertDirectiveEscape(proposals, 1, "");
});

it("# #", function() {
var proposals = compute("# #", 1, {
prefix: "",
line: "# #"
});
assertDirectiveEscape(proposals, 1, "");
});

it("# #", function() {
var proposals = compute("# #", 2, {
prefix: "",
line: "# #"
});
assertDirectiveEscape(proposals, 2, "");
});

it("#e", function() {
var proposals = compute("#e", 2, {
prefix: "e",
line: "#e"
});
assertDirectiveEscape(proposals, 2, "e");
});

it("# e", function() {
var proposals = compute("# e", 3, {
prefix: "e",
line: "# e"
});
assertDirectiveEscape(proposals, 3, "e");
});

it("#E", function() {
var proposals = compute("#E", 2, {
prefix: "E",
line: "#E"
});
assertDirectiveEscape(proposals, 2, "E");
});

it("#eS", function() {
var proposals = compute("#eS", 3, {
prefix: "eS",
line: "#eS"
});
assertDirectiveEscape(proposals, 3, "eS");
});

it("#e ", function() {
var proposals = compute("#e ", 3, {
prefix: "",
line: "#e "
});
assert.equal(proposals.length, 0);
});

it("#\n#", function() {
var proposals = compute("#\n#", 3, {
prefix: "",
line: "#"
});
assert.equal(proposals.length, 0);
});

it("#\n#e", function() {
var proposals = compute("#\n#e", 4, {
prefix: "e",
line: "#e"
});
assert.equal(proposals.length, 0);
});
});
})

describe('ONBUILD nesting', function() {
describe('prefix', function() {
it('ONBUILD W', function() {
Expand Down
Expand Up @@ -30,10 +30,12 @@ define([

Objects.mixin(DockerContentAssist.prototype, {
computeProposals: function (buffer, offset, context) {
var firstCommentIdx = -1;
var escapeCharacter = "\\";
directiveCheck: for (var i = 0; i < buffer.length; i++) {
switch (buffer.charAt(i)) {
case '#':
firstCommentIdx = i;
// in the first comment of the file, look for directives
var directive = "";
var capture = false;
Expand Down Expand Up @@ -70,7 +72,7 @@ define([
break;
}
}
break;
break directiveCheck;
case ' ':
case '\t':
// ignore whitespace
Expand All @@ -88,6 +90,14 @@ define([
commentCheck: for (i = offset - 1; i >= 0; i--) {
switch (buffer.charAt(i)) {
case '#':
if (i === firstCommentIdx) {
// we're in the first comment, might need to suggest
// the escape directive as a proposal
var directivePrefix = buffer.substring(i + 1, offset).trimLeft().toLowerCase();
if ("escape".indexOf(directivePrefix) === 0) {
return [ createEscape(context.prefix, offset - context.prefix.length, this.markdowns["escape"]) ];
}
}
// in a comment, no proposals to suggest
return [];
case ' ':
Expand Down Expand Up @@ -765,6 +775,25 @@ define([
};
}

function createEscape(prefix, offset, markdown) {
return {
name: "escape",
description: "=`",
proposal: "escape=`",
prefix: prefix,
overwrite: true,
positions: [
// linked mode for '`'
{
offset: offset + 7,
length: 1
}
],
escapePosition: offset + 8,
hover: markdown
};
}

return {
DockerContentAssist : DockerContentAssist
};
Expand Down
Expand Up @@ -182,6 +182,15 @@ define([
"WORKDIR relative/path\n" +
"```" +
i18nUtil.formatMessage.call(null, dockerMessages["hoverOnlineDocumentationFooter"], "https://docs.docker.com/engine/reference/builder/#workdir")
},

escape: {
type: "markdown",
content: dockerMessages["hoverEscape"] +
"```\n" +
"# escape=`\n" +
"```" +
i18nUtil.formatMessage.call(null, dockerMessages["hoverOnlineDocumentationFooter"], "https://docs.docker.com/engine/reference/builder/#escape")
}
};

Expand Down Expand Up @@ -218,6 +227,43 @@ define([
return editorContext.getLineStart(line);
}).then(function(lineStart) {
for (var i = lineStart; i < textLength; i++) {
if (content.charAt(i) === '#') {
// might be hovering over a directive
var directive = "";
var directiveOffset = -1;
var stop = false;
for (var j = i + 1; j < textLength; j++) {
if (content.charAt(j) === '=') {
if (directiveOffset === -1) {
// record the end offset for the directive if not already recorded
directiveOffset = j;
}
break;
} else if (content.charAt(j) === ' ' || content.charAt(j) === '\t'
|| content.charAt(j) === '\r' || content.charAt(j) === '\n') {
if (directive !== "" && !stop) {
// directive has been captured, stop and record the ending offset
directiveOffset = j;
stop = true;
}
continue;
}

if (stop) {
// a whitespace was encountered and we should stop capturing but
// another character was found, so this is not a directive
return null;
} else {
// capture the directive
directive = directive + content.charAt(j);
}
}
// check to make sure the user is hovering over the directive itself
if (i <= context.offset && context.offset <= j) {
return markdowns[directive.toLowerCase()];
}
return null;
}
// skip initial whitespace at the beginning of the line
if (content.charAt(i) !== ' ' && content.charAt(i) !== '\t') {
for (var j = i + 1; j < textLength; j++) {
Expand Down
Expand Up @@ -36,6 +36,8 @@ define({
"hoverWorkdir": "Set the working directory for any subsequent `ADD`, `COPY`, `CMD`, `ENTRYPOINT`, or `RUN` instructions that follow it in the `Dockerfile`.\n\n",
"hoverOnlineDocumentationFooter": "\n\n[Online documentation](${0})",

"hoverEscape": "Sets the character to use to escape characters and newlines in this Dockerfile. If unspecified, the default escape character is `\\`.\n\n",

"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",
"proposalHealthcheckExec": "Define how Docker should test the container to check that it is still working. There can only be one `HEALTHCHECK` instruction in a `Dockerfile`.\n\nSince Docker 1.12\n\n",
Expand Down

0 comments on commit ecb6c46

Please sign in to comment.