gopls 加入互動式重構,LSP 提案讓語言伺服器能向使用者提問
Go Blog(Hongxiang Jiang) · 2026-10-16
gopls 的重構不再只能靠游標位置猜使用者想做什麼:它現在可以在計算編輯內容之前,先彈出對話框詢問參數。這是 Go 團隊與 Dart 團隊合作、向 Language Server Protocol(LSP)提出的新能力(microsoft/language-server-protocol#1164),目前已在 gopls v0.24.0 與 Go 官方 VS Code 擴充套件 v0.56.0 可用。
原本的問題
LSP 裡幾乎所有程式碼轉換都像沒有參數的函式:編輯器一觸發,伺服器就必須立刻算出編輯。唯一的例外是重新命名,因為它需要一個參數(新名稱),協定才為它另開了 PrepareRename RPC。
伺服器只能從游標位置與外圍語法樹推測意圖。推測不出來時只有兩條路:套用一體適用的預設值,或乾脆不提供這個重構。文章明確指出,限制不在 gopls 算不出編輯,而在 LSP 沒有讓伺服器向使用者要資料的機制。
例如「Add struct tags」過去一律產生 camelCase 的 json tag;要 yaml、db 或 snake_case 就得自己改。有些語言擴充套件的作法是把對話框邏輯寫死在單一編輯器裡,但這會把只遵守標準 LSP、用戶端程式碼很少的 Neovim、Emacs 擋在外面,也違背 LSP 讓語言功能與編輯器解耦的初衷。
核心改動
提案建立一個標準機制,讓語言伺服器在計算文字編輯前先收集使用者輸入。文章提到設計是無狀態的表單協定,並且把互動放在 LSP 的 command 層,所以不限於程式碼編輯。完整規格與否決過的替代方案寫在 LSP Interactive Code Actions 設計文件裡,本文未展開。
以 Add struct tags 為例,流程變成:
- 游標放在 struct 上,選「Add struct tags」。
- 第一個對話框預填
json,可改成json,yaml,db等組合。 - 第二個對話框選命名慣例:camelCase、snake_case 或 PascalCase。
- gopls 套用結果。
| 舊行為 | 新行為(選 json,db 與 snake_case) |
|---|---|
| |
「Remove struct tags」也改為詢問要刪哪些 key,例如只刪 db 而保留 json,不再整批清掉。
實作與進度
「Add methods to X to implement an interface…」自 gopls v0.23.0 起可用:輸入欄位會即時向 gopls 查詢,補全工作區與標準函式庫的型別名稱(文章舉 maphash.Hasher)。若介面有型別參數,會再逐一詢問具體型別,然後產生帶正確 receiver 名稱與實例化簽名的方法 stub,取代過去複製介面再手改,或用外部工具 impl 的做法。
「Move declaration」目前是預覽,文章說排在之後的 gopls 版本。它會跳出檔案選擇器,可選既有的 .go 檔或新路徑,並把宣告連同只有它使用的未匯出輔助宣告一起搬走,同時更新工作區內的引用並整理兩邊的 import。範例中型別 Foo、它的方法 Print 與只被它用到的常數 bar 一起從 package from 搬到 to,無關的 Baz 留在原處。
另外 v0.24.0 附了一個彩蛋:游標放在名為 dragonSlayer 的套件層級變數上,會出現「Slay the dragon」code action。它的用途是給編輯器實作者一個多步驟對話框的自足測試案例。
影響範圍
- Go 開發者:升級到 gopls
v0.24.0與 vscode-gov0.56.0才會看到對話框;舊版的 Add struct tags 仍是固定 camelCasejson。 - Neovim、Emacs 使用者:文章說已有概念驗證的用戶端整合,但不是正式發行功能。要用得上,編輯器端必須實作這套表單協定。
- LSP 用戶端與其他語言伺服器作者:這是仍在討論的提案,不是已定案的規格。要依賴它,應先追蹤 #1164 的設計文件。
文章列出的後續方向包含 Change Signature(golang/go#38028,需要新參數順序與預設引數運算式)、命名衝突時詢問使用者,以及測試與 benchmark 的 profiling 旗標設定。Move Declaration 對應 golang/go#70583,Implement Interface 對應 golang/vscode-go#1547,Modify Struct Tags 對應 golang/vscode-go#2002。