cursor_open.nvim - Neovimから Cursor を起動するプラグインの紹介

に公開

cursor_open.nvim - Neovimから Cursor を起動するプラグインの紹介

はじめに

Neovimを開発でもよく使っているのですが、最近はCursorがかなり便利でよく使っています。
コーディングやドキュメント編集はメインのNeovimで行いつつ、CursorのAI機能周りを併用したいと言う気持ちでneovimからcursorを開けるコマンドを作成しました。

cursor_open.nvim

https://github.com/yuucu/cursor_open.nvim

cursor_open.nvim というプラグインを作成しました。同じ悩みを持つvimmerの方などに使ってもらえると嬉しいです(;p;)!

主な機能

  • 現在編集中のファイルをCursorで開く
  • Gitリポジトリを検出して、ルートとして開く機能
  • 既存のCursorウィンドウを再利用するか新規ウィンドウで開くかを選択可能

インストール方法

packer.nvim を使用する場合

use 'yuucu/cursor_open.nvim'

lazy.nvim を使用する場合

{
  'yuucu/cursor_open.nvim',
  cmd = { 'CursorOpen' },
  keys = {
    { '<leader>oc', ':CursorOpen<CR>', desc = '[O]pen in [C]ursor' },
    { '<leader>oC', ':CursorOpen!<CR>', desc = '[O]pen in [C]ursor (new window)' },
  },
  config = function()
    require('cursor_open').setup()
  end
}

実際の風景

実装したもの

  • 現在編集中のファイルをCursorで開くコマンド
  • Gitリポジトリのルートを検出し、適切なワークスペースで開く機能
  • 既存のCursorウィンドウを再利用するオプション
  • 新しいウィンドウで強制的に開くオプション

実装

以下が実装したプラグインのコードです。

local M = {}

-- デフォルト設定
local defaults = {
    keymaps = {
        open = '<leader>oc',
        open_new = '<leader>oC',
    },
}

-- 設定
local config = {}

-- Git リポジトリルートを取得する関数
local function get_git_root(path)
    local result = vim.fn.system('git -C ' .. vim.fn.shellescape(path) .. ' rev-parse --show-toplevel 2>/dev/null')
    if vim.v.shell_error ~= 0 then
        return nil
    end
    return result:gsub('\n', '')
end

-- 現在のファイルを Cursor で開く
local function open_in_cursor(opts)
    opts = opts or {}
    local force_new = opts.bang or false

    local file_path = vim.fn.expand('%:p')
    local line_num = vim.fn.line('.')
    local col_num = vim.fn.col('.')

    -- Git リポジトリルートを取得(可能な場合)
    local git_root = get_git_root(vim.fn.expand('%:p:h'))

    local cursor_args = {}

    -- フラグの設定
    if force_new then
        table.insert(cursor_args, '-n') -- 新しいウィンドウを強制
    elseif git_root then
        -- 強制フラグがない場合は既存のワークスペースを再利用
        table.insert(cursor_args, '-r')
    end

    -- ワークスペースとファイルの設定
    if git_root then
        table.insert(cursor_args, git_root)
    end

    -- ファイル位置の指定
    table.insert(cursor_args, '-g')
    table.insert(cursor_args, string.format('%s:%s:%s', file_path, line_num, col_num))

    -- コマンドの実行
    local cmd = 'cursor ' .. table.concat(cursor_args, ' ')
    local job_id = vim.fn.jobstart(cmd, { detach = true })

    if job_id <= 0 then
        vim.notify('Cursorの起動に失敗しました', vim.log.levels.ERROR)
    end
end

-- プラグインの初期化
function M.setup(opts)
    -- デフォルト設定とユーザー設定のマージ
    config = vim.tbl_deep_extend('force', defaults, opts or {})

    -- CursorOpen コマンドの作成
    vim.api.nvim_create_user_command('CursorOpen', function(cmd_opts)
        open_in_cursor({ bang = cmd_opts.bang })
    end, { bang = true })

    -- キーマッピングの設定
    if config.keymaps.open then
        vim.keymap.set('n', config.keymaps.open, ':CursorOpen<CR>',
            { desc = '[O]pen in [C]ursor', noremap = true, silent = true })
    end

    if config.keymaps.open_new then
        vim.keymap.set('n', config.keymaps.open_new, ':CursorOpen!<CR>',
            { desc = '[O]pen in [C]ursor (new window)', noremap = true, silent = true })
    end
end

return M

実装の解説

Gitリポジトリの検出

local function get_git_root(path)
    local result = vim.fn.system('git -C ' .. vim.fn.shellescape(path) .. ' rev-parse --show-toplevel 2>/dev/null')
    if vim.v.shell_error ~= 0 then
        return nil
    end
    return result:gsub('\n', '')
end

この関数は指定されたパスに対してGitリポジトリのルートディレクトリを検出します。リポジトリが見つからない場合はnilを返します。

Cursorを開く機能

open_in_cursor 関数は、Neovimで開いているファイルをCursorで開くための中核機能です。いくつかの部分に分けて見ていきます。

1. カーソル位置情報の取得

local function open_in_cursor(opts)
    opts = opts or {}
    local force_new = opts.bang or false

    local file_path = vim.fn.expand('%:p')  -- 現在のファイルの絶対パス
    local line_num = vim.fn.line('.')       -- 現在のカーソル行
    local col_num = vim.fn.col('.')         -- 現在のカーソル列

    -- Git リポジトリルートを取得
    local git_root = get_git_root(vim.fn.expand('%:p:h'))
    local cursor_args = {}

現在編集中のファイルのパスとカーソル位置を取得します。また、そのファイルが存在するディレクトリのGitリポジトリルートも検出します。

2. コマンド引数の構築

    -- ウィンドウモードの設定
    if force_new then
        table.insert(cursor_args, '-n')  -- 新しいウィンドウを強制
    elseif git_root then
        table.insert(cursor_args, '-r')  -- 既存ワークスペースを再利用
    end

    -- ワークスペースとファイル位置の指定
    if git_root then
        table.insert(cursor_args, git_root)  -- Gitリポジトリをワークスペースとして指定
    end
    
    table.insert(cursor_args, '-g')
    table.insert(cursor_args, string.format('%s:%s:%s', file_path, line_num, col_num))

ここでは、コマンドライン引数を条件に応じて構築しています。force_newフラグが設定されていれば新しいウィンドウを開き、Gitリポジトリが見つかった場合はそのルートパスをワークスペースとして指定します。そして、ファイルパスと行・列の位置を指定します。

3. Cursorの起動

    -- 非同期でCursorを起動
    local cmd = 'cursor ' .. table.concat(cursor_args, ' ')
    local job_id = vim.fn.jobstart(cmd, { detach = true })

    if job_id <= 0 then
        vim.notify('Cursorの起動に失敗しました', vim.log.levels.ERROR)
    end
end

最後に、構築したコマンドを vim.fn.jobstart() を使って非同期に実行します。detach = true オプションにより、NeovimはCursorの終了を待たずに操作を続けることができます。

仕組み

この機能は以下のように動作します:

  1. 現在編集中のファイルのパスとカーソル位置を取得
  2. ファイルが属するGitリポジトリのルートを検出
  3. コマンドのオプションに応じて適切なCursorの起動引数を構築
  4. vim.fn.jobstart()を使って非同期にCursorを起動

Cursorが起動すると、指定されたファイルが開かれ、Neovimでのカーソルと同じ位置にカーソルが移動します。

まとめ

この実装により、Neovimとの快適な連携が可能になりました...!
neovimを使いつつ、cursorを連携することで開発がより豊かになると思います!

ぜひプラグインをよろしくお願いします。

https://github.com/yuucu/cursor_open.nvim

Discussion