Skip to content

Commit 0362099

Browse files
committed
Broad rename working
1 parent 594abb5 commit 0362099

13 files changed

Lines changed: 877 additions & 76 deletions

TECHNICAL.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,7 +1592,16 @@ If `resolveAlias(oldPageName)` returns the same page whose filename is `oldPageN
15921592
15931593
**Workspace-wide link update:**
15941594
1595-
`updateLinksInWorkspace()` finds all `.md` and `.markdown` files, parses each for wikilinks, and creates a `WorkspaceEdit` that replaces every `[[oldPageName]]` with `[[newPageName]]`. After applying the edit, it saves modified files.
1595+
`updateLinksInWorkspace()` finds all `.md` and `.markdown` files, parses each for wikilinks, and now uses a split write strategy:
1596+
1597+
1. **Already-open documents** are updated through a `WorkspaceEdit` so their live editor buffers stay authoritative.
1598+
2. **Closed documents** are rewritten directly on disk using `workspace.fs.writeFile()` after a raw `fs.readFile()` pass.
1599+
1600+
This avoids VS Code opening newly-dirty tabs for files that were closed before the rename, which in turn avoids working-copy save conflicts on those reference files.
1601+
1602+
**Index-driven candidate narrowing:**
1603+
1604+
Rename refactors no longer have to discover rewrite candidates by scanning the entire workspace up front. `IndexService.findPagesLinkingToPageNames(...)` queries the `links` table for distinct source pages whose indexed `page_name` matches one of the renamed targets. `updateLinksInWorkspace()` now accepts those candidate URIs and only opens that bounded set of files when a candidate set is provided. The actual rewrite still parses the real file text before editing, so the index narrows the search space but does not become the edit source of truth.
15961605
15971606
**Notification progress:**
15981607
@@ -1631,9 +1640,13 @@ This selection logic is isolated in `WikilinkExplorerMergeService.ts` so the amb
16311640
The user-confirmed refactor work that follows explorer renames is now extracted into `WikilinkExplorerRenameRefactorService.ts`. That helper applies the same notification UX as in-editor renames:
16321641
16331642
1. Accepted merge operations show `AS Notes: Applying rename updates` while the merge, delete, and target re-index complete.
1634-
2. Accepted workspace-wide reference updates show `AS Notes: Updating wikilink references` while link replacement and stale-scan refresh complete.
1643+
2. Accepted workspace-wide reference updates show `AS Notes: Updating wikilink references` while index-driven candidate rewrite and targeted file re-indexing complete.
16351644
3. Declined explorer prompts do not show progress notifications.
16361645
1646+
For explorer renames, the old broad `staleScan()` follow-up has been replaced with targeted re-indexing of the files actually edited by the refactor. That removes a second whole-tree pass from the common rename path.
1647+
1648+
`updateLinksInWorkspace()` no longer auto-saves affected open editors after applying workspace edits. Instead, both the in-editor and explorer rename flows now use `reindexWorkspaceUri(...)`: if an affected file is currently open, it is re-indexed from the live editor buffer via `indexFileContent(...)`; otherwise it falls back to `indexScanner.indexFile(...)`. `WikilinkRenameTracker` still re-indexes the initiating document from `document.getText()` when its URI is stable, and remaps any old source candidate URI to the post-rename or post-merge target before follow-up indexing. Combined with the direct-to-disk rewrite path for closed files, this avoids save conflicts on both open reference files and files that were previously closed, as well as attempts to reopen a source file that has just been renamed or deleted.
1649+
16371650
### Re-entrancy guard
16381651
16391652
The `isProcessing` flag prevents document-change events fired by the rename operation itself (file renames, workspace edits) from being treated as new user edits. It is set to `true` before any rename work begins and cleared in the `finally` block.

docs-src/docs/Backlinks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ order: 3
44

55
# Backlinks
66

7-
The Backlinks panel shows every note in your workspace that links to a given page. It is one of the most powerful navigation tools in AS Notes — use it to understand how ideas connect across your knowledge base.
7+
The Backlinks panel shows every note in your workspace that links to a given page. Use it to understand how ideas connect across your knowledge base.
88

99
## Opening the Panel
1010

@@ -24,7 +24,7 @@ For example, if `Project.md` contains:
2424
- [[NGINX]]
2525
```
2626

27-
…then the backlink chain for `NGINX` from `Project.md` would be:
27+
then the backlink chain for `NGINX` from `Project.md` would be:
2828

2929
```
3030
Project → Tasks → NGINX

docs-src/docs/Getting Started.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This page gets you from zero to a working AS Notes workspace in a few minutes.
88

99
## 1. Install the Extension
1010

11-
Install **AS Notes** from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=appsoftwareltd.as-notes).
11+
Install **AS Notes** from [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=appsoftwareltd.as-notes) / [Open VSX](https://open-vsx.org/extension/appsoftwareltd/as-notes)
1212

1313
Open VS Code, go to the Extensions view (`Ctrl+Shift+X`), search for **AS Notes**, and click **Install**.
1414

@@ -33,9 +33,9 @@ Look for the AS Notes sidebar icon to view Search, Calendar, Kanban Boards and T
3333

3434
## 4. Write Your First Note
3535

36-
Create a new `.md` file and start writing. Type `[[` anywhere to trigger [[Wikilinks]] autocomplete - a list of all your pages appears immediately.
36+
Create a new `.md` file and start writing. Type `[[` anywhere to trigger [[Wikilinks]] autocomplete - a list of all your pages appears immediately (unless this is your first page and wikilink, in which case it will be added too the index ready for referencing).
3737

38-
Try adding a task `- [ ] task text` and try toggling the task state from the task management side panel.
38+
Next, try adding a task `- [ ] task text` (`Ctrl+Shift+Enter` / `Cmd+Shift+Enter`) and try toggling the task state from the task management side panel or by clicking the check box or by cycling the keyboard shortcut.
3939

4040
## Excluding Files from the Index
4141

@@ -62,8 +62,8 @@ If the index ever becomes stale or corrupted, run **AS Notes: Rebuild Index** fr
6262

6363
## Cleaning the Workspace
6464

65-
If the extension is in a bad state (e.g. persistent errors after a crash), run **AS Notes: Clean Workspace** to remove the `.asnotes/` directory and reset all in-memory state. Your `.asnotesignore` file is preserved. Run **AS Notes: Initialise Workspace** afterwards to start fresh.
65+
Run **AS Notes: Clean Workspace** to remove the `.asnotes/` directory and reset all in-memory state. Your `.asnotesignore` file is preserved. Run **AS Notes: Initialise Workspace** afterwards to start fresh.
6666

6767
## Compatibility With Other Tools
6868

69-
AS Notes workspaces are plain markdown files in plain folders - they are compatible with Obsidian and Logseq due to similar file structures. Be aware there are format and behavioural differences, but you can use the same notes folder with multiple tools.
69+
AS Notes workspaces are plain markdown files in plain folders - they are largely compatible with Obsidian and Logseq due to similar file structures. Be aware there are format and behavioural differences, but you can use the same notes folder with multiple tools.

docs-src/docs/index.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ AS Notes brings markdown and [[wikilink]] editing for notes, documentation, blog
88

99
**Capture ideas, link concepts, write, and stay focused - without ever leaving your editor.**
1010

11-
> **This documentation was written and generated using AS Notes. See [[Publishing a Static Site]] for how you can use AS Notes for your docs, including deploying to GitHub Pages**.
11+
> **This documentation was written and generated using AS Notes. See [[Publishing a Static Site]] for how you can use AS Notes for your docs, including deploying to GitHub Pages, Cloudflare and More**.
1212
13-
> **Install:** [https://marketplace.visualstudio.com/items?itemName=appsoftwareltd.as-notes](https://marketplace.visualstudio.com/items?itemName=appsoftwareltd.as-notes)
14-
15-
> **GitHub:** [https://github.com/appsoftwareltd/as-notes](https://github.com/appsoftwareltd/as-notes)
13+
> **Install:** [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=appsoftwareltd.as-notes) / [Open VSX](https://open-vsx.org/extension/appsoftwareltd/as-notes)
14+
> **GitHub:** [github.com/appsoftwareltd/as-notes](https://github.com/appsoftwareltd/as-notes)
1615
1716
![AS Notes editor screenshot](../assets/images/as-notes-editor-screenshot.png)
1817

@@ -22,13 +21,12 @@ If you've already installed AS Notes and want to get started, see [[Getting Star
2221

2322
## Why VS Code?
2423

25-
Using VS Code as your notes app gives you a huge amount for free before you even start using AS Notes features:
24+
Using VS Code as your notes app gives you a huge amount for free in addition the features that AS Notes provides:
2625

27-
- Cross-platform and web-based (via VS Code Workspaces)
26+
- Cross-platform compatibility and web access (via VS Code Workspaces)
2827
- Tabs, file explorer, themes, keyboard shortcuts
29-
- A vast extension library — Mermaid diagrams, Vim mode, and more, all usable alongside AS Notes
28+
- A vast extension library
3029
- AI chat (GitHub Copilot, Claude, etc.) to query and work with your notes
31-
- Outliner-style indentation via `Ctrl+[` / `Ctrl+]`
3230
- Syntax highlighting for code embedded in your notes
3331

3432
## Features at a Glance
@@ -65,4 +63,4 @@ AS Notes is privacy-first. It never connects to external servers. All indexing,
6563

6664
## Licence
6765

68-
See [[Licence Rationale]] for an explanation of the source-available licence model.
66+
See [[Licence]]

vs-code-extension/src/IndexService.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,34 @@ export class IndexService {
570570
return this.mapLinkRows(result[0].values);
571571
}
572572

573+
/**
574+
* Find distinct source pages containing links to any of the supplied page names.
575+
* Used to narrow rename refactors to only files that could contain matches.
576+
*/
577+
findPagesLinkingToPageNames(pageNames: string[]): PageRow[] {
578+
this.ensureOpen();
579+
if (pageNames.length === 0) { return []; }
580+
581+
const placeholders = pageNames.map(() => '?').join(', ');
582+
const result = this.db!.exec(
583+
`SELECT DISTINCT p.id, p.path, p.filename, p.title, p.mtime, p.indexed_at
584+
FROM pages p
585+
JOIN links l ON l.source_page_id = p.id
586+
WHERE l.page_name IN (${placeholders})
587+
ORDER BY p.path COLLATE NOCASE`,
588+
pageNames,
589+
);
590+
if (result.length === 0) { return []; }
591+
return result[0].values.map(row => ({
592+
id: row[0] as number,
593+
path: row[1] as string,
594+
filename: row[2] as string,
595+
title: row[3] as string,
596+
mtime: row[4] as number,
597+
indexed_at: row[5] as number,
598+
}));
599+
}
600+
573601
/**
574602
* Get the total number of links across all pages.
575603
* Uses a single `COUNT(*)` query — O(1) regardless of table size.

vs-code-extension/src/WikilinkExplorerRenameRefactorService.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
pickUniqueExplorerMergeTarget,
99
} from './WikilinkExplorerMergeService.js';
1010
import { toNotesRelativePath } from './NotesRootService.js';
11-
import { updateLinksInWorkspace } from './WikilinkRefactorService.js';
11+
import { reindexWorkspaceUri, updateLinksInWorkspace } from './WikilinkRefactorService.js';
1212
import { withWikilinkRenameProgress } from './WikilinkRenameProgressService.js';
1313

1414
interface ExplorerRenameFile {
@@ -20,7 +20,7 @@ interface ExplorerRenameRefactorDeps {
2020
files: ExplorerRenameFile[];
2121
renameTrackerIsRenaming: boolean;
2222
wikilinkService: WikilinkService;
23-
indexService: Pick<IndexService, 'findPagesByFilename' | 'removePage'>;
23+
indexService: Pick<IndexService, 'findPagesByFilename' | 'removePage' | 'findPagesLinkingToPageNames' | 'indexFileContent'>;
2424
indexScanner: Pick<IndexScanner, 'staleScan' | 'indexFile'>;
2525
notesRootPath?: string;
2626
safeSaveToFile: () => boolean;
@@ -95,12 +95,13 @@ export async function handleExplorerRenameRefactors({
9595
);
9696
edit.replace(targetUri, fullRange, mergedContent);
9797
await vscode.workspace.applyEdit(edit);
98-
await targetDoc.save();
9998

10099
progress.report('Refreshing index');
101100
await vscode.workspace.fs.delete(newUri);
102101
indexService.removePage(newPath);
103-
try { await indexScanner.indexFile(targetUri); } catch { /* best effort */ }
102+
try {
103+
await reindexWorkspaceUri(targetUri, { indexService, indexScanner, notesRootPath });
104+
} catch { /* best effort */ }
104105
safeSaveToFile();
105106
});
106107
}
@@ -130,11 +131,31 @@ export async function handleExplorerRenameRefactors({
130131
if (choice !== 'Yes') { return; }
131132

132133
await withWikilinkRenameProgress('AS Notes: Updating wikilink references', async (progress) => {
134+
const rootUri = notesRootPath
135+
? vscode.Uri.file(notesRootPath)
136+
: vscode.workspace.workspaceFolders?.[0]?.uri;
137+
const candidateUris = rootUri
138+
? indexService.findPagesLinkingToPageNames(linkRenames.map(rename => rename.oldPageName))
139+
.map(page => vscode.Uri.joinPath(rootUri, page.path))
140+
: [];
141+
133142
progress.report('Updating links across workspace');
134-
await updateLinksInWorkspace(wikilinkService, linkRenames);
143+
const affectedUris = await updateLinksInWorkspace(
144+
wikilinkService,
145+
linkRenames,
146+
candidateUris.length > 0 ? { candidateUris } : undefined,
147+
);
135148

136149
progress.report('Refreshing index');
137-
try { await indexScanner.staleScan(); } catch { /* best effort */ }
150+
for (const uri of affectedUris) {
151+
try {
152+
await reindexWorkspaceUri(uri, {
153+
indexService,
154+
indexScanner,
155+
notesRootPath,
156+
});
157+
} catch { /* best effort */ }
158+
}
138159
if (safeSaveToFile()) {
139160
refreshProviders();
140161
}

0 commit comments

Comments
 (0)