obsidian#EditorSuggest TypeScript Examples
The following examples show how to use
obsidian#EditorSuggest.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: tagSuggest.ts From obsidian-map-view with GNU General Public License v3.0 | 5 votes |
export class TagSuggest extends EditorSuggest<SuggestInfo> {
private app: App;
constructor(app: App, settings: PluginSettings) {
super(app);
this.app = app;
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
file: TFile
): EditorSuggestTriggerInfo | null {
const line = editor.getLine(cursor.line);
// Start by verifying that the current line has an inline location.
// If it doesn't, we don't wanna trigger the completion even if the user
// starts typing 'tag:'
const hasLocationMatch = matchInlineLocation(line);
if (!hasLocationMatch || hasLocationMatch.length == 0) return null;
const tagMatch = getTagUnderCursor(line, cursor.ch);
if (tagMatch)
return {
start: { line: cursor.line, ch: tagMatch.index },
end: {
line: cursor.line,
ch: tagMatch.index + tagMatch[0].length,
},
query: tagMatch[1],
};
return null;
}
getSuggestions(context: EditorSuggestContext): SuggestInfo[] {
const noPound = (tagName: string) => {
return tagName.startsWith('#') ? tagName.substring(1) : tagName;
};
const tagQuery = context.query ?? '';
// Find all tags that include the query
const matchingTags = utils
.getAllTagNames(this.app)
.map(value => noPound(value))
.filter((value) =>
value.toLowerCase().includes(tagQuery.toLowerCase())
);
return matchingTags.map((tagName) => {
return {
tagName: tagName,
context: context,
};
});
}
renderSuggestion(value: SuggestInfo, el: HTMLElement) {
el.setText(value.tagName);
}
selectSuggestion(value: SuggestInfo, evt: MouseEvent | KeyboardEvent) {
const currentCursor = value.context.editor.getCursor();
const linkResult = `tag:${value.tagName} `;
value.context.editor.replaceRange(
linkResult,
value.context.start,
value.context.end
);
}
}
Example #2
Source File: main.ts From obsidian-emoji-shortcodes with MIT License | 5 votes |
class EmojiSuggester extends EditorSuggest<string> {
plugin: EmojiShortcodesPlugin;
constructor(plugin: EmojiShortcodesPlugin) {
super(plugin.app);
this.plugin = plugin;
}
onTrigger(cursor: EditorPosition, editor: Editor, _: TFile): EditorSuggestTriggerInfo | null {
if (this.plugin.settings.suggester) {
const sub = editor.getLine(cursor.line).substring(0, cursor.ch);
const match = sub.match(/:\S+$/)?.first();
if (match) {
return {
end: cursor,
start: {
ch: sub.lastIndexOf(match),
line: cursor.line,
},
query: match,
}
}
}
return null;
}
getSuggestions(context: EditorSuggestContext): string[] {
let emoji_query = context.query.replace(':', '');
return Object.keys(emoji).filter(p => p.includes(emoji_query));
}
renderSuggestion(suggestion: string, el: HTMLElement): void {
const outer = el.createDiv({ cls: "ES-suggester-container" });
outer.createDiv({ cls: "ES-shortcode" }).setText(suggestion.replace(/:/g, ""));
//@ts-expect-error
outer.createDiv({ cls: "ES-emoji" }).setText(emoji[suggestion]);
}
selectSuggestion(suggestion: string): void {
if(this.context) {
(this.context.editor as Editor).replaceRange(this.plugin.settings.immediateReplace ? emoji[suggestion] : `${suggestion} `, this.context.start, this.context.end);
}
}
}
Example #3
Source File: suggest.ts From obsidian-admonition with MIT License | 5 votes |
export class CalloutSuggest extends EditorSuggest<string> {
constructor(public plugin: ObsidianAdmonition) {
super(plugin.app);
}
getSuggestions(ctx: EditorSuggestContext) {
return Object.keys(this.plugin.admonitions).filter((p) =>
p.toLowerCase().contains(ctx.query.toLowerCase())
);
}
renderSuggestion(text: string, el: HTMLElement) {
el.createSpan({ text });
}
selectSuggestion(value: string, evt: MouseEvent | KeyboardEvent): void {
if (!this.context) return;
const line = this.context.editor
.getLine(this.context.end.line)
.slice(this.context.end.ch);
const [_, exists] = line.match(/^(\] ?)/) ?? [];
this.context.editor.replaceRange(
`${value}] `,
this.context.start,
{
...this.context.end,
ch:
this.context.start.ch +
this.context.query.length +
(exists?.length ?? 0)
},
"admonitions"
);
this.context.editor.setCursor(
this.context.start.line,
this.context.start.ch + value.length + 2
);
this.close();
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
file: TFile
): EditorSuggestTriggerInfo {
const line = editor.getLine(cursor.line);
//not inside the bracket
if (/> \[!\w+\]/.test(line.slice(0, cursor.ch))) return null;
if (!/> \[!\w*/.test(line)) return null;
const match = line.match(/> \[!(\w*)\]?/);
if (!match) return null;
const [_, query] = match;
if (
!query ||
Object.keys(this.plugin.admonitions).find(
(p) => p.toLowerCase() == query.toLowerCase()
)
) {
return null;
}
const matchData = {
end: cursor,
start: {
ch: match.index + 4,
line: cursor.line
},
query
};
return matchData;
}
}
Example #4
Source File: suggest.ts From obsidian-admonition with MIT License | 5 votes |
export class AdmonitionSuggest extends EditorSuggest<string> {
constructor(public plugin: ObsidianAdmonition) {
super(plugin.app);
}
getSuggestions(ctx: EditorSuggestContext) {
return Object.keys(this.plugin.admonitions).filter((p) =>
p.toLowerCase().contains(ctx.query.toLowerCase())
);
}
renderSuggestion(text: string, el: HTMLElement) {
el.createSpan({ text });
}
selectSuggestion(value: string, evt: MouseEvent | KeyboardEvent): void {
if (!this.context) return;
this.context.editor.replaceRange(
`${value}`,
this.context.start,
this.context.end,
"admonitions"
);
this.close();
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
file: TFile
): EditorSuggestTriggerInfo {
const line = editor.getLine(cursor.line);
if (!/```ad-\w+/.test(line)) return null;
const match = line.match(/```ad-(\w+)/);
if (!match) return null;
const [_, query] = match;
if (
!query ||
Object.keys(this.plugin.admonitions).find(
(p) => p.toLowerCase() == query.toLowerCase()
)
) {
return null;
}
const matchData = {
end: cursor,
start: {
ch: match.index + 6,
line: cursor.line
},
query
};
return matchData;
}
}
Example #5
Source File: Autocomplete.ts From Templater with GNU Affero General Public License v3.0 | 4 votes |
export class Autocomplete extends EditorSuggest<TpSuggestDocumentation> {
//private in_command = false;
// https://regex101.com/r/ocmHzR/1
private tp_keyword_regex =
/tp\.(?<module>[a-z]*)?(?<fn_trigger>\.(?<fn>[a-z_]*)?)?$/;
private documentation: Documentation;
private latest_trigger_info: EditorSuggestTriggerInfo;
private module_name: ModuleName | string;
private function_trigger: boolean;
private function_name: string;
constructor(private app: App, private plugin: TemplaterPlugin) {
super(app);
this.documentation = new Documentation(this.app);
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
file: TFile
): EditorSuggestTriggerInfo | null {
const range = editor.getRange(
{ line: cursor.line, ch: 0 },
{ line: cursor.line, ch: cursor.ch }
);
const match = this.tp_keyword_regex.exec(range);
if (!match) {
return null;
}
let query: string;
const module_name = match.groups["module"] || "";
this.module_name = module_name;
if (match.groups["fn_trigger"]) {
if (module_name == "" || !is_module_name(module_name)) {
return;
}
this.function_trigger = true;
this.function_name = match.groups["fn"] || "";
query = this.function_name;
} else {
this.function_trigger = false;
query = this.module_name;
}
const trigger_info: EditorSuggestTriggerInfo = {
start: { line: cursor.line, ch: cursor.ch - query.length },
end: { line: cursor.line, ch: cursor.ch },
query: query,
};
this.latest_trigger_info = trigger_info;
return trigger_info;
}
getSuggestions(
context: EditorSuggestContext
): Array<TpSuggestDocumentation> {
let suggestions: Array<TpSuggestDocumentation>;
if (this.module_name && this.function_trigger) {
suggestions =
this.documentation.get_all_functions_documentation(
this.module_name as ModuleName
);
} else {
suggestions =
this.documentation.get_all_modules_documentation();
}
if (!suggestions) {
return [];
}
return suggestions.filter(s => s.name.startsWith(context.query));
}
renderSuggestion(value: TpSuggestDocumentation, el: HTMLElement): void {
el.createEl("b", { text: value.name });
el.createEl("br");
if (this.function_trigger && is_function_documentation(value)) {
el.createEl("code", { text: value.definition });
}
if (value.description) {
el.createEl("div", { text: value.description });
}
}
selectSuggestion(
value: TpSuggestDocumentation,
evt: MouseEvent | KeyboardEvent
): void {
const active_view =
this.app.workspace.getActiveViewOfType(MarkdownView);
if (!active_view) {
// TODO: Error msg
return;
}
active_view.editor.replaceRange(
value.name,
this.latest_trigger_info.start,
this.latest_trigger_info.end
);
if (this.latest_trigger_info.start.ch == this.latest_trigger_info.end.ch) {
// Dirty hack to prevent the cursor being at the
// beginning of the word after completion,
// Not sure what's the cause of this bug.
const cursor_pos = this.latest_trigger_info.end;
cursor_pos.ch += value.name.length;
active_view.editor.setCursor(cursor_pos);
}
}
}
Example #6
Source File: date-suggest.ts From nldates-obsidian with MIT License | 4 votes |
export default class DateSuggest extends EditorSuggest<IDateCompletion> {
private plugin: NaturalLanguageDates;
private app: App;
constructor(app: App, plugin: NaturalLanguageDates) {
super(app);
this.app = app;
this.plugin = plugin;
// @ts-ignore
this.scope.register(["Shift"], "Enter", (evt: KeyboardEvent) => {
// @ts-ignore
this.suggestions.useSelectedItem(evt);
return false;
});
if (this.plugin.settings.autosuggestToggleLink) {
this.setInstructions([{ command: "Shift", purpose: "Keep text as alias" }]);
}
}
getSuggestions(context: EditorSuggestContext): IDateCompletion[] {
const suggestions = this.getDateSuggestions(context);
if (suggestions.length) {
return suggestions;
}
// catch-all if there are no matches
return [{ label: context.query }];
}
getDateSuggestions(context: EditorSuggestContext): IDateCompletion[] {
if (context.query.match(/^time/)) {
return ["now", "+15 minutes", "+1 hour", "-15 minutes", "-1 hour"]
.map((val) => ({ label: `time:${val}` }))
.filter((item) => item.label.toLowerCase().startsWith(context.query));
}
if (context.query.match(/(next|last|this)/i)) {
const reference = context.query.match(/(next|last|this)/i)[1];
return [
"week",
"month",
"year",
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
]
.map((val) => ({ label: `${reference} ${val}` }))
.filter((items) => items.label.toLowerCase().startsWith(context.query));
}
const relativeDate =
context.query.match(/^in ([+-]?\d+)/i) || context.query.match(/^([+-]?\d+)/i);
if (relativeDate) {
const timeDelta = relativeDate[1];
return [
{ label: `in ${timeDelta} minutes` },
{ label: `in ${timeDelta} hours` },
{ label: `in ${timeDelta} days` },
{ label: `in ${timeDelta} weeks` },
{ label: `in ${timeDelta} months` },
{ label: `${timeDelta} days ago` },
{ label: `${timeDelta} weeks ago` },
{ label: `${timeDelta} months ago` },
].filter((items) => items.label.toLowerCase().startsWith(context.query));
}
return [{ label: "Today" }, { label: "Yesterday" }, { label: "Tomorrow" }].filter(
(items) => items.label.toLowerCase().startsWith(context.query)
);
}
renderSuggestion(suggestion: IDateCompletion, el: HTMLElement): void {
el.setText(suggestion.label);
}
selectSuggestion(suggestion: IDateCompletion, event: KeyboardEvent | MouseEvent): void {
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeView) {
return;
}
const includeAlias = event.shiftKey;
let dateStr = "";
let makeIntoLink = this.plugin.settings.autosuggestToggleLink;
if (suggestion.label.startsWith("time:")) {
const timePart = suggestion.label.substring(5);
dateStr = this.plugin.parseTime(timePart).formattedString;
makeIntoLink = false;
} else {
dateStr = this.plugin.parseDate(suggestion.label).formattedString;
}
if (makeIntoLink) {
dateStr = generateMarkdownLink(
this.app,
dateStr,
includeAlias ? suggestion.label : undefined
);
}
activeView.editor.replaceRange(dateStr, this.context.start, this.context.end);
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
file: TFile
): EditorSuggestTriggerInfo {
if (!this.plugin.settings.isAutosuggestEnabled) {
return null;
}
const triggerPhrase = this.plugin.settings.autocompleteTriggerPhrase;
const startPos = this.context?.start || {
line: cursor.line,
ch: cursor.ch - triggerPhrase.length,
};
if (!editor.getRange(startPos, cursor).startsWith(triggerPhrase)) {
return null;
}
const precedingChar = editor.getRange(
{
line: startPos.line,
ch: startPos.ch - 1,
},
startPos
);
// Short-circuit if `@` as a part of a word (e.g. part of an email address)
if (precedingChar && /[`a-zA-Z0-9]/.test(precedingChar)) {
return null;
}
return {
start: startPos,
end: cursor,
query: editor.getRange(startPos, cursor).substring(triggerPhrase.length),
};
}
}
Example #7
Source File: locationSuggest.ts From obsidian-map-view with GNU General Public License v3.0 | 4 votes |
export class LocationSuggest extends EditorSuggest<SuggestInfo> {
private cursorInsideGeolinkFinder = /\[(.*?)\]\(geo:.*?\)/g;
private lastSearchTime = 0;
private delayInMs = 250;
private settings: PluginSettings;
private searcher: GeoSearcher;
constructor(app: App, settings: PluginSettings) {
super(app);
this.settings = settings;
this.searcher = new GeoSearcher(app, settings);
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
file: TFile
): EditorSuggestTriggerInfo | null {
const currentLink = this.getGeolinkOfCursor(cursor, editor);
if (currentLink)
return {
start: { line: cursor.line, ch: currentLink.index },
end: { line: cursor.line, ch: currentLink.linkEnd },
query: currentLink.name,
};
return null;
}
async getSuggestions(
context: EditorSuggestContext
): Promise<SuggestInfo[]> {
if (context.query.length < 2) return [];
return await this.getSearchResultsWithDelay(context);
}
renderSuggestion(value: SuggestInfo, el: HTMLElement) {
el.setText(value.name);
}
selectSuggestion(value: SuggestInfo, evt: MouseEvent | KeyboardEvent) {
// Replace the link under the cursor with the retrieved location.
// We call getGeolinkOfCursor again instead of using the original context because it's possible that
// the user continued to type text after the suggestion was made
const currentCursor = value.context.editor.getCursor();
const linkOfCursor = this.getGeolinkOfCursor(
currentCursor,
value.context.editor
);
const finalResult = `[${value.context.query}](geo:${value.location.lat},${value.location.lng})`;
value.context.editor.replaceRange(
finalResult,
{ line: currentCursor.line, ch: linkOfCursor.index },
{ line: currentCursor.line, ch: linkOfCursor.linkEnd }
);
if (utils.verifyOrAddFrontMatter(value.context.editor, 'locations', ''))
new Notice(
"The note's front matter was updated to denote locations are present"
);
}
getGeolinkOfCursor(cursor: EditorPosition, editor: Editor) {
const results = editor
.getLine(cursor.line)
.matchAll(this.cursorInsideGeolinkFinder);
if (!results) return null;
for (let result of results) {
const linkName = result[1];
if (
cursor.ch >= result.index &&
cursor.ch < result.index + linkName.length + 2
)
return {
index: result.index,
name: linkName,
linkEnd: result.index + result[0].length,
};
}
return null;
}
async getSearchResultsWithDelay(
context: EditorSuggestContext
): Promise<SuggestInfo[] | null> {
const timestamp = Date.now();
this.lastSearchTime = timestamp;
const Sleep = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
await Sleep(this.delayInMs);
if (this.lastSearchTime != timestamp) {
// Search is canceled by a newer search
return null;
}
// After the sleep our search is still the last -- so the user stopped and we can go on
const searchResults = await this.searcher.search(context.query);
let suggestions: SuggestInfo[] = [];
for (const result of searchResults)
suggestions.push({
...result,
context: context,
});
return suggestions;
}
async selectionToLink(editor: Editor) {
const selection = editor.getSelection();
const results = await this.searcher.search(selection);
if (results && results.length > 0) {
const firstResult = results[0];
const location = firstResult.location;
editor.replaceSelection(
`[${selection}](geo:${location.lat},${location.lng})`
);
new Notice(firstResult.name, 10 * 1000);
if (utils.verifyOrAddFrontMatter(editor, 'locations', ''))
new Notice(
"The note's front matter was updated to denote locations are present"
);
} else {
new Notice(`No location found for the term '${selection}'`);
}
}
}