Skip to content

Add richer autocompletion for variables - #3

Merged
shakeelmohamed merged 4 commits into
mainfrom
dev
Sep 1, 2026
Merged

shakeelmohamed merged 4 commits into
mainfrom
dev

Conversation

@shakeelmohamed

@shakeelmohamed shakeelmohamed commented Sep 1, 2026 •

Copy link
Copy Markdown
Member

Summary by Sourcery

Provide richer GLSL autocomplete with type-aware members and constructors, while adding shader interaction analytics and persistent autocomplete settings.

New Features:

  • Add context-aware GLSL completion for vector swizzles, matrix indexing skeletons, and type constructors.
  • Enable autocomplete by default and persist the user's autocomplete preference across reloads.
  • Add fingerprinted GA4 tracking for shader sharing and presentation starts.

Enhancements:

  • Add variable scanning to resolve declared GLSL types for member completion, with contextual completion hints in the editor popup.

Tests:

  • Add unit and end-to-end coverage for variable scanning, member and constructor completions, autocomplete behavior, preference persistence, and analytics tracking.

Chores:

  • Update generated documentation assets and include the GA4 tag in the application pages.

@sourcery-ai

sourcery-ai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Introduces context-aware GLSL autocomplete by scanning declared variable types, offering vector swizzles, matrix indexing skeletons, and constructor snippets, while enabling autocomplete by default with persisted preferences and adding unit/end-to-end coverage; the deployed HTML/assets and analytics snippet are also updated.

Sequence diagram for context-aware GLSL member completion

sequenceDiagram
    participant User
    participant Editor as CodeMirror Editor
    participant Source as glslCompletionSource
    participant Vars as glsl-vars
    participant Popup as Completion Popup

    User->>Editor: Type `.` or `[` after identifier
    Editor->>Editor: insertThenComplete(ch)
    Editor->>Source: startCompletion()
    Source->>Vars: scanVariables(document)
    Vars-->>Source: variable name to type map
    Source->>Vars: membersForType(type)
    Vars-->>Source: swizzles or matrix index skeleton
    Source-->>Popup: Return context-specific completions
    Popup-->>User: Show members with context hints
Loading

Sequence diagram for persisted autocomplete preference

sequenceDiagram
    participant User
    participant Main as Application Boot
    participant Storage as localStorage
    participant Editor

    Main->>Storage: getItem(acStoreKey)
    Storage-->>Main: Stored preference or null
    Main->>Editor: setAutocomplete(checked)
    User->>Main: Toggle autocomplete checkbox
    Main->>Editor: setAutocomplete(checked)
    Main->>Storage: setItem(acStoreKey, 1 or 0)
Loading

Flow diagram for GLSL completion context routing

flowchart LR
    A[Completion request] --> B{Previous character}
    B -->|.| C[resolveType]
    C --> D[membersForType]
    D --> E[Vector swizzles]
    B -->|"["| F[resolveType]
    F --> G[membersForType]
    G --> H[Matrix index skeleton]
    B -->|Word| I[GLSL_COMPLETIONS]
    I --> J[constructorCompletions]
    J --> K[Keywords functions types constructors]
Loading

File-Level Changes

Change Details Files
Adds shallow GLSL variable type discovery and type-specific completion metadata.
  • Scans declarations and tracks the last declared type per variable.
  • Provides vector swizzles, matrix indexing skeletons, and constructor snippets.
  • Leaves scalar, sampler, and user-defined types without member completions.
src/glsl-vars.ts
test/glsl-vars.test.ts
Routes CodeMirror completion requests by syntactic context.
  • Adds dot-member, matrix-bracket, and word completion branches.
  • Combines existing keyword/function completions with type constructor options.
  • Resolves the identifier before a trigger using the current document scan.
src/glsl-completions.ts
Automatically opens member completion after trigger characters.
  • Intercepts . and [ to insert the character and start completion.
  • Registers the trigger keymap with high priority.
  • Adds subdued completion detail styling for contextual hints.
src/editor.ts
Makes autocomplete enabled by default and persists the user preference.
  • Initializes the checkbox from localStorage with an on-by-default fallback.
  • Synchronizes editor state and stores changes when the toggle is modified.
  • Adds end-to-end coverage for defaults and reload persistence.
index.html
docs/index.html
src/main.ts
test/boot.e2e.ts
Adds end-to-end coverage for richer completion behavior.
  • Verifies vector swizzle suggestions and non-duplicated dot insertion.
  • Verifies matrix bracket skeleton insertion and constructor suggestions.
test/autocomplete.e2e.ts
Updates the tracked implementation status and deployed assets.
  • Marks variable-member autocomplete as complete and documents deferred reassignment tracking.
  • Regenerates the deployed JavaScript bundle reference.
  • Adds the Google Analytics gtag snippet to the source and deployed HTML.
OPEN_ISSUES.md
docs/assets/index-DdY9hDmK.js
docs/index.html
index.html

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 6 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/glsl-vars.ts" line_range="86-94" />
<code_context>
+ * `m[0][0]` indexing skeleton whose row index is left selected. Scalars,
+ * samplers, and arrays have no such accessors.
+ */
+export function membersForType(type: string): Completion[] {
+  if (vectorComponents(type) !== null) {
+    // The user already typed `.`, so each completion applies just the swizzle
+    // char at the cursor (from = after the dot), never a leading `.`.
+    return SWIZZLE.map(({ char, detail }) => ({
+      label: "." + char,
+      type: "keyword",
+      apply: char,
+      detail,
+    }));
+  }
+  if (matrixDimension(type) !== null) {
</code_context>
<issue_to_address>
**issue (bug_risk):** `membersForType` returns every swizzle component for every vector dimension, so `vec2` and `vec3` offer invalid members such as `.z`, `.w`, `.b`, `.a`, `.p`, and `.q`. Accepting those completions inserts GLSL that fails to compile.

**Triggers:** When the declared variable is a `vec2` or `vec3`.

**Suggested fix:** Limit the position, color, and texture-coordinate swizzles to the components supported by the vector dimension.

```suggestion
  const components = vectorComponents(type);
  if (components !== null) {
    // The user already typed `.`, so each completion applies just the swizzle
    // char at the cursor (from = after the dot), never a leading `.`.
    return SWIZZLE.filter((_, index) => index % 4 < components).map(({ char, detail }) => ({
      label: "." + char,
      type: "keyword",
      apply: char,
      detail,
    }));
```
</issue_to_address>

### Comment 2
<location path="src/glsl-completions.ts" line_range="43-45" />
<code_context>
+  const ch = pos > 0 ? line[pos - 1] : "";
+
+  // ── Bracket context: `m[` — matrix-index skeleton ─────────────────────
+  if (ch === "[" && pos >= 2 && /\w/.test(line[pos - 2])) {
+    const type = resolveType(ctx, pos - 1);
+    if (type) {
+      const options = membersForType(type);
+      // from covers the typed `[`, so the skeleton replaces it.
+      if (options.length > 0) return { from: ctx.pos - 1, options, validFor: /^\[?\d*$/ };
+    }
+    return null;
</code_context>
<issue_to_address>
**issue (bug_risk):** Bracket completion reuses the vector dot-member options while replacing the typed `[`. Accepting a vector option after `v[` inserts only `x`, `y`, or another swizzle character, producing malformed code such as `vx` instead of a numeric index.

**Triggers:** When a vector variable is followed by `[`.

**Suggested fix:** Provide numeric index completions for vectors or return no vector options in bracket context; do not reuse dot-member completions.

```suggestion
      if (/^[ib]?vec\d$/.test(type)) return null;
      const options = membersForType(type);
      // from covers the typed `[`, so the skeleton replaces it.
      if (options.length > 0) return { from: ctx.pos - 1, options, validFor: /^\[?\d*$/ };
```
</issue_to_address>

### Comment 3
<location path="src/glsl-vars.ts" line_range="116" />
<code_context>
+
+/** Constructor completions shown alongside the bare type keyword. */
+export function constructorCompletions(): Completion[] {
+  return CONSTRUCTOR_TYPES.map((type) =>
+    snippetCompletion(type + "($0)", { label: type, detail: "constructor", boost: -100 } as Completion)
+  );
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** The constructor snippets use `$0` instead of CodeMirror's `${0}` placeholder syntax, so accepting a constructor completion inserts the literal `$0` into the document rather than creating a cursor/selection placeholder.

**Triggers:** When a user accepts a constructor completion such as `vec3`.

**Suggested fix:** Use CodeMirror snippet placeholders such as `vec3(${0})` or `vec3(${1})` in the template.

```suggestion
    snippetCompletion(type + "(${0})", { label: type, detail: "constructor", boost: -100 } as Completion)
```
</issue_to_address>

### Comment 4
<location path="src/glsl-vars.ts" line_range="64-74" />
<code_context>
+export function scanVariables(src: string): Map<string, string> {
+  const vars = new Map<string, string>();
+  // Optional qualifier span and precision, then <type> <name> [array] [= init];
+  const decl = /^(?:(?:const|uniform|in|out|varying|attribute)\s+)?(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+(\w+)(?:\s*\[[^\]]*\])?(?:\s*=\s*[^;]*?)?\s*;?$/;
+
+  for (const raw of src.split("\n")) {
+    const line = raw.split("//")[0].trim();
+    const m = line.match(decl);
+    if (!m) continue;
+    // Only a known type token counts as a declaration — this rejects
+    // statements like `return value;` from being misread as `type name`.
+    if (!KNOWN_TYPES.has(m[1])) continue;
+    vars.set(m[2], m[1]);
+  }
+  return vars;
</code_context>
<issue_to_address>
**issue (bug_risk):** The declaration regex only accepts one complete declaration per line, so legal declarations with multiple declarators such as `vec3 a, b;`, or declarations sharing a line with braces or other statements, are not recorded and receive no member completion.

**Triggers:** When shader code uses multiple declarators or places a declaration alongside another statement on the same line.

**Suggested fix:** Tokenize declarations independently of line boundaries, or at least support comma-separated declarators and leading/trailing statements.

```suggestion
  const decl = /(?:(?:const|uniform|in|out|varying|attribute)\s+)?(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+([^;]+);/g;
  const source = src.replace(/\/\/[^\n]*/g, "");
  let m: RegExpExecArray | null;
  while ((m = decl.exec(source))) {
    // Only a known type token counts as a declaration — this rejects
    // statements like `return value;` from being misread as `type name`.
    if (!KNOWN_TYPES.has(m[1])) continue;
    for (const declarator of m[2].split(",")) {
      const name = declarator.trim().match(/^(\w+)(?:\s*\[[^\]]*\])?(?:\s*=\s*.*)?$/);
      if (name) vars.set(name[1], m[1]);
    }
  }
```
</issue_to_address>

### Comment 5
<location path="src/glsl-vars.ts" line_range="61-73" />
<code_context>
+ * (redeclaration updates the type); plain reassignment (`q = other;`) is not
+ * tracked — the type is left at the last declaration.
+ */
+export function scanVariables(src: string): Map<string, string> {
+  const vars = new Map<string, string>();
+  // Optional qualifier span and precision, then <type> <name> [array] [= init];
+  const decl = /^(?:(?:const|uniform|in|out|varying|attribute)\s+)?(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+(\w+)(?:\s*\[[^\]]*\])?(?:\s*=\s*[^;]*?)?\s*;?$/;
+
+  for (const raw of src.split("\n")) {
+    const line = raw.split("//")[0].trim();
+    const m = line.match(decl);
+    if (!m) continue;
+    // Only a known type token counts as a declaration — this rejects
+    // statements like `return value;` from being misread as `type name`.
+    if (!KNOWN_TYPES.has(m[1])) continue;
+    vars.set(m[2], m[1]);
+  }
+  return vars;
</code_context>
<issue_to_address>
**issue (bug_risk):** The global name-to-type map ignores lexical scope and resolves the last textual declaration everywhere. A later declaration of the same name in another block or function overwrites the type used for earlier code, so member completion suggests members for the wrong type.

**Triggers:** When the shader redeclares the same variable name in different scopes or functions.

**Suggested fix:** Track declarations by scope and resolve the declaration visible at the completion position.
</issue_to_address>

### Comment 6
<location path="src/main.ts" line_range="153-156" />
<code_context>
+// Single source of truth: checkbox checked ⇔ localStorage. Persist so the user's
+// preference survives reloads. Autocomplete defaults ON for new visitors.
+const acStoreKey = "glsl.autocomplete";
+const storedAc = localStorage.getItem(acStoreKey);
+if (storedAc !== null) {
+  acCheckbox.checked = storedAc === "1";
+}
+editor.setAutocomplete(acCheckbox.checked);
 acCheckbox.addEventListener("change", () => {
</code_context>
<issue_to_address>
**issue (bug_risk):** The new autocomplete preference accesses `localStorage` without the try/catch used by the existing persistence module. In private-browsing or storage-blocked contexts, `getItem` or `setItem` throws and the boot/change handler fails instead of preserving editor usability.

**Triggers:** When the browser denies localStorage access, such as some private-browsing or embedded contexts.

**Suggested fix:** Wrap preference reads and writes in best-effort try/catch helpers, matching `loadDoc` and `storeDoc`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 6 findings to address first, and the new Google Analytics script executes third-party code and sends visitor activity to an external service for every user by default. Reverting removes future collection but cannot retract data already transmitted, and the tracking decision takes effect immediately without an application failure signaling a mistaken choice.

Blocking findings: src/glsl-vars.ts:94, src/glsl-completions.ts:45, src/glsl-vars.ts:116, src/glsl-vars.ts:74, src/glsl-vars.ts:73, and 1 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/glsl-vars.ts Outdated
Comment thread src/glsl-completions.ts
Comment thread src/glsl-vars.ts Outdated
Comment thread src/glsl-vars.ts
Comment on lines +64 to +74
const decl = /^(?:(?:const|uniform|in|out|varying|attribute)\s+)?(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+(\w+)(?:\s*\[[^\]]*\])?(?:\s*=\s*[^;]*?)?\s*;?$/;

for (const raw of src.split("\n")) {
const line = raw.split("//")[0].trim();
const m = line.match(decl);
if (!m) continue;
// Only a known type token counts as a declaration — this rejects
// statements like `return value;` from being misread as `type name`.
if (!KNOWN_TYPES.has(m[1])) continue;
vars.set(m[2], m[1]);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The declaration regex only accepts one complete declaration per line, so legal declarations with multiple declarators such as vec3 a, b;, or declarations sharing a line with braces or other statements, are not recorded and receive no member completion.

Triggers: When shader code uses multiple declarators or places a declaration alongside another statement on the same line.

Suggested fix: Tokenize declarations independently of line boundaries, or at least support comma-separated declarators and leading/trailing statements.

Suggested change
const decl = /^(?:(?:const|uniform|in|out|varying|attribute)\s+)?(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+(\w+)(?:\s*\[[^\]]*\])?(?:\s*=\s*[^;]*?)?\s*;?$/;
for (const raw of src.split("\n")) {
const line = raw.split("//")[0].trim();
const m = line.match(decl);
if (!m) continue;
// Only a known type token counts as a declaration — this rejects
// statements like `return value;` from being misread as `type name`.
if (!KNOWN_TYPES.has(m[1])) continue;
vars.set(m[2], m[1]);
}
const decl = /(?:(?:const|uniform|in|out|varying|attribute)\s+)?(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+([^;]+);/g;
const source = src.replace(/\/\/[^\n]*/g, "");
let m: RegExpExecArray | null;
while ((m = decl.exec(source))) {
// Only a known type token counts as a declaration — this rejects
// statements like `return value;` from being misread as `type name`.
if (!KNOWN_TYPES.has(m[1])) continue;
for (const declarator of m[2].split(",")) {
const name = declarator.trim().match(/^(\w+)(?:\s*\[[^\]]*\])?(?:\s*=\s*.*)?$/);
if (name) vars.set(name[1], m[1]);
}
}

Comment thread src/glsl-vars.ts
Comment on lines +61 to +73
export function scanVariables(src: string): Map<string, string> {
const vars = new Map<string, string>();
// Optional qualifier span and precision, then <type> <name> [array] [= init];
const decl = /^(?:(?:const|uniform|in|out|varying|attribute)\s+)?(?:(?:highp|mediump|lowp)\s+)?(\w+)\s+(\w+)(?:\s*\[[^\]]*\])?(?:\s*=\s*[^;]*?)?\s*;?$/;

for (const raw of src.split("\n")) {
const line = raw.split("//")[0].trim();
const m = line.match(decl);
if (!m) continue;
// Only a known type token counts as a declaration — this rejects
// statements like `return value;` from being misread as `type name`.
if (!KNOWN_TYPES.has(m[1])) continue;
vars.set(m[2], m[1]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The global name-to-type map ignores lexical scope and resolves the last textual declaration everywhere. A later declaration of the same name in another block or function overwrites the type used for earlier code, so member completion suggests members for the wrong type.

Triggers: When the shader redeclares the same variable name in different scopes or functions.

Suggested fix: Track declarations by scope and resolve the declaration visible at the completion position.

Comment thread src/main.ts Outdated
@shakeelmohamed
shakeelmohamed merged commit a97cefe into main Sep 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant