From f8441a35e7c040cf4fa810c454f421158e5e8465 Mon Sep 17 00:00:00 2001 From: Taichi Maeda Date: Sat, 20 Apr 2024 02:05:31 +0900 Subject: [PATCH] Update context detection code --- src/api/clients/gemini.ts | 1 - src/api/clients/openai-compatible.ts | 11 ++-- src/api/index.ts | 6 +- src/api/prompts/block-quote/index.ts | 14 ++++ src/api/prompts/block-quote/params.json | 3 - src/api/prompts/block-quote/system.txt | 12 ++++ src/api/prompts/code-block/index.ts | 30 +++++++-- src/api/prompts/code-block/params.json | 3 - src/api/prompts/code-block/system.txt | 12 ++++ src/api/prompts/context.ts | 5 +- src/api/prompts/generator.ts | 62 ++++++++++++++++++ src/api/prompts/heading/index.ts | 20 ++++++ src/api/prompts/heading/params.json | 3 - src/api/prompts/heading/system.txt | 12 ++++ src/api/prompts/index.ts | 22 ++----- src/api/prompts/list-item/index.ts | 14 ++++ src/api/prompts/list-item/params.json | 3 - src/api/prompts/list-item/system.txt | 13 ++++ src/api/prompts/math-block/index.ts | 20 ++++++ src/api/prompts/math-block/params.json | 3 - src/api/prompts/math-block/system.txt | 13 ++++ src/api/prompts/paragraph/index.ts | 14 ++++ src/api/prompts/paragraph/params.json | 3 - src/api/prompts/paragraph/system.txt | 12 ++++ src/api/prompts/system.txt | 10 --- src/api/proxies/memory-cache.ts | 33 ++++------ src/api/proxies/usage-monitor.ts | 4 +- src/editor/extension.ts | 1 - src/editor/listener.ts | 86 +++---------------------- 29 files changed, 281 insertions(+), 164 deletions(-) delete mode 100644 src/api/prompts/block-quote/params.json create mode 100644 src/api/prompts/block-quote/system.txt delete mode 100644 src/api/prompts/code-block/params.json create mode 100644 src/api/prompts/code-block/system.txt create mode 100644 src/api/prompts/generator.ts delete mode 100644 src/api/prompts/heading/params.json create mode 100644 src/api/prompts/heading/system.txt delete mode 100644 src/api/prompts/list-item/params.json create mode 100644 src/api/prompts/list-item/system.txt delete mode 100644 src/api/prompts/math-block/params.json create mode 100644 src/api/prompts/math-block/system.txt delete mode 100644 src/api/prompts/paragraph/params.json create mode 100644 src/api/prompts/paragraph/system.txt delete mode 100644 src/api/prompts/system.txt diff --git a/src/api/clients/gemini.ts b/src/api/clients/gemini.ts index 9bf4fb9..4612a7e 100644 --- a/src/api/clients/gemini.ts +++ b/src/api/clients/gemini.ts @@ -9,7 +9,6 @@ export class GeminiAPIClient implements APIClient { throw new Error('Method not implemented.'); } fetchCompletions( - language: string, prefix: string, suffix: string, ): Promise { diff --git a/src/api/clients/openai-compatible.ts b/src/api/clients/openai-compatible.ts index 519a77c..2435853 100644 --- a/src/api/clients/openai-compatible.ts +++ b/src/api/clients/openai-compatible.ts @@ -61,7 +61,7 @@ export abstract class OpenAICompatibleAPIClient implements APIClient { } } - async fetchCompletions(language: string, prefix: string, suffix: string) { + async fetchCompletions(prefix: string, suffix: string) { const { settings } = this.plugin; if (this.openai === undefined) { @@ -69,9 +69,10 @@ export abstract class OpenAICompatibleAPIClient implements APIClient { } try { - const completions = await this.openai.completions.create({ - prompt: `Continue the following code written in ${language} language:\n\n${prefix}`, - suffix, + // TODO: + // Get messages from the prompt generator. + const completions = await this.openai.chat.completions.create({ + messages: [], model: settings.completions.model, max_tokens: settings.completions.maxTokens, temperature: settings.completions.temperature, @@ -90,7 +91,7 @@ export abstract class OpenAICompatibleAPIClient implements APIClient { outputTokens, ); - return completions.choices[0].text; + return completions.choices[0].message.content ?? undefined; } catch (error) { console.error(error); new Notice( diff --git a/src/api/index.ts b/src/api/index.ts index 2aa860c..9e99472 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,9 +2,5 @@ import { ChatMessage } from '../types'; export interface APIClient { fetchChat(messages: ChatMessage[]): AsyncGenerator; - fetchCompletions( - language: string, - prefix: string, - suffix: string, - ): Promise; + fetchCompletions(prefix: string, suffix: string): Promise; } diff --git a/src/api/prompts/block-quote/index.ts b/src/api/prompts/block-quote/index.ts index e69de29..8ff3d6c 100644 --- a/src/api/prompts/block-quote/index.ts +++ b/src/api/prompts/block-quote/index.ts @@ -0,0 +1,14 @@ +import { FewShotPrompt } from '..'; +import example1Assistant from './example1/assistant.txt'; +import example1User from './example1/user.md'; +import system from './system.txt'; + +export const BlockQuotePrompt: FewShotPrompt = { + system, + examples: [ + { + user: example1User, + assistant: example1Assistant, + }, + ], +}; diff --git a/src/api/prompts/block-quote/params.json b/src/api/prompts/block-quote/params.json deleted file mode 100644 index e5ca2a7..0000000 --- a/src/api/prompts/block-quote/params.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "stop": ["\n\n"] -} diff --git a/src/api/prompts/block-quote/system.txt b/src/api/prompts/block-quote/system.txt new file mode 100644 index 0000000..293b6ca --- /dev/null +++ b/src/api/prompts/block-quote/system.txt @@ -0,0 +1,12 @@ +Complete the most suitable text at the location of the . +The is located within a Markdown block quote. +Your answer must complete this quote in a way that fits the context of the surrounding text. +Your answer must be written in the same language as the surrounding text. +Your answer must not overlap with any text adjacent to the . +Your answer must have the following format: + +Here, you write the language of your response e.g. English, Chinese, TypeScript, Python. + +Here, you reason about the answer, using the 80/20 rule for clarity and conciseness. + +Here, you write the text that should be inserted at the location of the . \ No newline at end of file diff --git a/src/api/prompts/code-block/index.ts b/src/api/prompts/code-block/index.ts index 19dc590..4a82bba 100644 --- a/src/api/prompts/code-block/index.ts +++ b/src/api/prompts/code-block/index.ts @@ -1,8 +1,26 @@ -import { FewShowExample } from '../example'; -import assistant from './assistant.txt'; -import user from './user.md'; +import { FewShotPrompt } from '..'; +import example1Assistant from './example1/assistant.txt'; +import example1User from './example1/user.md'; +import example2Assistant from './example2/assistant.txt'; +import example2User from './example2/user.md'; +import example3Assistant from './example3/assistant.txt'; +import example3User from './example3/user.md'; +import system from './system.txt'; -export const CODE_BLOCK_EXAMPLE: FewShowExample = { - user, - assistant, +export const CodeBlockPrompt: FewShotPrompt = { + system, + examples: [ + { + user: example1User, + assistant: example1Assistant, + }, + { + user: example2User, + assistant: example2Assistant, + }, + { + user: example3User, + assistant: example3Assistant, + }, + ], }; diff --git a/src/api/prompts/code-block/params.json b/src/api/prompts/code-block/params.json deleted file mode 100644 index ce71a08..0000000 --- a/src/api/prompts/code-block/params.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "stop": ["```", "````"] -} diff --git a/src/api/prompts/code-block/system.txt b/src/api/prompts/code-block/system.txt new file mode 100644 index 0000000..eeb173e --- /dev/null +++ b/src/api/prompts/code-block/system.txt @@ -0,0 +1,12 @@ +Complete the most suitable text at the location of the . +The is located within a Markdown codeblock, written in the language {{LANGUAGE}}. +Your answer must complete this code block in the language {{LANGUAGE}}. +Your answer must not complete any text outside this code block. +Your answer must not overlap with any text adjacent to the . +Your answer must have the following format: + +Here, you write the language of your response e.g. English, Chinese, TypeScript, Python. + +Here, you reason about the answer, using the 80/20 rule for clarity and conciseness. + +Here, you write the text that should be inserted at the location of the . \ No newline at end of file diff --git a/src/api/prompts/context.ts b/src/api/prompts/context.ts index 4a2e6ef..90c9521 100644 --- a/src/api/prompts/context.ts +++ b/src/api/prompts/context.ts @@ -71,6 +71,9 @@ export function getLanguage(prefix: string, suffix: string): string { } function isCursorInBlock(text: string, regex: RegExp): boolean { - const blocks = text.match(regex) ?? []; + const blocks = text.match(regex) as string[] | null; + if (blocks === null) { + return false; + } return blocks.some((block) => block.includes(CURSOR_CHAR)); } diff --git a/src/api/prompts/generator.ts b/src/api/prompts/generator.ts new file mode 100644 index 0000000..944579d --- /dev/null +++ b/src/api/prompts/generator.ts @@ -0,0 +1,62 @@ +import Markpilot from 'src/main'; +import { ChatMessage } from 'src/types'; +import { FewShotPrompt } from '.'; +import { BlockQuotePrompt } from './block-quote'; +import { CodeBlockPrompt } from './code-block'; +import { Context, getContext, getLanguage } from './context'; +import { HeadingPrompt } from './heading'; +import { ListItemPrompt } from './list-item'; +import { MathBlockPrompt } from './math-block'; +import { ParagraphPrompt } from './paragraph'; + +const PROMPTS: Record = { + heading: HeadingPrompt, + paragraph: ParagraphPrompt, + 'list-item': ListItemPrompt, + 'block-quote': BlockQuotePrompt, + 'math-block': MathBlockPrompt, + 'code-block': CodeBlockPrompt, +}; + +export class PromptGenerator { + constructor(private plugin: Markpilot) {} + + generate(prefix: string, suffix: string): ChatMessage[] { + const { settings } = this.plugin; + + const windowSize = settings.completions.windowSize; + const truncatedPrefix = prefix.slice( + prefix.length - windowSize / 2, + prefix.length, + ); + const truncatedSuffix = suffix.slice(0, windowSize / 2); + + const context = getContext(prefix, suffix); + const prompt = PROMPTS[context]; + if (context === 'code-block') { + const language = getLanguage(prefix, suffix); + prompt.system = prompt.system.replace('{{LANGUAGE}}', language); + } + + return [ + { + role: 'system', + content: prompt.system, + }, + ...prompt.examples.flatMap((example) => [ + { + role: 'user', + content: example.user, + }, + { + role: 'assistant', + content: example.assistant, + }, + ]), + { + role: 'user', + content: `${truncatedPrefix}${truncatedSuffix}`, + }, + ] as ChatMessage[]; + } +} diff --git a/src/api/prompts/heading/index.ts b/src/api/prompts/heading/index.ts index e69de29..c6291fb 100644 --- a/src/api/prompts/heading/index.ts +++ b/src/api/prompts/heading/index.ts @@ -0,0 +1,20 @@ +import { FewShotPrompt } from '..'; +import example1Assistant from './example1/assistant.txt'; +import example1User from './example1/user.md'; +import example2Assistant from './example2/assistant.txt'; +import example2User from './example2/user.md'; +import system from './system.txt'; + +export const HeadingPrompt: FewShotPrompt = { + system, + examples: [ + { + user: example1User, + assistant: example1Assistant, + }, + { + user: example2User, + assistant: example2Assistant, + }, + ], +}; diff --git a/src/api/prompts/heading/params.json b/src/api/prompts/heading/params.json deleted file mode 100644 index f29602f..0000000 --- a/src/api/prompts/heading/params.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "stop": ["\n"] -} diff --git a/src/api/prompts/heading/system.txt b/src/api/prompts/heading/system.txt new file mode 100644 index 0000000..4d9b1d7 --- /dev/null +++ b/src/api/prompts/heading/system.txt @@ -0,0 +1,12 @@ +Complete the most suitable text at the location of the . +The is located within a Markdown heading. +Your answer must complete the title for this heading that fits the context of the surrounding text. +Your answer must be written in the same language as the surrounding text. +Your answer must not overlap with any text adjacent to the . +Your answer must have the following format: + +Here, you write the language of your response e.g. English, Chinese, TypeScript, Python. + +Here, you reason about the answer, using the 80/20 rule for clarity and conciseness. + +Here, you write the text that should be inserted at the location of the . \ No newline at end of file diff --git a/src/api/prompts/index.ts b/src/api/prompts/index.ts index 1b9a18d..bab195c 100644 --- a/src/api/prompts/index.ts +++ b/src/api/prompts/index.ts @@ -1,21 +1,9 @@ -import systemPrompt from './system.txt'; +export interface FewShotPrompt { + system: string; + examples: FewShotExample[]; +} -export interface FewShowExample { +export interface FewShotExample { user: string; assistant: string; } - -export class PromptGenerator { - private systemPrompt = systemPrompt; - - generate(prefix: string, suffix: string): string { - // TODO: - // 1. Determine the context from prefix and suffix. - // 2. Generate a prompt based on the context, with prefix and suffix trimmed according to window size. - const language = 'english'; - if (language) { - return this.systemPrompt.replace('{{LANGUAGE}}', language); - } - return ''; - } -} diff --git a/src/api/prompts/list-item/index.ts b/src/api/prompts/list-item/index.ts index e69de29..3fc1fd3 100644 --- a/src/api/prompts/list-item/index.ts +++ b/src/api/prompts/list-item/index.ts @@ -0,0 +1,14 @@ +import { FewShotPrompt } from '..'; +import example1Assistant from './example1/assistant.txt'; +import example1User from './example1/user.md'; +import system from './system.txt'; + +export const ListItemPrompt: FewShotPrompt = { + system, + examples: [ + { + user: example1User, + assistant: example1Assistant, + }, + ], +}; diff --git a/src/api/prompts/list-item/params.json b/src/api/prompts/list-item/params.json deleted file mode 100644 index e5ca2a7..0000000 --- a/src/api/prompts/list-item/params.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "stop": ["\n\n"] -} diff --git a/src/api/prompts/list-item/system.txt b/src/api/prompts/list-item/system.txt new file mode 100644 index 0000000..a19f8d1 --- /dev/null +++ b/src/api/prompts/list-item/system.txt @@ -0,0 +1,13 @@ +Complete the most suitable text at the location of the . +The is located within a Markdown list item. +Your answer must complete one or multiple list items for this list that fits the context of the surrounding text. +Your answer must not complete any text that is not part of this list. +Your answer must be written in the same language as the surrounding text. +Your answer must not overlap with any text adjacent to the . +Your answer must have the following format: + +Here, you write the language of your response e.g. English, Chinese, TypeScript, Python. + +Here, you reason about the answer, using the 80/20 rule for clarity and conciseness. + +Here, you write the text that should be inserted at the location of the . \ No newline at end of file diff --git a/src/api/prompts/math-block/index.ts b/src/api/prompts/math-block/index.ts index e69de29..c6ad7eb 100644 --- a/src/api/prompts/math-block/index.ts +++ b/src/api/prompts/math-block/index.ts @@ -0,0 +1,20 @@ +import { FewShotPrompt } from '..'; +import example1Assistant from './example1/assistant.txt'; +import example1User from './example1/user.md'; +import example2Assistant from './example2/assistant.txt'; +import example2User from './example2/user.md'; +import system from './system.txt'; + +export const MathBlockPrompt: FewShotPrompt = { + system, + examples: [ + { + user: example1User, + assistant: example1Assistant, + }, + { + user: example2User, + assistant: example2Assistant, + }, + ], +}; diff --git a/src/api/prompts/math-block/params.json b/src/api/prompts/math-block/params.json deleted file mode 100644 index 6de3036..0000000 --- a/src/api/prompts/math-block/params.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "stop": ["$ ", "$$"] -} diff --git a/src/api/prompts/math-block/system.txt b/src/api/prompts/math-block/system.txt new file mode 100644 index 0000000..373980a --- /dev/null +++ b/src/api/prompts/math-block/system.txt @@ -0,0 +1,13 @@ +Complete the most suitable text at the location of the . +The is located within a Markdown math block. +Your answer must only contain LaTeX code that captures the math discussed in the surrounding text. +Your answer must not contain any text that is not part of the LaTeX code. +Your answer must be written in the same language as the surrounding text. +Your answer must not overlap with any text adjacent to the . +Your answer must have the following format: + +Here, you write the language of your response e.g. English, Chinese, TypeScript, Python. + +Here, you reason about the answer, using the 80/20 rule for clarity and conciseness. + +Here, you write the text that should be inserted at the location of the . \ No newline at end of file diff --git a/src/api/prompts/paragraph/index.ts b/src/api/prompts/paragraph/index.ts index e69de29..1e92634 100644 --- a/src/api/prompts/paragraph/index.ts +++ b/src/api/prompts/paragraph/index.ts @@ -0,0 +1,14 @@ +import { FewShotPrompt } from '..'; +import example1Assistant from './example1/assistant.txt'; +import example1User from './example1/user.md'; +import system from './system.txt'; + +export const ParagraphPrompt: FewShotPrompt = { + system, + examples: [ + { + user: example1User, + assistant: example1Assistant, + }, + ], +}; diff --git a/src/api/prompts/paragraph/params.json b/src/api/prompts/paragraph/params.json deleted file mode 100644 index e5ca2a7..0000000 --- a/src/api/prompts/paragraph/params.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "stop": ["\n\n"] -} diff --git a/src/api/prompts/paragraph/system.txt b/src/api/prompts/paragraph/system.txt new file mode 100644 index 0000000..3ea01c0 --- /dev/null +++ b/src/api/prompts/paragraph/system.txt @@ -0,0 +1,12 @@ +Complete the most suitable text at the location of the . +The is located within a Markdown paragraph. +Your answer must complete one or multiple sentences to this paragraph that fit the surrounding text. +Your answer must be written in the same language as the surrounding text. +Your answer must not overlap with any text adjacent to the . +Your answer must have the following format: + +Here, you write the language of your response e.g. English, Chinese, TypeScript, Python. + +Here, you reason about the answer, using the 80/20 rule for clarity and conciseness. + +Here, you write the text that should be inserted at the location of the . \ No newline at end of file diff --git a/src/api/prompts/system.txt b/src/api/prompts/system.txt deleted file mode 100644 index 3d026d7..0000000 --- a/src/api/prompts/system.txt +++ /dev/null @@ -1,10 +0,0 @@ -Predict the most logical text written in the language {{LANGUAGE}} at the location of the . -Your answer can be either code, a single word, or multiple sentences, depending on the language . -Your answer cannot have any overlapping text directly surrounding the . -Your answer must have the following format: - -Here, you write the language of your answer, e.g. English, Chinese, Python, JSON etc. - -Here, you reason about the answer; use the 80/20 principle to be brief. - -Here, you write the text that should be at the location of \ No newline at end of file diff --git a/src/api/proxies/memory-cache.ts b/src/api/proxies/memory-cache.ts index af4b8f3..37b4a05 100644 --- a/src/api/proxies/memory-cache.ts +++ b/src/api/proxies/memory-cache.ts @@ -16,33 +16,30 @@ export class MemoryCacheProxy implements APIClient { return this.client.fetchChat(messages); } - async fetchCompletions(language: string, prefix: string, suffix: string) { + async fetchCompletions(prefix: string, suffix: string) { const { settings } = this.plugin; if (!settings.cache.enabled) { - const completions = await this.client.fetchCompletions( - language, - prefix, - suffix, - ); + const completions = await this.client.fetchCompletions(prefix, suffix); return completions; } - // Extra whitespaces should not affect the completions. - const compactPrefix = prefix.replace(/\s\s+/g, ' '); - const compactSuffix = suffix.replace(/\s\s+/g, ' '); - // Use half the window size // because some characters may have overflowed due to extra whitespaces. const windowSize = settings.completions.windowSize / 2; - const truncatedPrefix = compactPrefix.slice( - compactPrefix.length - windowSize / 2, - compactPrefix.length, + const truncatedPrefix = prefix.slice( + prefix.length - windowSize / 2, + prefix.length, ); - const truncatedSuffix = compactSuffix.slice(0, windowSize / 2); + const truncatedSuffix = suffix.slice(0, windowSize / 2); + + // Extra whitespaces should not affect the completions. + // We remove them after truncating the prefix and suffix for efficiency. + const compactPrefix = truncatedPrefix.replace(/\s\s+/g, ' '); + const compactSuffix = truncatedSuffix.replace(/\s\s+/g, ' '); const hash = createHash('sha256') - .update(`${language} ${truncatedPrefix} ${truncatedSuffix} `, 'utf8') + .update(`${compactPrefix} ${compactSuffix} `, 'utf8') .digest('hex'); if (await this.store.has(hash)) { @@ -50,11 +47,7 @@ export class MemoryCacheProxy implements APIClient { return cache; } - const completions = await this.client.fetchCompletions( - language, - prefix, - suffix, - ); + const completions = await this.client.fetchCompletions(prefix, suffix); if (completions === undefined) { return undefined; } diff --git a/src/api/proxies/usage-monitor.ts b/src/api/proxies/usage-monitor.ts index b9b3c6e..de64e1b 100644 --- a/src/api/proxies/usage-monitor.ts +++ b/src/api/proxies/usage-monitor.ts @@ -30,7 +30,7 @@ export class UsageMonitorProxy implements APIClient { yield* this.client.fetchChat(messages); } - async fetchCompletions(language: string, prefix: string, suffix: string) { + async fetchCompletions(prefix: string, suffix: string) { if (this.hasReachedLimit()) { new Notice( 'Monthly usage limit reached. Please increase the limit to keep on using chat view.', @@ -38,6 +38,6 @@ export class UsageMonitorProxy implements APIClient { return; } - return await this.client.fetchCompletions(language, prefix, suffix); + return await this.client.fetchCompletions(prefix, suffix); } } diff --git a/src/editor/extension.ts b/src/editor/extension.ts index b10d683..53043b1 100644 --- a/src/editor/extension.ts +++ b/src/editor/extension.ts @@ -9,7 +9,6 @@ import { completionsStateField } from './state'; import { completionsRenderPlugin } from './view'; export type CompletionsFetcher = ( - language: string, prefix: string, suffix: string, ) => Promise; diff --git a/src/editor/listener.ts b/src/editor/listener.ts index 545a92b..5ab7443 100644 --- a/src/editor/listener.ts +++ b/src/editor/listener.ts @@ -1,12 +1,10 @@ -import { EditorState } from '@codemirror/state'; import { EditorView, ViewUpdate } from '@codemirror/view'; import { Notice } from 'obsidian'; import Markpilot from 'src/main'; import { CompletionsFetcher } from './extension'; -import { LanguageAlias, languagesAliases } from './languages'; import { setCompletionsEffect, unsetCompletionsEffect } from './state'; -function showCompletions(fetcher: CompletionsFetcher, plugin: Markpilot) { +function showCompletions(fetcher: CompletionsFetcher) { let lastHead = -1; let latestCompletionsId = 0; @@ -41,14 +39,13 @@ function showCompletions(fetcher: CompletionsFetcher, plugin: Markpilot) { const currentCompletionsId = ++latestCompletionsId; // Get the completions context with code blocks taken into account. - const { language, prefix, suffix } = getCompletionsContext(state, plugin); + const prefix = state.sliceDoc(0, head); + const suffix = state.sliceDoc(head, length); // Fetch completions from the server. - const completions = await fetcher(language, prefix, suffix).catch( - (error) => { - new Notice('Failed to fetch completions: ', error); - return undefined; - }, - ); + const completions = await fetcher(prefix, suffix).catch((error) => { + new Notice('Failed to fetch completions: ', error); + return undefined; + }); // if fetch has failed, ignore and return. if (completions === undefined) { return; @@ -65,74 +62,7 @@ function showCompletions(fetcher: CompletionsFetcher, plugin: Markpilot) { }; } -// NOTE: -// This is a bare-bone implementation -// because I was unable to find a parser that outputs an AST -// with the information indicating where each node spans. -function getCompletionsContext(state: EditorState, plugin: Markpilot) { - const head = state.selection.main.head; - const length = state.doc.length; - const prefix = state.sliceDoc(0, head); - const suffix = state.sliceDoc(head, length); - - const windowSize = plugin.settings.completions.windowSize; - const context = { - language: 'markdown', - prefix: prefix.slice(prefix.length - windowSize / 2, prefix.length), - suffix: suffix.slice(0, windowSize / 2), - }; - - // Pattern for the code block delimiter e.g. ```python or ``` - let pattern; - - let prefixChars = 0; - const prefixLines = prefix.split('\n').reverse(); - - for (const [i, line] of prefixLines.entries()) { - // Check if the line starts with a code block pattern. - const parts = /^(```|````|~~~|~~~~)/.exec(line); - if (parts !== null) { - pattern = parts[1]; - - // Check if the line ends with a language identifier. - const language = line.slice(pattern.length).trim(); - if (language === '') { - // Return default context as closing code block pattern is detected. - return context; - } else { - // Otherwise update the context with the language and prefix. - context.language = - languagesAliases[language as LanguageAlias] || language.toLowerCase(); - context.prefix = prefix.slice( - prefix.length - prefixChars, - prefix.length, - ); - break; - } - } else if (i === prefixLines.length - 1) { - // Return default context as no code block pattern is detected. - return context; - } - prefixChars += line.length + 1; - } - - let suffixChars = 0; - const suffixLines = suffix.split('\n'); - - for (const line of suffixLines) { - // Check if the line ends with the code block pattern detected above. - const parts = new RegExp(`^${pattern}\\s*$`).exec(line); - if (parts !== null) { - context.suffix = suffix.slice(0, suffixChars); - break; - } - suffixChars += line.length + 1; - } - - return context; -} - export const showCompletionsOnUpdate = ( fetcher: CompletionsFetcher, plugin: Markpilot, -) => EditorView.updateListener.of(showCompletions(fetcher, plugin)); +) => EditorView.updateListener.of(showCompletions(fetcher));