diff --git a/denops/gin/action/branch_delete.ts b/denops/gin/action/branch_delete.ts index 6d645f80..b3a8100d 100644 --- a/denops/gin/action/branch_delete.ts +++ b/denops/gin/action/branch_delete.ts @@ -4,7 +4,7 @@ import { define, GatherCandidates, Range } from "./core.ts"; export type Candidate = | { kind: "remote"; branch: string; remote: string } - | { kind?: "alias" | "local"; branch: string }; + | { kind?: "alias" | "local"; branch: string; worktree?: string }; export async function init( denops: Denops, @@ -50,11 +50,19 @@ async function doDelete( ]); break; default: - await denops.dispatch("gin", "command", "", [ - "branch", - force ? "-D" : "-d", - x.branch, - ]); + if (x.worktree) { + await denops.dispatch("gin", "command", "", [ + "worktree", + "remove", + x.worktree, + ]); + } else { + await denops.dispatch("gin", "command", "", [ + "branch", + force ? "-D" : "-d", + x.branch, + ]); + } break; } } diff --git a/denops/gin/action/branch_move.ts b/denops/gin/action/branch_move.ts index a4cfccf9..415ade2c 100644 --- a/denops/gin/action/branch_move.ts +++ b/denops/gin/action/branch_move.ts @@ -3,7 +3,7 @@ import * as batch from "jsr:@denops/std@^7.0.0/batch"; import * as helper from "jsr:@denops/std@^7.0.0/helper"; import { define, GatherCandidates, Range } from "./core.ts"; -export type Candidate = { branch: string }; +export type Candidate = { branch: string; worktree?: string }; export async function init( denops: Denops, @@ -40,21 +40,41 @@ async function doMove( if (!x) { return; } - const from = x.branch; - const name = await helper.input(denops, { - prompt: `Rename (from ${from}): `, - text: from, - }); - await denops.cmd('redraw | echo ""'); - if (!name) { - await helper.echoerr(denops, "Cancelled"); - return; + if (x.worktree) { + const from = x.worktree; + const newPath = await helper.input(denops, { + prompt: `New path (from ${from}): `, + text: from, + }); + await denops.cmd('redraw | echo ""'); + if (!newPath) { + await helper.echoerr(denops, "Cancelled"); + return; + } + await denops.dispatch("gin", "command", "", [ + "worktree", + "move", + ...(force ? ["--force"] : []), + from, + newPath, + ]); + } else { + const from = x.branch; + const newName = await helper.input(denops, { + prompt: `Rename (from ${from}): `, + text: from, + }); + await denops.cmd('redraw | echo ""'); + if (!newName) { + await helper.echoerr(denops, "Cancelled"); + return; + } + await denops.dispatch("gin", "command", "", [ + "branch", + ...(force ? ["--force"] : []), + "--move", + from, + newName, + ]); } - await denops.dispatch("gin", "command", "", [ - "branch", - ...(force ? ["--force"] : []), - "--move", - from, - name, - ]); } diff --git a/denops/gin/action/branch_new.ts b/denops/gin/action/branch_new.ts index afc37849..1997d69e 100644 --- a/denops/gin/action/branch_new.ts +++ b/denops/gin/action/branch_new.ts @@ -45,19 +45,19 @@ async function doNew( const xs = await gatherCandidates(denops, bufnr, range); const x = xs.at(0); const from = x?.target ?? "HEAD"; - const name = await helper.input(denops, { + const branchName = await helper.input(denops, { prompt: `New branch (from ${from}): `, text: from, }); await denops.cmd('redraw | echo ""'); - if (!name) { + if (!branchName) { await helper.echoerr(denops, "Cancelled"); return; } await denops.dispatch("gin", "command", "", [ "switch", force ? "-C" : "-c", - name, + branchName, from, ]); } @@ -68,13 +68,17 @@ async function doNewOrphan( _range: Range, _gatherCandidates: GatherCandidates, ): Promise { - const name = await helper.input(denops, { + const branchName = await helper.input(denops, { prompt: "New branch (orphan): ", }); await denops.cmd('redraw | echo ""'); - if (!name) { + if (!branchName) { await helper.echoerr(denops, "Cancelled"); return; } - await denops.dispatch("gin", "command", "", ["switch", "--orphan", name]); + await denops.dispatch("gin", "command", "", [ + "switch", + "--orphan", + branchName, + ]); } diff --git a/denops/gin/action/rebase.ts b/denops/gin/action/rebase.ts index f4d7d39e..619ddf3e 100644 --- a/denops/gin/action/rebase.ts +++ b/denops/gin/action/rebase.ts @@ -18,6 +18,13 @@ export async function init( (denops, bufnr, range) => doRebase(denops, bufnr, range, gatherCandidates), ); + await define( + denops, + bufnr, + "rebase:x", + (denops, bufnr, range) => + doRebase(denops, bufnr, range, gatherCandidates, true), + ); await define( denops, bufnr, @@ -25,6 +32,13 @@ export async function init( (denops, bufnr, range) => doRebaseInteractive(denops, bufnr, range, gatherCandidates), ); + await define( + denops, + bufnr, + "rebase:i:x", + (denops, bufnr, range) => + doRebaseInteractive(denops, bufnr, range, gatherCandidates, true), + ); await define( denops, bufnr, @@ -40,16 +54,26 @@ async function doRebase( bufnr: number, range: Range, gatherCandidates: GatherCandidates, + execute?: boolean, ): Promise { const xs = await gatherCandidates(denops, bufnr, range); const x = xs.at(0); if (!x) { return; } - await denops.dispatch("gin", "command", "", [ - "rebase", - x.commit, - ]); + const args = ["rebase", x.commit]; + if (execute) { + const cmd = await helper.input(denops, { + prompt: "Execute command after rebase: ", + }) ?? ""; + await denops.cmd('redraw | echo ""'); + if (!cmd) { + await helper.echoerr(denops, "Cancelled"); + return; + } + args.push("-x", cmd); + } + await denops.dispatch("gin", "command", "", args); // suppress false-positive detection of file changes await denops.cmd("silent checktime"); @@ -60,20 +84,33 @@ async function doRebaseInteractive( bufnr: number, range: Range, gatherCandidates: GatherCandidates, + execute?: boolean, ): Promise { const xs = await gatherCandidates(denops, bufnr, range); const x = xs.at(0); if (!x) { return; } - // NOTE: - // We must NOT await the command otherwise Vim would freeze - // because command proxy could not work if we await here. - denops.dispatch("gin", "command", "", [ + const args = [ "rebase", "--interactive", x.commit, - ]).catch(async (e) => { + ]; + if (execute) { + const cmd = await helper.input(denops, { + prompt: "Execute command after rebase: ", + }) ?? ""; + await denops.cmd('redraw | echo ""'); + if (!cmd) { + await helper.echoerr(denops, "Cancelled"); + return; + } + args.push("-x", cmd); + } + // NOTE: + // We must NOT await the command otherwise Vim would freeze + // because command proxy could not work if we await here. + denops.dispatch("gin", "command", "", args).catch(async (e) => { await helper.echoerr(denops, e.toString()); }).then( // suppress false-positive detection of file changes diff --git a/denops/gin/action/worktree_new.ts b/denops/gin/action/worktree_new.ts new file mode 100644 index 00000000..ecb58e0c --- /dev/null +++ b/denops/gin/action/worktree_new.ts @@ -0,0 +1,87 @@ +import type { Denops } from "jsr:@denops/std@^7.0.0"; +import * as batch from "jsr:@denops/std@^7.0.0/batch"; +import * as helper from "jsr:@denops/std@^7.0.0/helper"; +import { define, GatherCandidates, Range } from "./core.ts"; + +export type Candidate = { target?: string }; + +export async function init( + denops: Denops, + bufnr: number, + gatherCandidates: GatherCandidates, +): Promise { + await batch.batch(denops, async (denops) => { + await define( + denops, + bufnr, + "worktree", + (denops, bufnr, range) => + doNew(denops, bufnr, range, false, gatherCandidates), + ); + await define( + denops, + bufnr, + "worktree:force", + (denops, bufnr, range) => + doNew(denops, bufnr, range, true, gatherCandidates), + ); + await define( + denops, + bufnr, + "worktree:orphan", + (denops, bufnr, range) => + doNewOrphan(denops, bufnr, range, gatherCandidates), + ); + }); +} + +async function doNew( + denops: Denops, + bufnr: number, + range: Range, + force: boolean, + gatherCandidates: GatherCandidates, +): Promise { + const xs = await gatherCandidates(denops, bufnr, range); + const x = xs.at(0); + const target = x?.target ?? "HEAD"; + const worktreePath = await helper.input(denops, { + prompt: `Worktree path (for ${target}): `, + text: `.worktrees/${target}`, + }); + await denops.cmd('redraw | echo ""'); + if (!worktreePath) { + await helper.echoerr(denops, "Cancelled"); + return; + } + await denops.dispatch("gin", "command", "", [ + "worktree", + "add", + ...(force ? ["-f"] : []), + worktreePath, + target, + ]); +} + +async function doNewOrphan( + denops: Denops, + _bufnr: number, + _range: Range, + _gatherCandidates: GatherCandidates, +): Promise { + const worktreePath = await helper.input(denops, { + prompt: "Worktree path (orphan): ", + text: `.worktrees/orphan`, + }); + await denops.cmd('redraw | echo ""'); + if (!worktreePath) { + await helper.echoerr(denops, "Cancelled"); + return; + } + await denops.dispatch("gin", "command", "", [ + "worktree", + "add", + "--orphan", + worktreePath, + ]); +} diff --git a/denops/gin/command/branch/edit.ts b/denops/gin/command/branch/edit.ts index 81c170f9..db0f9000 100644 --- a/denops/gin/command/branch/edit.ts +++ b/denops/gin/command/branch/edit.ts @@ -19,6 +19,7 @@ import { init as initActionMerge } from "../../action/merge.ts"; import { init as initActionRebase } from "../../action/rebase.ts"; import { init as initActionSwitch } from "../../action/switch.ts"; import { init as initActionYank } from "../../action/yank.ts"; +import { init as initActionWorktreeNew } from "../../action/worktree_new.ts"; import { Branch, parse as parseBranch } from "./parser.ts"; export async function edit( @@ -71,6 +72,7 @@ export async function exec( await initActionBranchDelete(denops, bufnr, gatherCandidates); await initActionBranchMove(denops, bufnr, gatherCandidates); await initActionBranchNew(denops, bufnr, gatherCandidates); + await initActionWorktreeNew(denops, bufnr, gatherCandidates); await initActionBrowse(denops, bufnr, async (denops, bufnr, range) => { const xs = await gatherCandidates(denops, bufnr, range); return xs.map((b) => ({ commit: b.target, ...b }));