Skip to content

Commit 3892fcf

Browse files
authored
feat: add scope completion in VSCode extension (#35)
* feat: add scope completion in VSCode extension * build: replace xvfb-run command with setup-xvfb action for headless testing
1 parent b00daa3 commit 3892fcf

File tree

14 files changed

+443
-13
lines changed

14 files changed

+443
-13
lines changed

.changeset/eleven-laws-care.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"google-workspace-developer-tools": minor
3+
---
4+
5+
Implement code completion for OAuth2 scopes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: "Setup Xvfb"
16+
description: "Install and start Xvfb virtual display server for headless testing"
17+
runs:
18+
using: "composite"
19+
steps:
20+
- name: Install Xvfb
21+
shell: bash
22+
run: |
23+
sudo apt-get update
24+
sudo apt-get install -y xvfb
25+
- name: Start Xvfb
26+
shell: bash
27+
run: |
28+
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
29+
echo "DISPLAY=:99.0" >> $GITHUB_ENV

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
- run: pnpm build
3838
- run: pnpm lint
3939
- run: pnpm check
40+
- uses: ./.github/actions/setup-xvfb
4041
- run: pnpm test
4142
- run: pnpm ci:package
4243
- run: |

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ jobs:
3131
- run: pnpm build
3232
- run: pnpm lint
3333
- run: pnpm check
34+
- uses: ./.github/actions/setup-xvfb
3435
- run: pnpm test
3536
- run: pnpm ci:package

.github/workflows/update.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
- run: pnpm ci:update
3232
- run: pnpm lint
3333
- run: pnpm check
34+
- uses: ./.github/actions/setup-xvfb
3435
- run: pnpm test
3536
- name: Check for changes
3637
id: changes

packages/vscode-extension/.vscode-test.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ import { defineConfig } from "@vscode/test-cli";
1818

1919
export default defineConfig({
2020
files: "out/test/**/*.test.cjs",
21+
launchArgs: ["--user-data-dir=/tmp/vscode-test-user-data"],
22+
version: "insiders",
2123
});

packages/vscode-extension/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ Automatically validate and document Google Workspace OAuth2 scopes in your code:
1717
- **Hover Documentation**: See scope descriptions, associated APIs, and documentation links on hover
1818
- **Multi-API Support**: Coverage for all Google Workspace APIs (Gmail, Drive, Calendar, Chat, Admin, and more)
1919

20-
![OAuth2 Scope Linting](https://raw.githubusercontent.com/googleworkspace/vscode-extension/main/packages/vscode-extension/assets/scope-diagnostics.png)
20+
Get code completions for all Google OAuth2 scopes:
21+
22+
![OAuth2 Scope Linting & Completions](https://raw.githubusercontent.com/googleworkspace/vscode-extension/main/packages/vscode-extension/assets/scope-completion.gif)
2123

2224
**Scope Classifications:**
2325

717 KB
Loading

packages/vscode-extension/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "google-workspace-developer-tools",
33
"displayName": "Google Workspace Developer Tools",
44
"description": "Lint Google Workspace OAuth2 scopes and more.",
5-
"version": "0.5.4",
5+
"version": "0.5.6",
66
"publisher": "google-workspace",
77
"license": "Apache-2.0",
88
"private": true,
@@ -54,13 +54,13 @@
5454
"scripts": {
5555
"build": "tsup",
5656
"check": "tsc --noEmit",
57-
"ci:package": "vsce package",
57+
"ci:package": "vsce package --no-dependencies",
5858
"ci:publish": "ovsx publish --skip-duplicate && vsce publish --skip-duplicate",
5959
"ci:version": "changeset version && pnpm i --lockfile-only --engine-strict=false",
6060
"ci:update": "tsx scripts/fetch-apis.ts",
6161
"dev": "tsup --watch",
6262
"pretest": "pnpm build",
63-
"test": "xvfb-run -a vscode-test",
63+
"test": "vscode-test",
6464
"vscode:prepublish": "tsup"
6565
},
6666
"devDependencies": {
@@ -76,5 +76,8 @@
7676
"tsup": "catalog:",
7777
"tsx": "catalog:",
7878
"typescript": "catalog:"
79+
},
80+
"dependencies": {
81+
"vitest": "^4.0.4"
7982
}
8083
}

packages/vscode-extension/src/extension.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,35 @@
1717
import * as vscode from "vscode";
1818
import { SCOPES, ScopeClassification, getScopeMarkdown } from "./scopes.js";
1919

20+
export function scopeCompletion(
21+
document: vscode.TextDocument,
22+
position: vscode.Position,
23+
): vscode.CompletionItem[] {
24+
const completionItems: vscode.CompletionItem[] = [];
25+
26+
const lineText = document.lineAt(position.line).text;
27+
const textBeforeCursor = lineText.substring(0, position.character);
28+
29+
const match = textBeforeCursor.match(
30+
/(https:\/\/www\.googleapis\.com\/auth\/[a-zA-Z._-]*)$/,
31+
);
32+
33+
if (!match) {
34+
return completionItems;
35+
}
36+
37+
for (const [scope] of SCOPES.entries()) {
38+
if (scope.startsWith(match[1])) {
39+
const item = new vscode.CompletionItem(scope.split("/").pop() ?? scope);
40+
item.insertText = scope.replace(match[1], "");
41+
completionItems.push(item);
42+
}
43+
}
44+
return completionItems.sort((a, b) =>
45+
String(a.label).localeCompare(String(b.label)),
46+
);
47+
}
48+
2049
export function activate(context: vscode.ExtensionContext) {
2150
if (vscode.lm.registerMcpServerDefinitionProvider) {
2251
context.subscriptions.push(
@@ -77,6 +106,20 @@ export function activate(context: vscode.ExtensionContext) {
77106
vscode.languages.createDiagnosticCollection("scopes");
78107
context.subscriptions.push(scopeDiagnostics);
79108

109+
const scopeCompletionProvider =
110+
vscode.languages.registerCompletionItemProvider(
111+
{ scheme: "file" },
112+
{
113+
provideCompletionItems(document, position) {
114+
console.log(position);
115+
return scopeCompletion(document, position);
116+
},
117+
},
118+
"/",
119+
".",
120+
);
121+
context.subscriptions.push(scopeCompletionProvider);
122+
80123
function updateDiagnostics(
81124
document: vscode.TextDocument,
82125
collection: vscode.DiagnosticCollection,

0 commit comments

Comments
 (0)