提交 34e37cf1 编写于 作者: T Tomas Vik

Merge branch '55-CI-variable-auto-complete' into 'main'

feat(editor): auto completion for CI variables

See merge request gitlab-org/gitlab-vscode-extension!140
......@@ -40,6 +40,13 @@ lint_commit:
- if: '$CI_MERGE_REQUEST_IID && $CI_PROJECT_VISIBILITY == "public"' # lint.js script makes an API call without authentication
when: always
check-ci-variables:
stage: test
script:
- npm ci
- npm run update-ci-variables
allow_failure: true # could be caused by changes in gitlab-org/gitlab repo, not related to current branch
test-unit:
stage: test
script:
......@@ -123,4 +130,4 @@ secret_detection:
# We can't run the secrets detection on tags: https://gitlab.com/gitlab-org/gitlab/-/issues/254199
rules:
- if: $CI_COMMIT_TAG
when: "never"
when: 'never'
......@@ -50,6 +50,8 @@ Supports multiple GitLab instances [Read more](#multiple-gitlab-instances).
Published also on [Open VSX Registry](https://open-vsx.org/extension/GitLab/gitlab-workflow).
You can use [autocompletion of GitLab CI variables](#ci-variable-autocompletion) in your `.gitlab-ci.yml`.
#### View issue and MR details and comments in VS Code
![_issues-in-vscode](https://gitlab.com/gitlab-org/gitlab-vscode-extension/raw/main/src/assets/_issues-in-vscode.png)
......@@ -170,13 +172,13 @@ Each query is an entry of the json array. Each entry can have the following valu
**`name`** _(required: true)_ : The label to show in the GitLab panel
**`type`** _(required: false, default: merge\_requests)_ : The type of GitLab items to return. If snippets is selected, none of the other filter will work. Epics will work only on GitLab ultimate/gold. Possible values: issues, merge_requests, epics, snippets, vulnerabilities.
**`type`** _(required: false, default: merge_requests)_ : The type of GitLab items to return. If snippets is selected, none of the other filter will work. Epics will work only on GitLab ultimate/gold. Possible values: issues, merge_requests, epics, snippets, vulnerabilities.
**`noItemText`** _(required: false, default: "No items found.")_ : The text to show if the query returns no items.
**`maxResults`** _(required: false, default: 20)_ : The maximum number of results to show
**`orderBy`** _(required: false, default: created\_at)_ : Return issues ordered by the selected value. It is not applicable for vulnerabilities. Possible values: created_at, updated_at, priority, due_date, relative_position, label_priority, milestone_due, popularity, weight.
**`orderBy`** _(required: false, default: created_at)_ : Return issues ordered by the selected value. It is not applicable for vulnerabilities. Possible values: created_at, updated_at, priority, due_date, relative_position, label_priority, milestone_due, popularity, weight.
**`sort`** _(required: false, default: desc)_ : Return issues sorted in ascending or descending order. It is not applicable for vulnerabilities. Possible values: asc, desc.
......@@ -206,7 +208,7 @@ Each query is an entry of the json array. Each entry can have the following valu
**`searchIn`** _(required: false, default: all)_ : Modify the scope of the search attribute. It is not applicable for epics and vulnerabilities. Possible values: all, title, description.
**`searchIn`** _(required: false, default: all)_ : Modify the scope of the excludeSearch attribute. Works only with issues. Possible values: all, title, description.
**`searchIn`** _(required: false, default: all)_ : Modify the scope of the excludeSearch attribute. Works only with issues. Possible values: all, title, description.
**`createdAfter`** _(required: false)_ : Return GitLab items created after the given date. It is not applicable for vulnerabilities.
......@@ -271,6 +273,7 @@ If your current project is a GitLab project, the extension will do the following
GitLab Workflow extension provides you two types of search. Search with filters and Advanced Search.
#### Search with filters
It allows users to search issues/MRs against their title and description fields. In the search input, you can type your search term and hit Enter, for example, `Inconsistent line endings for HEX files` or `Pipelines should ignore retried builds`.
It can become more powerful by allowing you to filter issues/MRs by author, assignee, milestone, title etc. Below is the full list of supported filter tokens
......@@ -299,6 +302,7 @@ It can become more powerful by allowing you to filter issues/MRs by author, assi
![_advanced-search.gif](https://gitlab.com/gitlab-org/gitlab-vscode-extension/raw/main/src/assets/_advanced-search.gif)
#### Search with Advanced Search
GitLab provides [Advanced Search feature which is backed by Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html). Please see [Advanced Search syntax](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for more details.
### Create snippet
......@@ -333,6 +337,12 @@ Using this command, you can quickly validate GitLab CI configuration.
![_validate-ci-config.gif](https://gitlab.com/gitlab-org/gitlab-vscode-extension/raw/main/src/assets/_validate-ci-config.gif)
### CI variable autocompletion
Quickly find the CI variable you are looking for with the CI variable autocompletion.
![screenshot of the CI variable autocompletion](src/assets/_ci_variable_autocomplete.png)
---
## Contribution
......
......@@ -513,7 +513,8 @@
"autofix": "eslint --fix . && prettier --write '**/*.{js,ts,vue,json}' && cd src/webview && npm run autofix",
"publish": "vsce publish",
"webview": "cd src/webview && npm run watch",
"version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md"
"version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
"update-ci-variables": "node ./scripts/update_ci_variables.js"
},
"devDependencies": {
"@types/jest": "^26.0.14",
......
const fs = require('fs');
const path = require('path');
const fetch = require('cross-fetch');
const VARIABLE_JSON_PATH = path.join(__dirname, '../src/completion/ci_variables.json');
async function fetchDocumentation() {
return fetch(
'https://gitlab.com/gitlab-org/gitlab/-/raw/master/doc/ci/variables/predefined_variables.md',
).then(res => res.text());
}
function parseDocumentation(variableMarkdown) {
const lines = variableMarkdown.split('\n');
const tableStartLine = lines.findIndex(l => l.startsWith('| Variable '));
const tableLines = lines.slice(tableStartLine + 2);
const variables = tableLines.map(l => {
const [, nameSegment, , , descriptionSegment] = l.split('|');
if (!nameSegment) return undefined;
return {
name: nameSegment.trim().replace(/`/g, ''),
description: descriptionSegment.trim(),
};
});
const json = JSON.stringify(variables.filter(Boolean), undefined, 2);
return `${json}\n`;
}
function loadExistingVariablesJson() {
return fs.readFileSync(VARIABLE_JSON_PATH).toString();
}
function writeVariablesJson(json) {
return fs.writeFileSync(VARIABLE_JSON_PATH, `${json}\n`);
}
async function run() {
const onlineDoc = await fetchDocumentation();
const onlineVariablesJson = parseDocumentation(onlineDoc);
const existingVariablesJson = loadExistingVariablesJson();
if (process.env.CI && onlineVariablesJson !== existingVariablesJson) {
console.error(
'❌ ./src/utils/ci_variables.json has changes, please execute `npm run update-ci-variables` to fix this.',
);
process.exit(1);
}
if (onlineVariablesJson !== existingVariablesJson) {
writeVariablesJson(onlineVariablesJson);
console.log('✅ ./src/utils/ci_variables.json was updated successfully.');
} else {
console.log('ℹ️ No changes to ./src/utils/ci_variables.json.');
}
}
run();
import * as vscode from 'vscode';
import gitlabCiVariables = require('./ci_variables.json');
const findDollarSignIndex = (document: vscode.TextDocument, position: vscode.Position): number => {
const textUntilPosition = document.lineAt(position).text.substr(0, position.character);
return textUntilPosition.lastIndexOf('$');
};
export class CiCompletionProvider implements vscode.CompletionItemProvider {
// eslint-disable-next-line class-methods-use-this
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList<vscode.CompletionItem>> {
const linePrefix = findDollarSignIndex(document, position);
return gitlabCiVariables.map(({ name, description }) => {
const item = new vscode.CompletionItem(`$${name}`, vscode.CompletionItemKind.Constant);
item.documentation = new vscode.MarkdownString(description);
item.range = new vscode.Range(position.with(undefined, linePrefix), position);
return item;
});
}
}
此差异已折叠。
......@@ -17,6 +17,7 @@ const checkDeprecatedCertificateSettings = require('./check_deprecated_certifica
const { ApiContentProvider } = require('./review/api_content_provider');
const { REVIEW_URI_SCHEME } = require('./constants');
const { USER_COMMANDS, PROGRAMMATIC_COMMANDS } = require('./command_names');
const { CiCompletionProvider } = require('./completion/ci_completion_provider');
vscode.gitLabWorkflow = {
sidebarDataProviders: [],
......@@ -79,6 +80,16 @@ const registerCommands = (context, outputChannel) => {
registerSidebarTreeDataProviders();
};
const registerCiCompletion = context => {
const subscription = vscode.languages.registerCompletionItemProvider(
{ pattern: '**/.gitlab-ci.{yml,yaml}' },
new CiCompletionProvider(),
'$',
);
context.subscriptions.push(subscription);
};
const activate = context => {
const outputChannel = vscode.window.createOutputChannel('GitLab Workflow');
initializeLogging(line => outputChannel.appendLine(line));
......@@ -88,6 +99,7 @@ const activate = context => {
tokenService.init(context);
tokenServiceWrapper.init(context);
checkDeprecatedCertificateSettings(context);
registerCiCompletion(context);
};
exports.activate = activate;
const assert = require('assert');
const vscode = require('vscode');
const { createAndOpenFile, closeAndDeleteFile } = require('./test_infrastructure/helpers');
const ciVariables = require('../../src/completion/ci_variables.json');
describe('CI variable completion', () => {
describe('.gitlab-ci.yml', () => {
const gitlabCiYml = vscode.Uri.parse(
`${vscode.workspace.workspaceFolders[0].uri.fsPath}/.gitlab-ci.yml`,
);
const write = async string => {
const editor = vscode.window.activeTextEditor;
await editor.edit(editBuilder => {
editBuilder.insert(editor.selection.start, string);
});
};
const openCompletion = async position => {
return vscode.commands.executeCommand(
'vscode.executeCompletionItemProvider',
gitlabCiYml,
position,
);
};
beforeEach(async () => {
const ext = vscode.extensions.getExtension('gitlab.gitlab-workflow');
await ext.activate();
await createAndOpenFile(gitlabCiYml);
});
afterEach(async () => {
await closeAndDeleteFile(gitlabCiYml);
});
it("won't complete when no dollar is found", async () => {
const text = 'image: alpine:';
await write(text);
const position = new vscode.Position(0, text.length - 1);
const completions = await openCompletion(position);
assert.deepStrictEqual(
completions.items.filter(item => item.kind === vscode.CompletionItemKind.Constant),
[],
);
});
it('will complete for one variable', async () => {
const text = '$CI_COMMIT_HASH';
await write(text);
const position = new vscode.Position(0, text.length - 1);
const completions = await openCompletion(position);
assert.strictEqual(completions.items.length, ciVariables.length);
const { start, end } = completions.items[0].range;
assert.deepStrictEqual(start.character, 0);
assert.deepStrictEqual(end, position);
});
it('will handle multiple $ characters on the same line', async () => {
const text = ` if: '$CI_COMMIT_BRANCH == "master" && $CI_PIPELINE_SOURCE == "schedule" && $FREQUEN`;
await write(text);
const position = new vscode.Position(0, text.length - 1);
const completions = await openCompletion(position);
const { start } = completions.items[0].range;
assert.deepStrictEqual(
start.character,
text.length - 8,
'completion item should start at the position of the last $',
);
});
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册