Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c33e3d9
chore: propagate main into development (#123)
Wikid82 Nov 9, 2026
5f58db1
fix(deps): update @typescript/native to version 7.1.0-dev.20261109.1
Wikid82 Nov 9, 2026
11f973a
fix(deps): update vite to version 8.3.7
Wikid82 Nov 9, 2026
f108206
chore(deps): regenerate lockfile and dedupe rolldown under vite
Wikid82 Nov 9, 2026
0a979b5
fix(deps): update TypeScript ESLint packages and add update script
Wikid82 Nov 9, 2026
3161629
fix(deps): update @typescript/native to version 7.1.0-dev.20261110.1
Wikid82 Nov 10, 2026
501c5e4
chore(deps): disable vscode dependency updates in Renovate configuration
Wikid82 Nov 10, 2026
986fe8a
chore: propagate main into development (#125)
Wikid82 Nov 10, 2026
154f197
fix(deps): update vite to version 8.3.8
Wikid82 Nov 11, 2026
f0d26c0
chore: update typescript dependency to version 7.1.0-dev.20261111.1
Wikid82 Nov 11, 2026
a243cac
chore: update prettier to version 3.10.0 and add script for installation
Wikid82 Nov 11, 2026
0708886
fix(deps): update vscode and @types/vscode to version 1.146.0
Wikid82 Nov 11, 2026
745be42
feat: detect stale bookmarks and add a Remove Broken Bookmarks sweep
Wikid82 Nov 11, 2026
92e06f0
feat: detect stale bookmarks and add a Remove Broken Bookmarks sweep …
Wikid82 Nov 11, 2026
de97a26
feat: apply remove and move-to-folder to every selected bookmark
Wikid82 Nov 11, 2026
8a6dfaa
feat: apply remove and move-to-folder to every selected bookmark (#127)
Wikid82 Nov 11, 2026
7039553
feat: add copy path and reveal actions to the bookmark context menu
Wikid82 Nov 11, 2026
1c96ea1
feat: add copy path and reveal actions to the bookmark context menu (…
Wikid82 Nov 11, 2026
acd9d3d
feat: let users set a custom icon and color per bookmark folder
Wikid82 Nov 11, 2026
c443576
feat: let users set a custom icon and color per bookmark folder (#129)
Wikid82 Nov 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
318 changes: 295 additions & 23 deletions extension.ts

Large diffs are not rendered by default.

563 changes: 293 additions & 270 deletions package-lock.json

Large diffs are not rendered by default.

76 changes: 70 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
},
"homepage": "https://github.com/Wikid82/Workspace-File-Bookmarks#readme",
"engines": {
"vscode": "^1.145.0"
"vscode": "^1.146.0"
},
"activationEvents": [],
"main": "./dist/extension.cjs",
Expand Down Expand Up @@ -148,6 +148,11 @@
"title": "Rename Folder",
"category": "Workspace File Bookmarks"
},
{
"command": "workspace-file-bookmarks.setFolderAppearance",
"title": "Set Folder Icon and Color...",
"category": "Workspace File Bookmarks"
},
{
"command": "workspace-file-bookmarks.deleteFolder",
"title": "Delete Folder",
Expand Down Expand Up @@ -177,6 +182,12 @@
"category": "Workspace File Bookmarks",
"icon": "$(list-tree)"
},
{
"command": "workspace-file-bookmarks.removeBrokenBookmarks",
"title": "Remove Broken Bookmarks...",
"category": "Workspace File Bookmarks",
"icon": "$(warning)"
},
{
"command": "workspace-file-bookmarks.exportBookmarks",
"title": "Export Bookmarks...",
Expand All @@ -194,6 +205,26 @@
"title": "Go to Bookmark...",
"category": "Workspace File Bookmarks",
"icon": "$(go-to-file)"
},
{
"command": "workspace-file-bookmarks.copyBookmarkPath",
"title": "Copy Path",
"category": "Workspace File Bookmarks"
},
{
"command": "workspace-file-bookmarks.copyBookmarkRelativePath",
"title": "Copy Relative Path",
"category": "Workspace File Bookmarks"
},
{
"command": "workspace-file-bookmarks.revealBookmarkInExplorer",
"title": "Reveal in Explorer",
"category": "Workspace File Bookmarks"
},
{
"command": "workspace-file-bookmarks.revealBookmarkInFileManager",
"title": "Reveal in File Manager",
"category": "Workspace File Bookmarks"
}
],
"menus": {
Expand Down Expand Up @@ -243,6 +274,11 @@
"when": "view == workspace-file-bookmarks-view && workspace-file-bookmarks.viewMode == list",
"group": "navigation@5"
},
{
"command": "workspace-file-bookmarks.removeBrokenBookmarks",
"when": "view == workspace-file-bookmarks-view",
"group": "8_maintenance@1"
},
{
"command": "workspace-file-bookmarks.exportBookmarks",
"when": "view == workspace-file-bookmarks-view",
Expand Down Expand Up @@ -275,6 +311,26 @@
"when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem",
"group": "1_modification"
},
{
"command": "workspace-file-bookmarks.revealBookmarkInExplorer",
"when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem",
"group": "2_reveal@1"
},
{
"command": "workspace-file-bookmarks.revealBookmarkInFileManager",
"when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem",
"group": "2_reveal@2"
},
{
"command": "workspace-file-bookmarks.copyBookmarkPath",
"when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem",
"group": "3_copy@1"
},
{
"command": "workspace-file-bookmarks.copyBookmarkRelativePath",
"when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem",
"group": "3_copy@2"
},
{
"command": "workspace-file-bookmarks.openAllInFolder",
"when": "view == workspace-file-bookmarks-view && viewItem == bookmarkFolder",
Expand All @@ -299,6 +355,11 @@
"command": "workspace-file-bookmarks.moveFolderToParent",
"when": "view == workspace-file-bookmarks-view && viewItem == bookmarkFolder",
"group": "1_modification"
},
{
"command": "workspace-file-bookmarks.setFolderAppearance",
"when": "view == workspace-file-bookmarks-view && viewItem == bookmarkFolder",
"group": "1_modification"
}
],
"editor/title": [
Expand Down Expand Up @@ -360,8 +421,11 @@
"@eslint/js": "^10.0.1",
"@types/mocha": "^10.0.10",
"@types/node": "^26.6.6",
"@types/vscode": "^1.145.0",
"@typescript/native": "npm:typescript@^7.1.0-dev.20261108.1",
"@types/vscode": "^1.146.0",
"@typescript-eslint/eslint-plugin": "^8.72.2",
"@typescript-eslint/parser": "^8.72.2",
"@typescript-eslint/utils": "^8.72.2",
"@typescript/native": "npm:typescript@^7.1.0-dev.20261111.1",
"@vitest/coverage-istanbul": "^5.1.0",
"@vitest/coverage-v8": "^5.1.0",
"@vitest/eslint-plugin": "^1.6.28",
Expand All @@ -374,10 +438,10 @@
"eslint": "^10.14.0",
"eslint-config-prettier": "^10.1.8",
"lefthook": "^2.2.2",
"prettier": "^3.9.10",
"prettier": "^3.10.0",
"typescript": "npm:@typescript/typescript6@^6.0.2",
"typescript-eslint": "^8.72.1",
"vite": "^8.3.6",
"typescript-eslint": "^8.72.2",
"vite": "^8.3.8",
"vitest": "^5.1.0"
},
"allowScripts": {
Expand Down
18 changes: 18 additions & 0 deletions scripts/npm/prettier.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../" && pwd)"

NPM_MODULES=(
"$REPO_ROOT"
)

for MODULE in "${NPM_MODULES[@]}"; do
echo "============================================================================"
echo "Updating: $MODULE"
echo "============================================================================"

cd "$MODULE" || exit 1
rm -rf node_modules package-lock.json
npm install prettier --save-dev
done
18 changes: 18 additions & 0 deletions scripts/npm/typescript-eslint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"

NPM_MODULES=(
"$REPO_ROOT"
)

for MODULE in "${NPM_MODULES[@]}"; do
echo "============================================================================"
echo "Updating: $MODULE"
echo "============================================================================"

cd "$MODULE" || exit 1
rm -rf node_modules package-lock.json
npm install typescript-eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/utils --save-dev
done
21 changes: 17 additions & 4 deletions test/activate.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { activate, deactivate } from '../extension';
import { createFakeContext } from './fakeContext';
import { commands, Position, Selection, Uri, window } from './vscode-mock';
import { commands, Position, Selection, Uri, window, workspace } from './vscode-mock';

describe('activate', () => {
it('creates the tree view and registers every command', () => {
Expand All @@ -11,8 +11,12 @@ describe('activate', () => {
activate(context as any);

expect(window.createTreeView).toHaveBeenCalledOnce();
expect(commands.registerCommand).toHaveBeenCalledTimes(24);
expect((context as any).subscriptions).toHaveLength(25); // tree view + 24 commands
expect(window.createTreeView).toHaveBeenCalledWith(
'workspace-file-bookmarks-view',
expect.objectContaining({ canSelectMany: true }),
);
expect(commands.registerCommand).toHaveBeenCalledTimes(30);
expect((context as any).subscriptions).toHaveLength(31); // tree view + 30 commands
});

it('exercises every registered command handler at least once', async () => {
Expand Down Expand Up @@ -57,6 +61,8 @@ describe('activate', () => {
await handlers.get('workspace-file-bookmarks.createSubfolder')?.({ folder });
await handlers.get('workspace-file-bookmarks.moveFolderToParent')?.({ folder });
await handlers.get('workspace-file-bookmarks.renameFolder')?.({ folder });
window.showQuickPick.mockResolvedValue(undefined);
await handlers.get('workspace-file-bookmarks.setFolderAppearance')?.({ folder });
await handlers.get('workspace-file-bookmarks.deleteFolder')?.({
folder,
bookmarks: [],
Expand All @@ -66,14 +72,21 @@ describe('activate', () => {
await handlers.get('workspace-file-bookmarks.openAllInFolder')?.({ folder, bookmarks: [] });
await handlers.get('workspace-file-bookmarks.setViewModeList')?.();
await handlers.get('workspace-file-bookmarks.setViewModeTree')?.();
workspace.fs.stat.mockResolvedValue({});
await handlers.get('workspace-file-bookmarks.removeBrokenBookmarks')?.();
window.showSaveDialog.mockResolvedValue(undefined);
await handlers.get('workspace-file-bookmarks.exportBookmarks')?.();
window.showOpenDialog.mockResolvedValue(undefined);
await handlers.get('workspace-file-bookmarks.importBookmarks')?.();
window.showQuickPick.mockResolvedValue(undefined);
await handlers.get('workspace-file-bookmarks.goToBookmark')?.();
await handlers.get('workspace-file-bookmarks.copyBookmarkPath')?.({ bookmark });
await handlers.get('workspace-file-bookmarks.copyBookmarkRelativePath')?.({ bookmark });
await handlers.get('workspace-file-bookmarks.revealBookmarkInExplorer')?.({ bookmark });
await handlers.get('workspace-file-bookmarks.revealBookmarkInFileManager')?.({ bookmark });

expect(handlers.size).toBe(24);
expect(commands.registerCommand).toHaveBeenCalledTimes(30);
expect((context as any).subscriptions).toHaveLength(31); // tree view + 30 commands
});
});

Expand Down
105 changes: 105 additions & 0 deletions test/bookmarkStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,3 +481,108 @@ describe('mergeImport', () => {
expect(store.getAllBookmarks()).toHaveLength(1);
});
});

describe('stale bookmark tracking', () => {
it('only fires a change event when the stale set actually changes', () => {
const store = newStore();
store.addBookmark(makeBookmark({ id: 'a' }));
let changes = 0;
store.onDidChange(() => changes++);

store.markStale('a');
store.markStale('a');
expect(changes).toBe(1);

store.clearStale('a');
expect(store.isStale('a')).toBe(false);
expect(changes).toBe(2);
});

it('removeStaleBookmarks drops only stale bookmarks and resets the stale set', () => {
const store = newStore();
store.addBookmark(makeBookmark({ id: 'a', uri: 'file:///repo/a.ts' }));
store.addBookmark(makeBookmark({ id: 'b', uri: 'file:///repo/b.ts' }));
store.markStale('a');

store.removeStaleBookmarks();

expect(store.getAllBookmarks().map((b) => b.id)).toEqual(['b']);
expect(store.isStale('a')).toBe(false);
});
});

describe('bulk bookmark operations', () => {
it('removeBookmarks removes only the named bookmarks in a single change event', () => {
const store = newStore();
for (const id of ['a', 'b', 'c']) {
store.addBookmark(makeBookmark({ id, uri: `file:///repo/${id}.ts` }));
}
let changes = 0;
store.onDidChange(() => changes++);

store.removeBookmarks(['a', 'c']);

expect(store.getAllBookmarks().map((b) => b.id)).toEqual(['b']);
expect(changes).toBe(1);
});

it('moveBookmarksToFolder moves only the named bookmarks in a single change event', () => {
const store = newStore();
for (const id of ['a', 'b', 'c']) {
store.addBookmark(makeBookmark({ id, uri: `file:///repo/${id}.ts` }));
}
const folder = store.createFolder('Backend');
let changes = 0;
store.onDidChange(() => changes++);

store.moveBookmarksToFolder(['a', 'b'], folder.id);

const byId = new Map(store.getAllBookmarks().map((b) => [b.id, b.folderId]));
expect(byId.get('a')).toBe(folder.id);
expect(byId.get('b')).toBe(folder.id);
expect(byId.get('c')).toBeNull();
expect(changes).toBe(1);
});
});

describe('setFolderAppearance', () => {
it('sets icon and color on only the targeted folder', () => {
const store = newStore();
const a = store.createFolder('A');
const b = store.createFolder('B');

store.setFolderAppearance(a.id, { icon: 'bug', color: 'charts.green' });

const folders = store.getAllFolders();
expect(folders.find((f) => f.id === a.id)).toMatchObject({
icon: 'bug',
color: 'charts.green',
});
expect(folders.find((f) => f.id === b.id)).not.toHaveProperty('icon');
});

it('clears icon and color when called with no values', () => {
const store = newStore();
const a = store.createFolder('A');
store.setFolderAppearance(a.id, { icon: 'bug', color: 'charts.green' });

store.setFolderAppearance(a.id, {});

expect(store.getAllFolders()[0]).toEqual(
expect.not.objectContaining({ icon: expect.anything(), color: expect.anything() }),
);
expect(store.getAllFolders()[0]).not.toHaveProperty('icon');
expect(store.getAllFolders()[0]).not.toHaveProperty('color');
});

it('preserves icon and color through mergeImport', () => {
const store = newStore();

store.mergeImport(
[],
[{ id: 'x', name: 'Imported', createdAt: 0, icon: 'rocket', color: 'charts.blue' }],
);

expect(store.getAllFolders()[0]).toMatchObject({ icon: 'rocket', color: 'charts.blue' });
});
});
Loading
Loading