tabby-quick-snips 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # tabby-quick-snips
2
+
3
+ Manual alias-based quick snippets plugin for [Tabby Terminal](https://tabby.sh).
4
+
5
+ This plugin runs on the Tabby UI side and shows a small suggestion overlay inside the terminal. It does not depend on the remote shell. Commands are defined manually by the user and matched with fuzzy search or short aliases such as `ps`, `reset`, or `db-update`.
6
+
7
+ ## Features
8
+
9
+ - Client-side autocomplete inside Tabby UI
10
+ - Manual command list management
11
+ - Alias/shortcut support via `shortcut` or `normalized`
12
+ - Fuzzy search like `ps` -> `docker compose ps`
13
+ - Inline dropdown overlay inside the terminal
14
+ - Keyboard navigation with `ArrowUp` and `ArrowDown`
15
+ - Accept suggestion with `Tab`
16
+ - Pin, edit, delete, import, and export entries
17
+ - Settings tab inside Tabby for managing entries
18
+
19
+ ## File structure
20
+
21
+ ```text
22
+ tabby-quick-snips/
23
+ ├── src/
24
+ │ ├── config.ts
25
+ │ ├── decorator.ts
26
+ │ ├── fuzzy.ts
27
+ │ ├── historyStore.ts
28
+ │ ├── hotkeys.ts
29
+ │ ├── index.ts
30
+ │ ├── settings.ts
31
+ │ ├── terminalState.ts
32
+ │ └── types.ts
33
+ ├── LICENSE
34
+ ├── package.json
35
+ ├── README.md
36
+ ├── tsconfig.json
37
+ └── webpack.config.js
38
+ ```
39
+
40
+ ## How it works
41
+
42
+ 1. You define aliases and commands in the `Quick Snips` settings tab.
43
+ 2. In terminal, type a partial alias or command, for example `ps` or `reset`.
44
+ 3. Press the configured hotkey to open the suggestion overlay.
45
+ 4. Use `ArrowUp` and `ArrowDown` to select a result.
46
+ 5. Press `Tab` to insert the selected command.
47
+
48
+ ## Installation
49
+
50
+ ### Local development install
51
+
52
+ 1. Clone or copy this plugin into its own folder.
53
+ 2. Install dependencies:
54
+
55
+ ```bash
56
+ npm install
57
+ ```
58
+
59
+ 3. Build the plugin:
60
+
61
+ ```bash
62
+ npm run build
63
+ ```
64
+
65
+ 4. Load it in Tabby.
66
+
67
+ Typical options:
68
+
69
+ - Symlink or copy the package into Tabby's plugin path.
70
+ - Or start Tabby with:
71
+
72
+ ```bash
73
+ TABBY_PLUGINS=/absolute/path/to/plugins tabby
74
+ ```
75
+
76
+ If you keep this package in `/absolute/path/to/plugins/tabby-quick-snips`, Tabby can discover it as a standalone plugin package.
77
+
78
+ ### Publish to npm later
79
+
80
+ This package is already structured as a standalone npm module:
81
+
82
+ - package name: `tabby-quick-snips`
83
+ - keyword: `tabby-plugin`
84
+ - build output: `dist/index.js`
85
+
86
+ Before publishing, update:
87
+
88
+ - `package.json` author/repository/homepage fields
89
+ - version number
90
+ - compatibility notes for the Tabby version you target
91
+
92
+ Then publish with:
93
+
94
+ ```bash
95
+ npm publish --access public
96
+ ```
97
+
98
+ ## Example usage
99
+
100
+ The plugin ships with these default entries:
101
+
102
+ ```json
103
+ [
104
+ {
105
+ "id": "cmd_6t82mzgvmnj17uvo",
106
+ "command": "docker compose ps",
107
+ "normalized": "ps",
108
+ "pinned": false
109
+ },
110
+ {
111
+ "id": "cmd_6t82mzgvmnj17uv2",
112
+ "command": "docker compose down; docker compose up -d --force-recreate",
113
+ "normalized": "reset",
114
+ "pinned": false
115
+ },
116
+ {
117
+ "id": "cmd_6t82mzgvmnj17uv3",
118
+ "command": "php bin/console doctrine:cache:clear-metadata; php bin/console doctrine:schema:update --force --no-interaction;",
119
+ "normalized": "db-update",
120
+ "pinned": false
121
+ },
122
+ {
123
+ "id": "demo_git_status",
124
+ "command": "git status",
125
+ "normalized": "git status",
126
+ "pinned": true
127
+ }
128
+ ]
129
+ ```
130
+
131
+ If your list gets messy, open `Quick Snips` settings and click `Reset Defaults`.
132
+
133
+ Then in terminal:
134
+
135
+ 1. Type `ps`
136
+ 2. Press `Cmd+P`
137
+ 3. Choose `docker compose ps`
138
+ 4. Press `Tab`
139
+
140
+ Or:
141
+
142
+ 1. Type `reset`
143
+ 2. Press `Cmd+P`
144
+ 3. Press `Tab`
145
+
146
+ ## Default behavior
147
+
148
+ - Hotkey: `Cmd+P`
149
+ - Auto show: disabled
150
+ - Minimum typed characters: `2`
151
+ - Suggestions come only from manually saved entries
152
+ - `Enter` does not accept suggestions
153
+ - `Tab` accepts the selected suggestion
154
+
155
+ ## Notes
156
+
157
+ - Data is stored in Tabby's local config under `historyAutocomplete`.
158
+ - The visible plugin name in Tabby is `Quick Snips`.
159
+ - `normalized` is treated as the alias trigger value.
160
+ - If `shortcut` exists, it is also used as the alias trigger value.
161
+ - The settings tab is the recommended way to manage entries.
162
+
163
+ ## Suggested next improvements
164
+
165
+ - Replace the simple settings form with a cleaner table editor.
166
+ - Add drag-and-drop sorting and sections/groups.
167
+ - Add optional per-profile command sets.
@@ -0,0 +1,26 @@
1
+ import { ConfigProvider } from 'tabby-core';
2
+ import type { CommandHistoryEntry, CommandHistoryStore } from './types';
3
+ export declare const DEFAULT_HISTORY_ENTRIES: CommandHistoryEntry[];
4
+ export declare const DEFAULT_HISTORY_STORE: CommandHistoryStore;
5
+ export declare class HistoryAutocompleteConfigProvider extends ConfigProvider {
6
+ defaults: {
7
+ historyAutocomplete: CommandHistoryStore;
8
+ };
9
+ platformDefaults: {
10
+ macOS: {
11
+ hotkeys: {
12
+ 'history-autocomplete.toggle': string[];
13
+ };
14
+ };
15
+ Windows: {
16
+ hotkeys: {
17
+ 'history-autocomplete.toggle': string[];
18
+ };
19
+ };
20
+ Linux: {
21
+ hotkeys: {
22
+ 'history-autocomplete.toggle': string[];
23
+ };
24
+ };
25
+ };
26
+ }
@@ -0,0 +1,27 @@
1
+ import { BaseTerminalTabComponent, TerminalDecorator } from 'tabby-terminal';
2
+ import { HistoryAutocompleteStore } from './historyStore';
3
+ export declare class HistoryAutocompleteDecorator extends TerminalDecorator {
4
+ private historyStore;
5
+ private sessions;
6
+ constructor(historyStore: HistoryAutocompleteStore);
7
+ attach(terminal: BaseTerminalTabComponent<any>): void;
8
+ private attachUi;
9
+ private onKeyDown;
10
+ private refreshSuggestions;
11
+ private render;
12
+ private makeFooterButton;
13
+ private acceptSelection;
14
+ private togglePinSelection;
15
+ private deleteSelection;
16
+ private editSelection;
17
+ private exportHistory;
18
+ private addEntry;
19
+ private importHistory;
20
+ private hide;
21
+ private sendInput;
22
+ private highlight;
23
+ private escapeHtml;
24
+ private relativeTime;
25
+ private matchesHotkey;
26
+ private getProfileId;
27
+ }
@@ -0,0 +1,3 @@
1
+ import { SuggestionResult, CommandHistoryEntry } from './types';
2
+ export declare function fuzzyScore(query: string, command: string): SuggestionResult | null;
3
+ export declare function rankHistory(query: string, entries: CommandHistoryEntry[], limit?: number): SuggestionResult[];
@@ -0,0 +1,23 @@
1
+ import { ConfigService } from 'tabby-core';
2
+ import { CommandHistoryEntry, CommandHistoryStore, SuggestionResult } from './types';
3
+ export declare class HistoryAutocompleteStore {
4
+ private config;
5
+ constructor(config: ConfigService);
6
+ private normalizeCommand;
7
+ private normalizeShortcut;
8
+ getStore(): CommandHistoryStore;
9
+ addManualEntry(command: string, profileId?: string | null, pinned?: boolean): Promise<void>;
10
+ getSuggestions(query: string, profileId?: string | null): SuggestionResult[];
11
+ pinEntry(entryId: string, pinned: boolean): Promise<void>;
12
+ deleteEntry(entryId: string): Promise<void>;
13
+ updateEntryCommand(entryId: string, command: string): Promise<void>;
14
+ exportJson(): string;
15
+ importJson(json: string): Promise<void>;
16
+ saveSettings(settings: Partial<Pick<CommandHistoryStore, 'hotkey' | 'autoShow' | 'maxEntries' | 'minChars' | 'scope'>>, entries?: CommandHistoryEntry[]): Promise<void>;
17
+ resetToDefaults(): Promise<void>;
18
+ private normalizeEntries;
19
+ private filterEntries;
20
+ private trimEntries;
21
+ private persist;
22
+ private makeId;
23
+ }
@@ -0,0 +1,6 @@
1
+ import { HotkeyDescription, HotkeyProvider } from 'tabby-core';
2
+ export declare class HistoryAutocompleteHotkeyProvider extends HotkeyProvider {
3
+ hotkeys: HotkeyDescription[];
4
+ constructor();
5
+ provide(): Promise<HotkeyDescription[]>;
6
+ }
@@ -0,0 +1,2 @@
1
+ export default class HistoryAutocompletePluginModule {
2
+ }
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ (()=>{"use strict";var e={28(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.HistoryAutocompleteConfigProvider=t.DEFAULT_HISTORY_STORE=t.DEFAULT_HISTORY_ENTRIES=void 0;const s=n(368);t.DEFAULT_HISTORY_ENTRIES=[{id:"cmd_6t82mzgvmnj17uvo",command:"docker compose ps",normalized:"ps",shortcut:"ps",pinned:!1,count:4,createdAt:"2026-04-03T15:01:47.412Z",lastUsedAt:"2026-04-03T15:13:24.152Z",profileIds:["local:default"]},{id:"cmd_6t82mzgvmnj17uv2",command:"docker compose down; docker compose up -d --force-recreate",normalized:"reset",shortcut:"reset",pinned:!1,count:4,createdAt:"2026-04-03T15:01:47.412Z",lastUsedAt:"2026-04-03T15:13:24.152Z",profileIds:["local:default"]},{id:"cmd_6t82mzgvmnj17uv3",command:"php bin/console doctrine:cache:clear-metadata; php bin/console doctrine:schema:update --force --no-interaction;",normalized:"db-update",shortcut:"db-update",pinned:!1,count:4,createdAt:"2026-04-03T15:01:47.412Z",lastUsedAt:"2026-04-03T15:13:24.152Z",profileIds:["local:default"]},{id:"demo_git_status",command:"git status",normalized:"git status",shortcut:"git status",pinned:!0,count:3,lastUsedAt:"2026-04-03T14:30:00.000Z",createdAt:"2026-04-03T14:30:00.000Z",profileIds:[]}],t.DEFAULT_HISTORY_STORE={version:1,scope:"both",maxEntries:2e3,autoShow:!1,minChars:2,hotkey:"Cmd-P",entries:t.DEFAULT_HISTORY_ENTRIES};class r extends s.ConfigProvider{constructor(){super(...arguments),this.defaults={historyAutocomplete:t.DEFAULT_HISTORY_STORE},this.platformDefaults={[s.Platform.macOS]:{hotkeys:{"history-autocomplete.toggle":["Cmd-P"]}},[s.Platform.Windows]:{hotkeys:{"history-autocomplete.toggle":["Cmd-P"]}},[s.Platform.Linux]:{hotkeys:{"history-autocomplete.toggle":["Cmd-P"]}}}}}t.HistoryAutocompleteConfigProvider=r},9(e,t,n){var s=this&&this.__decorate||function(e,t,n,s){var r,o=arguments.length,i=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(i=(o<3?r(i):o>3?r(t,n,i):r(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i},r=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)};Object.defineProperty(t,"__esModule",{value:!0}),t.HistoryAutocompleteDecorator=void 0;const o=n(430),i=n(319),a=n(859),c=n(621);let l=class extends i.TerminalDecorator{constructor(e){super(),this.historyStore=e,this.sessions=new WeakMap}attach(e){setTimeout(()=>this.attachUi(e)),e.destroyed$?.subscribe&&this.subscribeUntilDetached(e,e.destroyed$.subscribe(()=>{this.hide(e)}))}attachUi(e){const t=e.element?.nativeElement,n=e.content?.nativeElement??t;if(!n||this.sessions.has(e))return;"static"===getComputedStyle(n).position&&(n.style.position="relative");const s=document.createElement("div");s.className="history-autocomplete-overlay",s.style.cssText=["position:absolute","left:12px","right:12px","bottom:18px","z-index:30","display:none","background:#111827","color:#e5e7eb","border:1px solid rgba(255,255,255,0.08)","border-radius:10px","box-shadow:0 18px 40px rgba(0,0,0,0.45)","backdrop-filter: blur(12px)","overflow:hidden","font-family: ui-monospace, SFMono-Regular, Menlo, monospace","pointer-events:auto","max-width:min(720px, calc(100% - 24px))"].join(";");const r=document.createElement("div");r.style.cssText="max-height:260px;overflow:auto";const o=document.createElement("div");o.style.cssText=["display:flex","justify-content:space-between","gap:12px","padding:8px 12px","font-size:11px","color:#9ca3af","border-top:1px solid rgba(255,255,255,0.08)","background:rgba(255,255,255,0.02)"].join(";"),s.append(r,o),n.appendChild(s);const i=document.createElement("input");i.type="file",i.accept="application/json",i.style.display="none",n.appendChild(i);const a={host:n,keyTarget:t??n,overlay:s,list:r,footer:o,importInput:i,state:new c.TerminalInputState,suggestions:[],selectedIndex:0,visible:!1,manuallyOpened:!1};this.sessions.set(e,a),a.keyTarget.addEventListener("keydown",t=>this.onKeyDown(e,t),!0),i.addEventListener("change",()=>this.importHistory(e))}onKeyDown(e,t){const n=this.sessions.get(e);if(!n)return;if(this.matchesHotkey(e,t))return t.preventDefault(),t.stopPropagation(),n.manuallyOpened=!n.visible,void this.refreshSuggestions(e,n.state.getCurrentLine(),n.manuallyOpened);const s=n.state.applyKeydown(t);if(n.visible){if(n.manuallyOpened){if("ArrowDown"===t.key)return t.preventDefault(),n.selectedIndex=(n.selectedIndex+1)%Math.max(1,n.suggestions.length),void this.render(e);if("ArrowUp"===t.key)return t.preventDefault(),n.selectedIndex=(n.selectedIndex-1+Math.max(1,n.suggestions.length))%Math.max(1,n.suggestions.length),void this.render(e);if("Tab"===t.key){if(0===n.suggestions.length)return;return t.preventDefault(),t.stopPropagation(),void this.acceptSelection(e)}return"Escape"===t.key?(t.preventDefault(),void this.hide(e)):"Enter"===t.key?(this.hide(e),void n.state.setCurrentLine("")):"Delete"===t.key&&n.suggestions.length?(t.preventDefault(),void this.deleteSelection(e)):t.ctrlKey&&"p"===t.key.toLowerCase()&&n.suggestions.length?(t.preventDefault(),void this.togglePinSelection(e)):t.ctrlKey&&"e"===t.key.toLowerCase()&&n.suggestions.length?(t.preventDefault(),void this.editSelection(e)):void queueMicrotask(()=>{n.visible&&n.manuallyOpened&&this.refreshSuggestions(e,s,!0)})}}else"Enter"===t.key&&n.state.setCurrentLine("")}refreshSuggestions(e,t,n=!1){const s=this.sessions.get(e);if(!s)return;const r=s.manuallyOpened?t:t.trim();s.suggestions=this.historyStore.getSuggestions(r,this.getProfileId(e)),s.selectedIndex=Math.min(s.selectedIndex,Math.max(0,s.suggestions.length-1)),s.visible=n||s.suggestions.length>0,s.visible&&0!==s.suggestions.length||s.manuallyOpened&&t.trim().length>0&&(s.suggestions=this.historyStore.getSuggestions("",this.getProfileId(e)),s.selectedIndex=Math.min(s.selectedIndex,Math.max(0,s.suggestions.length-1)),s.visible=s.suggestions.length>0),s.visible&&0!==s.suggestions.length?(s.overlay.style.display="block",this.render(e)):this.hide(e)}render(e){const t=this.sessions.get(e);if(!t)return;t.list.innerHTML="",t.suggestions.forEach((n,s)=>{const r=document.createElement("div");r.style.cssText=["display:flex","align-items:center","gap:10px","padding:9px 12px","cursor:pointer","border-bottom:1px solid rgba(255,255,255,0.04)",s===t.selectedIndex?"background:rgba(59,130,246,0.22)":"background:transparent"].join(";");const o=document.createElement("div");o.textContent=n.entry.pinned?"★":"↳",o.style.cssText="width:18px;color:#93c5fd;flex:0 0 auto";const i=document.createElement("div");i.style.cssText="display:flex;flex-direction:column;min-width:0;flex:1 1 auto";const a=document.createElement("div");a.style.cssText="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:13px",a.innerHTML=this.highlight(n.entry.command,n.highlights);const c=document.createElement("div");c.style.cssText="font-size:11px;color:#9ca3af";const l=n.entry.shortcut?.trim();c.textContent=l&&l!==n.entry.command.trim().toLowerCase()?`${l} • ${n.entry.count}x used • ${this.relativeTime(n.entry.lastUsedAt)}`:`${n.entry.count}x used • ${this.relativeTime(n.entry.lastUsedAt)}`,i.append(a,c),r.append(o,i),r.addEventListener("mouseenter",()=>{t.selectedIndex=s,this.render(e)}),r.addEventListener("mousedown",n=>{n.preventDefault(),t.selectedIndex=s,this.acceptSelection(e)}),t.list.appendChild(r)}),t.footer.innerHTML="";const n=document.createElement("div");n.textContent="↑↓ navigate • Tab apply • Del remove • Ctrl+P pin • Ctrl+E edit";const s=document.createElement("div");s.style.cssText="display:flex;gap:10px",s.appendChild(this.makeFooterButton("Add",()=>{this.addEntry(e)})),s.appendChild(this.makeFooterButton("Export",()=>this.exportHistory())),s.appendChild(this.makeFooterButton("Import",()=>t.importInput.click())),t.footer.append(n,s)}makeFooterButton(e,t){const n=document.createElement("button");return n.type="button",n.textContent=e,n.style.cssText=["background:transparent","border:none","color:#cbd5e1","cursor:pointer","padding:0","font-size:11px"].join(";"),n.addEventListener("mousedown",e=>e.preventDefault()),n.addEventListener("click",t),n}async acceptSelection(e){const t=this.sessions.get(e);if(!t)return;const n=t.suggestions[t.selectedIndex];if(!n)return;const s=t.state.getCurrentLine(),r=n.entry.command,o=s.trim().toLowerCase(),i=r.toLowerCase();if(o&&i.startsWith(o)){const t=r.slice(s.length);this.sendInput(e,t)}else this.sendInput(e,""),this.sendInput(e,r);t.state.setCurrentLine(r),this.hide(e)}async togglePinSelection(e){const t=this.sessions.get(e),n=t?.suggestions[t.selectedIndex];n&&(await this.historyStore.pinEntry(n.entry.id,!n.entry.pinned),this.refreshSuggestions(e,t?.state.getCurrentLine()??"",!0))}async deleteSelection(e){const t=this.sessions.get(e),n=t?.suggestions[t.selectedIndex];n&&(await this.historyStore.deleteEntry(n.entry.id),this.refreshSuggestions(e,t?.state.getCurrentLine()??"",!0))}async editSelection(e){const t=this.sessions.get(e),n=t?.suggestions[t.selectedIndex];if(!n)return;const s=window.prompt("Edit command history entry",n.entry.command);s?.trim()&&(await this.historyStore.updateEntryCommand(n.entry.id,s),this.refreshSuggestions(e,t?.state.getCurrentLine()??"",!0))}exportHistory(){const e=new Blob([this.historyStore.exportJson()],{type:"application/json"}),t=URL.createObjectURL(e),n=document.createElement("a");n.href=t,n.download="tabby-quick-snips.json",n.click(),URL.revokeObjectURL(t)}async addEntry(e){const t=this.sessions.get(e),n=t?.state.getCurrentLine()??"",s=window.prompt("Add command to history",n);s?.trim()&&(await this.historyStore.addManualEntry(s,this.getProfileId(e)),t&&(t.manuallyOpened=!0),this.refreshSuggestions(e,s,!0))}async importHistory(e){const t=this.sessions.get(e),n=t?.importInput.files?.[0];if(!n)return;const s=await n.text();await this.historyStore.importJson(s),t.importInput.value="",this.refreshSuggestions(e,t?.state.getCurrentLine()??"",!0)}hide(e){const t=this.sessions.get(e);t&&(t.visible=!1,t.manuallyOpened=!1,t.overlay.style.display="none")}sendInput(e,t){const n=e.sendInput;"function"==typeof n&&n.call(e,Buffer.from(t))}highlight(e,t){const n=new Set(t);return Array.from(e).map((e,t)=>n.has(t)?`<span style="color:#ffffff;font-weight:700">${this.escapeHtml(e)}</span>`:`<span style="color:#cbd5e1">${this.escapeHtml(e)}</span>`).join("")}escapeHtml(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}relativeTime(e){const t=Date.now()-Date.parse(e),n=Math.floor(t/6e4);if(n<1)return"just now";if(n<60)return`${n}m ago`;const s=Math.floor(n/60);return s<24?`${s}h ago`:`${Math.floor(s/24)}d ago`}matchesHotkey(e,t){const n=(this.historyStore.getStore().hotkey||"Ctrl-Space").toLowerCase().split("-"),s="space"===n[n.length-1]?" ":n[n.length-1],r=n.includes("ctrl"),o=n.includes("alt"),i=n.includes("shift"),a=n.includes("meta")||n.includes("cmd");return t.key.toLowerCase()===s&&t.ctrlKey===r&&t.altKey===o&&t.shiftKey===i&&t.metaKey===a}getProfileId(e){const t=e.profile;return t?.id??t?.name??null}};t.HistoryAutocompleteDecorator=l,t.HistoryAutocompleteDecorator=l=s([(0,o.Injectable)(),r("design:paramtypes",[a.HistoryAutocompleteStore])],l)},324(e,t){function n(e){return e.trim().toLowerCase()}function s(e,t){if(0===t)return 10;const n=e[t-1];return/[\s:/._-]/.test(n)?6:0}function r(e,t){const r=n(e),o=n(t);if(!r)return{entry:null,score:0,highlights:[]};let i=0,a=0,c=0;const l=[];for(let e=0;e<o.length;e++)if(o[e]===r[i]){if(l.push(e),a+=4+s(o,e),e===i&&(a+=8),c>0&&(a+=5*c),c++,i++,i===r.length)return a+=Math.max(0,12-(e-l[0])),a+=o.startsWith(r)?20:0,a+=o.includes(` ${r}`)?8:0,{entry:null,score:a,highlights:l}}else c=0;return null}Object.defineProperty(t,"__esModule",{value:!0}),t.fuzzyScore=r,t.rankHistory=function(e,t,n=8){const s=e.trim().toLowerCase();return s?t.map(e=>{const t=r(s,e.command),n=e.shortcut?.trim()||e.normalized,o=n?r(s,n):null;if(!t&&!o)return null;const i=o?t?o.score>=t.score?o:t:o:t,a=Math.max(0,Math.min(20,e.count+Math.floor((Date.now()-Date.parse(e.lastUsedAt))/-864e5)));return{entry:e,score:i.score+(e.pinned?30:0)+a,highlights:i.highlights}}).filter(e=>Boolean(e)).sort((e,t)=>e.entry.pinned!==t.entry.pinned?Number(t.entry.pinned)-Number(e.entry.pinned):e.score!==t.score?t.score-e.score:Date.parse(t.entry.lastUsedAt)-Date.parse(e.entry.lastUsedAt)).slice(0,n):t.slice().sort((e,t)=>e.pinned!==t.pinned?Number(t.pinned)-Number(e.pinned):Date.parse(t.lastUsedAt)-Date.parse(e.lastUsedAt)).slice(0,n).map(e=>({entry:e,score:e.pinned?1e3:0,highlights:[]}))}},859(e,t,n){var s=this&&this.__decorate||function(e,t,n,s){var r,o=arguments.length,i=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(i=(o<3?r(i):o>3?r(t,n,i):r(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i},r=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)};Object.defineProperty(t,"__esModule",{value:!0}),t.HistoryAutocompleteStore=void 0;const o=n(430),i=n(368),a=n(28),c=n(324);let l=class{constructor(e){this.config=e}normalizeCommand(e){return e.trim().toLowerCase()}normalizeShortcut(e,t){const n=e?.trim().toLowerCase();return n||this.normalizeCommand(t)}getStore(){const e=this.config.store?.historyAutocomplete??{},t=Array.isArray(e.entries)&&e.entries.length?e.entries:a.DEFAULT_HISTORY_ENTRIES;return{...a.DEFAULT_HISTORY_STORE,...e,entries:this.normalizeEntries(t,e.maxEntries??a.DEFAULT_HISTORY_STORE.maxEntries)}}async addManualEntry(e,t,n=!1){const s=e.trim();if(!s)return;const r=this.getStore(),o=this.normalizeCommand(s),i=(new Date).toISOString(),a=r.entries.find(e=>this.normalizeCommand(e.command)===o);a?(a.command=s,a.pinned=a.pinned||n,a.lastUsedAt=i,t&&!a.profileIds.includes(t)&&a.profileIds.push(t)):r.entries.unshift({id:this.makeId(),command:s,normalized:o,shortcut:o,pinned:n,count:1,createdAt:i,lastUsedAt:i,profileIds:t?[t]:[]}),r.entries=this.trimEntries(r.entries,r.maxEntries),await this.persist(r)}getSuggestions(e,t){const n=this.getStore(),s=this.filterEntries(n,t);return(0,c.rankHistory)(e,s)}async pinEntry(e,t){const n=this.getStore(),s=n.entries.find(t=>t.id===e);s&&(s.pinned=t,await this.persist(n))}async deleteEntry(e){const t=this.getStore();t.entries=t.entries.filter(t=>t.id!==e),await this.persist(t)}async updateEntryCommand(e,t){const n=this.getStore(),s=n.entries.find(t=>t.id===e);s&&(s.command=t.trim(),s.normalized=this.normalizeShortcut(s.shortcut,s.command),s.lastUsedAt=(new Date).toISOString(),await this.persist(n))}exportJson(){return JSON.stringify(this.getStore(),null,2)}async importJson(e){const t=JSON.parse(e),n={...a.DEFAULT_HISTORY_STORE,...t,entries:Array.isArray(t.entries)?t.entries:[]};n.entries=this.trimEntries(n.entries,n.maxEntries).map(e=>({...e,normalized:this.normalizeShortcut(e.shortcut??e.normalized,e.command),shortcut:this.normalizeShortcut(e.shortcut??e.normalized,e.command),profileIds:Array.isArray(e.profileIds)?e.profileIds:[],pinned:Boolean(e.pinned),count:Math.max(1,e.count??1),createdAt:e.createdAt??(new Date).toISOString(),lastUsedAt:e.lastUsedAt??(new Date).toISOString()})),await this.persist(n)}async saveSettings(e,t){const n=this.getStore(),s={...n,...e,entries:t?this.normalizeEntries(t,e.maxEntries??n.maxEntries):n.entries};await this.persist(s)}async resetToDefaults(){await this.persist({...a.DEFAULT_HISTORY_STORE,entries:this.normalizeEntries(a.DEFAULT_HISTORY_ENTRIES,a.DEFAULT_HISTORY_STORE.maxEntries)})}normalizeEntries(e,t){return this.trimEntries(e,t).map(e=>({...e,id:e.id||this.makeId(),command:e.command.trim(),normalized:this.normalizeShortcut(e.shortcut??e.normalized,e.command),shortcut:this.normalizeShortcut(e.shortcut??e.normalized,e.command),pinned:Boolean(e.pinned),count:Math.max(1,e.count??1),createdAt:e.createdAt??(new Date).toISOString(),lastUsedAt:e.lastUsedAt??(new Date).toISOString(),profileIds:Array.isArray(e.profileIds)?e.profileIds:[]})).filter(e=>e.command)}filterEntries(e,t){return"global"===e.scope?e.entries:"profile"===e.scope&&t?e.entries.filter(e=>e.profileIds.includes(t)):(e.scope,e.entries)}trimEntries(e,t){return[...e.filter(e=>e.pinned),...e.filter(e=>!e.pinned).sort((e,t)=>Date.parse(t.lastUsedAt)-Date.parse(e.lastUsedAt))].slice(0,t)}async persist(e){this.config.store.historyAutocomplete.hotkey=e.hotkey,this.config.store.historyAutocomplete.autoShow=e.autoShow,this.config.store.historyAutocomplete.maxEntries=e.maxEntries,this.config.store.historyAutocomplete.minChars=e.minChars,this.config.store.historyAutocomplete.scope=e.scope,this.config.store.historyAutocomplete.entries=this.normalizeEntries(e.entries,e.maxEntries),await this.config.save()}makeId(){return`cmd_${Math.random().toString(36).slice(2,10)}${Date.now().toString(36)}`}};t.HistoryAutocompleteStore=l,t.HistoryAutocompleteStore=l=s([(0,o.Injectable)(),r("design:paramtypes",[i.ConfigService])],l)},771(e,t,n){var s=this&&this.__decorate||function(e,t,n,s){var r,o=arguments.length,i=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(i=(o<3?r(i):o>3?r(t,n,i):r(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i},r=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)};Object.defineProperty(t,"__esModule",{value:!0}),t.HistoryAutocompleteHotkeyProvider=void 0;const o=n(430),i=n(368);let a=class extends i.HotkeyProvider{constructor(){super(),this.hotkeys=[{id:"history-autocomplete.toggle",name:"Show command history suggestions"}]}async provide(){return this.hotkeys}};t.HistoryAutocompleteHotkeyProvider=a,t.HistoryAutocompleteHotkeyProvider=a=s([(0,o.Injectable)(),r("design:paramtypes",[])],a)},156(e,t,n){var s=this&&this.__decorate||function(e,t,n,s){var r,o=arguments.length,i=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(i=(o<3?r(i):o>3?r(t,n,i):r(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i};Object.defineProperty(t,"__esModule",{value:!0});const r=n(804),o=n(430),i=n(368),a=n(319),c=n(262),l=n(28),d=n(9),p=n(859),u=n(771),h=n(451);let m=class{};m=s([(0,o.NgModule)({declarations:[h.HistoryAutocompleteSettingsComponent],imports:[r.CommonModule],providers:[p.HistoryAutocompleteStore,{provide:a.TerminalDecorator,useClass:d.HistoryAutocompleteDecorator,multi:!0},{provide:i.ConfigProvider,useClass:l.HistoryAutocompleteConfigProvider,multi:!0},{provide:i.HotkeyProvider,useClass:u.HistoryAutocompleteHotkeyProvider,multi:!0},{provide:c.SettingsTabProvider,useClass:h.HistoryAutocompleteSettingsTabProvider,multi:!0}]})],m),t.default=m},451(e,t,n){var s=this&&this.__decorate||function(e,t,n,s){var r,o=arguments.length,i=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(i=(o<3?r(i):o>3?r(t,n,i):r(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i},r=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)};Object.defineProperty(t,"__esModule",{value:!0}),t.HistoryAutocompleteSettingsTabProvider=t.HistoryAutocompleteSettingsComponent=void 0;const o=n(430),i=n(262),a=n(859);let c=class{constructor(e){this.store=e,this.hotkey="Cmd-P",this.autoShow=!1,this.editableEntries=[],this.message="",this.reload()}any(e){return e}reload(){const e=this.store.getStore();this.hotkey=e.hotkey,this.autoShow=e.autoShow,this.editableEntries=e.entries.map(e=>({...e})),this.message=""}async save(){try{await this.store.saveSettings({hotkey:this.hotkey.trim()||"Cmd-P",autoShow:this.autoShow},this.editableEntries),this.message="Saved",this.reload()}catch(e){this.message=`Save failed: ${e?.message??"Unknown error"}`}}async resetDefaults(){try{await this.store.resetToDefaults(),this.message="Defaults restored",this.reload()}catch(e){this.message=`Reset failed: ${e?.message??"Unknown error"}`}}addEmptyEntry(){this.editableEntries=[...this.editableEntries,{id:"",command:"",normalized:"",shortcut:"",pinned:!1,count:1,createdAt:(new Date).toISOString(),lastUsedAt:(new Date).toISOString(),profileIds:[]}]}removeEntry(e){this.editableEntries=this.editableEntries.filter((t,n)=>n!==e)}updateEntry(e,t,n){this.editableEntries=this.editableEntries.map((s,r)=>r===e?{...s,[t]:n}:s)}};t.HistoryAutocompleteSettingsComponent=c,t.HistoryAutocompleteSettingsComponent=c=s([(0,o.Component)({selector:"quick-snips-settings",template:'\n <div class="content-box" style="padding: 24px; max-width: 900px;">\n <h2 style="margin-top: 0;">Quick Snips</h2>\n <p style="opacity: .8;">This plugin now uses only manual entries that you define here.</p>\n\n <div style="display:grid;grid-template-columns:180px minmax(0,1fr);gap:12px 16px;align-items:center;margin-bottom:20px;">\n <label>Hotkey</label>\n <input [value]="hotkey" (input)="hotkey = any($event.target).value" style="width: 220px;" />\n\n <label>Auto show</label>\n <input type="checkbox" [checked]="autoShow" (change)="autoShow = any($event.target).checked" />\n </div>\n\n <div style="display:flex;gap:8px;align-items:center;margin-bottom:10px;">\n <button\n type="button"\n (click)="addEmptyEntry()"\n style="background:#315efb;color:#fff;border:none;border-radius:8px;padding:8px 12px;cursor:pointer;"\n >Add Row</button>\n <button\n type="button"\n (click)="resetDefaults()"\n style="background:transparent;color:#fff;border:1px solid rgba(255,255,255,.25);border-radius:8px;padding:8px 12px;cursor:pointer;"\n >Reset Defaults</button>\n <span style="opacity:.75">Use Alias as the trigger text, then press Tab in terminal.</span>\n </div>\n\n <div style="margin-bottom:12px;opacity:.8;">Entries: {{ editableEntries.length }}</div>\n\n <div style="display:grid;grid-template-columns:180px minmax(0,1fr) 80px 90px;gap:8px;align-items:center;margin-bottom:8px;font-weight:600;opacity:.85;">\n <div>Alias</div>\n <div>Command</div>\n <div>Pinned</div>\n <div></div>\n </div>\n\n <div *ngFor="let entry of editableEntries; let i = index" style="display:grid;grid-template-columns:180px minmax(0,1fr) 80px 90px;gap:8px;align-items:center;margin-bottom:8px;">\n <input [value]="entry.shortcut || entry.normalized" (input)="updateEntry(i, \'shortcut\', any($event.target).value)" />\n <input [value]="entry.command" (input)="updateEntry(i, \'command\', any($event.target).value)" />\n <input type="checkbox" [checked]="entry.pinned" (change)="updateEntry(i, \'pinned\', any($event.target).checked)" />\n <button\n type="button"\n (click)="removeEntry(i)"\n style="background:transparent;color:#fff;border:1px solid rgba(255,255,255,.25);border-radius:8px;padding:8px 12px;cursor:pointer;"\n >Delete</button>\n </div>\n\n <div *ngIf="editableEntries.length === 0" style="margin:16px 0;opacity:.7;">\n No entries yet. Click <strong>Add Row</strong> to create one.\n </div>\n\n <div style="display:flex;gap:10px;align-items:center;margin-top:12px;">\n <button\n type="button"\n (click)="save()"\n style="background:#315efb;color:#fff;border:none;border-radius:8px;padding:8px 14px;cursor:pointer;"\n >Save</button>\n <button\n type="button"\n (click)="reload()"\n style="background:transparent;color:#fff;border:1px solid rgba(255,255,255,.25);border-radius:8px;padding:8px 14px;cursor:pointer;"\n >Reload</button>\n <span style="opacity:.8">{{ message }}</span>\n </div>\n </div>\n '}),r("design:paramtypes",[a.HistoryAutocompleteStore])],c);let l=class extends i.SettingsTabProvider{constructor(){super(...arguments),this.id="quick-snips",this.icon="keyboard",this.title="Quick Snips"}getComponentType(){return c}};t.HistoryAutocompleteSettingsTabProvider=l,t.HistoryAutocompleteSettingsTabProvider=l=s([(0,o.Injectable)()],l)},621(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.TerminalInputState=void 0,t.TerminalInputState=class{constructor(){this.currentLine=""}applyInput(e){const t=Array.from(e);let n;for(const e of t){const t=e.charCodeAt(0);"\r"!==e&&"\n"!==e?3!==t&&4!==t&&12!==t&&21!==t?23!==t?127!==t&&8!==t?t<32||127===t||(this.currentLine+=e):this.currentLine=this.currentLine.slice(0,-1):this.currentLine=this.currentLine.replace(/\s*\S+\s*$/,""):this.currentLine="":(n=this.currentLine.trim(),this.currentLine="")}return{commandToStore:n,currentLine:this.currentLine}}getCurrentLine(){return this.currentLine}setCurrentLine(e){this.currentLine=e}applyKeydown(e){return e.metaKey||e.altKey?this.currentLine:e.ctrlKey?("u"===e.key.toLowerCase()?this.currentLine="":"w"===e.key.toLowerCase()&&(this.currentLine=this.currentLine.replace(/\s*\S+\s*$/,"")),this.currentLine):"Backspace"===e.key?(this.currentLine=this.currentLine.slice(0,-1),this.currentLine):("Delete"===e.key||"Enter"===e.key||"Tab"===e.key||e.key.startsWith("Arrow")||"Escape"===e.key||1===e.key.length&&(this.currentLine+=e.key),this.currentLine)}}},804(e){e.exports=require("@angular/common")},430(e){e.exports=require("@angular/core")},368(e){e.exports=require("tabby-core")},262(e){e.exports=require("tabby-settings")},319(e){e.exports=require("tabby-terminal")}},t={},n=function n(s){var r=t[s];if(void 0!==r)return r.exports;var o=t[s]={exports:{}};return e[s].call(o.exports,o,o.exports,n),o.exports}(156);module.exports=n})();
@@ -0,0 +1,24 @@
1
+ import { SettingsTabProvider } from 'tabby-settings';
2
+ import { HistoryAutocompleteStore } from './historyStore';
3
+ import type { CommandHistoryEntry } from './types';
4
+ export declare class HistoryAutocompleteSettingsComponent {
5
+ private store;
6
+ hotkey: string;
7
+ autoShow: boolean;
8
+ editableEntries: CommandHistoryEntry[];
9
+ message: string;
10
+ constructor(store: HistoryAutocompleteStore);
11
+ any(value: EventTarget | null): any;
12
+ reload(): void;
13
+ save(): Promise<void>;
14
+ resetDefaults(): Promise<void>;
15
+ addEmptyEntry(): void;
16
+ removeEntry(index: number): void;
17
+ updateEntry(index: number, field: keyof CommandHistoryEntry, value: any): void;
18
+ }
19
+ export declare class HistoryAutocompleteSettingsTabProvider extends SettingsTabProvider {
20
+ id: string;
21
+ icon: string;
22
+ title: string;
23
+ getComponentType(): any;
24
+ }
@@ -0,0 +1,10 @@
1
+ export declare class TerminalInputState {
2
+ private currentLine;
3
+ applyInput(chunk: string): {
4
+ commandToStore?: string;
5
+ currentLine: string;
6
+ };
7
+ getCurrentLine(): string;
8
+ setCurrentLine(value: string): void;
9
+ applyKeydown(event: KeyboardEvent): string;
10
+ }
@@ -0,0 +1,25 @@
1
+ export interface CommandHistoryEntry {
2
+ id: string;
3
+ command: string;
4
+ normalized: string;
5
+ shortcut?: string;
6
+ pinned: boolean;
7
+ count: number;
8
+ lastUsedAt: string;
9
+ createdAt: string;
10
+ profileIds: string[];
11
+ }
12
+ export interface CommandHistoryStore {
13
+ version: number;
14
+ scope: 'global' | 'profile' | 'both';
15
+ maxEntries: number;
16
+ autoShow: boolean;
17
+ minChars: number;
18
+ hotkey: string;
19
+ entries: CommandHistoryEntry[];
20
+ }
21
+ export interface SuggestionResult {
22
+ entry: CommandHistoryEntry;
23
+ score: number;
24
+ highlights: number[];
25
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "tabby-quick-snips",
3
+ "version": "0.1.0",
4
+ "description": "Quick alias snippets plugin for Tabby Terminal",
5
+ "author": "Tamer",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tamert/tabby-quick-snips.git"
10
+ },
11
+ "homepage": "https://github.com/tamert/tabby-quick-snips#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/tamert/tabby-quick-snips/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "main": "dist/index.js",
19
+ "typings": "dist/index.d.ts",
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "keywords": [
26
+ "tabby",
27
+ "tabby-plugin",
28
+ "terminal",
29
+ "autocomplete",
30
+ "snippets",
31
+ "quick-commands"
32
+ ],
33
+ "scripts": {
34
+ "build": "webpack --mode production",
35
+ "watch": "webpack --mode development --watch"
36
+ },
37
+ "peerDependencies": {
38
+ "@angular/common": "^15.2.10",
39
+ "@angular/core": "^15.2.10",
40
+ "tabby-core": "^1.0.163",
41
+ "tabby-settings": "^1.0.163",
42
+ "tabby-terminal": "^1.0.163"
43
+ },
44
+ "devDependencies": {
45
+ "@angular/common": "^15.2.10",
46
+ "@angular/core": "^15.2.10",
47
+ "@types/node": "^18.19.130",
48
+ "tabby-core": "^1.0.163",
49
+ "tabby-settings": "^1.0.163",
50
+ "tabby-terminal": "^1.0.163",
51
+ "ts-loader": "^9.5.1",
52
+ "typescript": "^5.4.5",
53
+ "webpack": "^5.91.0",
54
+ "webpack-cli": "^5.1.4"
55
+ }
56
+ }