Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/providers/completionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class CompletionProvider implements vscode.CompletionItemProvider {
this.logger.debug(`[CompletionProvider] Version regex match result: ${versionMatch ? 'MATCHED' : 'NO MATCH'}`, 'CompletionProvider');
if (versionMatch) {
this.logger.debug(`[CompletionProvider] Detected version completion request after @`, 'CompletionProvider');
return this.provideVersionCompletions(linePrefix, document.uri);
return this.provideVersionCompletions(linePrefix, position, document.uri);
}

// Suggest components after "component: "
Expand All @@ -74,7 +74,11 @@ export class CompletionProvider implements vscode.CompletionItemProvider {
/**
* Provide version/tag completions for a component URL after @
*/
private async provideVersionCompletions(linePrefix: string, forUri: vscode.Uri): Promise<vscode.CompletionItem[]> {
private async provideVersionCompletions(
linePrefix: string,
position: vscode.Position,
forUri: vscode.Uri,
): Promise<vscode.CompletionItem[]> {
this.logger.debug(`[CompletionProvider] Starting version completions for: "${linePrefix}"`, 'CompletionProvider');

// Extract the component URL before the @
Expand All @@ -86,6 +90,10 @@ export class CompletionProvider implements vscode.CompletionItemProvider {

const componentUrlBase = urlMatch.base;
const currentVersionInput = urlMatch.partialVersion;
// Each item replaces only the text after `@`. Without an explicit range VS Code filters the items against the
// word at the cursor, and a YAML word pattern that allows `/` and `@` (the Red Hat YAML extension's does) makes
// that word the whole reference, which no version matches.
const versionRange = new vscode.Range(position.line, urlMatch.versionStart, position.line, position.character);

this.logger.debug(`[CompletionProvider] Component URL base: ${componentUrlBase}`, 'CompletionProvider');
this.logger.debug(`[CompletionProvider] Current version input: "${currentVersionInput}"`, 'CompletionProvider');
Expand Down Expand Up @@ -160,6 +168,7 @@ export class CompletionProvider implements vscode.CompletionItemProvider {
item.documentation = `Use version ${version} of component ${componentName}`;
// Set the insertion text to just the version (since we're after the @)
item.insertText = version;
item.range = versionRange;
// Add sort priority based on version type
if (version === chosenVersion) {
item.sortText = '0' + version;
Expand Down
11 changes: 7 additions & 4 deletions src/utils/componentReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,13 @@ export function referenceToUrl(reference: string): string {
/**
* Detect a version being typed after `@` in a `component:` value.
*
* @returns The reference before `@` exactly as written — GitLab variables included, for the caller to expand — and the
* partial version after it, or `null` when the line isn't a component version slot.
* @returns The reference before `@` exactly as written — GitLab variables included, for the caller to expand — the
* partial version after it, and the column that partial version starts at (right after the `@`), or `null`
* when the line isn't a component version slot.
*/
export function parseVersionCompletionPrefix(linePrefix: string): { base: string; partialVersion: string } | null {
export function parseVersionCompletionPrefix(
linePrefix: string,
): { base: string; partialVersion: string; versionStart: number } | null {
const match = VERSION_PREFIX_REGEX.exec(linePrefix);
return match ? { base: match[1], partialVersion: match[2] } : null;
return match ? { base: match[1], partialVersion: match[2], versionStart: linePrefix.length - match[2].length } : null;
}
23 changes: 20 additions & 3 deletions tests/unit/componentReference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,29 @@ suite('matchComponentValue', () => {

suite('parseVersionCompletionPrefix', () => {
test('matches a scheme-less reference', () => {
assert.deepStrictEqual(parseVersionCompletionPrefix(' - component: gitlab.com/g/p/c@1.'), {
const prefix = ' - component: gitlab.com/g/p/c@1.';
assert.deepStrictEqual(parseVersionCompletionPrefix(prefix), {
base: 'gitlab.com/g/p/c',
partialVersion: '1.',
versionStart: prefix.indexOf('@') + 1,
});
});

test('matches an empty partial version', () => {
assert.deepStrictEqual(parseVersionCompletionPrefix(' - component: gitlab.com/g/p/c@'), {
const prefix = ' - component: gitlab.com/g/p/c@';
assert.deepStrictEqual(parseVersionCompletionPrefix(prefix), {
base: 'gitlab.com/g/p/c',
partialVersion: '',
versionStart: prefix.length,
});
});

test('tolerates an opening quote', () => {
assert.deepStrictEqual(parseVersionCompletionPrefix(` - component: "gitlab.com/g/p/c@v2`), {
const prefix = ` - component: "gitlab.com/g/p/c@v2`;
assert.deepStrictEqual(parseVersionCompletionPrefix(prefix), {
base: 'gitlab.com/g/p/c',
partialVersion: 'v2',
versionStart: prefix.indexOf('@') + 1,
});
});

Expand All @@ -107,10 +113,21 @@ suite('parseVersionCompletionPrefix', () => {
assert.deepStrictEqual(parseVersionCompletionPrefix(' - component: $CI_SERVER_FQDN/g/p/c@1'), {
base: '$CI_SERVER_FQDN/g/p/c',
partialVersion: '1',
versionStart: ' - component: $CI_SERVER_FQDN/g/p/c@'.length,
});
assert.deepStrictEqual(parseVersionCompletionPrefix(' - component: ${CI_SERVER_FQDN}/g/p/c@'), {
base: '${CI_SERVER_FQDN}/g/p/c',
partialVersion: '',
versionStart: ' - component: ${CI_SERVER_FQDN}/g/p/c@'.length,
});
});

test('places the version slot after the @ of a partial version that itself contains a slash', () => {
const prefix = ' - component: gitlab.com/g/p/c@feature/my-';
assert.deepStrictEqual(parseVersionCompletionPrefix(prefix), {
base: 'gitlab.com/g/p/c',
partialVersion: 'feature/my-',
versionStart: prefix.indexOf('@') + 1,
});
});
});
Loading