@groeponline/pi-wishcraft 0.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +68 -0
- package/CHANGELOG.md +724 -0
- package/CONTRIBUTING.md +37 -0
- package/README.md +648 -0
- package/RELEASE.md +117 -0
- package/ROADMAP.md +52 -0
- package/bash-mode/completion-providers.ts +269 -0
- package/bash-mode/completion.ts +416 -0
- package/bash-mode/editor-ghost.ts +40 -0
- package/bash-mode/editor-input.ts +80 -0
- package/bash-mode/editor.ts +437 -0
- package/bash-mode/history.ts +263 -0
- package/bash-mode/shell-session.ts +286 -0
- package/bash-mode/transcript.ts +108 -0
- package/bash-mode/types.ts +80 -0
- package/index.ts +6 -0
- package/package.json +55 -0
- package/queue/store.ts +443 -0
- package/queue/types.ts +54 -0
- package/src/config/custom-items.ts +182 -0
- package/src/config/extension-statuses.ts +51 -0
- package/src/config/layout.ts +60 -0
- package/src/config/parse.ts +127 -0
- package/src/config/powerline-config.ts +18 -0
- package/src/config/presets.ts +245 -0
- package/src/config/primitives.ts +117 -0
- package/src/config/segment-ids.ts +114 -0
- package/src/config/segment-options.ts +128 -0
- package/src/config/settings-patch.ts +26 -0
- package/src/config/types.ts +277 -0
- package/src/core/frontmatter.ts +40 -0
- package/src/editor/autocomplete-chain.ts +41 -0
- package/src/extension/activate.ts +28 -0
- package/src/extension/bash-mode-actions.ts +104 -0
- package/src/extension/commands.ts +268 -0
- package/src/extension/constants.ts +46 -0
- package/src/extension/custom-editor.ts +406 -0
- package/src/extension/git-invalidation.ts +40 -0
- package/src/extension/layout.ts +160 -0
- package/src/extension/menu-views.ts +393 -0
- package/src/extension/powerline-widgets.ts +95 -0
- package/src/extension/prompt-history.ts +219 -0
- package/src/extension/queue-commands.ts +245 -0
- package/src/extension/queue-context.ts +12 -0
- package/src/extension/queue-integration.ts +434 -0
- package/src/extension/segment-context.ts +212 -0
- package/src/extension/session-lifecycle.ts +373 -0
- package/src/extension/settings-io.ts +202 -0
- package/src/extension/shortcuts-config.ts +357 -0
- package/src/extension/shortcuts-router.ts +383 -0
- package/src/extension/skills/inline-invocation.ts +174 -0
- package/src/extension/skills/ook.md +6 -0
- package/src/extension/skills/test.md +6 -0
- package/src/extension/stale-context.ts +10 -0
- package/src/extension/stash-history.ts +103 -0
- package/src/extension/state.ts +159 -0
- package/src/extension/status-line-renderers.ts +222 -0
- package/src/extension/types.ts +97 -0
- package/src/extension/vibe-command.ts +160 -0
- package/src/extension/welcome-control.ts +27 -0
- package/src/extension/welcome-integration.ts +153 -0
- package/src/git/status.ts +332 -0
- package/src/paths/agent-dirs.ts +67 -0
- package/src/render/timer.ts +46 -0
- package/src/segments/core.ts +256 -0
- package/src/segments/custom.ts +114 -0
- package/src/segments/index.ts +3 -0
- package/src/segments/registry.ts +87 -0
- package/src/segments/shared.ts +36 -0
- package/src/segments/system.ts +235 -0
- package/src/segments/usage.ts +178 -0
- package/src/shell/cd-command.ts +190 -0
- package/src/shortcuts/matching.ts +61 -0
- package/src/theme/colors.ts +60 -0
- package/src/theme/icons.ts +175 -0
- package/src/theme/separators.ts +41 -0
- package/src/theme/theme.ts +211 -0
- package/src/tools/graph.ts +75 -0
- package/src/tools/patch.ts +179 -0
- package/src/tools/ripgrep.ts +104 -0
- package/src/usage/context.ts +97 -0
- package/src/usage/ledger.ts +293 -0
- package/src/usage/rates.ts +155 -0
- package/src/welcome/auto-dismiss.ts +43 -0
- package/src/welcome/banner.ts +68 -0
- package/src/welcome/discover.ts +234 -0
- package/src/welcome/format.ts +18 -0
- package/src/welcome/index.ts +5 -0
- package/src/welcome/layout.ts +36 -0
- package/src/welcome/overlay.ts +80 -0
- package/src/welcome/renderer.ts +157 -0
- package/src/welcome/sessions.ts +107 -0
- package/src/welcome/types.ts +41 -0
- package/src/welcome/widgets/graph-widget.ts +25 -0
- package/src/welcome/widgets/index.ts +20 -0
- package/src/welcome/widgets/queue-widget.ts +26 -0
- package/src/welcome/widgets/sessions-widget.ts +23 -0
- package/src/welcome/widgets/shortcuts-widget.ts +17 -0
- package/src/welcome/widgets/system-widget.ts +29 -0
- package/src/working-vibes/generate.ts +144 -0
- package/src/working-vibes/index.ts +24 -0
- package/src/working-vibes/manager.ts +198 -0
- package/src/working-vibes/provider.ts +163 -0
- package/src/working-vibes/storage.ts +357 -0
- package/theme.example.json +24 -0
- package/tsconfig.json +13 -0
package/RELEASE.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Releasing `@groeponline/pi-wishcraft`
|
|
2
|
+
|
|
3
|
+
This is the GroepOnline fork of `pi-powerline-footer`. It ships to npm as the
|
|
4
|
+
scoped package `@groeponline/pi-wishcraft` and installs locally from the
|
|
5
|
+
repo path. This doc records the end-to-end release flow and how to verify it.
|
|
6
|
+
|
|
7
|
+
## Identity
|
|
8
|
+
|
|
9
|
+
- **GitHub repo:** `GroepOnline/pi-wishcraft` (fork of `nicobailon/pi-powerline-footer`)
|
|
10
|
+
- **npm package:** `@groeponline/pi-wishcraft` (scoped, `--access public`)
|
|
11
|
+
- **Default branch:** `main`
|
|
12
|
+
- **Local checkout:** `/home/joep/pi-wishcraft`
|
|
13
|
+
- **Pi settings (`~/.pi/agent/settings.json`):** packages entry `/home/joep/pi-wishcraft` (local path, not the npm name) and `"powerline": "chef"`. Local edits are picked up on `/reload` — no reinstall needed.
|
|
14
|
+
|
|
15
|
+
## The release flow
|
|
16
|
+
|
|
17
|
+
A release is: bump version → roll CHANGELOG → commit → tag → push. The tag
|
|
18
|
+
triggers CI which tests and publishes to npm.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 1. Make sure the working tree is clean and tests/typecheck pass (see Verification)
|
|
22
|
+
npm run typecheck # tsc --noEmit
|
|
23
|
+
npm test # node --experimental-strip-types --test tests/**/*.test.ts
|
|
24
|
+
|
|
25
|
+
# 2. Release (patch | minor | major | explicit 1.2.3)
|
|
26
|
+
npm run release minor # = node scripts/release.mjs minor
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`scripts/release.mjs` (zero deps, node built-ins only) does:
|
|
30
|
+
|
|
31
|
+
1. Bumps `version` in `package.json`.
|
|
32
|
+
2. Replaces the top `## [Unreleased]` heading in `CHANGELOG.md` with `## [<version>] - <YYYY-MM-DD>`.
|
|
33
|
+
3. `git add package.json CHANGELOG.md`
|
|
34
|
+
4. `git commit -m "chore: release <version>"`
|
|
35
|
+
5. `git tag -a v<version> -m "Release <version>"`
|
|
36
|
+
|
|
37
|
+
It does **not** push — it prints the push command. Push with the GroepOnline SSH
|
|
38
|
+
identity (the laptop default `ssh`/OnlineChef key is denied for this org):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
GIT_SSH_COMMAND='ssh -F ~/.ssh/config-groeponline -o IdentityFile=~/.ssh/sheesh' \
|
|
42
|
+
git push origin HEAD --tags
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- SSH host alias: `github.com-groeponline` (defined in `~/.ssh/config-groeponline` with `IdentityFile ~/.ssh/sheesh`, `IdentitiesOnly yes`).
|
|
46
|
+
- Push actor: `chefadmin-netizen` (GroepOnline SSH). API/`gh` actor: `MisterWanted` (FG) via `chef-gh`.
|
|
47
|
+
|
|
48
|
+
## CI publish (on tag)
|
|
49
|
+
|
|
50
|
+
`.github/workflows/release.yml` triggers on `push: tags: ['v*']`:
|
|
51
|
+
|
|
52
|
+
1. `actions/checkout@v4`
|
|
53
|
+
2. `actions/setup-node@v4` (node 24, `registry-url: https://registry.npmjs.org`)
|
|
54
|
+
3. `npm ci --ignore-scripts` (installs devDependencies incl. `typescript`)
|
|
55
|
+
4. `npm run typecheck` (tsc)
|
|
56
|
+
5. `npm test` (node test runner)
|
|
57
|
+
6. `npm publish --access public` with `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}`
|
|
58
|
+
|
|
59
|
+
`NPM_TOKEN` is a GitHub Actions secret on the repo (publish rights to the
|
|
60
|
+
`@groeponline` scope). It is **not** readable after being set; verify it works by
|
|
61
|
+
checking that the publish step on a real release succeeds and `npm view` resolves
|
|
62
|
+
the new version (see below).
|
|
63
|
+
|
|
64
|
+
The package has no build step — `*.ts` files are shipped directly (`pi.extensions:
|
|
65
|
+
["./index.ts"]`), and Pi loads them with TypeScript stripping at runtime.
|
|
66
|
+
|
|
67
|
+
## Verification (the "status" line)
|
|
68
|
+
|
|
69
|
+
After a release, confirm each layer. Example for `0.15.0`:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# 1. Local: typecheck + tests green BEFORE tagging
|
|
73
|
+
npm run typecheck # exit 0
|
|
74
|
+
npm test # "ℹ pass 170", "ℹ fail 0"
|
|
75
|
+
|
|
76
|
+
# 2. Tag pushed
|
|
77
|
+
git ls-remote --tags origin | grep v0.15.0 # shows v0.15.0 on origin
|
|
78
|
+
|
|
79
|
+
# 3. CI Release workflow
|
|
80
|
+
gh run list --repo GroepOnline/pi-wishcraft --workflow release.yml \
|
|
81
|
+
--limit 5 --json databaseId,status,conclusion,headBranch,url
|
|
82
|
+
gh run view <databaseId> --repo GroepOnline/pi-wishcraft \
|
|
83
|
+
--log-failed # or --log for full transcript
|
|
84
|
+
|
|
85
|
+
# 4. npm registry (published + propagated)
|
|
86
|
+
npm view @groeponline/pi-wishcraft version # 0.15.0
|
|
87
|
+
npm view @groeponline/pi-wishcraft dist-tags # { latest: '0.15.0' }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A release is "done" when all four hold: local green, tag on origin, CI
|
|
91
|
+
`success`, and `npm view` returns the new version.
|
|
92
|
+
|
|
93
|
+
## Known gotchas
|
|
94
|
+
|
|
95
|
+
- **npm propagation delay.** Right after `npm publish` the public registry can
|
|
96
|
+
404 on `npm view` for a short window (seconds to ~1 minute) even though the
|
|
97
|
+
publish succeeded. The CI log line `+ @groeponline/pi-wishcraft@<v>`
|
|
98
|
+
and HTTP `200` from `https://registry.npmjs.org/@groeponline%2fpi-wishcraft`
|
|
99
|
+
are the real success signals; retry `npm view` if you see an early 404.
|
|
100
|
+
- **Wrong SSH key = `Permission denied (publickey)` / `denied to OnlineChef`.**
|
|
101
|
+
Always push with `GIT_SSH_COMMAND='ssh -F ~/.ssh/config-groeponline -o IdentityFile=~/.ssh/sheesh'`.
|
|
102
|
+
- **TPS used to show absurd values** (e.g. `tps:12775`) because `sessionStartTime`
|
|
103
|
+
resets on extension reload while output is cumulative. Fixed in 0.15.0 with a
|
|
104
|
+
rolling token-rate. If TPS ever looks wrong again, check that `tpsSegment` still
|
|
105
|
+
uses the rolling/EMA path, not `output / elapsed`.
|
|
106
|
+
- **Open-ports used to count raw `ss` lines** (IPv4+IPv6 double counting).
|
|
107
|
+
Fixed in 0.15.0 to dedupe by port number; column parsing is column-agnostic for
|
|
108
|
+
both `ss` and `netstat`.
|
|
109
|
+
- The footer is rendered by Pi core (`FooterComponent.render` returns `string[]`),
|
|
110
|
+
so the live status bar cannot be clicked. Interactivity is delivered via
|
|
111
|
+
`alt+p` (navigable segment overlay), `/tps`, and `/open-ports`.
|
|
112
|
+
|
|
113
|
+
## Versioning
|
|
114
|
+
|
|
115
|
+
- **patch** (`0.15.0 → 0.15.1`): bug fixes, doc only.
|
|
116
|
+
- **minor** (`0.15.x → 0.16.0`): new segments/presets, features, additive changes.
|
|
117
|
+
- **major**: breaking changes to settings shape or exported API.
|
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Roadmap — what to improve & how to integrate better
|
|
2
|
+
|
|
3
|
+
Status after 0.17.2. Package renamed to `@groeponline/pi-wishcraft` from v0.18.0. Concrete, prioritized. Not commitments — pick what Joep wants.
|
|
4
|
+
|
|
5
|
+
## Next release (0.18.0 — pi-wishcraft)
|
|
6
|
+
|
|
7
|
+
- Package renamed from `@groeponline/pi-powerline-footer` to `@groeponline/pi-wishcraft`
|
|
8
|
+
- src/ restructure committed (all root-level monofiles moved to `src/<domain>/`)
|
|
9
|
+
- Flaky `process.env.USER` test fixed
|
|
10
|
+
- `npx madge --circular src index.ts bash-mode queue` added as CI step
|
|
11
|
+
|
|
12
|
+
## Correctness (done, keep honest)
|
|
13
|
+
|
|
14
|
+
- TPS = 1s sliding window over a 5s sample ring. **Verify it stays this way** if anyone touches `tpsSegment`. Do not regress to session-average or per-render EMA (both spike: `tps:12775`, `tps:1118`).
|
|
15
|
+
- Open-ports = unique TCP by default; `openPorts.includeUdp` opts in. Column parsing is column-agnostic (ss + netstat).
|
|
16
|
+
- Context segment hides on `contextWindow <= 0` (no more `NaN`/`??`).
|
|
17
|
+
|
|
18
|
+
## Configurability improvements (next, low effort)
|
|
19
|
+
|
|
20
|
+
1. **Labels for every segment, not just tps/open_ports.** Today `segmentLabels` is applied inside the two custom segments. Generalize: apply in `renderSegment` so `model`, `git`, `cost`, `context_pct`, `time`, … all honor a label. Small diff, big consistency win.
|
|
21
|
+
2. **Per-segment `format`/`template` override.** Let users set e.g. `segmentOptions.tps.template: "{value} tok/s"` or `time.format: "HH:mm"`. Template > label for full control.
|
|
22
|
+
3. **Visibility toggle per segment from the menu.** `Configure…` already changes preset/TPS/UDP/labels; add "disable segment" / "enable segment" that writes `powerline.disabledSegments` live (no edit-the-JSON round-trip).
|
|
23
|
+
4. **Preset editor in the menu.** Build a custom preset interactively (pick left/right segments) and save it to settings — today custom presets are JSON-only.
|
|
24
|
+
5. **`segmentOptions.tps.windowMs`** — expose the 1s window length so fast models (Groq) can widen it for a smoother read.
|
|
25
|
+
|
|
26
|
+
## Interaction improvements (the "levendig + klikbaar" thread)
|
|
27
|
+
|
|
28
|
+
1. **Sub-menus via stacked overlays.** `ctx.ui.custom()` closes on `done()`; a sub-menu opens a new `custom()`. Today the menu uses `ctx.ui.select` (flat). Upgrade the navigator + configure to overlay `SelectList` for arrow nav + descriptions, matching the segment-navigator chrome.
|
|
29
|
+
2. **Per-segment "more info" on a second key.** In the navigator, `enter` activates, `→`/`tab` opens a detail view for that segment (full ports list, git diff summary, cost breakdown, context window math). This is the "soort gelijk menu maar anders waar je bijv alle open ports kan zien" Joep asked for.
|
|
30
|
+
3. **Live refresh while the overlay is open.** Overlays re-render on `tui.requestRender()`; wire a light timer so the ports list / TPS detail tick while open.
|
|
31
|
+
4. **Mouse support** — not available; Pi core owns the footer. Keep documenting that overlay nav is the equivalent.
|
|
32
|
+
|
|
33
|
+
## Integration with ChefGroep surfaces
|
|
34
|
+
|
|
35
|
+
1. **ChefBar / command center.** Expose powerline state (preset, TPS, ports, cost) via `ctx.ui.setStatus("powerline-tps", …)` so ChefBar/other extensions can read it. Already partly there (extension statuses); formalize a stable key set (`powerline.tps`, `powerline.ports`, `powerline.preset`).
|
|
36
|
+
2. **joep-ops / Vault dashboard.** The same `ss`/token data could feed a fleet dashboard. Factor `countListeningPorts` + a token-rate sampler into a tiny shared module both can import (keep it in this repo, export it).
|
|
37
|
+
3. **PostHog.** Capture preset/menu usage events (`powerline.preset_changed`, `powerline.menu_opened`) per the org instrumentation rule — only if Joep opts in; the bar itself stays telemetry-free.
|
|
38
|
+
4. **Fleet (sofie/bc-scan-2).** `open_ports` is laptop-local. A fleet variant could SSH-probe a named host's ports; gate behind a `segmentOptions.openPorts.host` setting.
|
|
39
|
+
|
|
40
|
+
## Release/quality
|
|
41
|
+
|
|
42
|
+
1. **Dependabot vulns** — GitHub flagged 7 (3 high, 4 moderate) on default branch. Triage; most are devDep transitive. Add `npm audit` to CI as non-blocking info, or pin.
|
|
43
|
+
2. **Test the menu.** The overlay/menu functions aren't unit-tested (hard to test `ctx.ui.custom` headless). Add a thin `SelectList`-based pure function for "build segment items" and test that, leaving the overlay shell untested.
|
|
44
|
+
3. **Typecheck in pre-push.** Add a `prepublishOnly` that runs `tsc` so a bad type never ships even if CI is skipped.
|
|
45
|
+
4. `npm deprecate @groeponline/pi-powerline-footer "Renamed to @groeponline/pi-wishcraft"` after first successful publish under new name
|
|
46
|
+
5. `prepublishOnly` script with `tsc` to prevent bad types from shipping
|
|
47
|
+
|
|
48
|
+
## Not doing (YAGNI)
|
|
49
|
+
|
|
50
|
+
- No mouse/click on the live footer (Pi core limitation; overlay nav is the path).
|
|
51
|
+
- No custom "embed/component" registration for footer segments — segments are data (text), and `customItems` + `command/env/static` segments already cover user-defined content. `setWidget`/`registerEntryRenderer` are for chat, not the footer.
|
|
52
|
+
- No third control surface — keep interactivity in this extension's overlays + commands.
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AutocompleteItem,
|
|
3
|
+
AutocompleteProvider,
|
|
4
|
+
AutocompleteSuggestions,
|
|
5
|
+
} from "@earendil-works/pi-tui";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getOneOffBashCommandContext,
|
|
9
|
+
isExtendedCompletionItem,
|
|
10
|
+
supportsShouldTriggerFileCompletion,
|
|
11
|
+
} from "./completion.ts";
|
|
12
|
+
import type { ExtendedCompletionItem } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
function applyExtendedCompletion(
|
|
15
|
+
lines: string[],
|
|
16
|
+
cursorLine: number,
|
|
17
|
+
item: ExtendedCompletionItem,
|
|
18
|
+
): {
|
|
19
|
+
lines: string[];
|
|
20
|
+
cursorLine: number;
|
|
21
|
+
cursorCol: number;
|
|
22
|
+
} {
|
|
23
|
+
const currentLine = lines[cursorLine] || "";
|
|
24
|
+
const startCol = Math.max(0, Math.min(item.startCol, currentLine.length));
|
|
25
|
+
const endCol = Math.max(startCol, Math.min(item.endCol, currentLine.length));
|
|
26
|
+
const nextLine =
|
|
27
|
+
currentLine.slice(0, startCol) +
|
|
28
|
+
item.replacement +
|
|
29
|
+
currentLine.slice(endCol);
|
|
30
|
+
const nextLines = [...lines];
|
|
31
|
+
nextLines[cursorLine] = nextLine;
|
|
32
|
+
return {
|
|
33
|
+
lines: nextLines,
|
|
34
|
+
cursorLine,
|
|
35
|
+
cursorCol: startCol + item.replacement.length,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class BashAutocompleteProvider implements AutocompleteProvider {
|
|
40
|
+
async getSuggestions(): Promise<AutocompleteSuggestions | null> {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
applyCompletion(
|
|
45
|
+
lines: string[],
|
|
46
|
+
cursorLine: number,
|
|
47
|
+
cursorCol: number,
|
|
48
|
+
item: AutocompleteItem,
|
|
49
|
+
): {
|
|
50
|
+
lines: string[];
|
|
51
|
+
cursorLine: number;
|
|
52
|
+
cursorCol: number;
|
|
53
|
+
} {
|
|
54
|
+
if (!isExtendedCompletionItem(item)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"Expected an extended completion item for bash autocomplete",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return applyExtendedCompletion(lines, cursorLine, item);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
shouldTriggerFileCompletion(): boolean {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class OneOffBashAutocompleteProvider implements AutocompleteProvider {
|
|
69
|
+
async getSuggestions(): Promise<AutocompleteSuggestions | null> {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
applyCompletion(
|
|
74
|
+
lines: string[],
|
|
75
|
+
cursorLine: number,
|
|
76
|
+
cursorCol: number,
|
|
77
|
+
item: AutocompleteItem,
|
|
78
|
+
): {
|
|
79
|
+
lines: string[];
|
|
80
|
+
cursorLine: number;
|
|
81
|
+
cursorCol: number;
|
|
82
|
+
} {
|
|
83
|
+
if (!isExtendedCompletionItem(item)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"Expected an extended completion item for one-off bash autocomplete",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return applyExtendedCompletion(lines, cursorLine, item);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
shouldTriggerFileCompletion(
|
|
93
|
+
lines: string[],
|
|
94
|
+
cursorLine: number,
|
|
95
|
+
cursorCol: number,
|
|
96
|
+
): boolean {
|
|
97
|
+
const bang =
|
|
98
|
+
cursorLine === 0 ? getOneOffBashCommandContext(lines[0] || "") : null;
|
|
99
|
+
return bang !== null && cursorCol >= bang.offset;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getProviderTriggerCharacters(
|
|
104
|
+
provider: AutocompleteProvider | undefined,
|
|
105
|
+
): string[] {
|
|
106
|
+
const candidate =
|
|
107
|
+
provider && typeof provider === "object"
|
|
108
|
+
? Reflect.get(provider, "triggerCharacters")
|
|
109
|
+
: undefined;
|
|
110
|
+
return Array.isArray(candidate)
|
|
111
|
+
? candidate.filter(
|
|
112
|
+
(character): character is string =>
|
|
113
|
+
typeof character === "string" && character.length === 1,
|
|
114
|
+
)
|
|
115
|
+
: [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class ModeAwareAutocompleteProvider implements AutocompleteProvider {
|
|
119
|
+
readonly triggerCharacters: string[];
|
|
120
|
+
private readonly defaultProvider: AutocompleteProvider | undefined;
|
|
121
|
+
private readonly bashProvider: AutocompleteProvider;
|
|
122
|
+
private readonly oneOffBashProvider: AutocompleteProvider;
|
|
123
|
+
private readonly isBashModeActive: () => boolean;
|
|
124
|
+
|
|
125
|
+
constructor(
|
|
126
|
+
defaultProvider: AutocompleteProvider | undefined,
|
|
127
|
+
bashProvider: AutocompleteProvider,
|
|
128
|
+
oneOffBashProvider: AutocompleteProvider,
|
|
129
|
+
isBashModeActive: () => boolean,
|
|
130
|
+
) {
|
|
131
|
+
this.defaultProvider = defaultProvider;
|
|
132
|
+
this.bashProvider = bashProvider;
|
|
133
|
+
this.oneOffBashProvider = oneOffBashProvider;
|
|
134
|
+
this.isBashModeActive = isBashModeActive;
|
|
135
|
+
this.triggerCharacters = [
|
|
136
|
+
...new Set([
|
|
137
|
+
...getProviderTriggerCharacters(defaultProvider),
|
|
138
|
+
...getProviderTriggerCharacters(bashProvider),
|
|
139
|
+
...getProviderTriggerCharacters(oneOffBashProvider),
|
|
140
|
+
]),
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async getSuggestions(
|
|
145
|
+
lines: string[],
|
|
146
|
+
cursorLine: number,
|
|
147
|
+
cursorCol: number,
|
|
148
|
+
options: { signal: AbortSignal; force?: boolean },
|
|
149
|
+
): Promise<AutocompleteSuggestions | null> {
|
|
150
|
+
if (this.isBashModeActive()) {
|
|
151
|
+
return this.bashProvider.getSuggestions(
|
|
152
|
+
lines,
|
|
153
|
+
cursorLine,
|
|
154
|
+
cursorCol,
|
|
155
|
+
options,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const shouldUseOneOffBash =
|
|
160
|
+
supportsShouldTriggerFileCompletion(this.oneOffBashProvider) &&
|
|
161
|
+
this.oneOffBashProvider.shouldTriggerFileCompletion(
|
|
162
|
+
lines,
|
|
163
|
+
cursorLine,
|
|
164
|
+
cursorCol,
|
|
165
|
+
);
|
|
166
|
+
if (shouldUseOneOffBash) {
|
|
167
|
+
return this.oneOffBashProvider.getSuggestions(
|
|
168
|
+
lines,
|
|
169
|
+
cursorLine,
|
|
170
|
+
cursorCol,
|
|
171
|
+
options,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
this.defaultProvider?.getSuggestions(lines, cursorLine, cursorCol, options) ??
|
|
177
|
+
null
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
applyCompletion(
|
|
182
|
+
lines: string[],
|
|
183
|
+
cursorLine: number,
|
|
184
|
+
cursorCol: number,
|
|
185
|
+
item: AutocompleteItem,
|
|
186
|
+
prefix: string,
|
|
187
|
+
) {
|
|
188
|
+
if (this.isBashModeActive()) {
|
|
189
|
+
return this.bashProvider.applyCompletion(
|
|
190
|
+
lines,
|
|
191
|
+
cursorLine,
|
|
192
|
+
cursorCol,
|
|
193
|
+
item,
|
|
194
|
+
prefix,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const shouldUseOneOffBash =
|
|
199
|
+
supportsShouldTriggerFileCompletion(this.oneOffBashProvider) &&
|
|
200
|
+
this.oneOffBashProvider.shouldTriggerFileCompletion(
|
|
201
|
+
lines,
|
|
202
|
+
cursorLine,
|
|
203
|
+
cursorCol,
|
|
204
|
+
);
|
|
205
|
+
if (shouldUseOneOffBash) {
|
|
206
|
+
return this.oneOffBashProvider.applyCompletion(
|
|
207
|
+
lines,
|
|
208
|
+
cursorLine,
|
|
209
|
+
cursorCol,
|
|
210
|
+
item,
|
|
211
|
+
prefix,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!this.defaultProvider) {
|
|
216
|
+
return { lines, cursorLine, cursorCol };
|
|
217
|
+
}
|
|
218
|
+
return this.defaultProvider.applyCompletion(
|
|
219
|
+
lines,
|
|
220
|
+
cursorLine,
|
|
221
|
+
cursorCol,
|
|
222
|
+
item,
|
|
223
|
+
prefix,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
shouldTriggerFileCompletion(
|
|
228
|
+
lines: string[],
|
|
229
|
+
cursorLine: number,
|
|
230
|
+
cursorCol: number,
|
|
231
|
+
): boolean {
|
|
232
|
+
if (this.isBashModeActive()) {
|
|
233
|
+
if (!supportsShouldTriggerFileCompletion(this.bashProvider)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
const bashProvider = this.bashProvider;
|
|
237
|
+
return bashProvider.shouldTriggerFileCompletion(
|
|
238
|
+
lines,
|
|
239
|
+
cursorLine,
|
|
240
|
+
cursorCol,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const oneOffBashProvider = this.oneOffBashProvider;
|
|
245
|
+
const shouldUseOneOffBash =
|
|
246
|
+
supportsShouldTriggerFileCompletion(oneOffBashProvider) &&
|
|
247
|
+
oneOffBashProvider.shouldTriggerFileCompletion(
|
|
248
|
+
lines,
|
|
249
|
+
cursorLine,
|
|
250
|
+
cursorCol,
|
|
251
|
+
);
|
|
252
|
+
if (shouldUseOneOffBash) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (
|
|
257
|
+
!this.defaultProvider ||
|
|
258
|
+
!supportsShouldTriggerFileCompletion(this.defaultProvider)
|
|
259
|
+
) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const defaultProvider = this.defaultProvider;
|
|
263
|
+
return defaultProvider.shouldTriggerFileCompletion(
|
|
264
|
+
lines,
|
|
265
|
+
cursorLine,
|
|
266
|
+
cursorCol,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|