Skip to content

Commit

Permalink
Add a "select with custom alias" pop-up command #282
Browse files Browse the repository at this point in the history
  • Loading branch information
tadashi-aikawa committed Feb 18, 2024
1 parent 17a37dc commit e809aa5
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 14 deletions.
2 changes: 2 additions & 0 deletions src/setting/settings.ts
Expand Up @@ -47,6 +47,7 @@ export interface Settings {
// key customization
hotkeys: {
select: Hotkey[];
"select with custom alias": Hotkey[];
up: Hotkey[];
down: Hotkey[];
"select 1st": Hotkey[];
Expand Down Expand Up @@ -156,6 +157,7 @@ export const DEFAULT_SETTINGS: Settings = {
// key customization
hotkeys: {
select: [{ modifiers: [], key: "Enter" }],
"select with custom alias": [],
up: [{ modifiers: [], key: "ArrowUp" }],
down: [{ modifiers: [], key: "ArrowDown" }],
"select 1st": [],
Expand Down
52 changes: 38 additions & 14 deletions src/ui/AutoCompleteSuggest.ts
Expand Up @@ -109,6 +109,8 @@ export class AutoCompleteSuggest
activeLeafChangeRef: EventRef;
metadataCacheChangeRef: EventRef;

spareEditorSuggestContext: EditorSuggestContext | null = null;

private constructor(app: App, statusBar: ProviderStatusBar) {
super(app);
this.appHelper = new AppHelper(app);
Expand Down Expand Up @@ -503,6 +505,20 @@ export class AutoCompleteSuggest
["select 7th", (evt) => commands.select(this, evt, 6)],
["select 8th", (evt) => commands.select(this, evt, 7)],
["select 9th", (evt) => commands.select(this, evt, 8)],
[
"select with custom alias",
(evt) => {
// For restore the context of suggestions to insert editor after submitted input
this.spareEditorSuggestContext = this.context;

commands.selectWithCustomAlias(this, evt).then((item) => {
if (item) {
this.selectSuggestion(item);
}
});
return false;
},
],
["open", (_) => commands.open(this)],
["completion", (_) => commands.completion(this)],
["insert as text", (evt) => commands.insertAsText(this, evt)],
Expand Down Expand Up @@ -973,9 +989,15 @@ export class AutoCompleteSuggest
}
}

constructInternalLinkText(word: InternalLinkWord): string {
constructInternalLinkText(
word: InternalLinkWord,
forceWithAlias: boolean,
): string {
// With aliases
if (this.settings.suggestInternalLinkWithAlias && word.aliasMeta) {
if (
(this.settings.suggestInternalLinkWithAlias || forceWithAlias) &&
word.aliasMeta
) {
const { link } = this.appHelper.optimizeMarkdownLinkText(
word.aliasMeta.origin,
)!;
Expand Down Expand Up @@ -1023,14 +1045,21 @@ export class AutoCompleteSuggest
: `[${displayed}](${encodeSpace(link)}.md)`;
}

selectSuggestion(word: Word, _evt: MouseEvent | KeyboardEvent): void {
if (!this.context) {
selectSuggestion(word: Word): void {
let forceWithAlias = false;
let context = this.context;
if (!context) {
context = this.spareEditorSuggestContext;
this.spareEditorSuggestContext = null;
forceWithAlias = true;
}
if (!context) {
return;
}

let insertedText = word.value;
if (word.type === "internalLink") {
insertedText = this.constructInternalLinkText(word);
insertedText = this.constructInternalLinkText(word, forceWithAlias);
}

if (word.type === "frontMatter") {
Expand Down Expand Up @@ -1063,14 +1092,14 @@ export class AutoCompleteSuggest
}
}

const editor = this.context.editor;
const editor = context.editor;
editor.replaceRange(
insertedText,
{
...this.context.start,
...context.start,
ch: this.contextStartCh + word.offset!,
},
this.context.end,
context.end,
);

if (positionToMove !== -1) {
Expand All @@ -1084,12 +1113,7 @@ export class AutoCompleteSuggest
}

// The workaround of strange behavior for that cursor doesn't move after completion only if it doesn't input any word.
if (
this.appHelper.equalsAsEditorPosition(
this.context.start,
this.context.end,
)
) {
if (this.appHelper.equalsAsEditorPosition(context.start, context.end)) {
editor.setCursor(
editor.offsetToPos(
editor.posToOffset(editor.getCursor()) + insertedText.length,
Expand Down
68 changes: 68 additions & 0 deletions src/ui/component/InputDialog.ts
@@ -0,0 +1,68 @@
import { Modal } from "obsidian";

export class InputDialog extends Modal {
inputEl!: HTMLInputElement;
promise!: Promise<string | null>;
submitted = false;

constructor(
public args: {
title: string;
placeholder?: string;
defaultValue?: string;
},
) {
super(app);
}

onOpen(): void {
this.titleEl.setText(this.args.title);

this.inputEl = this.contentEl.createEl("input", {
type: "text",
placeholder: this.args.placeholder ?? "",
cls: "carnelian-input-dialog-input",
value: this.args.defaultValue,
});
}

/**
* This function returns
* - Promise<string> if submitted not empty string
* - Promise<""> if submitted empty string
* - Promise<null> if canceled
*/
open(args?: { initialSelect: boolean }): Promise<string | null> {
super.open();

this.promise = new Promise<string | null>((resolve) => {
const listener = (ev: KeyboardEvent) => {
if (ev.isComposing) {
return;
}
if (ev.code === "Enter") {
ev.preventDefault();
resolve(this.inputEl.value);
this.submitted = true;
this.close();
}
};

this.inputEl.addEventListener("keydown", listener);

this.onClose = () => {
super.onClose();
this.inputEl.removeEventListener("keydown", listener);
if (!this.submitted) {
resolve(null);
}
};

if (args?.initialSelect) {
this.inputEl.select();
}
});

return this.promise;
}
}
39 changes: 39 additions & 0 deletions src/ui/popup-commands.ts
@@ -1,6 +1,8 @@
import type { AutoCompleteSuggest } from "./AutoCompleteSuggest";
import { Notice } from "obsidian";
import { excludeEmoji, findCommonPrefix } from "../util/strings";
import { InputDialog } from "./component/InputDialog";
import type { InternalLinkWord } from "src/model/Word";

export type CommandReturnType = boolean | undefined;

Expand All @@ -27,6 +29,43 @@ export function select(
}
}

export async function selectWithCustomAlias(
popup: AutoCompleteSuggest,
evt: KeyboardEvent,
): Promise<InternalLinkWord | null> {
if (!popup.context || evt.isComposing) {
return null;
}

if (popup.selectionLock) {
popup.close();
return null;
}

const item = popup.suggestions.values[popup.suggestions.selectedItem];
if (item.type !== "internalLink") {
return null;
}

const input = await new InputDialog({
title: "Type custom alias",
defaultValue: item.value,
}).open({ initialSelect: true });
if (!input) {
return null;
}

if (item.value === input) {
return item;
}

item.aliasMeta = {
origin: item.aliasMeta?.origin ?? item.value,
};
item.value = input;
return item;
}

export function insertAsText(
popup: AutoCompleteSuggest,
evt: KeyboardEvent,
Expand Down

0 comments on commit e809aa5

Please sign in to comment.