-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
111 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,40 @@ | ||
// ============================================================================= | ||
// File : main.ts | ||
// Author : yukimemi | ||
// Last Change : 2024/01/07 23:32:31. | ||
// Last Change : 2024/01/08 02:00:26. | ||
// ============================================================================= | ||
|
||
import xdg from "https://deno.land/x/[email protected]/src/mod.deno.ts"; | ||
import * as vars from "https://deno.land/x/[email protected]/variable/mod.ts"; | ||
import * as helper from "https://deno.land/x/[email protected]/helper/mod.ts"; | ||
import * as fn from "https://deno.land/x/[email protected]/function/mod.ts"; | ||
import { ensure, is } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import * as batch from "https://deno.land/x/[email protected]/batch/mod.ts"; | ||
import * as buffer from "https://deno.land/x/[email protected]/buffer/mod.ts"; | ||
import * as lambda from "https://deno.land/x/[email protected]/lambda/mod.ts"; | ||
import * as option from "https://deno.land/x/[email protected]/option/mod.ts"; | ||
import * as datetime from "https://deno.land/[email protected]/datetime/mod.ts"; | ||
import * as fn from "https://deno.land/x/[email protected]/function/mod.ts"; | ||
import * as helper from "https://deno.land/x/[email protected]/helper/mod.ts"; | ||
import * as option from "https://deno.land/x/[email protected]/option/mod.ts"; | ||
import * as autocmd from "https://deno.land/x/[email protected]/autocmd/mod.ts"; | ||
import * as vars from "https://deno.land/x/[email protected]/variable/mod.ts"; | ||
import type { Denops } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import xdg from "https://deno.land/x/[email protected]/src/mod.deno.ts"; | ||
import { assert, is } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { ensureFile } from "https://deno.land/[email protected]/fs/mod.ts"; | ||
import { getLogger, handlers, setup } from "https://deno.land/[email protected]/log/mod.ts"; | ||
import * as batch from "https://deno.land/x/[email protected]/batch/mod.ts"; | ||
import { join } from "https://deno.land/[email protected]/path/mod.ts"; | ||
import { ensureFile } from "https://deno.land/[email protected]/fs/mod.ts"; | ||
import type { Denops } from "https://deno.land/x/[email protected]/mod.ts"; | ||
|
||
import { Futago } from "./futago.ts"; | ||
|
||
let debug = false; | ||
const buffers = new Map<number, { | ||
buf: buffer.OpenResult; | ||
lines: string[]; | ||
}>(); | ||
|
||
function getNow(): string { | ||
return datetime.format(new Date(), "yyyy-MM-ddTHH-mm-ss.SSS"); | ||
} | ||
|
||
async function getLastLineNumber(denops: Denops, bufnr: number): Promise<number> { | ||
const lines = await fn.getbufline(denops, bufnr, 1, "$"); | ||
return lines.length; | ||
} | ||
|
||
export async function main(denops: Denops): Promise<void> { | ||
debug = await vars.g.get(denops, "futago_debug", false); | ||
|
@@ -58,40 +71,91 @@ export async function main(denops: Denops): Promise<void> { | |
async startChat(): Promise<void> { | ||
futago.startChat(); | ||
|
||
const now = datetime.format(new Date(), "yyyy-MM-ddTHH-mm-ss.SSS"); | ||
const now = getNow(); | ||
const bufname = `futago://chat/${now}`; | ||
const buf = await buffer.open(denops, bufname); | ||
const bufnr = buf.bufnr; | ||
const buf = await buffer.open(denops, bufname, { opener: "tabnew" }); | ||
|
||
await batch.batch(denops, async () => { | ||
await option.filetype.setBuffer( | ||
await option.filetype.setBuffer(denops, buf.bufnr, "markdown"); | ||
await option.buftype.setBuffer(denops, buf.bufnr, "acwrite"); | ||
await option.buflisted.setBuffer(denops, buf.bufnr, true); | ||
await option.swapfile.setBuffer(denops, buf.bufnr, false); | ||
await option.wrap.setBuffer(denops, buf.bufnr, true); | ||
await option.modified.setBuffer(denops, buf.bufnr, false); | ||
await buffer.replace(denops, buf.bufnr, [`You: ${now}`, `-------------`, ``]); | ||
}); | ||
|
||
await denops.cmd("normal! G"); | ||
await denops.cmd("startinsert"); | ||
|
||
buffers.set(buf.bufnr, { buf, lines: await fn.getbufline(denops, buf.bufnr, 1, "$") }); | ||
}, | ||
|
||
async sendChatMessage(bufnr: unknown): Promise<void> { | ||
assert(bufnr, is.Number); | ||
try { | ||
const bufInfo = buffers.get(bufnr); | ||
if (bufInfo == undefined) { | ||
throw new Error(`Buffer not found: ${bufnr}`); | ||
} | ||
const lines = await fn.getbufline(denops, bufInfo.buf.bufnr, 1, "$"); | ||
if (lines === bufInfo.lines) { | ||
return; | ||
} | ||
const startLineIndex = lines.findLastIndex((_, index, obj) => | ||
obj[index].startsWith("You:") && obj[index + 1] === "-------------" | ||
) + 2; | ||
|
||
const prompt = lines.slice(startLineIndex); | ||
logger.debug(`You: ${getNow()}}`); | ||
logger.debug(prompt.join("\n")); | ||
logger.debug(`-------------`); | ||
|
||
if ( | ||
prompt.every((line) => | ||
line.trim() === "" | ||
) | ||
) { | ||
return; | ||
} | ||
|
||
const result = await futago.sendMessageStream(prompt.join("\n")); | ||
await fn.appendbufline( | ||
denops, | ||
bufnr, | ||
"futago.chat", | ||
bufInfo.buf.bufnr, | ||
await getLastLineNumber(denops, bufInfo.buf.bufnr), | ||
["", `Gemini: ${getNow()}`, "-------------", ""], | ||
); | ||
await option.buftype.setBuffer(denops, bufnr, "prompt"); | ||
await option.buflisted.setBuffer(denops, bufnr, true); | ||
await option.swapfile.setBuffer(denops, bufnr, false); | ||
await fn.bufload(denops, bufnr); | ||
await fn.prompt_setprompt(denops, bufnr, `futago >> `); | ||
await denops.cmd( | ||
"call prompt_setcallback(bufnr, function('futago#internal#callback_helper', [denops_name, bufnr, lambda_id]))", | ||
{ | ||
bufnr, | ||
denops_name: denops.name, | ||
lambda_id: lambda.register( | ||
denops, | ||
async (p) => { | ||
const prompt = ensure(p, is.String); | ||
await promptCallback(denops, futago, bufnr, prompt); | ||
}, | ||
), | ||
}, | ||
for await (const chunk of result.stream) { | ||
const chunkText = chunk.text(); | ||
logger.debug(chunkText); | ||
let insertLineNum = await getLastLineNumber(denops, bufInfo.buf.bufnr); | ||
const lines = chunkText.split(/\r?\n/); | ||
const lastLine = await fn.getbufline(denops, bufInfo.buf.bufnr, insertLineNum); | ||
await fn.setbufline(denops, bufInfo.buf.bufnr, insertLineNum++, [ | ||
lastLine + lines[0], | ||
...lines.slice(1), | ||
]); | ||
} | ||
await fn.appendbufline( | ||
denops, | ||
bufInfo.buf.bufnr, | ||
await getLastLineNumber(denops, bufInfo.buf.bufnr), | ||
["", `You: ${getNow()}`, "-------------", ""], | ||
); | ||
await helper.execute(denops, `tabnew ${bufname}`); | ||
await helper.execute(denops, "setlocal wrap"); | ||
await helper.execute(denops, "startinsert"); | ||
}); | ||
|
||
buffers.set(bufInfo.buf.bufnr, { | ||
buf: bufInfo.buf, | ||
lines: await fn.getbufline(denops, bufInfo.buf.bufnr, 1, "$"), | ||
}); | ||
|
||
await denops.cmd(`redraw!`); | ||
await denops.cmd("normal! G"); | ||
} catch (e) { | ||
logger.error(e); | ||
} finally { | ||
await option.modified.setBuffer(denops, bufnr, false); | ||
} | ||
}, | ||
}; | ||
|
||
|
@@ -104,48 +168,13 @@ export async function main(denops: Denops): Promise<void> { | |
command! FutagoStart call s:${denops.name}_notify('startChat', []) | ||
`, | ||
); | ||
} | ||
|
||
async function promptCallback( | ||
denops: Denops, | ||
futago: Futago, | ||
bufnr: number, | ||
prompt: string, | ||
) { | ||
const logger = debug ? getLogger("futago-debug") : getLogger("futago"); | ||
try { | ||
logger.debug(`# user:`); | ||
logger.debug(prompt); | ||
logger.debug(`-------------`); | ||
|
||
if (prompt === "exit" || prompt === "quit") { | ||
await helper.execute(denops, `bunload! ${bufnr}`); | ||
return; | ||
} | ||
|
||
const result = await futago.sendMessageStream(prompt); | ||
logger.debug(`# futago:`); | ||
const info = await fn.getbufinfo(denops, bufnr); | ||
let insertLineNum = info[0].linecount - 1; | ||
await fn.appendbufline(denops, bufnr, insertLineNum++, "------------------------------"); | ||
await fn.appendbufline(denops, bufnr, insertLineNum++, ""); | ||
|
||
for await (const chunk of result.stream) { | ||
const chunkText = chunk.text(); | ||
logger.debug(chunkText); | ||
const info = await fn.getbufinfo(denops, bufnr); | ||
let insertLineNum = info[0].linecount - 1; | ||
const lines = chunkText.split(/\r?\n/); | ||
const lastLine = await fn.getbufline(denops, bufnr, insertLineNum); | ||
await fn.setbufline(denops, bufnr, insertLineNum++, lastLine + lines[0]); | ||
if (lines.length > 0) { | ||
await fn.appendbufline(denops, bufnr, insertLineNum, lines.slice(1)); | ||
} | ||
} | ||
} catch (e) { | ||
logger.error(e); | ||
} finally { | ||
await option.buftype.setBuffer(denops, bufnr, "prompt"); | ||
await fn.setbufvar(denops, bufnr, "&modified", 0); | ||
} | ||
await autocmd.group(denops, "futago_chat_buffer", (helper) => { | ||
helper.remove("*"); | ||
helper.define( | ||
"BufWriteCmd", | ||
"futago://chat/*", | ||
`call denops#notify("${denops.name}", "sendChatMessage", [bufnr()])`, | ||
); | ||
}); | ||
} |