---
title: "Interactive refactoring in gopls - Angryecho"
description: "When editing code in a modern IDE or editor, developers rely on its refactoring features to automate common transformations."
canonical_url: "https://www.angryecho.com/c/Goyu_yan/p/P2TBBk101QUasYkRzRA61"
meta:
  "og:description": "When editing code in a modern IDE or editor, developers rely on its refactoring features to automate common transformations."
  "og:image": "https://www.angryecho.com/_og/d/c_Post%2Ctitle_~5ZCQ5qe9572R%2Cdescription_~5ZCQ5qe95LiA5LiL77yM5bCx5LiK5ZCQ5qe9572R44CC%2CpostId_P2TBBk101QUasYkRzRA61%2Crevision_~MjAyNi0xMC0yOFQwMjo1NjowMC4yNzc5NDRa%2Cp_Ii9jL0dveXVfeWFuL3AvUDJUQkJrMTAxUVVhc1lrUnpSQTYxIg%2Cs_ttU3d-0jeISBpbFi.png"
  "og:image:height": "630"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Interactive refactoring in gopls"
---

> Index of every page on this site: [llms.txt](https://www.angryecho.com/llms.txt)

# **Interactive refactoring in gopls**

When editing code in a modern IDE or editor, developers rely on its refactoring features to automate common transformations. Today, we are excited to introduce **interactive refactoring** in [gopls](https://go.dev/gopls), powered by a new capability we are proposing for the [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/) that brings dynamic, multi-step dialogs directly into your favorite editor. Crucially, we designed this capability (in collaboration with the Dart team) not just for Go, but as an open standard for all programming languages and editor ecosystems. Working support is already available in gopls [v0.24.0](https://go.dev/gopls/release/v0.24.0) and the official Go extension for VS Code v0.56.0.

### What is the Language Server Protocol (LSP)?

The LSP is an open standard that decouples language-specific features from specific text editors. As you navigate and edit code, your editor (such as VS Code, Neovim, or Emacs) and a language server (like gopls) engage in a continuous JSON-RPC exchange to provide features like autocomplete, hover documentation, diagnostics, and code actions.

### Multi-step code transformation

Today, almost every code transformation in the LSP is like a function of no parameters: you trigger the operation, and the server immediately computes the edits. (Renaming a symbol is the sole exception: because it is a function of one parameter, the desired new name, it required its own dedicated `PrepareRename` RPC in the protocol.)

![图片](https://go.dev/blog/interactive-refactoring/no-question.png)

*"Add struct tags", without questions*

The language server must deduce the user’s intent from context alone, namely the cursor position and the enclosing syntax tree. When that context is not enough, the server is forced into one of two choices: apply a one-size-fits-all default, or refrain from offering the transformation altogether. The limitation lies not in gopls’s ability to compute the necessary edits, but in the LSP’s lack of a mechanism for language servers to ask the user for input.

For many transformations, this parameterless model works well. But many others need additional input from the user: which struct tags to add and in what naming convention, which interface to implement, which file to move a declaration to. Without a way to ask, gopls could only guess—“Add struct tags”, for example, always produced `json` tags in `camelCase`—or not offer the refactoring at all. With interactive refactoring, language servers can now easily provide new operations with one or more parameters.

![图片](https://go.dev/blog/interactive-refactoring/with-question.png)

*"Add struct tags", with questions*

While some language extensions have worked around this limitation by embedding custom, language-specific dialog logic directly into individual editors, doing so leaves behind other editors (such as Neovim and Emacs) that stick strictly to the LSP with minimal custom client code. More importantly, editor-specific workarounds go against the fundamental purpose of the LSP: to decouple language-specific features from editors so that each server improvement benefits every client.

Our [LSP interactive refactoring proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/76331-lsp-interactive-refactoring.md) ([microsoft/language-server-protocol#1164](https://github.com/microsoft/language-server-protocol/issues/1164)) solves this by establishing a standard mechanism for language servers to collect user input before computing text edits. For Go developers, this unlocks a suite of long-awaited refactorings, including:

- [golang/go#70583](https://go.dev/issue/70583): Move Declaration
- [golang/go#38028](https://go.dev/issue/38028): Change Signature
- [golang/vscode-go#1547](https://github.com/golang/vscode-go/issues/1547): Implement Interface
- [golang/vscode-go#2002](https://github.com/golang/vscode-go/issues/2002): Modify Struct Tags

among many others.

## Interactive refactorings in action

**Adding and removing struct tags (gopls v0.23.0)**

When configuring serialization formats, developers frequently need custom tags and casing conventions. Previously, running “Add struct tags” unconditionally injected `json` tags using `camelCase` formatting.

With interactive refactoring:

1. Place your cursor on any struct definition and select the **“Add struct tags”** code action.
2. An input dialog appears, pre-filled with `json`, allowing you to customize any combination of tags (such as `json,yaml,db`).
3. A second dialog lets you select your desired naming convention (such as `camelCase`, `snake_case`, or `PascalCase`).
4. Gopls generates and applies the formatted struct tags across your type definition.

*Interactive "Add struct tags" in VS Code*

Here is the before-and-after transformation:

```
type UserProfile struct {
ID        int64
FirstName string
LastName  string
EmailAddr string
}
```

```
type UserProfile struct {
ID        int64  `json:"id" db:"id"`
FirstName string `json:"first_name" db:"first_name"`
LastName  string `json:"last_name" db:"last_name"`
EmailAddr string `json:"email_addr" db:"email_addr"`
}
```

Similarly, **“Remove struct tags”** now asks you for the specific tag keys you wish to delete (for example, removing only `db` tags while preserving the `json` tag), rather than wiping out all struct tags indiscriminately.

*Interactive "Remove struct tags" in VS Code*

**Implementing interfaces with dynamic symbol search**

When creating a type to satisfy an interface, developers previously had to copy the interface declaration and manually turn it into a set of methods on the receiver type (or rely on external tools like `impl` by manually typing the receiver and qualifying the name of the interface).

Since gopls v0.23.0, with interactive refactoring:

1. Place your cursor on any concrete type and select the **“Add methods to X to implement an interface…”** code action.
2. A text entry field appears, inviting you to choose the name of an interface type. It makes dynamic queries to gopls to auto-complete names of types in your workspace and the standard library (for example, `maphash.Hasher`).
3. If the selected interface has type parameters, gopls asks for a concrete type argument for each type parameter (such as `Foo`).
4. Gopls generates complete method stubs with appropriate receiver names and instantiated method signatures.

*Interactive "Add methods to implement an interface..." in VS Code*

```
type Foo struct{}

type MyHasher struct{}
```

```
type Foo struct{}

type MyHasher struct{}

// Equal implements [maphash.Hasher].
func (m *MyHasher) Equal(x Foo, y Foo) bool {
panic("unimplemented")
}

// Hash implements [maphash.Hasher].
func (m *MyHasher) Hash(*maphash.Hash, Foo) {
panic("unimplemented")
}
```

**Moving declarations across files and packages (preview)**

Moving declarations manually by cutting and pasting can easily break references and requires tedious import cleanup across multiple files. While scheduled for an upcoming gopls release, our working implementation already demonstrates how interactive file selection streamlines cross-file and cross-package moves:

1. Place your cursor on any top-level declaration and select the **“Move declaration X”** code action.
2. A file picker dialog lets you choose an existing destination `.go` file or specify a new file path.
3. Gopls moves the declaration along with its dependent declarations (such as unexported helpers or methods) while leaving unrelated symbols in place, updates references across your workspace, and cleans up imports in both the source and destination files.

*Interactive "Move declaration" in VS Code*

In the example below, we move the type `Foo` from package `from` to package `to`. Its method `Print` and the unexported constant `bar`, which only `Foo` uses, move along with it, while the unrelated function `Baz` stays behind:

```
// from/from.go
package from

import (
"fmt"
"strings"
)

const bar = "bar"

// Doc comment for Foo
type Foo struct{}

func (f Foo) Print() {
fmt.Println(bar)
}

func Baz() string {
return strings.ToUpper("baz")
}
```

```
// from/from.go
package from

import "strings"

func Baz() string {
return strings.ToUpper("baz")
}

// to/to.go
package to

import "fmt"

const bar = "bar"

// Doc comment for Foo
type Foo struct{}

func (f Foo) Print() {
fmt.Println(bar)
}
```

**Slaying the dragon (gopls v0.24.0)**

As a demonstration of multi-step interactive dialogs and a self-contained test case for client editor implementations, gopls v0.24.0 offers a **“Slay the dragon”** code action when you place your cursor on a package-level variable named `dragonSlayer` (for example, `var dragonSlayer string`). Happy hunting!

（节选）

> 原文： [The Go Blog｜Interactive refactoring in gopls](https://go.dev/blog/interactive-refactoring)，2026-10-16

排序方式 :

### **成为第一条评论！**

Nobody's responded to this post yet. Add your thoughts and get the conversation going.

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://www.angryecho.com/c/Goyu_yan/p/P2TBBk101QUasYkRzRA61#post","@type":"DiscussionForumPosting","commentCount":0,"dateModified":"2026-10-28T02:56:00.277Z","datePublished":"2026-10-16T04:00:00.000Z","description":"When editing code in a modern IDE or editor, developers rely on its refactoring features to automate common transformations.","headline":"Interactive refactoring in gopls","isPartOf":"https://www.angryecho.com/c/Goyu_yan","mainEntityOfPage":"https://www.angryecho.com/c/Goyu_yan/p/P2TBBk101QUasYkRzRA61","text":"When editing code in a modern IDE or editor, developers rely on its refactoring\nfeatures to automate common transformations. Today, we are excited to introduce\ninteractive refactoring in gopls, powered by a new capability we are\nproposing for the Language Server Protocol (LSP)\nthat brings dynamic, multi-step dialogs directly into your favorite editor.\nCrucially, we designed this capability (in collaboration with the Dart team)\nnot just for Go, but as an open standard for all programming languages and\neditor ecosystems. Working support is already available in gopls v0.24.0 and\nthe official Go extension for VS Code v0.56.0.\n\nWhat is the Language Server Protocol (LSP)?\n\nThe LSP is an open standard that decouples language-specific\nfeatures from specific text editors. As you navigate and edit code, your\neditor (such as VS Code, Neovim, or Emacs) and a language server (like gopls)\nengage in a continuous JSON-RPC exchange to provide features like autocomplete,\nhover documentation, diagnostics, and code actions.\n\nMulti-step code transformation\n\nToday, almost every code transformation in the LSP is like a function of no\nparameters: you trigger the operation, and the server immediately computes the\nedits. (Renaming a symbol is the sole exception: because it is a function of one\nparameter, the desired new name, it required its own dedicated PrepareRename\nRPC in the protocol.)\n\n\n\n\"Add struct tags\", without questions\n\nThe language server must deduce the user’s intent from context alone, namely\nthe cursor position and the enclosing syntax tree. When that context is not\nenough, the server is forced into one of two choices: apply a one-size-fits-all\ndefault, or refrain from offering the transformation altogether. The limitation\nlies not in gopls’s ability to compute the necessary edits, but in the\nLSP’s lack of a mechanism for language servers to ask the user for input.\n\nFor many transformations, this parameterless model works well. But many others\nneed additional input from the user: which struct tags to add and in what\nnaming convention, which interface to implement, which file to move a\ndeclaration to. Without a way to ask, gopls could only guess—“Add struct\ntags”, for example, always produced json tags in camelCase—or not offer the\nrefactoring at all. With interactive refactoring, language servers can now\neasily provide new operations with one or more parameters.\n\n\n\n\"Add struct tags\", with questions\n\nWhile some language extensions have worked around this limitation by embedding\ncustom, language-specific dialog logic directly into individual editors, doing\nso leaves behind other editors (such as Neovim and Emacs) that stick strictly\nto the LSP with minimal custom client code. More importantly, editor-specific\nworkarounds go against the fundamental purpose of the LSP: to decouple\nlanguage-specific features from editors so that each server improvement benefits\nevery client.\n\nOur LSP interactive refactoring proposal\n(microsoft/language-server-protocol#1164)\nsolves this by establishing a standard mechanism for\nlanguage servers to collect user input before computing text edits. For Go\ndevelopers, this unlocks a suite of long-awaited refactorings, including:\n\n\n\n\n\ngolang/go#70583: Move Declaration\n\n\n\ngolang/go#38028: Change Signature\n\n\n\ngolang/vscode-go#1547: Implement Interface\n\n\n\ngolang/vscode-go#2002: Modify Struct Tags\n\namong many others.\n\nInteractive refactorings in action\n\nAdding and removing struct tags (gopls v0.23.0)\n\nWhen configuring serialization formats, developers frequently need custom tags\nand casing conventions. Previously, running “Add struct tags” unconditionally\ninjected json tags using camelCase formatting.\n\nWith interactive refactoring:\n\n\n\n\n\nPlace your cursor on any struct definition and select the\n“Add struct tags” code action.\n\n\n\nAn input dialog appears, pre-filled with json, allowing you to customize\nany combination of tags (such as json,yaml,db).\n\n\n\nA second dialog lets you select your desired naming convention (such as\ncamelCase, snake_case, or PascalCase).\n\n\n\nGopls generates and applies the formatted struct tags across your type\ndefinition.\n\nInteractive \"Add struct tags\" in VS Code\n\nHere is the before-and-after transformation:\n\ntype UserProfile struct {\nID        int64\nFirstName string\nLastName  string\nEmailAddr string\n}\n\ntype UserProfile struct {\nID        int64  `json:\"id\" db:\"id\"`\nFirstName string `json:\"first_name\" db:\"first_name\"`\nLastName  string `json:\"last_name\" db:\"last_name\"`\nEmailAddr string `json:\"email_addr\" db:\"email_addr\"`\n}\n\nSimilarly, “Remove struct tags” now asks you for the specific tag keys\nyou wish to delete (for example, removing only db tags while preserving the json\ntag), rather than wiping out all struct tags indiscriminately.\n\nInteractive \"Remove struct tags\" in VS Code\n\nImplementing interfaces with dynamic symbol search\n\nWhen creating a type to satisfy an interface, developers previously had to copy\nthe interface declaration and manually turn it into a set of methods on the\nreceiver type (or rely on external tools like impl by manually typing the\nreceiver and qualifying the name of the interface).\n\nSince gopls v0.23.0, with interactive refactoring:\n\n\n\n\n\nPlace your cursor on any concrete type and select the\n“Add methods to X to implement an interface…” code action.\n\n\n\nA text entry field appears, inviting you to choose the name of an interface\ntype. It makes dynamic queries to gopls to auto-complete names of types in\nyour workspace and the standard library (for example, maphash.Hasher).\n\n\n\nIf the selected interface has type parameters, gopls asks for a\nconcrete type argument for each type parameter (such as Foo).\n\n\n\nGopls generates complete method stubs with appropriate receiver names and\ninstantiated method signatures.\n\nInteractive \"Add methods to implement an interface...\" in\nVS Code\n\ntype Foo struct{}\n\ntype MyHasher struct{}\n\ntype Foo struct{}\n\ntype MyHasher struct{}\n\n// Equal implements [maphash.Hasher].\nfunc (m *MyHasher) Equal(x Foo, y Foo) bool {\npanic(\"unimplemented\")\n}\n\n// Hash implements [maphash.Hasher].\nfunc (m *MyHasher) Hash(*maphash.Hash, Foo) {\npanic(\"unimplemented\")\n}\n\nMoving declarations across files and packages (preview)\n\nMoving declarations manually by cutting and pasting can easily break references\nand requires tedious import cleanup across multiple files. While scheduled for\nan upcoming gopls release, our working implementation already demonstrates how\ninteractive file selection streamlines cross-file and cross-package moves:\n\n\n\n\n\nPlace your cursor on any top-level declaration and select the\n“Move declaration X” code action.\n\n\n\nA file picker dialog lets you choose an existing destination .go file or\nspecify a new file path.\n\n\n\nGopls moves the declaration along with its dependent declarations (such as\nunexported helpers or methods) while leaving unrelated symbols in place,\nupdates references across your workspace, and cleans up imports in both the\nsource and destination files.\n\nInteractive \"Move declaration\" in VS Code\n\nIn the example below, we move the type Foo from package from to package\nto. Its method Print and the unexported constant bar, which only Foo\nuses, move along with it, while the unrelated function Baz stays behind:\n\n// from/from.go\npackage from\n\nimport (\n\"fmt\"\n\"strings\"\n)\n\nconst bar = \"bar\"\n\n// Doc comment for Foo\ntype Foo struct{}\n\nfunc (f Foo) Print() {\nfmt.Println(bar)\n}\n\nfunc Baz() string {\nreturn strings.ToUpper(\"baz\")\n}\n\n// from/from.go\npackage from\n\nimport \"strings\"\n\nfunc Baz() string {\nreturn strings.ToUpper(\"baz\")\n}\n\n// to/to.go\npackage to\n\nimport \"fmt\"\n\nconst bar = \"bar\"\n\n// Doc comment for Foo\ntype Foo struct{}\n\nfunc (f Foo) Print() {\nfmt.Println(bar)\n}\n\nSlaying the dragon (gopls v0.24.0)\n\nAs a demonstration of multi-step interactive dialogs and a self-contained test\ncase for client editor implementations, gopls v0.24.0 offers a “Slay the\ndragon” code action when you place your cursor on a package-level variable\nnamed dragonSlayer (for example, var dragonSlayer string). Happy hunting!\n\n（节选）\n\n\n\n原文：The Go Blog｜Interactive refactoring in gopls，2026-10-16","url":"https://www.angryecho.com/c/Goyu_yan/p/P2TBBk101QUasYkRzRA61","author":{"@type":"Person","name":"alexzms","url":"https://www.angryecho.com/u/H8Salexzms"},"image":["https://go.dev/blog/interactive-refactoring/no-question.png","https://go.dev/blog/interactive-refactoring/with-question.png"],"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/LikeAction","userInteractionCount":0},{"@type":"InteractionCounter","interactionType":"https://schema.org/DislikeAction","userInteractionCount":0},{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":0},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":0}]}]}
```

## Sitemap

See the full [sitemap](https://www.angryecho.com/sitemap.md) for all pages.
