Skip to content

Commit ddbb85c

Browse files
committed
[feature]: add search
1 parent 3bc3390 commit ddbb85c

File tree

7 files changed

+258
-26
lines changed

7 files changed

+258
-26
lines changed

README.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,6 @@
22

33
Code Notes is a VS Code extension that lets you take structured notes across your codebase and quickly jump between code and the notes that describe it.
44

5-
It’s built for developers who:
6-
7-
- Leave TODOs but never find them again
8-
9-
- Keep random markdown files with scattered annotations
10-
11-
- Review large codebases and want organized context
12-
13-
- Work in teams and want traceable, navigable notes
14-
15-
- Code Notes gives you a dedicated sidebar to manage notes, reference exact files + lines, preview content, and jump back into the code instantly.
16-
175
## ✨ Features
186

197
### 📂 Notes Sidebar View

package.json

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@
9393
{
9494
"command": "crosscodenotes.viewNoteAt",
9595
"title": "Code Notes: View Note at"
96+
},
97+
{
98+
"command": "crosscodenotes.searchNotes",
99+
"title": "Code Notes: Search Notes",
100+
"icon": "$(search)"
96101
}
97102
],
98103
"menus": {
@@ -114,6 +119,12 @@
114119
"title": "Open Notes Directory",
115120
"when": "view == codeNotesView",
116121
"group": "navigation"
122+
},
123+
{
124+
"command": "crosscodenotes.searchNotes",
125+
"title": "Search Notes",
126+
"when": "view == codeNotesView",
127+
"group": "navigation"
117128
}
118129
],
119130
"view/item/context": [
@@ -128,7 +139,14 @@
128139
"group": "inline"
129140
}
130141
]
131-
}
142+
},
143+
"keybindings": [
144+
{
145+
"command": "crosscodenotes.addReference",
146+
"key": "ctrl+shift+j",
147+
"mac": "cmd+shift+j"
148+
}
149+
]
132150
},
133151
"scripts": {
134152
"vscode:prepublish": "npm run compile",

src/codelens.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ export class NotesCodeLensProvider implements vscode.CodeLensProvider {
2929
const line = Number(refLine) - 1;
3030
const range = new vscode.Range(line, 0, line, 0);
3131

32-
console.log("kyonru lens", ref.notePath, refLine);
33-
3432
lenses.push(
3533
new vscode.CodeLens(range, {
3634
title: "View Note",

src/commands.ts

Lines changed: 224 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as path from "path";
33
import * as fs from "fs";
44

55
import { NoteItem, NotesTreeProvider } from "./treeView";
6-
import { createCommandName } from "./utils";
6+
import { createCommandName, getNotesDir } from "./utils";
77
import { NotesCodeLensProvider } from "./codelens";
88

99
export function gerRefreshTreeCommand(
@@ -26,7 +26,7 @@ export function getCreateNoteCommand(
2626
return vscode.commands.registerCommand(
2727
createCommandName("createNote"),
2828
async () => {
29-
const NOTES_DIR = context.globalStorageUri.fsPath;
29+
const NOTES_DIR = getNotesDir(context);
3030

3131
const noteName = await vscode.window.showInputBox({
3232
prompt: "Enter note name",
@@ -63,7 +63,7 @@ export function getSelectNoteCommand(
6363
return vscode.commands.registerCommand(
6464
createCommandName("selectNote"),
6565
async () => {
66-
const NOTES_DIR = context.globalStorageUri.fsPath;
66+
const NOTES_DIR = getNotesDir(context);
6767
const files = fs.readdirSync(NOTES_DIR).filter((f) => f.endsWith(".md"));
6868

6969
if (files.length === 0) {
@@ -78,7 +78,7 @@ export function getSelectNoteCommand(
7878
});
7979

8080
if (selected) {
81-
const NOTES_DIR = context.globalStorageUri.fsPath;
81+
const NOTES_DIR = getNotesDir(context);
8282

8383
const currentNote = path.join(NOTES_DIR, selected);
8484
context.workspaceState.update("currentNote", currentNote);
@@ -255,7 +255,7 @@ export function getOpenNotesDirCommand(context: vscode.ExtensionContext) {
255255
return vscode.commands.registerCommand(
256256
createCommandName("openNotesDir"),
257257
() => {
258-
const NOTES_DIR = context.globalStorageUri.fsPath;
258+
const NOTES_DIR = getNotesDir(context);
259259
vscode.env.openExternal(vscode.Uri.file(NOTES_DIR));
260260
}
261261
);
@@ -308,3 +308,222 @@ export function getViewNoteAtCommand() {
308308
}
309309
);
310310
}
311+
312+
export function getSearchNotesCommand(context: vscode.ExtensionContext) {
313+
// Command: Search notes
314+
return vscode.commands.registerCommand(
315+
createCommandName("searchNotes"),
316+
async () => {
317+
const NOTES_DIR = getNotesDir(context);
318+
319+
const searchQuery = await vscode.window.showInputBox({
320+
prompt: "Search in notes (annotations, file names, code)",
321+
placeHolder: "Enter search term...",
322+
});
323+
324+
if (!searchQuery || searchQuery.trim() === "") {
325+
return;
326+
}
327+
328+
const query = searchQuery.toLowerCase();
329+
const results: Array<{
330+
noteName: string;
331+
notePath: string;
332+
matches: Array<{
333+
type: "title" | "annotation" | "code" | "filename";
334+
line: number;
335+
fileName?: string;
336+
content: string;
337+
preview: string;
338+
}>;
339+
}> = [];
340+
341+
// Search through all notes
342+
const noteFiles = fs
343+
.readdirSync(NOTES_DIR)
344+
.filter((f) => f.endsWith(".md"));
345+
346+
for (const noteFile of noteFiles) {
347+
const notePath = path.join(NOTES_DIR, noteFile);
348+
const content = fs.readFileSync(notePath, "utf-8");
349+
const lines = content.split("\n");
350+
const noteName = path.basename(noteFile, ".md");
351+
const noteMatches: (typeof results)[0]["matches"] = [];
352+
353+
// Check note title
354+
if (noteName.toLowerCase().includes(query)) {
355+
noteMatches.push({
356+
type: "title",
357+
line: 1,
358+
content: noteName,
359+
preview: `Note title: ${noteName}`,
360+
});
361+
}
362+
363+
// Parse sections and search
364+
let currentSection: {
365+
fileName?: string;
366+
lineNum?: number;
367+
annotation?: string;
368+
code?: string;
369+
} = {};
370+
let inCodeBlock = false;
371+
let codeLines: string[] = [];
372+
373+
for (let i = 0; i < lines.length; i++) {
374+
const line = lines[i];
375+
const lineNum = i + 1;
376+
377+
// Detect section headers (## filename:line)
378+
const sectionMatch = line.match(/^## (.+?):(\d+)/);
379+
if (sectionMatch) {
380+
// Process previous section if it had matches
381+
if (currentSection.fileName) {
382+
const sectionContent = [
383+
currentSection.annotation,
384+
currentSection.code,
385+
]
386+
.filter(Boolean)
387+
.join(" ")
388+
.toLowerCase();
389+
390+
if (sectionContent.includes(query)) {
391+
if (currentSection.annotation?.toLowerCase().includes(query)) {
392+
noteMatches.push({
393+
type: "annotation",
394+
line: lineNum,
395+
fileName: currentSection.fileName,
396+
content: currentSection.annotation!,
397+
preview: `${currentSection.fileName}:${currentSection.lineNum} - ${currentSection.annotation}`,
398+
});
399+
}
400+
if (currentSection.code?.toLowerCase().includes(query)) {
401+
noteMatches.push({
402+
type: "code",
403+
line: lineNum,
404+
fileName: currentSection.fileName,
405+
content: currentSection.code!,
406+
preview: `${currentSection.fileName}:${currentSection.lineNum} - Code snippet`,
407+
});
408+
}
409+
}
410+
}
411+
412+
// Start new section
413+
currentSection = {
414+
fileName: sectionMatch[1],
415+
lineNum: parseInt(sectionMatch[2]),
416+
annotation: undefined,
417+
code: undefined,
418+
};
419+
codeLines = [];
420+
inCodeBlock = false;
421+
}
422+
423+
// Extract annotation
424+
const noteMatch = line.match(/\*\*Note:\*\* (.+)/);
425+
if (noteMatch) {
426+
currentSection.annotation = noteMatch[1];
427+
}
428+
429+
// Track code blocks
430+
if (line.startsWith("```")) {
431+
inCodeBlock = !inCodeBlock;
432+
continue;
433+
}
434+
435+
if (inCodeBlock) {
436+
codeLines.push(line);
437+
}
438+
}
439+
440+
// Process last section
441+
if (currentSection.fileName) {
442+
currentSection.code = codeLines.join("\n");
443+
const sectionContent = [
444+
currentSection.annotation,
445+
currentSection.code,
446+
]
447+
.filter(Boolean)
448+
.join(" ")
449+
.toLowerCase();
450+
451+
if (sectionContent.includes(query)) {
452+
if (currentSection.annotation?.toLowerCase().includes(query)) {
453+
noteMatches.push({
454+
type: "annotation",
455+
line: lines.length,
456+
fileName: currentSection.fileName,
457+
content: currentSection.annotation!,
458+
preview: `${currentSection.fileName}:${currentSection.lineNum} - ${currentSection.annotation}`,
459+
});
460+
}
461+
if (currentSection.code?.toLowerCase().includes(query)) {
462+
noteMatches.push({
463+
type: "code",
464+
line: lines.length,
465+
fileName: currentSection.fileName,
466+
content: currentSection.code!,
467+
preview: `${currentSection.fileName}:${currentSection.lineNum} - Code snippet`,
468+
});
469+
}
470+
}
471+
}
472+
473+
if (noteMatches.length > 0) {
474+
results.push({
475+
noteName,
476+
notePath,
477+
matches: noteMatches,
478+
});
479+
}
480+
}
481+
482+
// Display results
483+
if (results.length === 0) {
484+
vscode.window.showInformationMessage(
485+
`No results found for "${searchQuery}"`
486+
);
487+
return;
488+
}
489+
490+
// Create quick pick items
491+
const items = results.flatMap((result) => {
492+
return result.matches.map((match) => {
493+
let icon = "$(file)";
494+
if (match.type === "title") {
495+
icon = "$(notebook)";
496+
} else if (match.type === "annotation") {
497+
icon = "$(note)";
498+
} else if (match.type === "code") {
499+
icon = "$(code)";
500+
}
501+
502+
return {
503+
label: `${icon} ${result.noteName}`,
504+
description: match.preview,
505+
detail:
506+
match.type === "code"
507+
? match.content.substring(0, 100) +
508+
(match.content.length > 100 ? "..." : "")
509+
: undefined,
510+
notePath: result.notePath,
511+
};
512+
});
513+
});
514+
515+
const selected = await vscode.window.showQuickPick(items, {
516+
placeHolder: `Found ${items.length} result${
517+
items.length === 1 ? "" : "s"
518+
} for "${searchQuery}"`,
519+
matchOnDescription: true,
520+
matchOnDetail: true,
521+
});
522+
523+
if (selected) {
524+
const doc = await vscode.workspace.openTextDocument(selected.notePath);
525+
await vscode.window.showTextDocument(doc);
526+
}
527+
}
528+
);
529+
}

src/extension.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import * as vscode from "vscode";
33
import * as path from "path";
44
import * as fs from "fs";
5-
import { createCommandName } from "./utils";
5+
import { createCommandName, getNotesDir } from "./utils";
66
import { NotesTreeProvider } from "./treeView";
77
import {
88
gerRefreshTreeCommand,
@@ -15,13 +15,14 @@ import {
1515
getSelectNoteCommand,
1616
getViewNoteAtCommand,
1717
getViewNoteCommand,
18+
getSearchNotesCommand,
1819
} from "./commands";
1920
import { NoteReference } from "./types";
2021
import { initCodeLensProvider, NotesCodeLensProvider } from "./codelens";
2122

2223
const init = async (context: vscode.ExtensionContext) => {
23-
if (!fs.existsSync(context.globalStorageUri.fsPath)) {
24-
fs.mkdirSync(context.globalStorageUri.fsPath, { recursive: true });
24+
if (!fs.existsSync(getNotesDir(context))) {
25+
fs.mkdirSync(getNotesDir(context), { recursive: true });
2526
}
2627

2728
const noteIndex: Record<string, NoteReference> = {};
@@ -74,6 +75,7 @@ export function activate(context: vscode.ExtensionContext) {
7475
);
7576
const openNoteFromTree = getOpenNoteFromTreeCommand(context, updateStatusBar);
7677
const viewNoteAt = getViewNoteAtCommand();
78+
const searchNotes = getSearchNotesCommand(context);
7779

7880
updateStatusBar();
7981

@@ -87,6 +89,7 @@ export function activate(context: vscode.ExtensionContext) {
8789
refreshTree,
8890
deleteNote,
8991
viewNoteAt,
92+
searchNotes,
9093
openNoteFromTree,
9194
treeView,
9295
statusBarItem

0 commit comments

Comments
 (0)