From a3b709c2b26decd61886c3245b7562ddf9b4403e Mon Sep 17 00:00:00 2001 From: Barrca <60709314+FBarrca@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:23:51 -0800 Subject: [PATCH] Commands and prefix can be set through the settings --- src/main.ts | 3 +-- src/modules/WidgetExtension.ts | 23 +++++++++++++---------- src/modules/commands/source.ts | 19 ++++++++++++++----- src/settings.ts | 28 +++++++++++++++++++++++----- 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/main.ts b/src/main.ts index 850ce2b..304c133 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,12 +17,11 @@ export default class InlineAIChatPlugin extends Plugin { this.chatapi = new ChatApiManager(this.settings, this.app); this.registerEditorExtension([ - FloatingTooltipExtension(this.chatapi), + FloatingTooltipExtension(this.chatapi, this), generatedResponseState, currentSelectionState, buildSelectionHiglightState, diffExtension - ]); // Add command to show tooltip diff --git a/src/modules/WidgetExtension.ts b/src/modules/WidgetExtension.ts index 27647d0..d7a908e 100644 --- a/src/modules/WidgetExtension.ts +++ b/src/modules/WidgetExtension.ts @@ -21,7 +21,7 @@ import { ChatApiManager } from "../api"; import { currentSelectionState, SelectionInfo } from "./SelectionState"; import { slashCommandAutocompletion } from "./commands/source"; -import { slashCommands } from "./commands/commands"; +import InlineAIChatPlugin from "src/main"; @@ -33,6 +33,7 @@ export const acceptTooltipEffect = StateEffect.define(); class FloatingWidget extends WidgetType { private chatApiManager: ChatApiManager; private selectionInfo: SelectionInfo | null; + private plugin: InlineAIChatPlugin; private outerEditorView: EditorView | null = null; @@ -48,10 +49,11 @@ class FloatingWidget extends WidgetType { private acceptButton!: HTMLButtonElement; private discardButton!: HTMLButtonElement; - constructor(chatApiManager: ChatApiManager, selectionInfo: SelectionInfo | null) { + constructor(chatApiManager: ChatApiManager, selectionInfo: SelectionInfo | null, plugin:InlineAIChatPlugin) { super(); this.chatApiManager = chatApiManager; this.selectionInfo = selectionInfo; + this.plugin = plugin; // Create main DOM structure using createEl this.dom = createEl("div", { cls: "cm-cursor-overlay", attr: { style: "user-select: none;" } }); @@ -152,8 +154,8 @@ class FloatingWidget extends WidgetType { ]), // 3) Enable slash-command autocompletion slashCommandAutocompletion({ - prefix: ':', - customCommands: slashCommands + prefix: this.plugin.settings.commandPrefix, + customCommands: this.plugin.settings.customCommands }) ], }), @@ -297,14 +299,15 @@ class FloatingWidget extends WidgetType { */ function renderFloatingWidget( state: EditorState, - chatApiManager: ChatApiManager + chatApiManager: ChatApiManager, + plugin:InlineAIChatPlugin ): DecorationSet { const firstSelectedRange = state.selection.ranges.find((range) => !range.empty) ?? state.selection.main; const selectionInfo = state.field(currentSelectionState, false) ?? null; const deco = Decoration.widget({ - widget: new FloatingWidget(chatApiManager, selectionInfo), + widget: new FloatingWidget(chatApiManager, selectionInfo, plugin), above: true, inline: true, side: -9999, @@ -323,7 +326,7 @@ function renderFloatingWidget( * When the user dismisses the tooltip, it clears the decoration set. * Otherwise, it returns the existing decoration set. */ -function FloatingTooltipState(chatApiManager: ChatApiManager) { +function FloatingTooltipState(chatApiManager: ChatApiManager, plugin:InlineAIChatPlugin) { return StateField.define({ create(state) { return Decoration.none; @@ -331,7 +334,7 @@ function FloatingTooltipState(chatApiManager: ChatApiManager) { update(decorations, tr) { // Recompute if the user triggers the command if (tr.effects.some((e) => e.is(commandEffect))) { - return renderFloatingWidget(tr.state, chatApiManager); + return renderFloatingWidget(tr.state, chatApiManager, plugin); } // Or dismiss it if (tr.effects.some((e) => e.is(dismissTooltipEffect))) { @@ -347,6 +350,6 @@ function FloatingTooltipState(chatApiManager: ChatApiManager) { /** * Extension enabling selection overlay widgets. */ -export function FloatingTooltipExtension(chatApiManager: ChatApiManager) { - return [FloatingTooltipState(chatApiManager)]; +export function FloatingTooltipExtension(chatApiManager: ChatApiManager, plugin:InlineAIChatPlugin) { + return [FloatingTooltipState(chatApiManager, plugin)]; } diff --git a/src/modules/commands/source.ts b/src/modules/commands/source.ts index 4211162..28a65fd 100644 --- a/src/modules/commands/source.ts +++ b/src/modules/commands/source.ts @@ -1,5 +1,12 @@ import { autocompletion, CompletionContext } from "@codemirror/autocomplete" -import { SlashCommand, slashCommands } from "./commands"; +/** + * + * Interface describing a slash command. + */ +export interface SlashCommand { + keyword: string; + prompt: string; +} // Factory function that creates a completion source with custom parameters @@ -8,10 +15,11 @@ function createSlashCommandSource(options: { customCommands: SlashCommand[] } = { prefix: '/', - customCommands: slashCommands + customCommands: [] }) { const { prefix, customCommands } = options; console.log(prefix, customCommands) + return (context: CompletionContext) => { let word = context.matchBefore(new RegExp(`^\\${prefix}\\w*`)) if (!word || (word.from == word.to && !context.explicit)) @@ -19,8 +27,9 @@ function createSlashCommandSource(options: { return { from: word.from+1, options: (customCommands).map(cmd => ({ - label: cmd.label, - type: "text" + label: cmd.keyword, + type: "text", + detail: cmd.prompt, })) } } @@ -31,4 +40,4 @@ export function slashCommandAutocompletion(options: { prefix?: string, customCom return autocompletion({ override: [createSlashCommandSource(options)] }) -} +} \ No newline at end of file diff --git a/src/settings.ts b/src/settings.ts index 23bd0ab..3d7b3bc 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,6 +1,7 @@ import { App, PluginSettingTab, Setting } from "obsidian"; import MyPlugin from "./main"; import { cursorPrompt, selectionPrompt } from "./default_prompts"; +import { SlashCommand } from "./modules/commands/source"; // Interface for the settings export interface InlineAISettings { @@ -10,7 +11,8 @@ export interface InlineAISettings { customURL?: string; // Add a custom URL field selectionPrompt: string; cursorPrompt: string; - customCommands: { name: string; prompt: string }[]; // Add custom commands array + customCommands: SlashCommand[]; + commandPrefix: string; // Add command prefix setting } // Default settings values @@ -22,6 +24,7 @@ export const DEFAULT_SETTINGS: InlineAISettings = { selectionPrompt: selectionPrompt, cursorPrompt: cursorPrompt, customCommands: [], // Default is an empty array + commandPrefix: "/" // Default command prefix }; // Settings tab class to display settings in Obsidian UI @@ -113,6 +116,21 @@ export class InlineAISettingsTab extends PluginSettingTab { .setName("Advanced") .setHeading(); + // Command Prefix setting + new Setting(containerEl) + .setName("Command Prefix") + .setDesc("The prefix used to trigger custom commands (e.g., /, !, #)") + .addText((text) => + text + .setPlaceholder("/") + .setValue(this.plugin.settings.commandPrefix) + .onChange(async (value) => { + // Ensure the prefix is not empty + this.plugin.settings.commandPrefix = value || "/"; + await this.plugin.saveSettings(); + }) + ); + // Selection Prompt setting new Setting(containerEl) .setName("Selection prompt") @@ -157,14 +175,14 @@ export class InlineAISettingsTab extends PluginSettingTab { // Display existing commands this.plugin.settings.customCommands.forEach((command, index) => { const setting = new Setting(containerEl) - .setName(`Command: ${command.name}`) + .setName(`Command: ${command.keyword}`) .setDesc("Edit the command prompt.") .addText((text) => text - .setValue(command.name) + .setValue(command.keyword) .setPlaceholder("Command name") .onChange(async (value) => { - this.plugin.settings.customCommands[index].name = value; + this.plugin.settings.customCommands[index].keyword = value; await this.plugin.saveSettings(); }) ) @@ -196,7 +214,7 @@ export class InlineAISettingsTab extends PluginSettingTab { .setButtonText("Add Command") .setCta() .onClick(async () => { - this.plugin.settings.customCommands.push({ name: "New Command", prompt: "" }); + this.plugin.settings.customCommands.push({ keyword: "new_command", prompt: "" }); await this.plugin.saveSettings(); this.display(); // Refresh the display })