Clarify composer context reference suggestions

This commit is contained in:
murashit 2026-07-10 20:35:50 +09:00
parent 0c587f72cd
commit e1e3786531
6 changed files with 49 additions and 13 deletions

View file

@ -1,6 +1,7 @@
export interface DailyNoteReferenceCandidate {
keyword: "today" | "tomorrow" | "yesterday";
display: string;
name: string;
path: string;
linktext: string;
}

View file

@ -94,6 +94,7 @@ const THREAD_COMMAND_SUGGESTION_POLICIES: Record<ThreadSuggestionCommand, Thread
};
const THREAD_SUGGESTION_COMMAND_PATTERN = new RegExp(`^/(${THREAD_SUGGESTION_COMMANDS.join("|")})\\s+([^\\s\\n]{0,120})$`);
const SELECTION_SUGGESTION_PREVIEW_LIMIT = 500;
export function parseSlashCommand(text: string): { command: SlashCommandName; args: string } | null {
const match = /^\/([A-Za-z-]+)(?:\s+([\s\S]*))?$/.exec(text);
@ -142,7 +143,7 @@ function activeContextReferenceSuggestions(
const suggestions: ComposerSuggestion[] = [];
if (references?.activeNote && "active".startsWith(query)) {
suggestions.push({
display: "Active file",
display: `Active · ${references.activeNote.name}`,
detail: references.activeNote.path,
replacement: activeNoteContextReferenceMarker(references.activeNote),
start,
@ -151,8 +152,8 @@ function activeContextReferenceSuggestions(
}
if (references?.selection && "selection".startsWith(query) && query !== "selection") {
suggestions.push({
display: "Selection",
detail: `${references.selection.path} ${formatComposerContextRange(references.selection.range)}`,
display: `Selection · ${references.selection.name} · ${formatComposerContextRange(references.selection.range)}`,
detail: selectionSuggestionPreview(references.selection),
replacement: selectionContextReferenceMarker(references.selection),
start,
selectionContext: references.selection,
@ -161,7 +162,7 @@ function activeContextReferenceSuggestions(
const matchingDailyNotes = dailyNoteReferenceList(dailyNoteReferences).filter((candidate) => candidate.keyword.startsWith(query));
suggestions.push(
...matchingDailyNotes.map((candidate) => ({
display: candidate.display,
display: `${candidate.display} · ${candidate.name}`,
detail: candidate.path,
replacement: `[[${candidate.linktext}]]`,
start,
@ -170,6 +171,11 @@ function activeContextReferenceSuggestions(
return suggestions.slice(0, 8);
}
function selectionSuggestionPreview(selection: SelectionContextReference): string {
const preview = selection.text.replace(/\s+/g, " ").trim();
return preview.length > SELECTION_SUGGESTION_PREVIEW_LIMIT ? `${preview.slice(0, SELECTION_SUGGESTION_PREVIEW_LIMIT - 1)}` : preview;
}
function dailyNoteReferenceList(
references: readonly DailyNoteReferenceCandidate[] | (() => readonly DailyNoteReferenceCandidate[]) | undefined,
): readonly DailyNoteReferenceCandidate[] {

View file

@ -3,7 +3,7 @@ import { type App, moment, normalizePath, TFile } from "obsidian";
import { appHasDailyNotesPluginLoaded, getDailyNoteSettings, type IPeriodicNoteSettings } from "obsidian-daily-notes-interface";
import type { DailyNoteReferenceCandidate } from "../../application/composer/daily-note-references";
import { linktextForFile } from "./vault-note-links.obsidian";
import { displayNameForFile, linktextForFile } from "./vault-note-links.obsidian";
const RELATIVE_DAILY_NOTES = [
{ keyword: "today", display: "Today", dayOffset: 0 },
@ -38,12 +38,17 @@ export function dailyNoteReferencesFromSettings(
return {
keyword,
display,
name: existingFile instanceof TFile ? displayNameForFile(existingFile) : dailyNoteName(filename),
path,
linktext: existingFile instanceof TFile ? linktextForFile(app, existingFile, sourcePath) : path.replace(/\.md$/i, ""),
};
});
}
function dailyNoteName(filename: string): string {
return (filename.split("/").at(-1) ?? filename).replace(/\.md$/i, "");
}
function dailyNotePath(folder: string, filename: string): string {
const markdownFilename = filename.toLowerCase().endsWith(".md") ? filename : `${filename}.md`;
return normalizePath(folder ? `${folder}/${markdownFilename}` : markdownFilename);

View file

@ -236,7 +236,7 @@ describe("composer suggestions", () => {
},
})[0],
).toMatchObject({
display: "Active file",
display: "Active · Beta Note",
detail: "topics/Beta Note.md",
replacement: "[[Beta Note]]",
});
@ -257,20 +257,20 @@ describe("composer suggestions", () => {
path: "topics/Beta Note.md",
linktext: "Beta Note",
range: { from: { line: 41, ch: 4 }, to: { line: 46, ch: 0 } },
text: "selected",
text: " selected\n\ttext ",
},
},
})[0],
).toMatchObject({
display: "Selection",
detail: "topics/Beta Note.md L42:C5-L47:C1",
display: "Selection · Beta Note · L42:C5-L47:C1",
detail: "selected text",
replacement: "[[Beta Note]] (L42:C5-L47:C1)",
selectionContext: {
name: "Beta Note",
path: "topics/Beta Note.md",
linktext: "Beta Note",
range: { from: { line: 41, ch: 4 }, to: { line: 46, ch: 0 } },
text: "selected",
text: " selected\n\ttext ",
},
});
expect(activeComposerSuggestions("/pla", notes, [])[0]?.replacement).toBe("/plan");
@ -281,23 +281,43 @@ describe("composer suggestions", () => {
expect(activeComposerSuggestions("/help", notes, [])).toEqual([]);
});
it("bounds selection preview text before rendering it in the suggestion list", () => {
const suggestion = activeComposerSuggestions("@sel", notes, [], [], [], null, {
contextReferences: {
activeNote: null,
selection: {
name: "Beta Note",
path: "topics/Beta Note.md",
linktext: "Beta Note",
range: { from: { line: 0, ch: 0 }, to: { line: 0, ch: 600 } },
text: "x".repeat(600),
},
},
})[0];
expect(suggestion?.detail).toBe(`${"x".repeat(499)}`);
});
it("resolves relative daily-note references to configured wikilinks", () => {
const dailyNoteReferences = [
{
keyword: "today",
display: "Today",
name: "2026-07-10",
path: "Journal/2026/07/2026-07-10.md",
linktext: "Journal/2026/07/2026-07-10",
},
{
keyword: "tomorrow",
display: "Tomorrow",
name: "2026-07-11",
path: "Journal/2026/07/2026-07-11.md",
linktext: "Journal/2026/07/2026-07-11",
},
{
keyword: "yesterday",
display: "Yesterday",
name: "2026-07-09",
path: "Journal/2026/07/2026-07-09.md",
linktext: "Journal/2026/07/2026-07-09",
},
@ -309,12 +329,12 @@ describe("composer suggestions", () => {
}),
).toMatchObject([
{
display: "Today",
display: "Today · 2026-07-10",
detail: "Journal/2026/07/2026-07-10.md",
replacement: "[[Journal/2026/07/2026-07-10]]",
},
{
display: "Tomorrow",
display: "Tomorrow · 2026-07-11",
detail: "Journal/2026/07/2026-07-11.md",
replacement: "[[Journal/2026/07/2026-07-11]]",
},
@ -324,7 +344,7 @@ describe("composer suggestions", () => {
dailyNoteReferences,
})[0],
).toMatchObject({
display: "Yesterday",
display: "Yesterday · 2026-07-09",
detail: "Journal/2026/07/2026-07-09.md",
replacement: "[[Journal/2026/07/2026-07-09]]",
});

View file

@ -48,18 +48,21 @@ describe("VaultNoteCandidateProvider", () => {
{
keyword: "today",
display: "Today",
name: "2026-07-10",
path: "Journal/2026/07/2026-07-10.md",
linktext: "2026-07-10",
},
{
keyword: "tomorrow",
display: "Tomorrow",
name: "2026-07-11",
path: "Journal/2026/07/2026-07-11.md",
linktext: "Journal/2026/07/2026-07-11",
},
{
keyword: "yesterday",
display: "Yesterday",
name: "2026-07-09",
path: "Journal/2026/07/2026-07-09.md",
linktext: "Journal/2026/07/2026-07-09",
},

View file

@ -178,6 +178,7 @@ describe("ChatComposerController", () => {
{
keyword: "today" as const,
display: "Today",
name: "2026-07-10",
path: "Journal/2026-07-10.md",
linktext: "Journal/2026-07-10",
},