Closed9
NeovimでDenoのファイル保存時にDeno fmtを走らせる
- 編集中のファイルがDenoのプロジェクトか再起的にディレクトリ走査して確かめる
- coc.nvimで、cocで現状動作しているLSPの状態を取得し、DenoのLSPが動いている場合はフォーマットをかけるようにする
最初は1で考えていたが、2を思いついたので路線変更
これはcoc.nvimの設定でtsserverをOFFにできるから
{
"deno.enable": true,
"deno.lint": false,
"deno.unstable": true,
"deno.config": "deno.json",
"tsserver.enable": false,
"prettier.enable": false
}
返却される値が存在しない場合は、null
。値の定義が配列の場合配列でそのまま返却される。
const result = await vars.g.get<string[]>(denops, pVals);
供養コード
const denoFmtFiles = await getDenoFmtFiles(denops);
const getDenoFmtFiles = async (denops: Denops): Promise<string[]> => {
const result = await vars.g.get<unknown>(denops, pVals.denoRuntimeFiles);
if (isStringArray(result)) {
return result;
} else {
return [];
}
};
export const isStringArray = (value: unknown): value is string[] => {
return Array.isArray(value) && value.every(item => typeof item === 'string');
}
CocのStatus
[
{
"id": "languageserver.golang",
"state": "init",
"languageIds": ["go"]
},
{
"id": "languageserver.ccls",
"state": "init",
"languageIds": ["c", "cc", "cpp", "c++", "objc", "objcpp"]
},
{
"id": "eslint",
"state": "running",
"languageIds": []
},
{
"id": "tsserver",
"state": "init",
"languageIds": ["typescript", "typescriptreact", "typescript.tsx", "typescript.jsx", "javascript", "javascriptreact", "javascript.jsx"]
},
{
"id": "deno",
"state": "starting",
"languageIds": ["json", "jsonc", "markdown", "javascript", "javascriptreact", "typescript", "typescriptreact"]
}
]
これで、denoのLSPの取得状態は取れそう
import { Denops } from "https://deno.land/x/denops_std@v5.0.1/mod.ts";
import * as fn from "https://deno.land/x/denops_std@v5.0.1/function/mod.ts";
type CocActionResult = {
id: string;
state: "init" | "starting" | "runnnig";
}[];
export const main = async (denops: Denops): Promise<void> => {
denops.dispatcher = {
echo: async (): Promise<void> => {
const lspList =
(await fn.call(denops, "CocAction", ["services"])) as CocActionResult;
const isRunningDenoLs = lspList.some((lsp) => lsp.id === "deno");
console.log(`isRunningDenoLs: ${isRunningDenoLs}`);
},
};
};
この機能(deno fmt --check
)いいな。今の処理全部無駄になりそう。
-
echo -n "バッファの内容" | deno fmt -
で結果を取得 - バッファの内容と1で
diff -u
でdiffをとってサードパーティのdiff_parserを利用してdiffパース - diffを元にバッファの書き換え
1の処理で今はまっており、文字列のエスケープ周りがdenoのソースコードとして、deno fmtに食わせるのとbashへのエスケープの2つ考える必要があって悩んでいた。
ここまでのバックアップ
import { Denops } from "https://deno.land/x/denops_std@v5.0.1/mod.ts";
import * as fn from "https://deno.land/x/denops_std@v5.0.1/function/mod.ts";
import * as batch from "https://deno.land/x/denops_std@v5.0.1/batch/mod.ts";
import { execPipedCmd } from "./shell.ts";
type CocActionResult = {
id: string;
state: "init" | "starting" | "runnnig";
}[];
type DiffItem = {
beforeFileName: string;
afterFileName: string;
hunks: {
header: {
beforeStartLine: number;
beforeLines: number;
afterStartLine: number;
afterLines: number;
};
lines: {
text: string;
mark: "nomodified" | "add" | "delete";
}[];
}[];
};
type DiffType = DiffItem[];
export const main = async (denops: Denops): Promise<void> => {
denops.dispatcher = {
fmt: () => {
return bufDenoFmt();
},
};
const bufDenoFmt = async () => {
const lspList =
(await fn.call(denops, "CocAction", ["services"])) as CocActionResult;
const isRunningDenoLs = lspList.some((lsp) => lsp.id === "deno");
console.log("start");
if (!isRunningDenoLs) {
return;
}
const curpos = await fn.getcurpos(denops);
const bufnr = await fn.bufnr(denops, "%");
const currentLines = (await denops.call(
"getbufline",
bufnr,
1,
"$",
)) as string[];
const cc = currentLines.join("\n");
const fc = await execDenoFmt(cc);
if (fc.stderr !== "") {
console.log(`stderr ${fc.stderr}`);
return;
}
const diff = await execDiff(cc, fc.stdout.trim());
if (diff == null || diff.stdout == "" || diff.stderr !== "") {
return;
}
console.log(`diff: ${diff.stdout}`)
const pDiff = parse(diff.stdout) as DiffType;
if (pDiff == null || pDiff.length == 0) {
return;
}
console.log(`pdff: ${JSON.stringify(pDiff)}`)
let promises: Promise<unknown>[] = [];
const edited = {
deleted: 0,
added: 0,
};
for (const hunk of pDiff[0].hunks) {
let lpos = hunk.header.beforeStartLine - edited.deleted + edited.added;
for (let i = 0; i < hunk.header.beforeLines; i++) {
if (hunk.lines[i].mark === "delete") {
console.log(`del :${lpos}`);
promises = [...promises, fn.deletebufline(denops, bufnr, lpos)];
edited.deleted += 1;
} else if (hunk.lines[i].mark === "nomodified") {
lpos += 1;
} else if (hunk.lines[i].mark === "add") {
console.log(`add :${lpos - 1}`);
promises = [
...promises,
fn.appendbufline(denops, bufnr, lpos - 1, hunk.lines[i].text),
];
edited.added += 1;
lpos += 1;
}
}
}
await batch.collect(denops, () => promises);
await fn.setpos(denops, ".", curpos);
};
};
const execDiff = async (a: string, b: string) => {
const diffCmd = [
"diff",
"-u",
`<(echo '${escape(a)}')`,
`<(echo '${escape(b)}')`,
];
return await execPipedCmd(diffCmd.join(" "));
};
const execDenoFmt = async (content: string) => {
const fmtCmd = [
"echo",
"-n",
`'${escape(escape(content))}'`,
"|",
"deno",
"fmt",
"-",
];
Deno.writeTextFile("a", fmtCmd.join(" "));
return await execPipedCmd(fmtCmd.join(" "));
};
const escape = (text: string): string => {
return text.replace(/'/g, "''").replace(/\\/g, "\\\\\\\\");
};
--check
用のparserを自前で書かないといけないんだけどね
そんな難しくなかった
stdinってあんま使わないほうがいい気がした
理由は、エスケープ周り。
できた!
このスクラップは2023/11/07にクローズされました