core

package
v0.0.0-...-904f9c7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Overview

Package core provides foundational utilities for the Silk UI framework.

Key components:

  • UUID generation and parsing
  • TDoc: tree-structured document format for persistence
  • Factory pattern for dynamic object creation
  • Signal-Slot event binding mechanism
  • Logging (Trace, Debug, Warn, Error)
  • Application lifecycle (EventLoop, Quit, AtExit)
  • File and path utilities

Index

Constants

View Source
const (
	ToolGoVet       = "go vet"
	ToolGoTestRace  = "go test -race"
	ToolGoTestProf  = "go test -profile"
	ToolPprofTop    = "go tool pprof"
	ToolGoToolTrace = "go tool trace"
	ToolGovulncheck = "govulncheck"
	ToolStaticcheck = "staticcheck"
)

Workflow ids. They double as ToolCommand.Tool tags, as Finding.Tool tags, and as the ids the Go Tools picker reports through its run callback, so a run's findings always group under the row that fired it.

View Source
const (
	FindingError   = "error"
	FindingWarning = "warning"
	FindingInfo    = "info"
)

Finding.Severity values. Strings rather than an enum because they come from three unrelated sources (buildissues.Severity.String(), a govulncheck section header, a pprof flat% threshold) and the pane only ever switches a colour on them.

View Source
const (
	DeployLocal = "local" // 产物留在本机 OutputDir
	DeploySSH   = "ssh"   // 产物推到 User@Host:RemoteDir
)

部署方式

View Source
const (
	ConflictMarkerOurs   = "<<<<<<<"
	ConflictMarkerBase   = "|||||||"
	ConflictMarkerSep    = "======="
	ConflictMarkerTheirs = ">>>>>>>"
)

git 冲突标记. 三方合并留下的记号一律是 7 个字符, 后面可跟一个空格和说明文字.

View Source
const Version = "2.5.0"

Version is the silk release version. CI/CD release builds are driven by pushing a matching git tag (see .github/workflows/release.yml).

Variables

View Source
var Default = NewTagDB()

Default is the singleton registry the designer binding resolves against.

View Source
var ErrNotGitRepo = errors.New("not a git repository")

ErrNotGitRepo 是供上层用 errors.Is 区分"非 git 仓库"场景的哨兵错误 IsGitRepo 已能做布尔判定, 这里仅为需要 error 语义的调用方保留一个稳定值.

View Source
var ErrSessionClosed = errors.New("debug session closed")

ErrSessionClosed 是 session 已 Close 之后一切 RPC 的统一返回错误 Close 会先关 conn 把阻塞中的 Decode 唤醒 -- rpcCall 醒来看到 closed 时也回它, 不把底层 "use of closed network connection" 之类的 read 错误抛给调用方. 导出以便上层 (silkide) 用 errors.Is 区分"用户主动 Stop"和真实错误, 前者不弹错误提示.

Functions

func AddFactoryAlias

func AddFactoryAlias(aliasName, realName string)

给软件工厂添加别名, 用aliasName也能生产realName对象的对象 别名本身不允许重复, 也不允许和实名重复 别名的别名不起作用

func AddedLinesByFile

func AddedLinesByFile(files []DiffFile) map[string][]int

AddedLinesByFile 把已解析的 diff 折叠成 "新文件路径 → 新增行号列表" 行号以新文件坐标(1-based) 为准, 顺序与 hunk 内出现顺序一致. 用途: 编辑器侧边栏 "我刚加的行" 标记. 规则:

  • 删除行(只在旧文件) 不计入;
  • 上下文行(同时存在新旧两份) 不计入;
  • 跳过 NewPath 为空的条目(纯删除文件), 它们没有 "新文件" 可挂.

func AppInstallDir

func AppInstallDir() string

程序安装目录 在目前的版本里, 此目录等于程序所在目录 但在编写代码时, 请不要用程序所在目录代替此目录, 因为以后会改变

func AppName

func AppName() string

获取应用程序的名字

func AppShortName

func AppShortName() string

func ApplyPatch

func ApplyPatch(original string, hunks []PatchHunk) (string, error)

ApplyPatch applies hunks, in order, to original and returns the new file content. Every context and deleted line is validated against the original at the line the hunk claims it sits on; the first mismatch aborts with an error naming the line, so a stale patch can never silently corrupt a file. An empty hunk slice returns original unchanged.

Trailing newline: the result keeps the original's terminator unless the patch replaces the final line, in which case the new final line's NoNewline bit decides. That makes Apply/Reverse an exact round-trip across the "\ No newline at end of file" marker.

func ApplyPatchSelected

func ApplyPatchSelected(original string, hunks []PatchHunk, hunkIndices []int) (string, error)

ApplyPatchSelected applies only the hunks at hunkIndices. Because every hunk's coordinates are relative to the original file, skipping a hunk needs no offset fixups — this is what makes per-hunk staging work. Indices may arrive in any order and may repeat; they are de-duplicated and sorted. An out-of-range index is an error (nothing is applied). No indices at all returns original unchanged.

func ApplyTextEdits

func ApplyTextEdits(text string, edits []LSPTextEdit) (string, error)

ApplyTextEdits 把一组 LSP TextEdit 应用到 text 上, 返回编辑后的文本 语义:

  • edits 为空 -> 原样返回 text, 无错误.
  • 每个 edit 的 Range 用 NewText 替换 (空 Range = 插入, 空 NewText = 删除).
  • 所有 edit 的坐标都按 *原始* text 解释; 内部按 start 降序应用以避免偏移漂移.
  • 区间重叠 -> 返回错误, 不产出半成品文本.
  • 行/列越界 -> 钳到文档末尾 (不 panic), 用于容忍 server 给的边界外位置.

不修改调用方传入的 edits 切片 (内部排序的是副本).

func AtExit

func AtExit(fn func())

func BuildFileCoverage

func BuildFileCoverage(blocks []CoverageBlock) map[string]*FileCoverage

BuildFileCoverage 把 block 列表折叠成每文件的逐行覆盖图 同一行被多个 block 覆盖时, "covered wins": 只要任何一个 block 报告 count>0, 该行就是 covered, 不会被 count==0 的 block 翻回去. 一行只有在没有任何 covered=true 的 block 提到它时, 才会被记为 Covered=false.

func CheckIface

func CheckIface(pi *interface{})

此函数检测接口是否良构, 如果接口非nil且含有空值则panic 此函数只在Debug版检测, Release版不检测 注: 在我们的架构里, 空值存放在接口里是非法的,

因为此时接口不为nil, 而指针却为nil, 容易引起混乱.

非法的例子:

func NewInt() interface{} {
   var p *int = nil
   return p // 危险! 把空指针赋值给接口
}

合法的例子:

func NewInt() interface{} {
   var p *int = nil
   if p == nil {
       return nil // 正确
   }
   return p // 正确, 此处不会返回空指针
}

func Close

func Close()

func Connect

func Connect(sigFunc, slotFunc interface{})

连接信号槽, 信号和槽均已确定 Connect(obj1.SigSubmit, obj2.OnObj1Submit) 注: 槽可以省略sender, 除此之外参数应匹配, 不匹配则输出警告, 且连接失败

func Connect1

func Connect1(sender interface{}, signal string, receiver interface{}, slotMethod string)

连接信号槽, 信号和槽均在运行时查询 Connect1(obj1, "SigSubmit", obj2, "OnObj1Submit") 或简写: Connect(obj1, "Submit", obj2, "OnObj1Submit") 注: 槽可以省略sender, 除此之外参数应匹配, 不匹配则输出警告, 且连接失败

func Connect2

func Connect2(sigFunc interface{}, receiver interface{}, slotMethod string)

连接信号槽, 信号已确定, 槽在运行时查询 Connect2(obj1.SigSubmit, obj2, "OnObj1Submit") 注: 槽可以省略sender, 除此之外参数应匹配, 不匹配则输出警告, 且连接失败

func Connect3

func Connect3(sender interface{}, signal string, slotFunc interface{})

连接信号槽, 信号在运行时查询, 槽已确定 Connect3(obj1, "SigSubmit", obj2.OnObj1Submit) 或简写: Connect2(obj1, "Submit", obj2.OnObj1Submit) 注: 槽可以省略sender, 除此之外参数应匹配, 不匹配则输出警告, 且连接失败

func CopyFile

func CopyFile(dst, src string) (err error)

func CoveragePercent

func CoveragePercent(fc *FileCoverage) float64

CoveragePercent 返回 BlocksCovered / BlocksTotal * 100 fc==nil 或 BlocksTotal==0 时返回 0

func DbgToolDir

func DbgToolDir() string

调试工具所在目录 在目前的版本里, 此目录等于ExeFileDir()+"/dbgtool"

func Debug

func Debug(a ...interface{})

输出调试Log, 且在前面添加"debug: "字样 此函数只在打开Debug开关时生效, 输出的Log不带堆栈信息

func DecodeGitQuotedPath

func DecodeGitQuotedPath(s string) string

DecodeGitQuotedPath 还原 git 的 C 风格引号路径(纯函数) 路径含空格、控制字符或(core.quotePath 默认开启时)非 ASCII 字节时, git 会把整段用双引号 包起来并按 C 字符串规则转义: \a \b \f \n \r \t \v \" \\ 以及其余字节写成三位八进制 \ooo. 非 ASCII 就是逐字节 \ooo, 所以先还原成字节再拼回字符串, UTF-8 自然复原 (例: "\346\265\213" → "测"). 不以双引号包裹的输入原样返回 —— 普通路径不需要处理. 未知转义(如 \q)保留反斜杠与字符本身, 结尾孤立的反斜杠也保留: 宁可多一个反斜杠, 也不静默吞掉信息. 永远不 panic.

func DecodeParams

func DecodeParams(m *LSPMessage, into interface{}) error

DecodeParams 把 m.Params 反序列化进 into 指向的对象 便利封装: 等价于 json.Unmarshal(m.Params, into), 但对常见空值更友好 (m == nil 或 Params 为空时不返回错误, into 保持零值)

func DefaultClientCapabilities

func DefaultClientCapabilities() json.RawMessage

DefaultClientCapabilities 返回 Initialize 在 Capabilities 缺省时用的能力集 返回的是一份拷贝: 调用方可以在它上面二次加工 (比如塞进自己的 initialize params) 而不影响后续调用.

func DesktopDir

func DesktopDir() string

func DocumentsDir

func DocumentsDir() string

func Error

func Error(a ...interface{})

输出错误Log, 且在前面添加"error: "字样 此函数只输出Log, 不退出程序也不触发panic

func EventLoop

func EventLoop()

运行事件循环

func ExeFile

func ExeFile() string

func ExeFileBaseName

func ExeFileBaseName(withExtension bool) string

func ExeFileDir

func ExeFileDir() string

func FactoryNameOf

func FactoryNameOf(i interface{}) string

func FindGoMod

func FindGoMod(startDir string) (string, bool)

FindGoMod 从startDir向上查找go.mod 找到则返回其绝对路径和true, 否则返回""和false

func FindGoWork

func FindGoWork(startDir string) (string, bool)

FindGoWork 从startDir向上查找go.work 找到则返回其绝对路径和true, 否则返回""和false

func FormatBuildTags

func FormatBuildTags(tags []string) string

FormatBuildTags 按 `go build -tags` 的形式把列表拼回一行

func GitAvailable

func GitAvailable() bool

GitAvailable 报告 PATH 上是否能找到 git 可执行文件

func GitCommitChanges

func GitCommitChanges(dir, message string) (string, error)

GitCommitChanges 用给定信息提交(`git commit -m <message>`)并返回新提交的短 hash (命名避开同名的 GitCommit 结构体 —— 那是 git log 的提交摘要类型). message 去空白后为空时直接返回 error 快速失败(git 本身也会拒绝空信息, 这里提前挡掉). 若暂存区为空, git commit 以 "nothing to commit" 非零退出, runGit 会把它转成带命令 上下文的 error 原样返回(与 GitRevParse 遇未知 ref 同一套路), 不 panic. 提交成功后再跑 `git rev-parse --short HEAD` 取回短 hash.

func GitCommitSubjectBody

func GitCommitSubjectBody(dir, rev string) (string, string, error)

GitCommitSubjectBody 返回某次提交的标题和正文(`git show -s --format=%s%n%n%b <rev>`) -s 抑制 diff 只出提交信息; %s 是标题(subject), %b 是正文(body), 中间用一个空行 (%n%n)分隔. 供在 GitShowCommit 的 diff 之上渲染一个提交头. 返回 (subject, body): 按第一个换行切出 subject, 其余去掉分隔空行后为 body; 仅有标题的提交 body 为 "". rev 空 → error; 未知 rev → runGit 的 error, 永远不 panic.

func GitCurrentBranch

func GitCurrentBranch(dir string) (string, error)

GitCurrentBranch 返回当前分支名(`git rev-parse --abbrev-ref HEAD`) 处于 detached HEAD 时 git 输出 "HEAD", 这里原样透传.

func GitDiffFile

func GitDiffFile(dir, file string) (string, error)

GitDiffFile 返回单个文件相对 HEAD 的 unified diff(`git diff HEAD -- <file>`) 文件无改动时返回 ("", nil).

func GitDiffHead

func GitDiffHead(dir string) (string, error)

GitDiffHead 返回 dir 工作树相对 HEAD 的完整 unified diff(`git diff HEAD`) 无改动时返回 ("", nil). 输出文本可直接喂给 ParseUnifiedDiff.

func GitHasStagedChanges

func GitHasStagedChanges(dir string) (bool, error)

GitHasStagedChanges 报告 index 相对 HEAD 是否有已暂存改动 走 `git diff --cached --quiet`: 退出码 0 表示无暂存改动, 退出码 1 表示有 —— 这个 1 是"存在 diff"的信号而非报错. 因此这里把退出码 1 映射成 (true, nil), 退出码 0 映射成 (false, nil); 只有其它退出码(如非仓库的 128)或超时才当真正的 error 返回. 供 UI 据此决定 Commit 按钮是否可点. 不 panic.

func GitRevParse

func GitRevParse(dir, ref string) (string, error)

GitRevParse 把一个 ref(如 "HEAD"、分支名)解析为完整 SHA(`git rev-parse <ref>`) 返回去掉首尾空白的 40-hex SHA. 未知 ref 时 git 非零退出, 返回带诊断的 error.

func GitShow

func GitShow(dir, rev, file string) (string, error)

GitShow 返回文件在某个修订版本下的内容(`git show <rev>:<file>`) 用于把工作副本与任意提交对比, 或展示"原始"内容. rev 可为 "HEAD"、分支名、SHA 等. 空文件返回 ("", nil); rev 或路径非法时 git 非零退出, 原样返回带诊断的 error.

func GitShowCommit

func GitShowCommit(dir, rev string) (string, error)

GitShowCommit 返回某次提交的完整 unified diff(`git show --no-color <rev>`) 供 IDE 的 Git History 面板在点击某条提交时展示该提交的全部改动 —— 注意与逐文件的 GitShow(dir, rev, file) 不同: 那个取的是单文件在某版本下的内容, 这里给的是"整个 提交"的差异. 用 --no-color 强制机器可读的纯文本输出(不受用户 color.ui=always 之类 配置影响, 否则 ANSI 转义会污染 +/- 前缀). 输出前半是提交头(commit/Author/Date/信息), 后半是标准 unified diff. 调用方可原样展示, 也可把整段输出直接喂给 core.ParseUnifiedDiff —— 后者的 default 分支会跳过提交头噪声, 只从 "diff --git" 起解析文件/hunk(与 GitDiffHead 同一套路). rev 去空白后为空时直接返回 error 快速失败; 未知 rev 时 git 非零退出, runGit 原样返回带诊断的 error, 永远不 panic.

func GitStage

func GitStage(dir string, paths []string) error

GitStage 把给定路径加入暂存区(`git add -- <paths...>`) 只暂存显式给出的路径(用 `--` 与选项分隔, 刻意不用 `git add .`), 供 Git Changes 面板逐文件勾选暂存. paths 为空时直接返回 nil 不调用 git —— 空 pathspec 的 `git add` 会报错, 且"没选任何文件"本就该是 no-op.

func GitStageAll

func GitStageAll(dir string) error

GitStageAll 暂存工作树里的全部改动(`git add -A`: 新增/修改/删除/未跟踪都进 index) 这是本封装里唯一的批量暂存形式 —— 需要"全部暂存"用它, 精确逐文件暂存用 GitStage.

func GitUnstage

func GitUnstage(dir string, paths []string) error

GitUnstage 把给定路径移出暂存区但保留工作树改动(`git reset HEAD -- <paths...>`) 与 GitStage 相反: 只把 index 里这些路径还原成 HEAD 版本, 不动工作副本. paths 为空时直接返回 nil 不调用 git.

func GoToolBinaries

func GoToolBinaries() []string

GoToolBinaries returns the distinct executables the workflows need, in first-use order: "go", "govulncheck", "staticcheck".

func GoToolBinary

func GoToolBinary(id string) string

GoToolBinary returns the executable a workflow needs on PATH: "go" for everything the toolchain ships, the tool's own name for the two third-party analyzers. An unknown id yields "" — treat that as unavailable rather than as "no binary needed".

func GoToolWorkflows

func GoToolWorkflows() []string

GoToolWorkflows returns the workflow ids in picker order. The slice is a copy, so a caller reordering it cannot reorder anyone else's picker.

func HasMainLoop

func HasMainLoop() bool

HasMainLoop reports whether a window backend has registered its event loop.

EventLoop panics without one, and that panic is the whole failure: the Windows backend once had its SetMainLoop call commented out, so every silk program compiled cleanly and then died at startup with "main loop mechanism unavailable". A build can never catch that; a test asserting this is true after importing gui can.

func IsDebugOn

func IsDebugOn() bool

获取调试开关状态 底层和应用层可根据本函数的返回值来决定是否显示调试信息

func IsGitRepo

func IsGitRepo(dir string) bool

IsGitRepo 报告 dir 是否在某个 git 工作树内 通过 `git rev-parse --is-inside-work-tree` 退出码为 0 且输出 "true" 判定.

func IsNil

func IsNil(i interface{}) bool

判断接口本身或接口的值是否为nil 注: 在我们的架构里, 空值存放在接口里是非法的,

因为此时接口不为nil, 而指针却为nil, 容易引起混乱.

此函数和reflect.IsNil()的区别是:

1 此函数也判断接口是否为nil
2 在参数类型不可能为nil时, 此函数不paninc, 而是返回true

func IsNotification

func IsNotification(m *LSPMessage) bool

IsNotification 在 ID 缺失时返回 true 调用方据此决定要不要等响应

func IsValidFileName

func IsValidFileName(s string) bool

检测字符串是否可以直接用作文件基本名 允许含有'.', 不允许含有目录分隔符 不允许有控制字符

func IsValidFileNameRune

func IsValidFileNameRune(r rune) bool

检测指定字符是否可以用在文件名里

func LiveCycleTrace

func LiveCycleTrace(ptr interface{}) (ps *string)

跟踪对象的生存周期 此函数传入对象指针, 返回一个跟踪用的字符串对象 使用方法: 把返回的字符串挂回到对象上, 使得字符串的生存期和对象相同

func LiveObjects

func LiveObjects() (ret []string)

func LocalDataDir

func LocalDataDir() string

本机用户设置目录 用来存放和工区无关用户设置, 例如用户习惯, 窗口位置等 在目前的版本里, 此目录等于ExeFileDir()+"/local" 但在编写代码时, 请不要用(ExeFileDir()+"/local")代替此目录, 因为以后会改变

func Log

func Log(a ...interface{})

输出Log, 相当于log.Print 注, 此函数仅为方便使用, 和log.Printf功能相同

func Logf

func Logf(format string, a ...interface{})

输出Log, 相当于log.Printf 注, 此函数仅为方便使用, 和log.Printf功能相同

func New

func New(name string) interface{}

用指定对象工厂创建对象 p, ok := c.New("MyStruct").(*MyStruct) 参见 Factory.New()

func NewUuidStr

func NewUuidStr() string

func ObjInfo

func ObjInfo(ptr interface{}) string
func report(level int, a ...interface{}) {
	//	logMutex.Lock()
	//	defer logMutex.Unlock()
	if level > logLevel {
		return
	}

	var category string
	if level <= logStackTraceLevel && logStackTraceDepth > 0 {
		var fns []*runtime.Func
		var pcs []uintptr
		var maxFuncLen = 1
		for n := logStackTraceDepth - 1; n >= 0; n-- {
			pc, file, _, ok := runtime.Caller(n + 2)
			if ok && n <= logStackTraceDepth {

				fn := runtime.FuncForPC(pc)
				fns = append(fns, fn)
				pcs = append(pcs, pc)
				funcLen := len(fn.Name())
				if funcLen > maxFuncLen {
					maxFuncLen = funcLen
				}

				if n == 0 {
					category = categoryOf(file)
				}
			}
		}

		spaceString := "                                                    "
		for n := 0; n < len(fns); n++ {
			fn := fns[n]
			name := fn.Name()
			file, line := fn.FileLine(pcs[n])
			funcLen := len(name)
			padding := maxFuncLen - funcLen + 1
			if padding >= len(spaceString) {
				padding = len(spaceString)
			}
			defer log.Printf("%2d %s()%s %s:%d", len(fns)-n-1, name, spaceString[0:padding], shortPath(file), line)
		}

	} else {
		_, file, _, ok := runtime.Caller(2)
		if ok {
			category = categoryOf(file)
		}
	}

	var s string
	if level < 4 {
		ls := logLevelText[level]
		s = `[` + category + `] ` + ls + ` ` + fmt.Sprint(a...)
	} else {
		s = `[` + category + `] ` + fmt.Sprint(a...)

	}
	log.Println(s)
	if level == 0 {
		panic(s)
	}
}

生成调试用的对象信息

func ParseBuildTags

func ParseBuildTags(s string) []string

ParseBuildTags 把用户输入的 build tag 串拆成列表 逗号和空白都当分隔符, 空片段丢弃, 顺序保留

func ParseToolVersion

func ParseToolVersion(output string) string

ParseToolVersion picks the version banner out of a version probe's output: the first non-empty line, trimmed. Every tool here prints its identity there ("go version go1.25.0 darwin/arm64", "staticcheck 2025.1 (v0.6.1)"), and the line is only ever shown to the user, so it is kept whole instead of being dissected per tool.

func ParseTraceServerURL

func ParseTraceServerURL(output string) string

ParseTraceServerURL returns the web-UI address from `go tool trace` output, or "" when it has not printed one yet. The last URL in the output wins: the tool logs its parsing progress first and announces the listening address last ("Serving web UI on http://127.0.0.1:57263"). The host opens that URL in a browser — the trace viewer produces no findings to fold into the pane.

func PersistFscan

func PersistFscan(r io.Reader, a ...interface{}) (n int, err error)

func PersistSaveFile

func PersistSaveFile(ro interface{}, compress bool, path string) error

func PersistSscan

func PersistSscan(s string, a ...interface{}) (n int, err error)

func PersistString

func PersistString(val interface{}) (string, error)

func ProfilerStart

func ProfilerStart() error

func ProfilerStop

func ProfilerStop()

func Quit

func Quit()

请求退出事件循环

func RebuildPatchSides

func RebuildPatchSides(original string, hunks []PatchHunk) (oldText, newText string, err error)

RebuildPatchSides is RebuildSides for a bare hunk slice.

func RegisterFactory

func RegisterFactory(name string, typ reflect.Type)

注册对象工厂 RegisterFactory("my.StructA", gui.TypeOf(my.StructA{}))

func RegisterLogSink

func RegisterLogSink(sink LogSink) (unregister func())

RegisterLogSink 注册一个日志订阅者, 返回的函数用于注销该订阅者 支持注册多个sink; 上层GUI(如LogPanel)可借此订阅日志而无需被core反向依赖

func RemovedLineCount

func RemovedLineCount(file DiffFile) int

RemovedLineCount 返回某文件中所有 hunk 内被删除的行数总和

func RenderConflictMarkers

func RenderConflictMarkers(chunks []MergeChunk, labels MergeLabels) []string

RenderConflictMarkers 把块列表渲染回文本行: 非冲突块直接输出 Resolved() 的行, 冲突块套上 git 冲突标记. 与 ParseConflictMarkers 互逆(标记后的说明文字来自 labels, 不存在块里).

func ResourceDir

func ResourceDir() string

静态数据目录 用来存放图标, 语言包等和程序一起发布的静态数据 在目前的版本里, 此目录等于程序所在目录 但在编写代码时, 请不要用程序所在目录代替此目录, 因为以后会改变

func RunGoList

func RunGoList(dir string, args ...string) (jsonOut string, diagnostics string, err error)

RunGoList 在 dir 目录下执行 `go list -json`, 分别返回 stdout(JSON)与 stderr(诊断). args 为空时默认补 "./..."(扫描当前模块所有 package)

stdout 与 stderr 必须分开: 早先这里用 CombinedOutput, 把 stderr 混进了 JSON 流. `go list` 在需要拉依赖时会往 stderr 打 "go: downloading ..." 之类的进度行, 混流后 json.Decoder 会在第一个字符 'g' 上失败 ——

go list parse: invalid character 'g' looking for beginning of value

而当时的解析循环一遇错就整体中断, 于是一行无害的进度信息就让全部 package 信息丢失. 这个故障是 Windows CI 上首次构建(依赖尚未缓存)时暴露出来的.

即使 cmd 执行失败也会把已收集到的输出返回: 模块状态有问题(缺依赖, 语法错误)时 `go list` 仍会为可用的 package 输出 JSON, 同时把诊断打到 stderr, 上层两者都要用.

func SetAppName

func SetAppName(s string)

设置应用程序的名字

func SetDirIcon

func SetDirIcon(dir, icoFile, info string) error

func SetLogOutput

func SetLogOutput(of io.Writer, forkToStd bool) (err error)

设置log输出目标 此函数会添加一个监视器, 对输出的Log进行分析, 在必要的时候自动添加堆栈信息以便调试 通常情况下, 底层会自动调用此函数, 应用层一般不用显式调用 注: 调用log.SetOutput() 会覆盖此函数的设置

func SetLogOutputFile

func SetLogOutputFile(filename string, forkToStd bool) (err error)

设置log输出目标, 参见 SetLogOutput

func SetMainLoop

func SetMainLoop(mainLoopFn, quitLoopFn func())

此函数供gui包调用, 应用层不需要调用此函数

func ShellOpen

func ShellOpen(x string) error

func Sleep

func Sleep(milliseconds int)

func TempDir

func TempDir() string

软件使用的临时文件目录 在目前的版本里, 此目录等于 os.TempDir()+"/" + AppShortName()

func ToValidFileName

func ToValidFileName(s string) string

把任意字符串转换为可以用作文件名的字符串 非法字符及前后空格将用'_'替换

func ToolVersionArgs

func ToolVersionArgs(name string) []string

ToolVersionArgs returns the argv that makes name print its version. The toolchain uses the `version` subcommand; the two analyzers both take `-version`. Anything else gets the conventional `--version`.

func Trace

func Trace(a ...interface{})

输出调试Log, 且在前面添加"trace: "字样 此函数只在打开Debug开关时生效, 输出的Log带有堆栈信息

func TypeErr

func TypeErr(a interface{}) error

生成"类型错误" 主要用来跟踪调试内部类型错误, 例如序列化不支持的类型, 对象不支持所需接口等 生成的错误中包含了对象的简要信息, 便于调试

func TypeInfo

func TypeInfo(ptr interface{}) string

生成调试用的类型信息

func TypeOf

func TypeOf(i interface{}) reflect.Type

此函数功能同reflect.TypeOf, 放在这里是为了便于使用

func VisualString

func VisualString(val interface{}) string

func Warn

func Warn(a ...interface{})

输出警告Log, 且在前面添加"warning: "字样

func WorkspaceDir

func WorkspaceDir() string

本机工作区目录 在目前的版本里, 此目录等于ExeFileDir()+"/workspace" 但在编写代码时, 请不要用(ExeFileDir()+"/workspace")代替此目录, 因为以后会改变

func WriteLSPMessage

func WriteLSPMessage(w io.Writer, m *LSPMessage) error

WriteLSPMessage 把一条 LSPMessage 序列化为 JSON 并按 LSP framing 写出 写出顺序:

  1. 序列化 body 以便准确算 Content-Length
  2. 写 "Content-Length: N\r\n\r\n"
  3. 写 body

JSONRPC 字段为空时默认填 "2.0", 让调用方可以省事地只填 Method/Params

Types

type Aggregator

type Aggregator struct {
	// contains filtered or unexported fields
}

Aggregator 把事件流折叠成按包组织的结果 不是并发安全的: 由单个读取协程喂数据, 要把状态交给别的线程(比如 GUI 线程) 先 Clone 一份.

func NewAggregator

func NewAggregator() *Aggregator

NewAggregator 建一个空聚合器

func (*Aggregator) AddEvent

func (a *Aggregator) AddEvent(ev TestEvent)

AddEvent 折叠一条已解析的事件 归属规则:

  • build-output / build-fail, 以及任何没有 Package 的事件 -> BuildOutput
  • 有 Package 没有 Test -> 包级(状态/耗时/包级输出)
  • 有 Package 有 Test -> 该测试自己的(状态/耗时/输出)

run/start/pause/cont 只负责把节点建出来(状态留在 Running), 不改状态. bench 视为基准的终态并记成 pass: 基准跑完只有这一条结果事件, 失败的基准会 单独发 fail.

func (*Aggregator) AddLine

func (a *Aggregator) AddLine(line string) TestEvent

AddLine 解析并折叠一行 返回解析出的事件; 返回值的 Action 为空表示这一行不是事件, 已被收进 BuildOutput. 空行直接忽略.

func (*Aggregator) BuildOutput

func (a *Aggregator) BuildOutput() []string

BuildOutput 返回所有非事件文本行(编译错误、go 命令自身的诊断)

func (*Aggregator) Clone

func (a *Aggregator) Clone() *Aggregator

Clone 深拷贝整个聚合结果 流式回调拿到的 *Aggregator 属于读取协程, 跨线程交付前先 Clone.

func (*Aggregator) Counts

func (a *Aggregator) Counts() (passed, failed, skipped int)

Counts 汇总所有包的 pass/fail/skip 数量

func (*Aggregator) FailedTests

func (a *Aggregator) FailedTests() []TestRef

FailedTests 按包/测试的首次出现顺序返回所有 fail 的测试(含子测试)

func (*Aggregator) Feed

func (a *Aggregator) Feed(r io.Reader, onLine func(ev TestEvent, raw string)) error

Feed 从 r 逐行读入整个事件流 onLine 非 nil 时在每行折叠之后回调一次(raw 是原始行, ev.Action 为空表示这行 不是事件), 流式 UI 用它做增量刷新. 单行超过 4MB 会返回错误, 此前折叠的结果 仍然保留.

func (*Aggregator) Package

func (a *Aggregator) Package(path string) *PackageResult

Package 按导入路径取一个包结果, 不存在返回 nil

func (*Aggregator) Packages

func (a *Aggregator) Packages() []*PackageResult

Packages 按首次出现顺序返回所有包结果

type AlarmDB

type AlarmDB struct {
	// contains filtered or unexported fields
}

AlarmDB tracks the live alarm state of many tags plus a bounded history of past transitions, and fans transitions out to subscribers. All methods are safe for concurrent use.

func NewAlarmDB

func NewAlarmDB() *AlarmDB

NewAlarmDB returns an empty db with the default history bound.

func NewAlarmDBWithHistory

func NewAlarmDBWithHistory(n int) *AlarmDB

NewAlarmDBWithHistory returns an empty db whose transition history is bounded to at most n entries.

func (*AlarmDB) Ack

func (db *AlarmDB) Ack(tagName string)

Ack acknowledges tagName's active alarm. It is a no-op when the tag has no active alarm or is already acknowledged; otherwise it records the ack and notifies subscribers (so a banner can stop flashing).

func (*AlarmDB) Active

func (db *AlarmDB) Active() []AlarmState

Active returns a snapshot of every currently active alarm, ordered for an operator alarm list: unacknowledged before acknowledged, then higher severity first, then oldest first, then by tag name.

func (*AlarmDB) History

func (db *AlarmDB) History() []AlarmState

History returns the bounded ring of past transitions, oldest -> newest.

func (*AlarmDB) Subscribe

func (db *AlarmDB) Subscribe(fn func(AlarmState)) CancelFunc

Subscribe registers fn to receive every future alarm transition (raise, re-raise, ack, clear). Unlike tag subscriptions it does not prime with the current state — call Active() for the initial snapshot. The returned CancelFunc is idempotent.

func (*AlarmDB) Update

func (db *AlarmDB) Update(tagName string, sev AlarmSeverity, value float64)

Update drives the alarm state machine for tagName toward severity sev:

  • sev != None with no active alarm -> raise (unacked)
  • sev != None with a different active severity -> re-raise (restamp, unack)
  • sev != None with the same active severity -> refresh value only, no event
  • sev == None with an active alarm -> clear (return to normal)
  • sev == None with no active alarm -> no-op

Every raise / re-raise / clear records one history entry and notifies subscribers; a same-severity refresh does neither (a poll re-reading an unchanged band does not spam the alarm list).

func (*AlarmDB) Watch

func (db *AlarmDB) Watch(t *Tag) CancelFunc

Watch subscribes to t and auto-evaluates every good-quality sample against t's Meta limits, driving Update. Bad/uncertain samples are ignored (a disconnected sensor reading 0 must not trip LoLo). The returned CancelFunc stops watching.

type AlarmSeverity

type AlarmSeverity int

AlarmSeverity classifies a value against a tag's alarm limits.

Ordering (None < Low < High < LowLow < HighHigh) is the numeric severity rank used to sort the active-alarm list; larger is "more urgent" on each side, and the LoLo/HiHi trip limits outrank the Lo/Hi warning limits.

const (
	None     AlarmSeverity = iota // value within limits (no alarm)
	Low                           // value <= Lo   (low warning)
	High                          // value >= Hi   (high warning)
	LowLow                        // value <= LoLo (low trip)
	HighHigh                      // value >= HiHi (high trip)
)

func EvaluateAlarm

func EvaluateAlarm(value float64, m Meta) AlarmSeverity

EvaluateAlarm classifies value against the limits in m. Boundaries are inclusive: value <= LoLo -> LowLow, else <= Lo -> Low, else >= HiHi -> HighHigh, else >= Hi -> High, else None. Each limit is skipped when unset (see limitSet), so a zero Meta{} always yields None.

func (AlarmSeverity) IsAlarm

func (s AlarmSeverity) IsAlarm() bool

IsAlarm reports whether the severity represents an active alarm condition.

func (AlarmSeverity) String

func (s AlarmSeverity) String() string

String renders the short SCADA label for the severity.

type AlarmState

type AlarmState struct {
	Tag      string        // tag name this alarm belongs to
	Severity AlarmSeverity // current severity band
	Active   bool          // true while the alarm condition holds
	Acked    bool          // operator has acknowledged this alarm
	Since    time.Time     // time the current severity was entered / cleared
	Value    float64       // value that produced this state
}

AlarmState is a snapshot of one tag's alarm at a point in its lifecycle. The same struct is returned by Active()/History() and delivered to subscribers.

For a live active alarm, Since is the time it entered its current severity ("active since"); acknowledging does not reset it. A cleared snapshot (Active == false, Severity == None) is stamped at the clear time.

type AttrReader

type AttrReader interface {
	// 读属性
	// key的要求同C语言变量命名, 可以有多级, 多级之间用'/'分隔
	// ptr必须为非空指针, 并指向兼容的变量类型
	// 属性将被读到ptr指向的变量中
	ReadAttr(key string, ptr interface{}) error
}

type AttrWriter

type AttrWriter interface {
	// 写属性
	// key的要求同C语言变量命名, 可以有多级, 多级之间用'/'分隔
	WriteAttr(key string, data interface{}) error
}

type Breakpoint

type Breakpoint struct {
	ID       int
	File     string
	Line     int
	Function string // 可选

	// Cond 是 Go 表达式条件, 空串表示无条件断点 (dlv Breakpoint.Cond).
	// 只有 cond 求值为 true 时才真正停下, 例如 "i == 3" / "err != nil".
	Cond string
	// HitCount 是 dlv 报告的累计命中次数 (totalHitCount), 只读:
	// AmendBreakpoint 不会把它写回去 (dlv 侧不接受重置).
	HitCount uint64
	// Tracepoint=true 时这个断点是一个 logpoint: dlv 命中后打印信息并自动继续,
	// 不把程序停住. 线缆上 dlv 把这个标志叫 "continue".
	Tracepoint bool
	// LogMessage 是 logpoint 命中时要 dlv 求值并打印的表达式, 映射到 dlv
	// Breakpoint.Variables 的第一个元素; 空串表示只打印命中位置本身.
	LogMessage string
	// Enabled=false 对应 dlv 的 disabled 断点: 仍在列表里但不生效.
	// 注意零值 -- 自己构造 Breakpoint 交给 AmendBreakpoint 时记得显式置 true,
	// 从 dlv 解码出来的断点一定带正确的 Enabled.
	Enabled bool
	// Verified=true 表示 dlv 真的把这个断点绑到了至少一个地址上 (addr/addrs 非空).
	// 行号落在没有代码的位置时 dlv 通常直接报错而不是给一个未绑定断点, 所以实践中
	// 它几乎总是 true; 保留该字段让 UI 能表达"待绑定/已失效"状态.
	Verified bool
}

Breakpoint 是用户/我们在源码某一行下的断点 除 ID/File/Line/Function 之外还带上 IDE 断点面板要编辑的属性; 它们与 Delve api.Breakpoint 的映射见 rpcBreakpoint. 结构体保持可比较 (全是标量), 上层可以直接用 == 判断两个断点是否等价.

type CancelFunc

type CancelFunc func()

CancelFunc removes a subscription; it is safe to call more than once.

type CoverageBlock

type CoverageBlock struct {
	File       string
	StartLine  int
	EndLine    int
	Covered    bool // count > 0
	Statements int  // numStmts
}

CoverageBlock 描述一个连续的可执行语句块

func ParseCoverage

func ParseCoverage(profile string) (mode string, blocks []CoverageBlock, err error)

ParseCoverage 解析 go cover 文本格式 返回:

mode   - "set"/"count"/"atomic", 缺省 header 时按 "set" 处理
blocks - 所有成功解析的块 (跳错收集策略, 即使有坏行也会返回到目前为止解析成功的块)
err    - 非 nil 表示有畸形行, 错误信息包含全部坏行的行号

解析器对空行宽容. 永远不 panic.

type DebugSession

type DebugSession struct {
	// contains filtered or unexported fields
}

DebugSession 是一个正在跑的 dlv headless 进程 + JSON-RPC 长连接 同一个 session 上的 rpcCall 串行化 (rpc 1.0 over TCP 无法多路复用)

func LaunchDebug

func LaunchDebug(packageDir string, args []string) (*DebugSession, error)

LaunchDebug 在 packageDir 目录下启动 dlv headless server 并连上去 args 是要传给被调试程序的命令行参数, 会跟在 "--" 之后. 若 dlv 不在 PATH 上则立即返回错误; 若 dlv 启动后 ~3 秒内仍连不上 也返回错误并尝试杀掉进程. 端口选择: 先拿一个空闲 TCP 端口的号, 再让 dlv 占用同一个端口. 这有一个无害的竞态窗口 -- 极少数情况下端口在我们 Listen.Close() 与 dlv Listen 之间被别的进程抢走, 但本机交互式调试场景几乎不会撞到, 不值得为它加复杂的重试.

func (*DebugSession) AmendBreakpoint

func (s *DebugSession) AmendBreakpoint(bp *Breakpoint) error

AmendBreakpoint 把 bp 的可编辑状态写回 dlv (条件 / logpoint / 启用位) 走 RPCServer.AmendBreakpoint, bp.ID 必须是 dlv 已知的断点 ID. 语义是"整体替换"而不是"局部合并": bp 里为零值的字段会把 dlv 上对应的设置清掉 (Cond="" 即取消条件), 所以正确用法是从 SetBreakpoint/ListBreakpoints 拿到对象, 改字段, 再整个传回来. File/Line 不能靠 amend 改 (dlv 忽略), 换行请删了重下. HitCount 是只读的, 不会被写回.

func (*DebugSession) ClearBreakpoint

func (s *DebugSession) ClearBreakpoint(id int) error

ClearBreakpoint 按 dlv 分配的 ID 删除一个断点 走 RPCServer.ClearBreakpoint (params {Id}). 断点不存在时 dlv 回错误, 原样上抛 -- UI 侧删一个已经没了的断点应当当成"已经是目标状态", 由调用方决定是否忽略. 应答里带着被删掉的断点, 我们不消费.

func (*DebugSession) ClearBreakpointByLocation

func (s *DebugSession) ClearBreakpointByLocation(file string, line int) error

ClearBreakpointByLocation 删除 file:line 上的断点 dlv 没有按位置删的 RPC, 这里先 ListBreakpoints 再按位置找 ID -- 编辑器 gutter 只知道文件和行号, 不该逼 UI 自己维护一张 id 表. 该行没有断点时返回错误. 路径比较见 sameSourceFile: dlv 一律回绝对路径, 而 IDE 手里可能是相对路径.

func (*DebugSession) Close

func (s *DebugSession) Close() error

Close 停掉 dlv: 标记 closed -> 关连接 -> 尽力 Detach -> kill 进程兜底 关键顺序: Close 不能一上来就等 s.mu -- 后台的 rpcCall (典型是 Continue 在等 stop state) 可能正持有 s.mu 阻塞在 Decode 上, 先等锁就是死锁 (IDE 里表现为 点 Stop 冻住主线程). 因此:

  1. closed 用独立的 atomic 打标记, 不经过 s.mu;
  2. 先 conn.Close() -- net.Conn 并发安全, 会让阻塞中的 Decode 立即带错返回, 对应的 rpcCall 看到 closed 后回 errSessionClosed 并释放 s.mu;
  3. 之后再拿 s.mu 做剩余清理. 此时 Detach 大概率失败 (conn 已关) -- 无妨, Process.Kill 兜底保证 dlv 子进程一定被终止.

Detach 任意错误都忽略 -- 进程都要终止了, 没必要把 RPC 错误返给上层

func (*DebugSession) Continue

func (s *DebugSession) Continue() (*StopState, error)

Continue 让被调程序运行直到下一个断点/退出. 阻塞直到 dlv 给出 stop state. dlv 的 RPCServer.Command 在 v2 下应答为 DebuggerState; 我们只关心当前线程的 停止位置. 程序已退出时 Exited=true, File/Line 为空.

func (*DebugSession) Eval

func (s *DebugSession) Eval(expr string, goroutineID, frame int) (Variable, error)

Eval 在 (goroutine, frame) 作用域下求值一个 Go 表达式 表达式形态遵循 dlv 文档: 支持局部/包级变量 + 成员/索引/解引用, 不支持函数调用. 这是 hover-to-inspect 和 watch panel 的基础. 默认只展开一层嵌套, 要更深走 LoadVariable.

func (*DebugSession) ListArgs

func (s *DebugSession) ListArgs(goroutineID int64, frame int) ([]Variable, error)

ListArgs 拉取指定 (goroutine, frame) 的函数参数 与 ListLocals 一起才是一帧的完整变量视图 (Qt Creator 的 Locals and Expressions 里 arguments 是单独一组). goroutineID<0 的含义同 ListLocals.

func (*DebugSession) ListBreakpoints

func (s *DebugSession) ListBreakpoints() ([]Breakpoint, error)

ListBreakpoints 拉取当前所有断点 dlv v2 应答里还会含一个 "unrecovered-panic" 等内部断点 (ID<0); 这里原样返回, 由上层决定是否过滤(IDE 显示侧通常会按 ID>0 筛一遍)

func (*DebugSession) ListGoroutines

func (s *DebugSession) ListGoroutines() ([]Goroutine, error)

ListGoroutines 拉取所有 goroutine (不分页) 等价于 ListGoroutinesPage(0, 0) -- dlv 里 Count=0 的语义就是"全部". 取 UserCurrentLoc 而非 CurrentLoc -- 用户关心自己写的代码而不是 runtime 帧.

func (*DebugSession) ListGoroutinesPage

func (s *DebugSession) ListGoroutinesPage(start, count int) ([]Goroutine, int, error)

ListGoroutinesPage 分页拉 goroutine 列表 start 是起始游标 (第一页传 0), count 是本页最多几条 (0 = 全部). 第二个返回值是下一页的游标 (dlv 的 Nextg): 0 表示已经到底了, 非 0 时把它当成 下一次调用的 start. 一个真实服务进程 goroutine 上万, 面板靠这个避免一次把整张 表拉回来 (也避免 dlv 一次给我们几 MB 的 JSON).

func (*DebugSession) ListLocals

func (s *DebugSession) ListLocals(goroutineID, frame int) ([]Variable, error)

ListLocals 拉取指定 (goroutine, frame) 的局部变量 goroutineID<0 -> 当前 goroutine (SwitchGoroutine 选过则是它); frame=0 -> 栈顶. 只含局部变量; 函数参数走 ListArgs -- dlv 把两者拆成了两个 RPC.

func (*DebugSession) LoadVariable

func (s *DebugSession) LoadVariable(expr string, goroutineID int64, frame, depth int) (Variable, error)

LoadVariable 是变量树"懒展开"的后端: 用户点开某个节点时才把那一层子变量拉回来 expr 是节点的完整路径表达式 ("x" / "x.Field" / "s[3]"), depth 直接映射到 dlv 的 LoadConfig.MaxVariableRecurse: 0 = 只加载这个节点本身, 1 = 连它的直接子项, 越大展开越深; depth<0 用默认的 1. 其它上限 (字符串长度/数组元素数) 与 Eval 一致. 典型用法: 面板拿到 Children==nil 的节点 -> 用户点开 -> LoadVariable(路径, g, f, 1) -> 用返回值的 Children 填充这一层. 深度别给大值, dlv 是同步的, 一次深展开会把 整个 session 卡住 (同一 session 上的 RPC 是串行的).

func (*DebugSession) Next

func (s *DebugSession) Next() (*StopState, error)

Next 是 IDE Debug 工具栏的 "Step Over": 执行当前行, 跨过 (不进入) 行内的函数 调用. 对应 dlv 的 "next". 与 Step 等价 -- Step 是历史别名, 两者发同一条命令.

func (*DebugSession) OnOutput

func (s *DebugSession) OnOutput(fn func(stream, line string))

OnOutput 注册 dlv 进程 (以及跟它共享 fd 的被调试程序) 的输出行回调 stream 是 "stdout" 或 "stderr", line 已去掉行尾的 \r\n. 注册之前积压的行会在 注册时按序补发一遍, 所以晚注册也看得到 dlv 的启动输出. fn 传 nil 表示取消订阅 (此时新行重新进 backlog). 回调在读取 goroutine 上同步执行: 不要在里面做阻塞的事, UI 侧应当转成一次异步刷新.

func (*DebugSession) Port

func (s *DebugSession) Port() int

Port 返回 dlv headless 监听的端口号, 给上层日志/UI 用

func (*DebugSession) Restart

func (s *DebugSession) Restart() error

Restart 把被调进程从头重跑一遍, 不重启 dlv 进程本身 走 RPCServer.Restart, 普通重启传 {Position:"", ResetArgs:false}: Position 空表示从入口重新开始 (非空时是 checkpoint/位置, record/replay 才用到), ResetArgs=false 保留原命令行参数. 断点默认跨 Restart 存活 -- dlv 会把它们 重新绑到新进程上, 所以重启后不必重新 SetBreakpoint. 应答里的 DiscardedBreakpoints 列出那些重新绑定失败而被丢弃的断点 (一般为空); 非空时仅 Warn 一条, 不当成错误 -- 重启本身已经成功, 个别断点丢失不该让调用方失败.

func (*DebugSession) SelectedGoroutine

func (s *DebugSession) SelectedGoroutine() int64

SelectedGoroutine 返回 SwitchGoroutine 记下的 goroutine id 没切过时返回 -1, 也就是 dlv 的"当前 goroutine".

func (*DebugSession) SetBreakpoint

func (s *DebugSession) SetBreakpoint(file string, line int) (*Breakpoint, error)

SetBreakpoint 在 file:line 处下断点, 返回 dlv 分配的 ID dlv 不要求 file 是绝对路径但强烈推荐, 否则其内部要靠 packageDir 解析

func (*DebugSession) SetConditionalBreakpoint

func (s *DebugSession) SetConditionalBreakpoint(file string, line int, cond string) (*Breakpoint, error)

SetConditionalBreakpoint 在 file:line 下一个带 Go 表达式条件的断点 只有当 cond 在该行求值为 true 时 dlv 才会真正停下 (例如 "i == 3" / "err != nil"). 这是 IDE "右键断点 -> 编辑条件" 的后端; cond 的语法与 Eval 表达式一致. 返回值形态与 SetBreakpoint 对齐 (同样是 *Breakpoint), 两者共用 createBreakpoint.

func (*DebugSession) SetVariable

func (s *DebugSession) SetVariable(symbol, value string, goroutineID, frame int) error

SetVariable 把 (goroutine, frame) 作用域下的某个变量 symbol 赋成 value 这是 IDE 变量面板 "双击改值" 动作的后端.走 dlv 的 RPCServer.Set, 参数 {Scope: EvalScope, Symbol, Value}, 应答为空 (无 result). symbol 是变量名 (例如 "x" 或 "p.Field"), value 是 Go 字面量字符串 (dlv 自己解析, 例如 "42" / "\"hi\"" / "true"). 类型不匹配或符号不存在时 dlv 回 error, 这里原样 wrap. goroutineID<0 表示当前 goroutine (SwitchGoroutine 选过则是它), frame=0 是栈顶.

func (*DebugSession) Stacktrace

func (s *DebugSession) Stacktrace(goroutineID, depth int) ([]StackFrame, error)

Stacktrace 返回当前/指定 goroutine 的调用栈 goroutineID < 0 表示"当前 goroutine": 若 SwitchGoroutine 选过一个就用它, 否则 交给 dlv 的 SelectedGoroutine. depth 是最多取几帧 (栈顶起算). Full=false 让 dlv 不带 Locals/Arguments -- 我们这里只画位置, 想看局部变量走 ListLocals/ListArgs. 这样应答体积更小.

func (*DebugSession) Step

func (s *DebugSession) Step() (*StopState, error)

Step 是 Next 的同义词 (step-over), 历史遗留方法, 保留以兼容已引用它的调用方 (silkide 等). 与 Next 一样发 dlv 的 "next": 同一 goroutine 内单步, 不进入 函数调用. 新代码应优先用 Next; Step 不再扩展语义.

func (*DebugSession) StepInto

func (s *DebugSession) StepInto() (*StopState, error)

StepInto 是 "Step Into": 进入当前行所调用的函数. 对应 dlv 的 "step". 当前行没有函数调用时 dlv 退化为一次 next.

func (*DebugSession) StepOut

func (s *DebugSession) StepOut() (*StopState, error)

StepOut 是 "Step Out": 运行到当前函数返回 (跳出当前帧). 对应 dlv 的 "stepOut".

func (*DebugSession) SwitchGoroutine

func (s *DebugSession) SwitchGoroutine(id int64) error

SwitchGoroutine 把 dlv 的"当前 goroutine"切到 id, 并把这个选择记在 session 上 之后 Stacktrace/ListLocals/ListArgs/Eval/LoadVariable/SetVariable 传负数 goroutineID (即"当前") 时都解析到 id -- 这是 IDE goroutine 面板双击一行之后 变量/调用栈跟着换上下文的机制. 走 RPCServer.Command 的 "switchGoroutine" 子命令, 要求程序处于停止状态; dlv 报错时不改动本地选择.

func (*DebugSession) ToggleBreakpoint

func (s *DebugSession) ToggleBreakpoint(file string, line int) (*Breakpoint, error)

ToggleBreakpoint 是编辑器 gutter 点击的后端: 该行没断点就下一个, 已有就删掉 新建时返回创建出来的断点; 删除时返回 (nil, nil) -- 调用方用 nil 判断"这次是删".

type DeployProfile

type DeployProfile struct {
	Kind      string `json:"kind"`
	Host      string `json:"host,omitempty"`
	User      string `json:"user,omitempty"`
	RemoteDir string `json:"remote_dir,omitempty"`
}

DeployProfile 描述产物的交付目标 Kind 为 "" 时按 DeployLocal 处理; Host/User/RemoteDir 只对 DeploySSH 有意义

type DiffFile

type DiffFile struct {
	OldPath string
	NewPath string
	Hunks   []DiffHunk
}

DiffFile 描述一个文件的差异. OldPath/NewPath 已经剥去 "a/"/"b/" 前缀; /dev/null 被规整为 "", 调用方据此区分新建(OldPath=="")和删除(NewPath=="").

func ParseUnifiedDiff

func ParseUnifiedDiff(src string) ([]DiffFile, error)

ParseUnifiedDiff 解析 unified diff 文本, 返回每个文件的差异 行边界用 bufio.Scanner 取, 不依赖文末换行; 永远不 panic; 单条畸形 @@ 头汇总到 wrapped error, 但其余文件/hunk 仍会出现在返回切片里. 当 src 为空(去掉首尾空白后)时返回 (nil, nil).

type DiffHunk

type DiffHunk struct {
	OldStart int
	OldCount int
	NewStart int
	NewCount int
	Lines    []DiffLine
}

DiffHunk 描述一个 @@ 头及其下属行 OldStart/NewStart 为 1-based 起始行号; OldCount/NewCount 在 @@ 省略时默认为 1.

type DiffLine

type DiffLine struct {
	Kind DiffLineKind
	Text string
}

DiffLine 是 hunk 中的一行 Text 不包含开头的 +/-/空格 标记字符; 对 NoNewline 类型 Text 固定为空.

type DiffLineKind

type DiffLineKind int

DiffLineKind 枚举 hunk 内单行的语义

const (
	DiffLineContext   DiffLineKind = iota // " " 上下文行, 新旧文件都有
	DiffLineAdded                         // "+" 仅出现在新文件
	DiffLineRemoved                       // "-" 仅出现在旧文件
	DiffLineNoNewline                     // "\ No newline at end of file" 占位
)

type Factory

type Factory interface {
	// 对象的类名
	Name() string

	// 创建对象
	// p := c.New().(*MyStruct)
	// 注: 动态创建的图元有两种释放方式, 一种是自动销毁, 另一种要用Close()关闭
	// 在不知道应如何销毁时, 应尝试Close(), 以免出现资源泄露:
	//	if ia, ok := p.(core.IClose); ok {
	//		ia.Close()
	//	}
	New() interface{}

	// 工厂注册的位置
	Location() string
}

Factory

func AllFactories

func AllFactories() []Factory

获取全部对象工厂

func FactoryOf

func FactoryOf(i interface{}) Factory

func FindFactory

func FindFactory(name string) Factory

根据名字查找对象工厂

type FileCoverage

type FileCoverage struct {
	File          string
	Covered       map[int]bool // line -> covered (covered wins: 一旦为 true 不会被任何 block 翻回 false)
	BlocksCovered int          // count of blocks with count > 0
	BlocksTotal   int
}

FileCoverage 是单个源文件的覆盖率折叠结果 Covered 仅包含被任意 block 提及的行; 未在任何 block 出现的行不会出现在 map 中(IDE 侧不应在这些行画 gutter)

type FilePatch

type FilePatch struct {
	OldPath string
	NewPath string
	Hunks   []PatchHunk
}

FilePatch is every hunk for one file. Paths have the a//b/ prefix stripped and /dev/null normalised to "": OldPath == "" is a new file, NewPath == "" a deleted one, and two different non-empty paths a rename.

func (FilePatch) Apply

func (f FilePatch) Apply(original string) (string, error)

Apply applies every hunk of the file patch to original.

func (FilePatch) ApplySelected

func (f FilePatch) ApplySelected(original string, hunkIndices []int) (string, error)

ApplySelected applies only the hunks at hunkIndices — the partial-staging primitive ("stage this hunk"). Indices refer to f.Hunks; order and duplicates do not matter.

func (FilePatch) IsAdd

func (f FilePatch) IsAdd() bool

IsAdd reports whether the patch creates the file (old side is /dev/null).

func (FilePatch) IsDelete

func (f FilePatch) IsDelete() bool

IsDelete reports whether the patch deletes the file (new side is /dev/null).

func (FilePatch) IsRename

func (f FilePatch) IsRename() bool

IsRename reports whether the file moved: both sides exist and differ.

func (FilePatch) Path

func (f FilePatch) Path() string

Path is the path to show for the patch: the new path when the file still exists, otherwise the old one (a deletion).

func (FilePatch) RebuildSides

func (f FilePatch) RebuildSides(original string) (oldText, newText string, err error)

RebuildSides reconstructs both complete sides of the patch from the original file content: the old side is original verbatim, the new side is original with every hunk applied. Unlike concatenating hunk bodies, this keeps the unchanged gaps between hunks, so a side-by-side viewer can show the whole file instead of only the changed neighbourhoods.

func (FilePatch) Reverse

func (f FilePatch) Reverse() FilePatch

Reverse flips every hunk of the file patch and swaps its paths, turning an "apply" patch into a "revert" patch. Hunk order is preserved: reversed coordinates are already in new-file space, which is the file the reverse patch applies to.

type Finding

type Finding struct {
	Tool     string
	File     string
	Line     int
	Col      int
	Severity string
	Message  string
	Code     string
}

Finding is one row in the Go Tools pane: a diagnostic, a vulnerability or a profile hot spot, normalised across every tool. File is empty (and Line/Col zero) when the tool reported no source location — a `pprof -top` row at function granularity, for instance — in which case the pane must not offer a jump. Code is the tool's own identifier for the finding when it has one: a staticcheck check id ("SA4006"), a Go vulnerability id ("GO-2024-2687"), a pprof flat percentage ("30.61%").

func ParseGovulncheck

func ParseGovulncheck(output string) []Finding

ParseGovulncheck parses govulncheck's text report. The shape it walks:

=== Symbol Results ===

Vulnerability #1: GO-2024-2687
    HTTP/2 CONTINUATION flood in net/http
  More info: https://pkg.go.dev/vuln/GO-2024-2687
  Standard library
    Found in: net/http@go1.22.1
    Fixed in: net/http@go1.22.2
    Example traces found:
      #1: cmd/main.go:24:11: main.main calls http.ListenAndServe

One Finding per "Vulnerability #N" block, in report order:

Code     — the GO-YYYY-NNNN id.
Message  — the (possibly line-wrapped) title, with the affected and
           fixed versions appended, because a vulnerability row
           without "fixed in" tells the user nothing actionable.
File/Line/Col — from the FIRST example trace, so a click lands on the
           call site in the user's own code. Blocks without traces
           (module-level results) carry no location.
Severity — error under "=== Symbol Results ===" (govulncheck proved
           the vulnerable symbol is reachable) and warning under the
           module/package sections (the dependency is vulnerable but
           the call was not observed). Reports with no section header
           at all — the pre-1.1 layout — are all errors.

Trailing prose ("Your code is affected by 1 vulnerability...") is ignored: title capture stops at the first labelled line or blank line inside a block, so only the real title is collected.

func ParsePprofTop

func ParsePprofTop(output string) []Finding

ParsePprofTop parses the `go tool pprof -top` table:

 flat  flat%   sum%        cum   cum%
300ms 30.61% 30.61%      400ms 40.82%  runtime.mallocgc
200ms 20.41% 51.02%      200ms 20.41%  paint.(*Font).TextExtents

One Finding per row, in pprof's own (descending flat) order:

Message  — "<frame> (flat 300ms 30.61%, cum 400ms 40.82%)", i.e. the
           frame plus the numbers that justify its position.
Code     — the flat percentage.
Severity — warning at or above pprofHotFlatPercent flat, else info.
           Profile rows are not defects, so nothing here is an error.
File/Line— set only when pprof was asked for -lines granularity and
           appended a "file.go:123" column; otherwise empty, and the
           pane offers no jump for the row.

Everything before the column header (File/Type/Duration/Showing/ Dropped preamble) is skipped, and the table ends at the first line with fewer than six columns.

func ParseRaceFindings

func ParseRaceFindings(output string) []Finding

ParseRaceFindings parses `go test -race` output. Two kinds of finding come back, in this order:

  1. every vet-style "file:line: message" line (build errors and the indented details under a `--- FAIL` header), via ParseVetFindings;

  2. one Finding per "WARNING: DATA RACE" report, located at the first stack frame under the first access:

    ================== WARNING: DATA RACE Write at 0x00c0000b4010 by goroutine 8: core.(*Bus).Publish() /Users/x/silk/core/bus.go:42 +0x64 ... ==================

    Code is "DATA RACE" and Message is the access line without its trailing colon ("Write at 0x00c0000b4010 by goroutine 8"), which is what identifies the report in a one-line row.

The two passes cannot double-count: a race report's frames carry a "+0x" offset and no ": " after the line number, so buildissues.Parse skips every line of them.

func ParseToolOutput

func ParseToolOutput(tool, output string) []Finding

ParseToolOutput folds a workflow's captured output into findings, dispatching on the workflow id. go tool trace yields none — it serves a web UI, see ParseTraceServerURL. Unknown ids fall through to the vet-style parser, which is the safe default: it only reports lines that carry a real "file:line: message" locator.

func ParseVetFindings

func ParseVetFindings(tool, output string) []Finding

ParseVetFindings converts vet-style output — "file:line[:col]: message" diagnostics, as emitted by go build, go vet, staticcheck and the indented details under a `--- FAIL` header — into findings tagged with tool. buildissues.Parse does the line recognition and severity call (its rules already skip package headers, test-runner status lines and stack frames); the only thing added here is lifting a staticcheck check id out of the message into Code.

type GitBlameLine

type GitBlameLine struct {
	Hash    string // commit hash, 已缩短为 8 位便于 gutter 展示
	Author  string
	Line    int    // 最终文件里的 1-based 行号
	Content string // 该行源码文本
}

GitBlameLine 是 `git blame` 里最终文件的一行归属信息 Hash 是该行最后一次改动的提交; Author 是该提交作者; Line 是 1-based 行号; Content 是该行源码文本(不含末尾换行).

func GitBlame

func GitBlame(dir, file string) ([]GitBlameLine, error)

GitBlame 返回文件每一行的最后改动归属(`git blame --line-porcelain -- <file>`) 即"git blame" gutter / 行注释视图. 用 --line-porcelain 这种稳定的机器格式: 每一行(最终文件的一行)对应一个 block —— 先是一行头

<40-hex> <orig-line> <final-line> [<num-lines>]

接着若干 "key value" 元数据行(author / author-time / ...), 最后一行 TAB 开头 即该行源码文本. 这里只取 hash(头行第一个字段)、author(`author ` 行)和那条 TAB 内容行. Hash 缩短到 8 位便于 gutter 展示. 健壮解析: 头行字段不齐、缺 author、缺内容行的畸形 block 直接跳过不收集, 永不 panic.

type GitBranch

type GitBranch struct {
	Name     string
	Ref      string
	Remote   bool
	Current  bool
	Hash     string // 短 SHA
	Subject  string // 分支尖端提交的标题
	Upstream string
	Ahead    int
	Behind   int
	Gone     bool
}

GitBranch 是 `git branch --list --all --format=...` 的一条分支 Name 是短名(本地 "main", 远程 "origin/main"), Ref 是完整引用名; Upstream 为空表示没有配上游; Ahead/Behind 是相对上游的领先/落后提交数; Gone 表示配了上游但那个引用已经不存在了(上游分支被删).

func ParseGitBranches

func ParseGitBranches(out string) []GitBranch

ParseGitBranches 解析 gitBranchFormat 的输出(纯函数) 一行一个引用, 字段以 0x1f 分隔. 跳过两类不是分支的行:

  • detached HEAD 的伪条目: git branch 会给它一行 "(HEAD detached at abc1234)", refname 不以 "refs/" 开头 —— 它不是引用, 当前是否游离用 GitCurrentBranch 判定.
  • refs/remotes/<remote>/HEAD: 指向远程默认分支的符号引用, 不是分支本身, 而且它的 refname:short 会缩成 "origin" 这种误导性的名字.

字段数不齐的行跳过并继续, 永远不 panic.

type GitCommit

type GitCommit struct {
	Hash    string
	Subject string
	Author  string
	Date    string
}

GitCommit 是 `git log` 的一条提交摘要

func GitLogFile

func GitLogFile(dir, file string, n int) ([]GitCommit, error)

GitLogFile 返回单个文件最近 n 条提交摘要(`git log -n <n> -- <file>`) 即"文件历史"视图. 与 GitShortLog 共用完全相同的 0x1f 字段格式和 parseShortLog 解析, 区别仅在末尾加了 `-- <file>` 把日志限定到该文件. 单行字段不齐时跳过.

func GitShortLog

func GitShortLog(dir string, n int) ([]GitCommit, error)

GitShortLog 返回最近 n 条提交摘要(`git log -n <n>`) 用 0x1f(unit separator)分隔 hash/subject/author/date 四个字段, 避免 subject 里出现普通分隔符导致误切. 单行字段数不足时跳过该行继续, 永远不 panic.

type GitConflict

type GitConflict struct {
	Path   string
	Stages []GitConflictStage
}

GitConflict 汇总一个未合并路径的全部 stage 记录

func ParseGitUnmerged

func ParseGitUnmerged(out string) []GitConflict

ParseGitUnmerged 解析 `git ls-files -u` 的输出(纯函数) 每条记录形如 "<mode> <sha> <stage>\t<path>", 同一个路径会出现 1~3 条(stage 1/2/3); 这里按路径归并, 保持首次出现的顺序, 同一路径的 Stages 按输入顺序追加. 记录分隔符兼容两种形态(见 splitGitRecords): 带 -z 是 NUL, 不带是换行. 不带 -z 时含特殊字符的路径会被 git 加引号并做 C 风格转义, 统一过 DecodeGitQuotedPath. 畸形记录(没有 TAB、字段数不对、stage 不在 1..3)跳过并继续, 永远不 panic.

type GitConflictStage

type GitConflictStage struct {
	Mode  string // 文件模式, 如 "100644"
	Hash  string // blob SHA
	Stage int
}

GitConflictStage 是一个冲突文件在某一 stage 上的 blob(`git ls-files -u` 的一行) Stage 取 1/2/3: 1=base(共同祖先), 2=ours(当前分支), 3=theirs(被合入的一侧). 缺哪个 stage 本身就是信息: 缺 1 是双方各自新增(add/add), 缺 2 是被我们删掉(deleted by us), 缺 3 是被对方删掉(deleted by them). Hash 可以直接喂给 GitShow 取出该版本内容.

type GitOps

type GitOps struct {
	Dir     string
	Runner  GitRunner     // nil 表示用内置的非交互 execGit
	Timeout time.Duration // <=0 表示用 gitOpsTimeout
}

GitOps 是绑定在某个工作树上的 git 操作句柄 Runner 为 nil 时走 execGit(真实 git, 非交互); Timeout 为 0 时用 gitOpsTimeout. 本身无状态(除这三个字段), 可以随手 new, 也可以在面板里长期持有.

func NewGitOps

func NewGitOps(dir string) *GitOps

NewGitOps 返回一个绑定 dir 的默认 GitOps(真实 git + 默认超时)

func (*GitOps) AddRemote

func (g *GitOps) AddRemote(name, url string) error

AddRemote 添加一个远程(`git remote add <name> <url>`) 同名远程已存在时 git 非零退出, 错误原样返回.

func (*GitOps) CheckoutBranch

func (g *GitOps) CheckoutBranch(name string) error

CheckoutBranch 切到已存在的分支(`git checkout <name>`) 工作树有会被覆盖的改动时 git 非零退出, 错误原样返回(该先 StashPush 或提交). 传远程跟踪分支名(如 "origin/main")时 git 会建一个同名本地分支并跟踪它.

func (*GitOps) CherryPick

func (g *GitOps) CherryPick(rev string) error

CherryPick 把一个提交摘到当前分支(`git cherry-pick <rev>`) rev 可以是 SHA、分支名或任何 revision 表达式. 冲突时非零退出并停在"摘取中"状态, 用 CherryPickContinue/CherryPickAbort 收场.

func (*GitOps) CherryPickAbort

func (g *GitOps) CherryPickAbort() error

CherryPickAbort 放弃摘取并回到起点(`git cherry-pick --abort`)

func (*GitOps) CherryPickContinue

func (g *GitOps) CherryPickContinue() error

CherryPickContinue 解决冲突后继续摘取(`git cherry-pick --continue`)

func (*GitOps) ConflictStatus

func (g *GitOps) ConflictStatus() ([]GitConflict, error)

ConflictStatus 列出当前所有未合并(冲突)的路径(`git ls-files -u -z`) 合并/变基/摘取/stash pop 冲突后用它驱动冲突列表与三方合并编辑器. 无冲突时返回 (nil, nil) —— 这是正常状态, 不是错误. 加 -z 让路径按原样 NUL 分隔输出, 彻底绕开引号与转义.

func (*GitOps) CreateBranch

func (g *GitOps) CreateBranch(name, startPoint string) error

CreateBranch 新建分支但不切过去(`git branch <name> [<start-point>]`) startPoint 为空表示从当前 HEAD 起分支; 非空时可以是分支名、标签或 SHA.

func (*GitOps) DeleteBranch

func (g *GitOps) DeleteBranch(name string, force bool) error

DeleteBranch 删除本地分支(`git branch -d|-D <name>`) force 为假时用 -d: 分支尚未合并进上游/当前分支时 git 会拒绝, 这是有意的保护; force 为真时用 -D, 无条件删除.

func (*GitOps) Fetch

func (g *GitOps) Fetch(remote string, prune bool) error

Fetch 从远程抓取(`git fetch [--prune] <remote>|--all`) remote 为空表示抓全部远程(--all). prune 为真时顺带删掉本地那些上游已消失的 远程跟踪引用 —— 这正是 ListBranches 里 Gone 标记的来源.

func (*GitOps) ListBranches

func (g *GitOps) ListBranches() ([]GitBranch, error)

ListBranches 列出本地与远程跟踪分支(`git branch --list --all --format=...`)

func (*GitOps) ListRemotes

func (g *GitOps) ListRemotes() ([]GitRemote, error)

ListRemotes 列出全部远程及其 URL(`git remote -v`)

func (*GitOps) MergeAbort

func (g *GitOps) MergeAbort() error

MergeAbort 放弃正在进行的合并并还原工作树(`git merge --abort`) 没有合并在进行时 git 非零退出, 错误原样返回.

func (*GitOps) Pull

func (g *GitOps) Pull(remote, branch string, rebase bool) error

Pull 拉取并合入(`git pull --rebase|--no-rebase [<remote> [<branch>]]`) 刻意总是显式给出 --rebase / --no-rebase: 二者都不给时, 若本地与上游已分叉且用户没配 pull.rebase, 新版 git 会直接 fatal("need to specify how to reconcile divergent branches"), 由调用方明确选一种反而更可预期. remote 为空表示用当前分支配置的上游; branch 只有在给了 remote 时才有意义.

func (*GitOps) Push

func (g *GitOps) Push(remote, branch string, setUpstream, force bool) error

Push 推送(`git push [--set-upstream] [--force-with-lease] [<remote> [<branch>]]`) setUpstream 即 "第一次推一个新分支并记住上游", 因此要求 remote 与 branch 都给全 —— `git push --set-upstream` 缺 refspec 时 git 自己也会报错, 这里提前挡掉. force 走 --force-with-lease 而不是 --force: 只有远程还停在我们上次见到的位置时才覆盖, 别人期间推过东西就拒绝, 不会静默吃掉他人的提交.

func (*GitOps) RebaseAbort

func (g *GitOps) RebaseAbort() error

RebaseAbort 放弃变基并回到起点(`git rebase --abort`)

func (*GitOps) RebaseContinue

func (g *GitOps) RebaseContinue() error

RebaseContinue 解决冲突后继续变基(`git rebase --continue`) 依赖 GIT_EDITOR=true: 需要确认提交信息时不会卡在编辑器上, 直接沿用现有信息.

func (*GitOps) RebaseStart

func (g *GitOps) RebaseStart(upstream string) error

RebaseStart 把当前分支变基到 upstream 上(`git rebase <upstream>`) 刻意不支持 -i: 交互式变基要一个能编辑 todo 列表的终端, 在 IDE 里只会变成挂死. 遇到冲突时 git 非零退出并停在"变基中"状态, 接着用 RebaseContinue/RebaseAbort 收场.

func (*GitOps) StashDrop

func (g *GitOps) StashDrop(ref string) error

StashDrop 丢弃一个 stash(`git stash drop [<ref>]`), ref 为空表示最新的那个

func (*GitOps) StashList

func (g *GitOps) StashList() ([]GitStash, error)

StashList 列出全部 stash(`git stash list`)

func (*GitOps) StashPop

func (g *GitOps) StashPop(ref string) error

StashPop 弹出一个 stash 并从栈上删掉它(`git stash pop [<ref>]`) ref 为空表示最新的 stash(stash@{0}). 有冲突时 git 非零退出且保留该 stash, 错误原样返回, 冲突文件可以用 ConflictStatus 查.

func (*GitOps) StashPush

func (g *GitOps) StashPush(message string, includeUntracked bool) error

StashPush 把当前改动存起来并还原工作树(`git stash push [-u] [-m <message>]`) message 为空(或全空白)时不带 -m, 让 git 生成默认的 "WIP on <branch>" 信息. includeUntracked 为真时连未跟踪文件一起收走(--include-untracked), 否则它们留在原地.

func (*GitOps) Status

func (g *GitOps) Status() ([]GitStatusEntry, error)

Status 返回工作树状态, 走 NUL 分隔的 `git status --porcelain=v1 -z` 与 core/git.go 的 GitStatusPorcelain 同一份 GitStatusEntry 结构, 区别在传输形态: -z 让 git 原样输出路径并以 NUL 收尾, 于是引号与 C 风格转义完全不参与, 含空格、TAB、 换行、非 ASCII 的路径都能原封不动拿到.

type GitRemote

type GitRemote struct {
	Name     string
	FetchURL string
	PushURL  string
}

GitRemote 是一个远程仓库及其 URL git 允许 fetch 与 push 指向不同 URL(remote.<name>.pushurl), 所以分成两个字段; 没有单独配 pushurl 时 PushURL 与 FetchURL 相同(git remote -v 会两行都打出来).

func ParseGitRemotes

func ParseGitRemotes(out string) []GitRemote

ParseGitRemotes 解析 `git remote -v` 的输出(纯函数) 每行形如 "<name>\t<url> (fetch)" 或 "<name>\t<url> (push)", 同一个远程占两行, 这里按名字归并, 保持首次出现的顺序. 顺带兼容不带 -v 的纯名字列表(没有 TAB 的行), 那种情况下只有名字、URL 留空. 畸形行跳过并继续, 永远不 panic.

type GitRunner

type GitRunner func(dir string, args ...string) (string, error)

GitRunner 是执行一条 git 命令的抽象 dir 是工作目录, args 是不含 "git" 本身的 argv, 返回 stdout 与错误. 生产路径是 GitOps.execGit(真的 fork git); 测试注入一个记录 argv 的假实现, 于是"每个操作拼出什么命令"可以被断言, 且测试完全不需要真仓库.

type GitStash

type GitStash struct {
	Ref     string
	Index   int
	Message string
	Author  string
	Date    string
}

GitStash 是 `git stash list` 的一条记录 Ref 是可直接喂给 StashPop/StashDrop 的选择子("stash@{0}"); Index 是从 Ref 里取出的序号(0 是最新), 取不出来时为 -1. Message 是 reflog 主题: 自动保存形如 "WIP on main: abc1234 subject", 带 -m 保存则形如 "On main: <自定义信息>".

func ParseGitStashList

func ParseGitStashList(out string) []GitStash

ParseGitStashList 解析 gitStashFormat 的 0x1f 分隔输出(纯函数) 用 0x1f(unit separator)分隔四个字段, 避免 stash 信息里的普通标点导致误切. 空行跳过, 字段数不齐的行跳过, 序号解析失败时 Index 记 -1 而不丢掉整条.

type GitStatusEntry

type GitStatusEntry struct {
	Staged   byte
	Unstaged byte
	Path     string
	OrigPath string
}

GitStatusEntry 是 `git status --porcelain=v1` 的一行 Staged 是 X 列(index 状态), Unstaged 是 Y 列(worktree 状态), 取值如 'M','A','D','R','?',' ' 等. 重命名时 OrigPath 为箭头左侧的旧路径.

func GitStatusPorcelain

func GitStatusPorcelain(dir string) ([]GitStatusEntry, error)

GitStatusPorcelain 解析 `git status --porcelain=v1` 的输出 每行前两个字符是 X(staged)/Y(unstaged)两列状态, 第 4 个字符起是路径. 重命名行形如 "R old -> new", 此时 Path 取箭头右侧新路径, OrigPath 取左侧旧路径. 路径含特殊字符时 git 会给整段加双引号并做 C 风格转义; 这里只做最小处理: 若路径被双引号包裹, 仅剥掉首尾引号, 不做完整的 C 风格反转义(超出当前范围). 单行畸形(长度不足)时跳过该行继续, 永远不 panic.

func ParseGitPorcelainLines

func ParseGitPorcelainLines(out string) []GitStatusEntry

ParseGitPorcelainLines 解析不带 -z 的 `git status --porcelain=v1` 输出(纯函数) 与 ParseGitPorcelainZ 同样产出 GitStatusEntry, 但要处理换行分隔形态下的两件麻烦事: 重命名行写作 "old -> new"(与 -z 的顺序相反), 且含特殊字符的路径被加引号并做了 C 风格转义 —— 后者交给 DecodeGitQuotedPath 完整还原(core/git.go 的老解析只剥引号). 用途是解析已经拿到手的非 -z 文本; 自己发命令时优先用 Status(-z).

func ParseGitPorcelainZ

func ParseGitPorcelainZ(data string) []GitStatusEntry

ParseGitPorcelainZ 解析 `git status --porcelain=v1 -z` 的输出(纯函数) 每条记录是 "XY <path>" 后跟一个 NUL: X 是 index 列, Y 是工作树列, 第三字节是分隔空格. 重命名/复制(X 或 Y 为 'R'/'C')会多占一个字段 —— 紧跟其后的下一个 NUL 字段是旧路径, 注意顺序与不带 -z 的 "old -> new" 相反: -z 下先出现的是新路径. 路径原样输出, 因此不需要任何反转义. 长度不足或第三字节不是空格的畸形记录跳过并继续, 永远不 panic.

type GoListModule

type GoListModule struct {
	Path string
	Main bool
	Dir  string
}

GoListModule 是 GoListPackage.Module 的内嵌子集 仅取 IDE 需要的三个字段; Path/Dir 用来区分主模块和依赖模块, Main 用来标识"是不是当前工作区里的那一个 module"

type GoListPackage

type GoListPackage struct {
	Dir          string
	ImportPath   string
	Name         string
	GoFiles      []string
	TestGoFiles  []string
	XTestGoFiles []string
	Module       *GoListModule // 指针: nil 表示输出里没有 Module 字段(GOPATH 模式或 stdlib)
}

GoListPackage 描述 `go list -json ./...` 输出中我们关心的字段 仅覆盖 IDE 所需的最小子集: 目录, 导入路径, 包名, GoFiles, 测试文件, 所属 Module 字段还可扩展(Imports, Deps, CompiledGoFiles, EmbedFiles 等), 但当前 IDE 还 没有使用场景, 先保持精简, 按需要再加

func LoadGoListJSON

func LoadGoListJSON(dir string, args ...string) ([]GoListPackage, error)

LoadGoListJSON 是 RunGoList + ParseGoListJSON 的便捷封装 即使 go list 本身报错也会尝试解析其 stdout(go list 有"边报错边出 JSON"的行为) 这样能在依赖缺失时仍把可用的 package 信息提交给 IDE

只解析 stdout; stderr 只在出错时附到 error 里供展示, 绝不喂给 JSON 解析器.

func ParseGoListJSON

func ParseGoListJSON(src string) ([]GoListPackage, error)

ParseGoListJSON 解析 `go list -json ./...` 的输出 `go list` 不输出 JSON 数组, 而是把每个 package 的对象逐个 pretty-print 拼接, 所以这里用 json.Decoder 在一个 strings.Reader 上循环 Decode, 直到 io.EOF. 遇到单个对象解析失败时跳过该对象继续, 最后把所有错误打包到一个 wrapped error 里返回, 调用方仍能拿到此前已成功解析的 package 切片. 永远不 panic.

type GoMod

type GoMod struct {
	Module    string
	GoVersion string
	// Toolchain 是 toolchain 指令的原始值(如 "go1.24.3" 或 "default"),
	// 没有该指令时为 "". 它比 go 指令更强: 有 toolchain 时实际构建用的是
	// 它指定的版本, 所以IDE展示"Go版本"应走 EffectiveGoVersion
	Toolchain string
	Requires  []GoModRequire
	Replaces  []GoModReplace
}

GoMod 描述一个go.mod文件中我们关心的内容 仅覆盖IDE所需的最小子集: module路径, go版本, toolchain, require, replace 未支持的指令: retract, exclude, godebug (这些在实际项目中较少见)

func LoadGoMod

func LoadGoMod(startDir string) (*GoMod, error)

LoadGoMod 从startDir向上查找go.mod并解析 找不到时返回wrapped error; 解析出错时仍可能返回部分结果

func ParseGoMod

func ParseGoMod(src string) (*GoMod, error)

ParseGoMod 解析go.mod文本 容忍块状的require/replace, 自动剥离 "// comments", 跳过空白行 当某行格式不规范时, 返回部分结果和包装后的error, 不会panic

func (*GoMod) EffectiveGoVersion

func (m *GoMod) EffectiveGoVersion() string

EffectiveGoVersion 返回该模块实际构建时使用的Go版本(不带"go"前缀) 有可用的 toolchain 指令时以它为准, 否则退回 go 指令的版本 "default" 是 toolchain 的特殊值, 表示"用本地默认工具链", 按无 toolchain 处理

type GoModReplace

type GoModReplace struct {
	From    string
	FromVer string // 可选, 可能为""
	To      string
	ToVer   string // 可选, 可能为""
}

GoModReplace 一行replace信息 形如 "replace A v1 => B v2", 其中版本号可省略

type GoModRequire

type GoModRequire struct {
	Path     string
	Version  string
	Indirect bool
}

GoModRequire 一行require信息

type GoWork

type GoWork struct {
	GoVersion string
	Uses      []string // 相对或绝对的模块目录
	Replaces  []GoModReplace
}

GoWork 描述一个go.work文件中我们关心的内容 仅覆盖IDE所需的最小子集: go版本, use目录列表, replace 未支持的指令: toolchain, godebug, godebug(...)块 (这些在实际go.work中较少出现且 不影响"哪些模块在工作区里"这一核心信号)

Replaces 复用 gomod.go 中的 GoModReplace: go.work 的 replace 指令与 go.mod 完全同形态 (from [ver] => to [ver]), 共用同一结构能让上层调用方 用同一套渲染/校验逻辑处理两边的替换规则, 因此这里不再定义平行类型.

func LoadGoWork

func LoadGoWork(startDir string) (*GoWork, error)

LoadGoWork 从startDir向上查找go.work并解析 找不到时返回wrapped error; 解析出错时仍可能返回部分结果

func ParseGoWork

func ParseGoWork(src string) (*GoWork, error)

ParseGoWork 解析go.work文本 容忍块状的use/replace, 自动剥离 "// comments", 跳过空白行 当某行格式不规范时, 返回部分结果和包装后的error, 不会panic

type Goroutine

type Goroutine struct {
	ID       int
	File     string
	Line     int
	Function string
}

Goroutine 是 dlv 看到的一个用户 goroutine CurrentLoc 是 PC 当前所在位置, UserCurrentLoc 是去掉 runtime 帧之后的用户视角位置. IDE 显示用的是后者 -- 用户更关心自己的代码, 不在乎卡在 runtime.gopark.

type IClose

type IClose interface {
	Close()
}

用来关闭对象的接口 此接口应安静地释放资源并返回, Close()以后对象失效, 并可随意抛弃. 注: 如果是文档类对象(即可以选择是否保存的), Close() 里不应执行保存操作.

type IEnumProperties

type IEnumProperties interface {
	EnumProperties(list IPropertyList)
}

IEnumProperties is implemented by objects that expose configurable properties to a property sheet or inspector.

type IIni

type IIni interface {
	AttrWriter
	AttrReader

	// 同步, 较费时, 建议调用频率>1分钟
	Sync()

	// 上次同步时间
	SyncTime() time.Time

	// 上次修改时间
	ModifiedTime() time.Time
}

Ini接口 Ini用来保存系列属性值, 平时驻留内存以保证读写效率 适当时候可调用Sync来和后台文件同步

type IPersist

type IPersist interface {
	OnPersistSave(p IPersistSaver)
	OnPersistLoad(p IPersistLoader)
}

func PersistLoad

func PersistLoad(doc PersistData) (IPersist, error)

func PersistLoadFile

func PersistLoadFile(path string) (IPersist, error)

type IPersistLoader

type IPersistLoader interface {
	Read(key string, ptr interface{}) error

	Enter(key string) bool
	Leave()
}

type IPersistSaver

type IPersistSaver interface {
	Write(key string, v interface{}) error

	Enter(key string) bool
	Leave()
}

type IPropertyList

type IPropertyList interface {
	AddProperty(id string, get, set interface{})
}

IPropertyList is the minimal interface for property enumeration. Widgets call AddProperty to expose their configurable properties.

type IPsLoaded

type IPsLoaded interface {
	OnPersistLoaded() error
}

type Kit

type Kit struct {
	Name      string            `json:"name"`
	GoExe     string            `json:"go_exe"`     // go 可执行文件路径, 默认 "go"
	GoVersion string            `json:"go_version"` // 不带 "go" 前缀, 如 "1.25.0"
	GOOS      string            `json:"goos"`
	GOARCH    string            `json:"goarch"`
	Tags      []string          `json:"tags,omitempty"`
	Env       map[string]string `json:"env,omitempty"`
	Race      bool              `json:"race,omitempty"`
	Coverage  bool              `json:"coverage,omitempty"`
	BuildMode string            `json:"build_mode,omitempty"` // go build -buildmode, "" = 默认
	OutputDir string            `json:"output_dir,omitempty"`
	Deploy    DeployProfile     `json:"deploy"`
}

Kit 一套具名的构建配置

func DefaultKit

func DefaultKit() Kit

DefaultKit 返回零配置的本机Kit: 用PATH上的go, 目标平台是当前平台, 产物留在当前目录. 它不起子进程, 因此可以在Init/热路径/测试里随意调用

func DetectKits

func DetectKits() ([]Kit, error)

DetectKits 探测本机可用的工具链 当前只认PATH上的 go: 用 `go env GOOS GOARCH GOVERSION` 问出它的目标平台和版本, 折成一个本机Kit返回. PATH上没有go时返回 (nil, error), 调用方据此退回 DefaultKit

func (Kit) BuildArgs

func (k Kit) BuildArgs() []string

BuildArgs 渲染该Kit对应的 `go build` 参数, 顺序固定: -tags, -race, -cover, -buildmode. 包路径和 -o 由调用方按 OutputDir 自行拼

func (Kit) BuildEnv

func (k Kit) BuildEnv() []string

BuildEnv 渲染该Kit要追加到 exec.Cmd.Env 的 "K=V" 列表 GOOS/GOARCH 在前, 其余自定义变量按key排序, 保证同一个Kit每次输出一致

func (Kit) Clone

func (k Kit) Clone() Kit

Clone 深拷贝Kit: Tags切片和Env映射都是新的 存储层/面板对外交付Kit时都走它, 免得调用方改到共享的底层容器

func (Kit) Validate

func (k Kit) Validate() error

Validate 检查Kit是否足以驱动一次构建 只校验构建系统真正会读的字段: 选择用的Name, 目标平台, -buildmode取值, build tag / 环境变量的格式, 以及部署配置自身是否自洽

type LSPApplyEditFunc

type LSPApplyEditFunc func(label string, edit *LSPWorkspaceEdit) bool

LSPApplyEditFunc 处理服务器发来的 workspace/applyEdit 宿主拿到已经解好的 LSPWorkspaceEdit (含 documentChanges 的版本号与资源操作), 把它落到编辑器/磁盘上, 然后返回是否真的应用成功 —— 这个布尔值会作为 {"applied": bool} 回给服务器, 服务器据此决定 executeCommand 是成功还是失败.

type LSPCallHierarchyCall

type LSPCallHierarchyCall struct {
	Item   LSPCallHierarchyItem
	Ranges []LSPRange
}

LSPCallHierarchyCall 是调用树上的一条边 Item 是"另一端": incomingCalls 时它是调用方 (from), outgoingCalls 时它是被调方 (to). Ranges 是调用点的位置 (规范里的 fromRanges), 用来在源码里逐个跳过去.

type LSPCallHierarchyItem

type LSPCallHierarchyItem struct {
	Name           string          `json:"name"`
	Kind           int             `json:"kind"` // LSP SymbolKind (Function=12, Method=6, ...)
	Detail         string          `json:"detail,omitempty"`
	URI            string          `json:"uri"`
	Range          LSPRange        `json:"range"`
	SelectionRange LSPRange        `json:"selectionRange"`
	Data           json.RawMessage `json:"data,omitempty"`
}

LSPCallHierarchyItem 是调用树上的一个节点 (一个函数/方法) JSON tag 跟线协议一一对应, 因为它既要被解码, 也要被 *原样回传*: callHierarchy/incomingCalls 与 outgoingCalls 的 params 就是 {item: <这个对象>}, 其中 Data 是服务器自己的私货 (gopls 拿它缓存位置信息), 必须一字不改地送回去, 所以留成 RawMessage.

type LSPClient

type LSPClient struct {
	// contains filtered or unexported fields
}

LSPClient 是一个跑着的 LSP 服务器子进程 + 跟它建立的 LSP 协议会话 同一个 client 可以并发 SendRequest, ID 分配/pending 路由由 mu 保护, 实际写 stdin 的字节流由 writeMu 串行化 -- WriteLSPMessage 内部分 "header 写" 和 "body 写" 两步, 多 goroutine 共用一个 pipe 时必须串起来, 否则两个并发 请求的 header/body 会交错, 把帧搞乱.

func LaunchLSPClient

func LaunchLSPClient(serverCmd string, args ...string) (*LSPClient, error)

LaunchLSPClient 拉起 serverCmd args... 并建立 LSP 长连接 pipe 的方向:

  • cmd.Stdin <- 我们写 (请求/通知)
  • cmd.Stdout -> 我们读 (响应/服务器通知)
  • cmd.Stderr -> 我们 drain (日志, 防止满管道导致服务器卡住)

读循环作为 goroutine 在返回前就跑起来; 任何启动错误都会清理子进程后回报.

func (*LSPClient) CallHierarchyPrepare

func (c *LSPClient) CallHierarchyPrepare(uri string, line, character int) ([]LSPCallHierarchyItem, error)

CallHierarchyPrepare 请求 textDocument/prepareCallHierarchy 调用层次是两步协议: 先用光标位置 prepare 出根节点 (可能多个, 比如同名方法), 再拿根节点去问 incoming/outgoing. 这一步不给结果就没法展开调用树.

func (*LSPClient) Close

func (c *LSPClient) Close() error

Close 优雅关闭: shutdown 请求 -> exit 通知 -> 关 stdin -> 等子进程 / 兜底 Kill 任何一步出错都不阻断后续: 最终目标是把子进程清掉, 不漏 fd / 进程. 跟 dlv.go 里 Close 的写法保持一致: 锁内只翻 closed 标志, 实际清理放在锁外.

func (*LSPClient) CodeAction

func (c *LSPClient) CodeAction(uri string, startLine, startChar, endLine, endChar int) ([]LSPCodeAction, error)

CodeAction 请求 textDocument/codeAction 并把灯泡菜单项的标题列出来 params 需要 range + context.diagnostics; 我们只想列"这个区间有哪些动作", 不带具体诊断, context.diagnostics 给空数组即可 (gopls 仍会给出 source/refactor 类项). 响应是一个数组, 每个元素是两种形态之一, server 混着发都合法:

  • 裸 Command: {title, command, arguments} -- 只有 title, 无 kind
  • CodeAction: {title, kind, edit?, command?} -- 有 kind

两者都带 title, 所以宽松解码: title 必取, kind / edit / command 有则取无则留空.

  • edit 是一份内联 WorkspaceEdit, 复用 rename 同款双形态折叠 (decodeWorkspaceEdit), 压平到 Edit.Changes 供 ApplyTextEdits 应用.
  • 裸 Command 形态没有 edit, 顶层的 command/title 折进 Command 字段.

缺省字段一律留 nil/空, 不在"形态合法但稀疏"的项上报错. 真正执行 command (workspace/executeCommand) 留作后续 commit, 这一版只把数据透出来. null (区间内无可用动作) 归一成空切片, 不当错误. 受默认 10s SendRequest 超时约束.

func (*LSPClient) CodeLens

func (c *LSPClient) CodeLens(uri string) ([]LSPCodeLens, error)

CodeLens 请求 textDocument/codeLens 拿文件里的可点动作 gopls 的 code lens 是"跑测试/跑基准/更新依赖/生成代码"这类入口: 它们挂在 func TestXxx 那一行上, 点一下就走 workspace/executeCommand. 拿到 Command 后 直接喂 ExecuteCommand 即可. null / 服务器不支持 -> 空切片.

func (*LSPClient) Completion

func (c *LSPClient) Completion(uri string, line, character int) ([]LSPCompletionItem, error)

Completion 请求 textDocument/completion 并返回补全项列表 gopls 在两种合法响应形状之间任意切换:

  • CompletionList: {"isIncomplete": bool, "items": []CompletionItem}
  • 直接的 []CompletionItem

这里都接住: 先按 CompletionList 解, items 非空就用; 否则当裸数组解. 受默认 10s SendRequest 超时约束.

func (*LSPClient) Definition

func (c *LSPClient) Definition(uri string, line, character int) ([]LSPLocation, error)

Definition 请求 textDocument/definition 并归一为 []LSPLocation 规范允许 server 在两种形态间任选:

  • 单个 Location 对象
  • []Location (gopls 在跨实例 / 嵌入 / 接口实现处会用这个)

null 表示没找到定义, 返回 (nil, nil), 上层照空切片处理即可. 受默认 10s SendRequest 超时约束.

func (*LSPClient) DidChange

func (c *LSPClient) DidChange(uri string, version int, fullText string) error

DidChange 发 textDocument/didChange 通知, 把整个文件最新内容推给 server LSP 支持 incremental sync (按 range 发 diff), 这里走最简单的 *full document sync*: 一次性把整篇 fullText 重新塞过去. 对一个文件大小一般 < 1MB 的 Go 项目, 完全够用, 也避免维护一份精确的 diff 状态机. version 是单调递增的 文档版本号, 跟前一次 didOpen/didChange 的 version 配套递增, 让 server 能 判别响应里的位置是基于哪个版本算出来的. 通知不会有响应; 失败仅意味着写 pipe 失败.

func (*LSPClient) DidClose

func (c *LSPClient) DidClose(uri string) error

DidClose 发 textDocument/didClose, 告诉服务器某文档已关闭. 不发的话 gopls 会一直持有该文档 (过期 version + 诊断), 每关一个 tab 泄漏一份幽灵文档.

func (*LSPClient) DidOpen

func (c *LSPClient) DidOpen(uri, languageID, text string, version int) error

DidOpen 发 textDocument/didOpen 通知, 把一个文件注册给服务器 gopls 在对一个文件做 hover/definition/completion 之前都需要先看到 didOpen -- 它不读文件系统, 内容以 client 这边的视图为准. languageID 一般是 "go".

func (*LSPClient) DocumentHighlight

func (c *LSPClient) DocumentHighlight(uri string, line, character int) ([]LSPDocumentHighlight, error)

DocumentHighlight 请求 textDocument/documentHighlight 并返回光标下符号在*当前文件* 内的所有出现处 (编辑器里"选中一个标识符, 同文件内所有同名引用泛起淡色底"的效果). 跟 References 的区别: 只在本文件里找, 不跨文件, 也不带 context. params 是共用的 TextDocumentPositionParams. 响应固定是 []DocumentHighlight (没有 definition 那种 多态), null (光标不在符号上) 归一成空切片, 不当错误. 受默认 10s SendRequest 超时约束.

func (*LSPClient) DocumentSymbol

func (c *LSPClient) DocumentSymbol(uri string) ([]LSPSymbol, error)

DocumentSymbol 请求 textDocument/documentSymbol 并归一成扁平/层级化的 LSPSymbol params 只要 {textDocument:{uri}}, 没有 position. 响应形状由 server 能力决定, 规范允许两种, gopls 走前者:

  • hierarchical []DocumentSymbol: {name, detail, kind, range, selectionRange, children []DocumentSymbol} -- 嵌套, 有 range/selectionRange, *没有* 顶层 location
  • legacy []SymbolInformation (扁平): {name, kind, location:{uri, range}, containerName} -- 没有 children, 位置藏在 location.range 里

区分手段: 探测数组里第一个元素有没有 "location" 字段. 有 -> SymbolInformation, 否则当 DocumentSymbol (它用 range/selectionRange, 没有顶层 location). 两种都 拿不准时偏向 DocumentSymbol -- 它是现代 server 的默认, 也是 gopls 的形态. DocumentSymbol 保留层级 (Children 递归填充); SymbolInformation 返回扁平切片 (Children 为空). null/空数组 -> 空切片, 不当错误. 受默认 10s SendRequest 超时约束.

func (*LSPClient) ExecuteCommand

func (c *LSPClient) ExecuteCommand(command string, arguments []json.RawMessage) (json.RawMessage, error)

ExecuteCommand 请求 workspace/executeCommand, 真正执行一条 command-form 的动作 CodeAction 返回的项分两类: 一类自带内联 edit (直接 ApplyTextEdits 就生效), 另一类只给一个 command + arguments (gopls 的 "organize imports" / "extract function" 等 refactor), 必须回抛给服务器执行 -- 那就是这个 RPC. 上层从 LSPCodeAction.Command 里取出 Command / Arguments 原样喂进来.

params 形状是 {command, arguments}. arguments 是一组已经序列化好的原始 JSON (每条 command 的参数形态各异, 不在这层解释); nil 时补成空数组 [] 而不是 null -- gopls 对 arguments 缺省/为 null 会报错, 空数组是最安全的取值.

返回服务器的原始 Result: 大多数 gopls 命令的副作用是反过来发一个 workspace/applyEdit 请求, result 本身回 null, 这种情况归一成 (nil, nil); 个别命令会回一个 JSON 结果, 原样透出给上层解释. server 端错误经 SendRequest 包好后原样透出. 受默认 10s SendRequest 超时约束.

func (*LSPClient) Formatting

func (c *LSPClient) Formatting(uri string) ([]LSPTextEdit, error)

Formatting 请求 textDocument/formatting 并返回把整篇文档格式化所需的编辑 gopls 对 Go 文件等价于跑一遍 gofmt: 它要 options{tabSize, insertSpaces}, Go 用 tab 缩进, 所以默认 {tabSize:4, insertSpaces:false} (insertSpaces=false 时 tabSize 仅作展示宽度提示, gopls 实际产出真 tab). 响应是 []TextEdit, null (无需改动 / server 不支持) 归一成空切片, 不当错误. 受默认 10s SendRequest 超时约束.

func (*LSPClient) Hover

func (c *LSPClient) Hover(uri string, line, character int) (*LSPHover, error)

Hover 请求 textDocument/hover 并把多态的 contents 压平成一个字符串 规范里 contents 有三种合法形态:

  • 字符串 -> 直接用
  • MarkupContent{kind, value} -> 取 value (gopls 默认走这个)
  • 数组 (string 或 MarkedString) -> 用 "\n" join

服务器返回 null (光标位置没有可悬停信息) 时, 返回 (nil, nil), 不当错误. 受默认 10s SendRequest 超时约束.

func (*LSPClient) Implementation

func (c *LSPClient) Implementation(uri string, line, character int) ([]LSPLocation, error)

Implementation 请求 textDocument/implementation: 接口方法 -> 所有实现 跟 Definition 是一对: Definition 从用法跳到声明, Implementation 从接口 (或接口方法) 跳到实现它的具体类型 (或方法). Go 项目里这是读代码的主力操作. 响应跟 definition 同样多态 (Location | []Location | []LocationLink), 一并归一.

func (*LSPClient) IncomingCalls

func (c *LSPClient) IncomingCalls(item LSPCallHierarchyItem) ([]LSPCallHierarchyCall, error)

IncomingCalls 请求 callHierarchy/incomingCalls: 谁调用了这个函数 item 必须是 CallHierarchyPrepare (或上一层 IncomingCalls) 返回的节点原物 -- 它的 Data 字段要原样回传给服务器.

func (*LSPClient) Initialize

func (c *LSPClient) Initialize(params LSPInitializeParams) (json.RawMessage, error)

Initialize 走 LSP 规范的 initialize -> initialized 握手 流程:

  1. 补默认 capabilities / workspaceFolders (调用方给了就不动)
  2. SendRequest("initialize", params) 阻塞等响应
  3. 记下服务器 capabilities (供 ServerCapabilities / 能力预判用)
  4. 发 "initialized" notification (规范要求)

返回的是 initialize 响应里的原始 Result (包含 server capabilities 等), 调用方按需 json.Unmarshal 到自己关心的结构上.

func (*LSPClient) InlayHint

func (c *LSPClient) InlayHint(uri string, startLine, startChar, endLine, endChar int) ([]LSPInlayHint, error)

InlayHint 请求 textDocument/inlayHint 拿一个区间内的内嵌提示 params 是 {textDocument, range}: 只问可视区间, 别整篇文件都要 (大文件上 服务器要算很久). 响应是 []InlayHint, 其中 label/tooltip 都是多态字段, 压平.

gopls 的每一类提示都是默认关闭的: provider 在, 但不给设置就回空数组. 要看到 提示得在 InitializationOptions / 配置里打开对应开关, 例如 {"hints":{"parameterNames":true,"assignVariableTypes":true}}.

func (*LSPClient) Notifications

func (c *LSPClient) Notifications() <-chan *LSPMessage

Notifications 暴露服务器主动推送的通知通道 典型消费者: publishDiagnostics, window/logMessage, window/showMessage. 通道是 buffered=64; 上层不消费时会丢消息 (见 routeMessage), 这是有意的: LSP 通知本质上 best-effort, 不应该让一个迟到的消费者把读循环堵死. 读循环退出时 (server 退出 / Close) 该通道会被 close, 因此上层可以放心用 `for m := range c.Notifications()` 消费 -- 循环随会话结束而终止, 不会把 drain goroutine 泄漏在每次 client 重启之后.

func (*LSPClient) OutgoingCalls

func (c *LSPClient) OutgoingCalls(item LSPCallHierarchyItem) ([]LSPCallHierarchyCall, error)

OutgoingCalls 请求 callHierarchy/outgoingCalls: 这个函数调用了谁

func (*LSPClient) PrepareRename

func (c *LSPClient) PrepareRename(uri string, line, character int) (*LSPPrepareRename, error)

PrepareRename 请求 textDocument/prepareRename, 在真正改名之前问一句"能改吗" 这是 IDE 里 F2 的第一步: 先拿到待改区间和建议名字, 弹输入框, 用户确认后才发 textDocument/rename. 没有它的话, 光标落在不可改名的位置 (关键字/字面量/标准 库符号) 时只能等 rename 报错, 体验上是"输入了新名字, 然后报错".

响应有三种合法形态, 这里都吃:

{"start":...,"end":...}                  裸 Range
{"range":{...},"placeholder":"Foo"}      Range + 建议名 (gopls 走这个)
{"defaultBehavior":true}                 让客户端按默认规则处理

null (不可改名) -> (nil, nil).

func (*LSPClient) References

func (c *LSPClient) References(uri string, line, character int, includeDecl bool) ([]LSPLocation, error)

References 请求 textDocument/references 并返回所有出现处 规范固定只返回 []Location 一种形态 (没有像 definition 那样的多态). includeDecl 控制是否把定义点也算一次引用, 通常上层是 true (跟 IDE 一致). 受默认 10s SendRequest 超时约束.

func (*LSPClient) Rename

func (c *LSPClient) Rename(uri string, line, character int, newName string) (*LSPWorkspaceEdit, error)

Rename 请求 textDocument/rename 并把跨工作区的改动归一成 LSPWorkspaceEdit 响应里的 WorkspaceEdit 有两种合法形态, server 任选:

  • changes: {uri: []TextEdit} -- 简单 map 形态, 优先吃这个
  • documentChanges: [{textDocument:{uri}, edits:[]TextEdit}] -- 带版本号的形态

处理顺序: 先看 changes, 非空就用; changes 缺省时再折叠 documentChanges 到同一个 Changes map (丢掉版本号, 上层只关心 uri->edits). 两者都给时以 changes 为准 -- 简单形态信息无损, 不必再读版本化形态. server 返回 null (符号不可改 / 没有出现处) 时返回 (nil, nil), 不当错误. 受默认 10s SendRequest 超时约束.

func (*LSPClient) SemanticTokensFull

func (c *LSPClient) SemanticTokensFull(uri string) (*LSPSemanticTokens, error)

SemanticTokensFull 请求 textDocument/semanticTokens/full 拿整篇文件的语义着色 语义着色是"编辑器着色跟编译器一致"的唯一途径: 正则/词法着色分不出一个标识符 是类型、变量还是函数, 更分不出它是不是标准库的.

注意 gopls 默认不开: 实测 v0.22 只在设置里 semanticTokens 为 true 时才登记 semanticTokensProvider, 所以宿主要在 LSPInitializeParams.InitializationOptions 里给 {"semanticTokens":true} (或在 SetConfigurationHandler 里回同样的设置). 没开时服务器 capabilities 里就没这一项, 本方法按能力缺失返回 (nil, nil).

func (*LSPClient) SemanticTokensFullDelta

func (c *LSPClient) SemanticTokensFullDelta(uri, previousResultID string) (*LSPSemanticTokensDelta, error)

SemanticTokensFullDelta 请求 textDocument/semanticTokens/full/delta 拿增量着色 previousResultID 是上一次 SemanticTokensFull / 本方法返回的 ResultID. 打字的时候 每次都拉整份 token 数组很浪费 (大文件几万个 uint32), 增量只回改动的那一段. 服务器可以随时决定"这次算不出增量", 直接回整份 —— 那时 Full=true, Data 有效. null / 服务器不支持 -> (nil, nil).

func (*LSPClient) SendNotification

func (c *LSPClient) SendNotification(method string, params interface{}) error

SendNotification 发一条无 ID 的 LSP 通知, 立刻返回 服务器不会回; 失败仅意味着写 pipe 失败 (子进程死了等).

func (*LSPClient) SendRequest

func (c *LSPClient) SendRequest(method string, params interface{}) (*LSPMessage, error)

SendRequest 发一条带 ID 的 LSP 请求, 等服务器把同 ID 响应送回来 串行/并发都安全. 默认 10s 超时, 超时后:

  • 把 pending[id] 清掉, 防止读循环之后误送到一个无人监听的 chan
  • 返回 context-deadline 风格错误

不取消子进程: 单条请求超时不应该直接撕掉整个会话, 上层若决定终止, 自行 Close.

func (*LSPClient) ServerCapabilities

func (c *LSPClient) ServerCapabilities() json.RawMessage

ServerCapabilities 返回 initialize 响应里的 capabilities 对象 (原始 JSON) 没握手 / 服务器没给时返回 nil. 用途举例: 从 semanticTokensProvider.legend 里读 token 类型表, 好把 SemanticTokensFull 的数字下标翻成名字.

func (*LSPClient) SetApplyEditHandler

func (c *LSPClient) SetApplyEditHandler(fn LSPApplyEditFunc)

SetApplyEditHandler 注册 workspace/applyEdit 的落地器 不注册时客户端回 {"applied": false, "failureReason": ...}: 协议上仍然完整 (服务器不会悬着), 但编辑不会生效. 可以传 nil 撤销.

func (*LSPClient) SetConfigurationHandler

func (c *LSPClient) SetConfigurationHandler(fn LSPConfigurationFunc)

SetConfigurationHandler 注册 workspace/configuration 的配置提供者 不注册时客户端仍然会回包 (每项 {}), 只是不带任何自定义设置. 可以传 nil 撤销.

func (*LSPClient) SignatureHelp

func (c *LSPClient) SignatureHelp(uri string, line, character int) (*LSPSignatureHelp, error)

SignatureHelp 请求 textDocument/signatureHelp 并压平成 LSPSignatureHelp params 是跟 hover/completion 同形的 TextDocumentPositionParams. 响应:

SignatureHelp{signatures []SignatureInformation, activeSignature, activeParameter}

其中每条 SignatureInformation 的 documentation 跟 hover.contents 同样是

string | MarkupContent | []MarkedString 多态, 直接复用 stringifyHoverContents.

形参 documentation 当前不暴露 (UI 只展 label), 真要时在 LSPSignature 上扩字段即可. server 返回 null (光标不在调用实参里) 时返回 (nil, nil), 不当错误. 受默认 10s SendRequest 超时约束.

func (*LSPClient) Subtypes

Subtypes 请求 typeHierarchy/subtypes: 这个类型的"下层" (Go: 实现它的类型)

func (*LSPClient) Supertypes

func (c *LSPClient) Supertypes(item LSPTypeHierarchyItem) ([]LSPTypeHierarchyItem, error)

Supertypes 请求 typeHierarchy/supertypes: 这个类型的"上层" (Go: 它实现的接口)

func (*LSPClient) TypeHierarchyPrepare

func (c *LSPClient) TypeHierarchyPrepare(uri string, line, character int) ([]LSPTypeHierarchyItem, error)

TypeHierarchyPrepare 请求 textDocument/prepareTypeHierarchy 跟调用层次一样是两步协议: 先 prepare 出根节点, 再 Supertypes / Subtypes 展开.

func (*LSPClient) WorkspaceSymbol

func (c *LSPClient) WorkspaceSymbol(query string) ([]LSPWorkspaceSymbol, error)

WorkspaceSymbol 请求 workspace/symbol 做项目级符号搜索, 是 DocumentSymbol 的工作区对偶 params 只要 {query}: 空串表示"全部符号" (gopls 会返回一个有上界的集合), 非空则做模糊匹配. 响应是一个数组, 每个元素在两种合法形态间任选, server 混发都合法:

  • legacy SymbolInformation: {name, kind, containerName, location:{uri, range}} -- location 是完整 Location, 带 range
  • 现代 WorkspaceSymbol: {name, kind, containerName, location:{uri}} -- location 可能只带 uri, range 省略 (真要坐标时 server 靠 workspaceSymbol/resolve 补)

两种形态塞进同一个解码结构即可: location 用 LSPLocation 收, range 缺省时 json 保持零值, 于是 Line/Character 自然落到 0 -- 正是"缺 range 就默认 0"的期望, 无需特判. null/空数组 -> 空切片, 不当错误; server 端错误经 SendRequest 包好后原样透出. 受默认 10s SendRequest 超时约束.

type LSPCodeAction

type LSPCodeAction struct {
	Title   string
	Kind    string            // 裸 Command 形态为空
	Edit    *LSPWorkspaceEdit // 无内联编辑时为 nil
	Command *LSPCommand       // 无命令时为 nil
}

LSPCodeAction 是 code action 菜单里的一项 (灯泡里的 quick-fix / refactor) Title 必有; Kind 在 CodeAction 形态下有 (例如 "quickfix" / "refactor.extract"), 裸 Command 形态没有 kind 留空. Edit / Command 携带"如何应用这一项":

  • Edit 非 nil 时是一份内联 WorkspaceEdit, 直接喂给 ApplyTextEdits 即可生效.
  • Command 非 nil 时是一条待执行命令 (裸 Command 形态, 或 CodeAction 自带 command).

两者都可能缺省 (留 nil), 也可能同时存在. 真正执行 command 走 workspace/executeCommand, 那是后续 commit 的事, 这一版只负责把数据透出来.

type LSPCodeLens

type LSPCodeLens struct {
	Range   LSPRange
	Command *LSPCommand
	Data    json.RawMessage
}

LSPCodeLens 是一条 code lens: 挂在某一行上的可点击动作 Command 缺省 (nil) 表示这条 lens 是"未解析"的 —— 规范允许服务器先回位置, 等客户端发 codeLens/resolve 再补 command. 我们不宣称 resolveSupport, gopls 会直接把 command 给全; Data 是服务器私货, 留着以备将来接 resolve.

type LSPCommand

type LSPCommand struct {
	Title     string          `json:"title"`
	Command   string          `json:"command"`
	Arguments json.RawMessage `json:"arguments,omitempty"`
}

LSPCommand 是 LSP 的 Command: 一个可执行命令的引用 (title + command + 参数) code action 既可能是裸 Command (顶层就是这个形状), 也可能是 CodeAction 里 内嵌的 command 字段. Arguments 保留成 RawMessage 原样透出 -- 不同 command 的参数形状各异, 留给上层在真正 workspace/executeCommand 时再解释.

type LSPCompletionItem

type LSPCompletionItem struct {
	Label      string `json:"label"`
	Detail     string `json:"detail,omitempty"`
	Kind       int    `json:"kind,omitempty"`
	InsertText string `json:"insertText,omitempty"`

	InsertTextFormat    int           `json:"insertTextFormat,omitempty"` // 1=PlainText, 2=Snippet
	SortText            string        `json:"sortText,omitempty"`
	FilterText          string        `json:"filterText,omitempty"`
	Documentation       string        `json:"documentation,omitempty"` // 压平后的文档串
	TextEdit            *LSPTextEdit  `json:"textEdit,omitempty"`
	AdditionalTextEdits []LSPTextEdit `json:"additionalTextEdits,omitempty"`
}

LSPCompletionItem 是补全列表里的一条 gopls 在补全里把签名 / 注释往 Detail/Documentation 里塞, Label 是用户看到的 标识. 光有 Label/InsertText 是不够的, 少了下面这些字段, 补全在编辑器里会明显 不对劲:

  • TextEdit 服务器给的精确替换区间. 只按 InsertText 在光标处插入, 遇到"已经打了半个前缀"或需要往前吃掉一段 (比如把 foo.Bar 补成 (*foo).Bar) 就会出现重复文本. 有 TextEdit 时必须用它, 而不是自己猜区间.
  • AdditionalTextEdits 补全的连带编辑, 典型就是 gopls 的 "unimported completion": 选中 fmt.Println 时顺手把 import "fmt" 加进文件头. 丢掉 它意味着补全出来的代码编译不过.
  • InsertTextFormat 2 = Snippet, 文本里带 $1/${1:name} 占位符; 当纯文本 插进去用户会看到一串 $1.
  • SortText/FilterText 服务器给的排序/过滤键. 拿 Label 排序会把 gopls 精心 排好的相关度打乱 (它靠 sortText 前缀控制次序).
  • Documentation 悬浮文档, 跟 hover 一样是 string|MarkupContent 多态, 这里压平成字符串.

两个多态字段 (documentation / textEdit) 用不了结构体 tag 直接解, 所以本类型 自带 UnmarshalJSON.

func (*LSPCompletionItem) UnmarshalJSON

func (it *LSPCompletionItem) UnmarshalJSON(data []byte) error

UnmarshalJSON 解一条 CompletionItem, 顺带把两个多态字段压平 规范里这两个字段可以是两种形状, 直接用 tag 解会整条报错:

  • documentation: string | MarkupContent{kind, value}
  • textEdit: TextEdit{range, newText} | InsertReplaceEdit{insert, replace, newText}

做法是先解进影子结构 (多态字段收成 RawMessage), 再逐个压平. 压不动的多态值 按"缺省"处理而不是报错: 一个字段的形状怪异不应该让整份补全列表作废.

type LSPConfigurationFunc

type LSPConfigurationFunc func(scopeURI, section string) json.RawMessage

LSPConfigurationFunc 为 workspace/configuration 的一个 item 提供配置 scopeURI 是请求作用域 (通常是某个 workspace folder, 可能为空), section 是 配置命名空间 (gopls 问的是 "gopls"). 返回 nil 表示"这一项没有配置", 客户端 会回一个空对象 {} —— 服务器据此走它自己的默认值.

type LSPDocumentChange

type LSPDocumentChange struct {
	Kind      string        // "edit" | "create" | "rename" | "delete"
	URI       string        // edit/create/delete 的目标; rename 时是 oldUri
	NewURI    string        // 仅 rename: newUri
	Version   int           // 仅 edit: textDocument.version
	Versioned bool          // version 是否真的给了 (区分"版本 0"与"没给版本")
	Edits     []LSPTextEdit // 仅 edit

	Overwrite         bool // create/rename 选项
	IgnoreIfExists    bool // create/rename 选项
	Recursive         bool // delete 选项
	IgnoreIfNotExists bool // delete 选项
}

LSPDocumentChange 是 documentChanges 数组里的一项 Kind 决定其余字段怎么读:

"edit"   对 URI 的文本编辑, Edits 有效; Version/Versioned 是服务器算这份
         edit 时看到的文档版本 (可选, 服务器给 null 时 Versioned=false)
"create" 新建 URI; IgnoreIfExists/Overwrite 是可选选项
"rename" 把 URI 改名成 NewURI; IgnoreIfExists/Overwrite 同上
"delete" 删除 URI; Recursive 表示目录递归删, IgnoreIfNotExists 容忍不存在

type LSPDocumentHighlight

type LSPDocumentHighlight struct {
	Range LSPRange `json:"range"`
	Kind  int      `json:"kind,omitempty"` // 1=Text, 2=Read, 3=Write (可选)
}

LSPDocumentHighlight 是 textDocument/documentHighlight 的一条命中: 光标下符号在 当前文件里的一处出现. Kind 标出这处是读还是写 (1=Text 通用, 2=Read, 3=Write), 现代编辑器据此把"读"和"写"用不同底色区分; server 可省略 kind, omitempty 收零值.

type LSPError

type LSPError struct {
	Code    int             `json:"code"`
	Message string          `json:"message"`
	Data    json.RawMessage `json:"data,omitempty"`
}

LSPError 是 JSON-RPC 2.0 中 response.error 的内嵌对象 Code 取标准定义(-32700 ParseError, -32600 InvalidRequest, ...), 但本 文件不去校验具体取值, 仅做透明承载

type LSPHover

type LSPHover struct {
	Contents string
}

LSPHover 是简化后的 hover 结果 把 LSP 那一坨 contents 多态形态压平成一个字符串: UI 层只用得到这个.

type LSPInitializeParams

type LSPInitializeParams struct {
	ProcessID             int                  `json:"processId"`
	RootURI               string               `json:"rootUri"`
	Capabilities          json.RawMessage      `json:"capabilities,omitempty"`
	WorkspaceFolders      []LSPWorkspaceFolder `json:"workspaceFolders,omitempty"`
	InitializationOptions json.RawMessage      `json:"initializationOptions,omitempty"`
}

LSPInitializeParams 是 "initialize" 请求体 早期版本只带 ProcessID + RootURI, 把 capabilities 整段省掉. 省掉的代价不是 "少几个可选字段", 而是服务器会把所有 client 能力当 false 处理, 于是一整批 功能被静默降级:

  • 不宣称 workspace.applyEdit / configuration -> gopls 不会发 workspace/applyEdit, command 形态的重构 (organize imports / extract) 执行完什么都不会发生;
  • 不宣称 codeAction.codeActionLiteralSupport -> gopls 只回裸 Command, LSPCodeAction.Edit 永远是 nil;
  • 不宣称 semanticTokens / inlayHint / callHierarchy / typeHierarchy -> 对应的 provider 根本不出现在 server capabilities 里, 请求直接 MethodNotFound.

因此 Capabilities 缺省时 Initialize 会填 DefaultClientCapabilities(): 一份"我们真的实现了"的能力集 (见 defaultClientCapabilitiesJSON). 调用方要 自定义时把自己的 JSON 塞进 Capabilities 即可, 不会被覆盖. WorkspaceFolders 缺省且 RootURI 非空时, 自动补一个以 RootURI 为根的 folder -- 多模块工作区可以显式给多个.

InitializationOptions 是服务器私有的启动设置. 有些能力只有在这里打开之后服务器 才会把它登记进 capabilities: 实测 gopls v0.22 要拿到 semanticTokensProvider 得给 {"semanticTokens":true}, 要拿到非空的 inlay hint 得给 {"hints":{...}}.

type LSPInlayHint

type LSPInlayHint struct {
	Position     LSPPosition
	Label        string
	Kind         int
	PaddingLeft  bool
	PaddingRight bool
	Tooltip      string
	TextEdits    []LSPTextEdit
}

LSPInlayHint 是一条内嵌提示 (编辑器里灰色的虚拟文本) Go 里两个典型用途: 显示 := 推导出来的类型, 以及在调用实参前显示形参名.

  • Label 压平后的提示文本 (规范允许 string 或 []InlayHintLabelPart)
  • Kind 1=Type, 2=Parameter (可选, 服务器可省略)
  • PaddingLeft/PaddingRight 渲染时要不要留一个空格
  • Tooltip 压平后的悬浮说明
  • TextEdits "接受这条提示"时要应用的编辑 (例如把推导类型真的写进代码)

type LSPLocation

type LSPLocation struct {
	URI   string   `json:"uri"`
	Range LSPRange `json:"range"`
}

LSPLocation 是带 URI 的源代码定位, definition/references 的基本返回单元

type LSPMessage

type LSPMessage struct {
	JSONRPC string           `json:"jsonrpc"`
	ID      *json.RawMessage `json:"id,omitempty"`
	Method  string           `json:"method,omitempty"`
	Params  json.RawMessage  `json:"params,omitempty"`
	Result  json.RawMessage  `json:"result,omitempty"`
	Error   *LSPError        `json:"error,omitempty"`
}

LSPMessage 是一个解码后的 JSON-RPC 2.0 消息 同一个结构体覆盖 request / response / notification 三种用途:

  • request: JSONRPC + ID(string|number) + Method + Params
  • response: JSONRPC + ID(string|number) + (Result XOR Error)
  • notification: JSONRPC + Method + Params, ID 为 nil

ID 之所以是 *json.RawMessage 而不是 interface{}/string/int, 是为了:

  • 准确区分 "ID 缺失" 和 "ID 为 0/空字符串"
  • 透明 round-trip: server 给的 ID 是字符串还是数字, 原样还给上层

Params/Result 同样保留为 RawMessage, 让具体 LSP method 的反序列化由更 高一层来做(通过 DecodeParams), 这里不耦合任何 method-specific 类型.

func NewNotification

func NewNotification(method string, params interface{}) (*LSPMessage, error)

NewNotification 构造一个 JSON-RPC 通知消息 (无 ID) LSP 中所有 "didChange/didOpen/..." 都是 notification, 服务器不会回复

func NewRequest

func NewRequest(id int, method string, params interface{}) (*LSPMessage, error)

NewRequest 构造一个 JSON-RPC 请求消息 id 以数字形式编码; LSP 协议两种都接受, 数字是绝大多数客户端的默认选择 params 可以是 nil/任意可 JSON-marshal 的值; 已经是 json.RawMessage 时 也会按 raw bytes 直接进对应字段

func ReadLSPMessage

func ReadLSPMessage(r *bufio.Reader) (*LSPMessage, error)

ReadLSPMessage 从 bufio.Reader 中读取一条完整的 LSP 消息 流程:

  1. 按行读 header, 行结束符必须是 "\r\n"; 空行(只剩 "\r\n")标志 header 结束
  2. header 中必须含有 "Content-Length: N", 大小写不敏感
  3. 用 io.ReadFull 严格读 N 字节作为 body, 短读直接报错
  4. 用 encoding/json 把 body 反序列化成 LSPMessage

任何畸形输入都通过 error 返回; 不会 panic

type LSPPosition

type LSPPosition struct {
	Line      int `json:"line"`
	Character int `json:"character"`
}

LSPPosition 是 LSP 中通用的"行/列"坐标 (零基)

type LSPPrepareRename

type LSPPrepareRename struct {
	Range           LSPRange
	Placeholder     string
	DefaultBehavior bool
}

LSPPrepareRename 是 textDocument/prepareRename 的结果 语义是"这个位置能不能改名, 以及要改的那段文本在哪":

  • Range 待改名标识符的区间 (UI 拿它做预选 + 高亮)
  • Placeholder 服务器建议的初始文本; 缺省时 UI 自己从 Range 取
  • DefaultBehavior 服务器说"按客户端默认规则判定" (没给 range 的那种形态)

type LSPRange

type LSPRange struct {
	Start LSPPosition `json:"start"`
	End   LSPPosition `json:"end"`
}

LSPRange 是 LSP 的 [start, end) 文本区间

type LSPSemanticToken

type LSPSemanticToken struct {
	Line      int
	Character int
	Length    int
	TokenType int
	Modifiers int
}

LSPSemanticToken 是展开后的一个语义 token (绝对坐标) TokenType / Modifiers 是下标/位掩码, 对应服务器 capabilities 里 semanticTokensProvider.legend 的 tokenTypes / tokenModifiers 两张表.

func DecodeSemanticTokenData

func DecodeSemanticTokenData(data []uint32) []LSPSemanticToken

DecodeSemanticTokenData 把规范的相对编码展开成绝对坐标的 token 列表 编码规则: 每 5 个 uint32 一组; deltaLine 是相对上一个 token 的行增量, deltaStartChar 在同一行时是相对上一个 token 起点的列增量, 换行时就是绝对列. 长度不足 5 的尾巴丢掉 (畸形数据, 不 panic).

type LSPSemanticTokens

type LSPSemanticTokens struct {
	ResultID string
	Data     []uint32
}

LSPSemanticTokens 是 textDocument/semanticTokens/full 的结果 Data 是规范定义的扁平 uint32 数组, 每 5 个一组描述一个 token:

deltaLine, deltaStartChar, length, tokenType, tokenModifiers

前两个是相对上一个 token 的增量 (所以不能单独看某一组). 用 DecodeSemanticTokenData 展开成绝对坐标. ResultID 用来后续要增量 (Delta).

type LSPSemanticTokensDelta

type LSPSemanticTokensDelta struct {
	ResultID string
	Full     bool
	Data     []uint32
	Edits    []LSPSemanticTokensEdit
}

LSPSemanticTokensDelta 是 semanticTokens/full/delta 的结果 服务器有两种合法回法, Full 标出是哪种:

  • Full=false: Edits 有效, 拿它去改上一次的 Data (省流量的正常路径)
  • Full=true: 服务器放弃增量, 直接回了整份 tokens, Data 有效

type LSPSemanticTokensEdit

type LSPSemanticTokensEdit struct {
	Start       int
	DeleteCount int
	Data        []uint32
}

LSPSemanticTokensEdit 是一次增量: 把 Data 里 [Start, Start+DeleteCount) 换成 Data

type LSPSignature

type LSPSignature struct {
	Label         string
	Documentation string
	Parameters    []string
}

LSPSignature 是签名提示里的一条函数签名, SignatureInformation 的最小子集

  • Label 整条签名文本 (例如 "Println(a ...any) (n int, err error)")
  • Documentation 压平后的文档串 (复用 hover 的 contents 压平逻辑)
  • Parameters 每个形参的 label 文本

type LSPSignatureHelp

type LSPSignatureHelp struct {
	Signatures      []LSPSignature
	ActiveSignature int
	ActiveParameter int
}

LSPSignatureHelp 是 textDocument/signatureHelp 压平后的结果 ActiveSignature / ActiveParameter 指示 UI 该高亮哪条签名 / 哪个形参 (0 基).

type LSPSymbol

type LSPSymbol struct {
	Name     string
	Detail   string
	Kind     int
	Line     int
	Children []LSPSymbol
}

LSPSymbol 是文件大纲里的一个符号 (函数/类型/变量等), 扁平化后的最小子集 对应 IDE 的 outline / breadcrumb. 字段语义见 DocumentSymbol:

  • Detail 声明摘要 (函数签名等), legacy SymbolInformation 没有, 留空
  • Kind LSP SymbolKind 数字枚举 (Function=12, Struct=23, ...)
  • Line 0 基行号, 取 range.start.line
  • Children 仅在 hierarchical (DocumentSymbol) 形态下填充; legacy 形态为空

type LSPTextEdit

type LSPTextEdit struct {
	Range   LSPRange `json:"range"`
	NewText string   `json:"newText"`
}

LSPTextEdit 是一处文本编辑: 在 Range 区间上用 NewText 替换 formatting/rename 都用它当返回单元 -- LSP 的 TextEdit 就是 {range, newText}.

type LSPTypeHierarchyItem

type LSPTypeHierarchyItem struct {
	Name           string          `json:"name"`
	Kind           int             `json:"kind"` // LSP SymbolKind (Struct=23, Interface=11, ...)
	Detail         string          `json:"detail,omitempty"`
	URI            string          `json:"uri"`
	Range          LSPRange        `json:"range"`
	SelectionRange LSPRange        `json:"selectionRange"`
	Data           json.RawMessage `json:"data,omitempty"`
}

LSPTypeHierarchyItem 是类型层次上的一个节点 (一个类型) 跟 LSPCallHierarchyItem 同形 (含必须原样回传的 Data), 但语义不同: Go 里 supertypes 是"这个类型实现的接口", subtypes 是"实现了这个接口的类型".

type LSPWorkspaceEdit

type LSPWorkspaceEdit struct {
	Changes         map[string][]LSPTextEdit // uri -> edits (两种形态都填)
	DocumentChanges []LSPDocumentChange      // 保序的 documentChanges; changes-only 形态下为空
}

LSPWorkspaceEdit 是 rename / code action / applyEdit 跨文件改动的结果 LSP 的 WorkspaceEdit 有两种合法形态 (changes map 与 documentChanges 数组):

  • Changes 两种形态都会填, 压平成 uri -> edits, 老调用方照旧可用.
  • DocumentChanges 仅 documentChanges 形态有. 它比 Changes 多两样 *不能丢* 的信息: 每个文档的版本号 (拿它跟编辑器里的 version 对一下, 就知道这份 edit 是不是已经过期), 以及 create/rename/delete 三种资源操作 —— 后者 压根不是文本编辑, 压平到 Changes 里会彻底消失, 于是 "把文件重命名" 这类 重构表现成"什么都没发生".

顺序也只有 DocumentChanges 保得住: 资源操作跟文本编辑是有先后的 (先建文件 再往里写), map 遍历没有顺序.

type LSPWorkspaceFolder

type LSPWorkspaceFolder struct {
	URI  string `json:"uri"`
	Name string `json:"name"`
}

LSPWorkspaceFolder 是 initialize 的 workspaceFolders 项 (也是 workspace/workspaceFolders 请求的响应单元)

type LSPWorkspaceSymbol

type LSPWorkspaceSymbol struct {
	Name          string
	Kind          int
	ContainerName string
	URI           string
	Line          int // 0 基, 取 location.range.start.line
	Character     int
}

LSPWorkspaceSymbol 是 workspace/symbol 项目级搜索里的一条命中 (IDE 的 Cmd+T "Go to Symbol in Workspace"). 跟文件级的 LSPSymbol 不同, 工作区符号一定带 URI (符号落在哪个文件) 和 ContainerName (所属包/类型), 所以单开一个类型而不是 复用扁平的 LSPSymbol:

  • Kind LSP SymbolKind 数字枚举 (Function=12, Struct=23, ...)
  • Line/Character 0 基坐标, 取 location.range.start

type LogLevel

type LogLevel int

LogLevel 标识一条Log的级别, 供日志订阅者(sink)区分处理

const (
	LevelDebug LogLevel = iota
	LevelInfo
	LevelWarn
	LevelError
)

type LogSink

type LogSink func(level LogLevel, message string)

LogSink 是日志订阅回调, 每产生一条Log都会以对应级别和最终文本调用一次

type MergeChunk

type MergeChunk struct {
	Kind   MergeKind
	Base   []string
	Ours   []string
	Theirs []string
}

MergeChunk 是合并结果里的一段, 三个字段分别是该段在 base/ours/theirs 中的原始行:

  • MergeStable: Ours 与 Theirs 内容相同(即最终文本), 但 Base 可能不同 —— 双方做了同样的改动时就是这种情况;
  • MergeOurs: Theirs == Base, 最终取 Ours;
  • MergeTheirs: Ours == Base, 最终取 Theirs;
  • MergeConflict: 三者互不相同, 需要人工选边.

空的一侧用 nil 表示(纯插入/纯删除), 便于 reflect.DeepEqual 断言. 所有切片都是新分配的副本, 不会别名到调用方传进来的 base/ours/theirs.

func Merge3

func Merge3(base, ours, theirs []string) []MergeChunk

Merge3 对 base/ours/theirs 三份行切片做 diff3 式三方合并, 返回按 base 顺序排列的块列表. 不改动入参; 三方全空时返回 nil.

非重叠的改动被自动合并(块的 Kind 是 ours 或 theirs), 只有双方对同一段 base 改出不同内容时才产出 MergeConflict. 相邻的 stable 块会被并成一块, 于是"重复改动"折叠后不会在列表里留下碎片.

func ParseConflictMarkers

func ParseConflictMarkers(lines []string) ([]MergeChunk, error)

ParseConflictMarkers 把一份已经带 git 冲突标记的文本解析回块列表: 标记之外的普通文本成为 stable 块(三面同内容), 每个标记块成为一个 MergeConflict 块(没有 "|||||||" 段时 Base 为 nil).

容错策略与 ParseUnifiedDiff 一致: 出错也返回已解析出的块, 错误汇总到 一个 wrapped error, 永不 panic. 具体地

  • 冲突块内又出现 "<<<<<<<": 记一条错, 丢掉半个块重新开始;
  • 缺 "=======" 就直接 ">>>>>>>": 记一条错, theirs 侧当空;
  • 文本结束时块还没闭合: 记一条错, 把已累积的三面作为冲突块交出.

"=======" / "|||||||" 只在冲突块内部才当分隔符, 于是 Markdown 的下划线 和注释里的分隔线不会被误判; 长度不等于 7 的连字符串同样只是普通文本.

func (MergeChunk) Resolved

func (c MergeChunk) Resolved() []string

Resolved 返回该块在最终文本中应当出现的行. conflict 块返回 nil —— 它必须先由调用方(冲突编辑器)选边. 返回的是内部切片, 调用方只读不改.

type MergeKind

type MergeKind int

MergeKind 标记一个合并块的性质

const (
	MergeStable   MergeKind = iota // 三方一致, 或双方做了完全相同的改动
	MergeOurs                      // 只有 ours 改动了这一段
	MergeTheirs                    // 只有 theirs 改动了这一段
	MergeConflict                  // 双方对同一段 base 做了不同的改动
)

func (MergeKind) String

func (k MergeKind) String() string

String 给出 kind 的稳定名字, 面板表头与测试都用它

type MergeLabels

type MergeLabels struct {
	Ours   string
	Base   string
	Theirs string
}

MergeLabels 是渲染冲突标记时跟在标记后面的说明文字(git 里通常是 "HEAD" 和分支名). Base 为空表示按 git 默认的 merge 风格输出 —— 不写 "|||||||" 段; 非空则输出 diff3 风格, 保留 base 段.

type Meta

type Meta struct {
	Unit               string  // "℃", "bar", "rpm"
	Min, Max           float64 // engineering range
	LoLo, Lo, Hi, HiHi float64 // alarm limits
	Desc               string  // human-readable description
}

Meta is static engineering metadata (drives tank %, gauge span, alarms).

type PackageResult

type PackageResult struct {
	Package string
	Status  TestStatus
	Elapsed time.Duration
	Tests   map[string]*TestResult
	Order   []string
	Output  []string
}

PackageResult 是一个包的聚合结果 Tests 以测试全名为键(含子测试); Order 记录首次出现顺序, 用它遍历才能得到 稳定输出(map 遍历顺序是随机的). Output 是包级输出(事件里没有 Test 字段的那些行, 例如 "ok pkg 0.31s"), 与测试自身的输出严格分开.

func (*PackageResult) Children

func (p *PackageResult) Children(name string) []*TestResult

Children 返回 name 的直接子测试, 首次出现顺序 只返回下一层: "A/b/c" 是 "A/b" 的孩子, 不是 "A" 的孩子.

func (*PackageResult) Counts

func (p *PackageResult) Counts() (passed, failed, skipped int)

Counts 统计本包 pass/fail/skip 的数量 计入子测试: 一个子测试失败时它的父测试也是 fail, 两条都算 —— 这与面板 "一行一个测试"的展示口径一致.

func (*PackageResult) Roots

func (p *PackageResult) Roots() []*TestResult

Roots 返回顶层测试(Parent == ""), 首次出现顺序

func (*PackageResult) Test

func (p *PackageResult) Test(name string) *TestResult

Test 按全名取一个测试, 不存在返回 nil

func (*PackageResult) TestList

func (p *PackageResult) TestList() []*TestResult

TestList 按首次出现顺序返回全部测试(含子测试)

type PatchHunk

type PatchHunk struct {
	OldStart int
	OldLines int
	NewStart int
	NewLines int
	Section  string // text after the closing "@@" (function context), may be empty
	Lines    []PatchLine
}

PatchHunk is one "@@ -OldStart,OldLines +NewStart,NewLines @@" block. Starts are 1-based; a count omitted in the header means 1 (git's shorthand for a single-line range). A creation hunk reads "@@ -0,0 +1,N @@", i.e. OldStart 0 / OldLines 0.

func (PatchHunk) Header

func (h PatchHunk) Header() string

Header re-renders the hunk's "@@ ... @@" line, counts included except for the ",1" git elides. Used as the header row of a hunk in a diff viewer.

func (PatchHunk) Reverse

func (h PatchHunk) Reverse() PatchHunk

Reverse flips the hunk so applying it undoes the original: the two ranges swap, additions become deletions and vice versa, context lines stay put. The no-newline bit rides along with the line it was attached to, which is exactly what a round-trip needs (the line that had no terminator on one side is the line that has none on the other).

func (PatchHunk) Stats

func (h PatchHunk) Stats() (added, deleted int)

Stats counts the added and deleted lines of the hunk.

type PatchLine

type PatchLine struct {
	Kind      PatchLineKind
	Text      string
	NoNewline bool
}

PatchLine is one line of a hunk body. Text excludes the leading +/-/space marker. NoNewline records that a "\ No newline at end of file" marker followed this line — i.e. this line is the last line of its side and it is not terminated by a newline. The marker is a property of the line before it, not a line of its own, so it never occupies a slot in either file.

type PatchLineKind

type PatchLineKind int

PatchLineKind classifies one line inside a hunk body.

const (
	PatchContext PatchLineKind = iota // " " unchanged, present on both sides
	PatchAdded                        // "+" present only in the new file
	PatchDeleted                      // "-" present only in the old file
)

func (PatchLineKind) String

func (k PatchLineKind) String() string

String renders the kind's diff prefix name, for error messages.

type PatchSet

type PatchSet struct {
	Files []FilePatch
}

PatchSet is a whole unified diff — every file it touches, in diff order.

func ParsePatchSet

func ParsePatchSet(src string) (PatchSet, error)

ParsePatchSet parses a unified diff (`git diff`, `diff -u`) into a PatchSet. Handles multiple files, "diff --git" / "rename from|to" identity, /dev/null add and delete sides, "@@" ranges with elided counts, and the "\ No newline at end of file" marker.

Hunk bodies are consumed by the counts in their own @@ header rather than by sniffing the first character. That is what makes a deleted source line like "-- x" (which reaches the diff as "--- x") parse as a deleted line instead of being mistaken for a "--- path" file header.

A malformed @@ header drops that one hunk and is reported through the returned error; every other file and hunk still comes back in the PatchSet. Empty input yields a zero PatchSet and no error.

type PersistData

type PersistData *TDoc

持久化的数据, 用以和普通*TDoc区分

func PersistSave

func PersistSave(ro interface{}) (doc PersistData, err error)

type ProfileOptions

type ProfileOptions struct {
	CPUProfile string   // -cpuprofile
	MemProfile string   // -memprofile
	TraceFile  string   // -trace, the input GoToolTraceCommand later views
	Bench      string   // -bench pattern; omitted when empty
	Packages   []string // defaults to ./...
}

ProfileOptions selects what a profiling `go test` run should write. Every path is optional: an empty field omits its flag, so the same builder covers "cpu only", "cpu + mem", or "just capture a trace". Paths should be absolute — `go test` resolves profile paths against the package directory, not against ToolCommand.Dir.

type Quality

type Quality uint8

Quality models OPC-style sample quality.

const (
	QualityBad Quality = iota
	QualityUncertain
	QualityGood
)

type StackFrame

type StackFrame struct {
	File     string
	Line     int
	Function string
}

StackFrame 是 goroutine 调用栈上的一帧 Delve Stackframe 还带 Locals/Arguments/FrameOffset 等; IDE 当前只画 File/Line/Function

type StopState

type StopState struct {
	Reason   string
	File     string
	Line     int
	Function string
}

StopState 表示 program 在某次 Continue/Step 之后停下来的位置和原因 注意 dlv 的 Reason 可能是 "breakpoint"/"next"/"step"/"exited" 等; "exited" 时 File/Line/Function 为空.

type StrErr

type StrErr string

func (StrErr) Error

func (s StrErr) Error() string

type StringReader

type StringReader string

func (*StringReader) Read

func (r *StringReader) Read(b []byte) (n int, err error)

type Subscriber

type Subscriber func(Value)

Subscriber is invoked with each new sample. Subscribe returns a CancelFunc.

type TDoc

type TDoc struct {
	// contains filtered or unexported fields
}

func LoadTDoc

func LoadTDoc(r io.Reader) (*TDoc, error)

func LoadTDoc1

func LoadTDoc1(r io.Reader, lineLimit uint64) (*TDoc, error)

lineLimit: 最多读的行数, 0表示无限

func LoadTDocFile

func LoadTDocFile(path string) (*TDoc, error)

func LoadTDocFile1

func LoadTDocFile1(path string, lineLimit uint64) (*TDoc, error)

lineLimit: 最多读的行数, 0表示无限

func LoadTDocStr

func LoadTDocStr(s string) (*TDoc, error)

加载字符串中的TDoc

func LoadTDocStr1

func LoadTDocStr1(s string, lineLimit uint64) (*TDoc, error)

加载字符串中的TDoc

func NewTDoc

func NewTDoc() *TDoc

func TDocMarshal

func TDocMarshal(a interface{}) (doc *TDoc, err error)

把对象编码成TDoc

func (*TDoc) AddChild

func (this *TDoc) AddChild(sub *TDoc)

func (*TDoc) Child

func (this *TDoc) Child(idx int) *TDoc

func (*TDoc) ChildByKey

func (this *TDoc) ChildByKey(k string, createMissing bool) *TDoc

func (*TDoc) Childdren

func (this *TDoc) Childdren() []*TDoc

返回所有子节点 此函数返回内部slice的引用, 不要修改此slice

func (*TDoc) ChildrenKeys

func (this *TDoc) ChildrenKeys() (keys []string)

func (*TDoc) Clear

func (this *TDoc) Clear()

func (*TDoc) Clone

func (this *TDoc) Clone() *TDoc

func (*TDoc) CopyChildrenFrom

func (this *TDoc) CopyChildrenFrom(src *TDoc)

从把src的子节点复制到本结点下 如果在复制前本结点非空, 则原有数据也会保留 重名子节点的处理同 AddChild

func (*TDoc) Detach

func (this *TDoc) Detach() *TDoc

func (*TDoc) HasChildren

func (this *TDoc) HasChildren() bool

func (*TDoc) HasValue

func (this *TDoc) HasValue() bool

func (*TDoc) InnerKeyPaths

func (this *TDoc) InnerKeyPaths(_ignoreVoid, _leafOnly bool) (paths []string)

枚举出所有符合条件的内层结点路径

func (*TDoc) InnerNodeByKeyPath

func (this *TDoc) InnerNodeByKeyPath(path string, createMissing bool) *TDoc

根据键值路径查找结点 前导'/'等同于无前导'/', 连续的多个'/'等同于单个'/' 目前的版本不支持".."

func (*TDoc) InnerNodes

func (this *TDoc) InnerNodes(_ignoreVoid, _leafOnly, _namedOnly bool) []*TDoc

枚举出所有符合条件的内层结点

func (*TDoc) Key

func (this *TDoc) Key() string

func (*TDoc) KeyPath

func (this *TDoc) KeyPath(from *TDoc) string

键值路径, 不存在时返回空字符串, 当前节点等于from时返回"/" from应为本结点的祖先, 否则路径不存在 路径中任意节点的key为空时,键值路径也不存在

func (*TDoc) Len

func (this *TDoc) Len() int

func (*TDoc) Parent

func (this *TDoc) Parent() *TDoc

父节点

func (*TDoc) ReadAttr

func (this *TDoc) ReadAttr(key string, ptr interface{}) error

读属性

func (*TDoc) Save

func (this *TDoc) Save(w io.Writer) error

保存

func (*TDoc) Save1

func (this *TDoc) Save1(w io.Writer, compress bool) error

保存

func (*TDoc) SaveFile

func (this *TDoc) SaveFile(path string) error

保存成文件

func (*TDoc) SaveFile1

func (this *TDoc) SaveFile1(path string, compress bool) error

保存成文件

func (*TDoc) SetKey

func (this *TDoc) SetKey(k string)

func (*TDoc) SetLen

func (this *TDoc) SetLen(n int)

func (*TDoc) SetValue

func (this *TDoc) SetValue(val interface{}) (err error)

func (*TDoc) Sort

func (this *TDoc) Sort(fn func(a, b *TDoc) bool)

func (*TDoc) String

func (this *TDoc) String() string

完整的文档

func (*TDoc) Unmarshal

func (this *TDoc) Unmarshal(ptr interface{}) error

把TDoc中的内容解码到ptr指向的对象中

func (*TDoc) Value

func (this *TDoc) Value(ptr interface{}) (err error)

func (*TDoc) WriteAttr

func (this *TDoc) WriteAttr(key string, data interface{}) error

写属性

type TDocIni

type TDocIni struct {
	// contains filtered or unexported fields
}

TDoc格式的ini文件 此为默认的ini文件格式

func LoadTDocIni

func LoadTDocIni(fname string, readonly bool) *TDocIni

加载TDoc格式的ini文件

func UserIni

func UserIni() *TDocIni

用户的默认Ini文件

func (*TDocIni) ModifiedTime

func (this *TDocIni) ModifiedTime() time.Time

func (*TDocIni) ReadAttr

func (this *TDocIni) ReadAttr(path string, ptr interface{}) error

读属性

func (*TDocIni) Sync

func (this *TDocIni) Sync()

func (*TDocIni) SyncTime

func (this *TDocIni) SyncTime() time.Time

func (*TDocIni) WriteAttr

func (this *TDocIni) WriteAttr(path string, data interface{}) error

写属性

type TDocPersist

type TDocPersist struct {
	// contains filtered or unexported fields
}

func (*TDocPersist) Enter

func (t *TDocPersist) Enter(key string) bool

func (*TDocPersist) Leave

func (t *TDocPersist) Leave()

func (*TDocPersist) Read

func (t *TDocPersist) Read(key string, ptr interface{}) error

func (*TDocPersist) Write

func (t *TDocPersist) Write(key string, v interface{}) error

type Tag

type Tag struct {
	// contains filtered or unexported fields
}

Tag is a single named real-time data point: a live value plus subscriber fan-out. All methods are safe for concurrent use (driver poller, simulator, UI thread).

func (*Tag) Meta

func (t *Tag) Meta() Meta

Meta returns the tag's static engineering metadata.

func (*Tag) Name

func (t *Tag) Name() string

Name returns the tag's registry key.

func (*Tag) Publish

func (t *Tag) Publish(v Value)

Publish stores a fully-formed sample (a driver may set Quality/Time itself) and fans out to subscribers — but only when the sample changes. If the incoming Raw and Quality both equal the current sample, Publish is a no-op: a poll loop that re-reads an unchanged value does not wake the UI (notify on change, not on same value).

The subscriber set is snapshotted under the lock and invoked unlocked, so a callback that (un)subscribes cannot deadlock. Callable from any goroutine.

func (*Tag) SetValue

func (t *Tag) SetValue(raw interface{})

SetValue stamps the payload QualityGood at time.Now and publishes it. Callable from any goroutine.

func (*Tag) Subscribe

func (t *Tag) Subscribe(fn Subscriber) CancelFunc

Subscribe registers fn and immediately primes it with the current sample so a freshly-bound widget paints live data at once. The returned CancelFunc is idempotent and safe to call from any goroutine (dynamic screens add and remove bindings at runtime).

func (*Tag) Value

func (t *Tag) Value() Value

Value returns the latest sample.

type TagDB

type TagDB struct {
	// contains filtered or unexported fields
}

TagDB is the process-wide tag registry: a thread-safe name -> *Tag map.

func NewTagDB

func NewTagDB() *TagDB

NewTagDB returns an empty registry.

func (*TagDB) All

func (db *TagDB) All() []*Tag

All returns a snapshot slice of every tag (for a tag browser / bind picker).

func (*TagDB) Get

func (db *TagDB) Get(name string) (*Tag, bool)

Get returns the tag or (nil, false).

func (*TagDB) GetOrCreate

func (db *TagDB) GetOrCreate(name string, meta Meta) *Tag

GetOrCreate atomically returns the existing tag or creates one with meta. meta is applied only on creation; a later call for an existing name returns the original tag and ignores the passed meta.

func (*TagDB) SetValue

func (db *TagDB) SetValue(name string, raw interface{})

SetValue pushes a value by name (a driver need not hold a *Tag). The tag is created on first use with zero Meta.

type TestAction

type TestAction string

TestAction 是事件的 Action 字段

const (
	TestActionStart  TestAction = "start"  // 包开始跑(go1.24+)
	TestActionRun    TestAction = "run"    // 测试开始
	TestActionPause  TestAction = "pause"  // t.Parallel 让出
	TestActionCont   TestAction = "cont"   // 并行测试恢复
	TestActionPass   TestAction = "pass"   // 通过(终态)
	TestActionFail   TestAction = "fail"   // 失败(终态)
	TestActionSkip   TestAction = "skip"   // 跳过(终态)
	TestActionOutput TestAction = "output" // 一段输出
	TestActionBench  TestAction = "bench"  // 基准结果行(基准的终态)

	TestActionBuildOutput TestAction = "build-output" // 编译诊断文本(go1.24+)
	TestActionBuildFail   TestAction = "build-fail"   // 编译失败(go1.24+)
)

type TestEvent

type TestEvent struct {
	Time    time.Time  `json:"Time,omitempty"`
	Action  TestAction `json:"Action"`
	Package string     `json:"Package,omitempty"`
	Test    string     `json:"Test,omitempty"`
	Elapsed float64    `json:"Elapsed,omitempty"`
	Output  string     `json:"Output,omitempty"`
	// ImportPath 只出现在 build-output / build-fail 事件上(这类事件没有 Package)
	ImportPath string `json:"ImportPath,omitempty"`
	// FailedBuild 出现在"因为编译失败而 fail"的包级事件上, 值是失败的 ImportPath
	FailedBuild string `json:"FailedBuild,omitempty"`
}

TestEvent 是流里的一条事件, 字段与 test2json 的输出一一对应 Elapsed 保留原始的"秒"单位; 折算成 Duration 用 Duration().

func ParseTestEvent

func ParseTestEvent(line string) (TestEvent, bool)

ParseTestEvent 解析一行事件 ok=false 表示这一行不是事件: 裸文本(老工具链把编译错误直接打在流里)、坏 JSON、 或缺 Action 的对象. 调用方应把这类行当作 build 输出保留而不是丢弃.

func (TestEvent) Duration

func (e TestEvent) Duration() time.Duration

Duration 把 Elapsed(秒)折算成 time.Duration 用四舍五入而非截断: 0.29 的 float64 表示略小于 0.29, 直接截断会得到 289999999ns 这种毛刺值.

type TestRef

type TestRef struct {
	Package string
	Test    string
}

TestRef 定位一个测试: 包导入路径 + 测试全名 有了包名, "在 ./... 上按裸测试名跑"导致的重名歧义就不存在了.

type TestResult

type TestResult struct {
	Name    string
	Status  TestStatus
	Elapsed time.Duration
	Output  []string
	Parent  string
}

TestResult 是一个测试(或子测试)的聚合结果 Name 是全名, 子测试形如 "Parent/Sub"; Parent 是直接父测试的全名, 顶层测试为空. Output 只包含"事件里 Test == Name"的那些输出行 —— 子测试的输出不会冒泡到父测试.

type TestStatus

type TestStatus int

TestStatus 是一个测试或一个包的状态 零值是 TestStatusRunning: 只收到 run 还没收到终态事件时就是这个状态 (流被 ctrl-c 掐断时, 半截的测试也停在这里).

const (
	TestStatusRunning TestStatus = iota
	TestStatusPass
	TestStatusFail
	TestStatusSkip
)

func (TestStatus) String

func (s TestStatus) String() string

String 返回小写状态名

type TodoIndex

type TodoIndex struct {
	// contains filtered or unexported fields
}

TodoIndex 是一份可增量维护的待办索引 全量重扫一棵大树对每次按键来说太贵, 所以索引按文件分桶: 编辑器保存(或改动)一个 文件就 UpdateFile 一次, 删除/关闭就 RemoveFile, 查询侧照旧拿到全工程视图. UpdateFile 吃的是内容而不是路径, 因此未保存的缓冲也能索引. 带 RWMutex, 允许后台 goroutine 更新、UI 线程查询.

func NewTodoIndex

func NewTodoIndex(tags ...TodoKind) *TodoIndex

NewTodoIndex 创建索引; 不传标记则用 DefaultTodoTags

func (*TodoIndex) All

func (ix *TodoIndex) All() []TodoItem

All 返回索引里的全部标记, 按 File 再 Line 排序

func (*TodoIndex) ByFile

func (ix *TodoIndex) ByFile(path string) []TodoItem

ByFile 返回 path 的标记副本(按行号)

func (*TodoIndex) ByTag

func (ix *TodoIndex) ByTag(kind TodoKind) []TodoItem

ByTag 返回全索引里某一类标记, 按 File 再 Line 排序

func (*TodoIndex) Files

func (ix *TodoIndex) Files() []string

Files 返回索引里仍有标记的文件路径, 按路径排序

func (*TodoIndex) Len

func (ix *TodoIndex) Len() int

Len 返回索引里的标记总数

func (*TodoIndex) RemoveFile

func (ix *TodoIndex) RemoveFile(path string)

RemoveFile 把 path 从索引里摘掉(文件被删除或关闭时调用)

func (*TodoIndex) ScanDir

func (ix *TodoIndex) ScanDir(dir string) error

ScanDir 用一次全量扫描给索引播种, 之后交给 UpdateFile/RemoveFile 增量维护 播种会清掉旧内容, 因此可以拿它做"重新打开工程".

func (*TodoIndex) UpdateFile

func (ix *TodoIndex) UpdateFile(path, content string) []TodoItem

UpdateFile 用 content 重新索引 path, 返回该文件的标记(副本) 没有标记(或文件类型不识别)时把该文件从索引里摘掉, 免得留下空桶.

type TodoItem

type TodoItem struct {
	File string   // 文件路径(随传入 dir 而定: dir 为绝对路径则此处也是绝对路径)
	Line int      // 1-based 行号
	Kind TodoKind // 标记类别
	Text string   // 关键字之后的文本, 已 TrimSpace
}

TodoItem 是扫描到的一条待办标记

func ScanTodos

func ScanTodos(dir string) ([]TodoItem, error)

ScanTodos 递归扫描 dir 下的源文件, 收集其中的待办标记(默认标记集) 语言支持与跳过规则见 TodoScanner.ScanDir; 需要自定义标记集时改用 NewTodoScanner(tags...).ScanDir(dir).

type TodoKind

type TodoKind string

TodoKind 是一个待办标记的类别

const (
	TodoTODO  TodoKind = "TODO"
	TodoFIXME TodoKind = "FIXME"
	TodoXXX   TodoKind = "XXX"
	TodoHACK  TodoKind = "HACK"
	TodoNOTE  TodoKind = "NOTE"
	TodoBUG   TodoKind = "BUG"
)

func DefaultTodoTags

func DefaultTodoTags() []TodoKind

DefaultTodoTags 返回默认标记集: TODO/FIXME/HACK/XXX/NOTE/BUG 返回的是副本, 调用方可以自由增删后交给 NewTodoScanner / NewTodoIndex.

type TodoScanner

type TodoScanner struct {
	// contains filtered or unexported fields
}

TodoScanner 是一个标记集可配置的待办扫描器 无可变状态(仅持有编译好的正则), 因此可并发复用. 语言判定见 todoLangForPath.

func NewTodoScanner

func NewTodoScanner(tags ...TodoKind) *TodoScanner

NewTodoScanner 创建扫描器; 不传标记(或全是空串)则用 DefaultTodoTags

func (*TodoScanner) ScanContent

func (s *TodoScanner) ScanContent(path, content string) []TodoItem

ScanContent 从内存里的文件内容抽取标记, 完全不碰磁盘 语言按 path 的后缀判定: Go 走 go/scanner(字符串字面量里的注释形状不会误报), C 系与 shell/python 系走逐行启发式, 其它后缀返回 nil. 这是 TodoIndex.UpdateFile 的底座 —— 编辑器里未保存的缓冲也能直接索引.

func (*TodoScanner) ScanDir

func (s *TodoScanner) ScanDir(dir string) ([]TodoItem, error)

ScanDir 递归扫描 dir 下所有可识别语言的源文件, 收集其中的待办标记 跳过 vendor/、node_modules/ 以及一切隐藏目录(.git/.idea/.vscode 等); 不识别的 文件类型忽略. 单个文件的读取错误经 Warn 记录后跳过, 继续扫描其余文件; 只有根目录 本身不可访问才返回错误. 结果按 File 再按 Line 排序, 保证稳定输出. 条目数触及 maxTodoItems 时提前结束并 Warn.

func (*TodoScanner) ScanFile

func (s *TodoScanner) ScanFile(path string) ([]TodoItem, error)

ScanFile 读取并扫描单个文件; 不识别的类型返回 (nil, nil)

func (*TodoScanner) Tags

func (s *TodoScanner) Tags() []TodoKind

Tags 返回该扫描器识别的标记集副本

type Tool

type Tool struct {
	Name      string
	Path      string
	Available bool
	Version   string
}

Tool is an external analyzer binary as found (or not found) on PATH. Version is filled only by DetectToolVersion — plain DetectTool leaves it empty because reading a version means starting a process.

func DetectGoTools

func DetectGoTools() []Tool

DetectGoTools reports availability for every binary the workflows need, in GoToolBinaries order. Versions are left empty — see DetectToolVersion for the variant that pays for a subprocess.

func DetectTool

func DetectTool(name string) Tool

DetectTool locates name on PATH. It never executes the binary, so it is safe on the UI thread and in tests; a missing tool is reported as Available false with an empty Path, never as an error.

func DetectToolVersion

func DetectToolVersion(name string) Tool

DetectToolVersion is DetectTool plus a version probe: it runs ToolVersionArgs under a toolVersionTimeout context and stores the banner in Version. It starts a process, so callers must keep it off the UI thread; a missing tool short-circuits without spawning anything, and a probe that fails or times out leaves Version empty rather than reporting the tool as unavailable.

type ToolCommand

type ToolCommand struct {
	Tool string
	Argv []string
	Env  []string
	Dir  string
}

ToolCommand is a built-but-unexecuted analyzer invocation. Argv[0] is the executable name (resolved through PATH by os/exec, which is why the builders emit the bare name rather than Tool.Path — Tool.Path exists for display and availability, not for spawning). Env holds extra "KEY=value" entries the host layers on top of os.Environ(); it is nil for every default build. Tool is the logical workflow id, carried so the findings a run produces can be grouped under the row that started it.

func GoTestProfileCommand

func GoTestProfileCommand(dir string, opt ProfileOptions) ToolCommand

GoTestProfileCommand builds the profiling test run: `go test [-bench=P] [-cpuprofile=F] [-memprofile=F] [-trace=F] <pkgs>`. Flag order is fixed so the argv is reproducible across runs.

func GoTestRaceCommand

func GoTestRaceCommand(dir string, pkgs ...string) ToolCommand

GoTestRaceCommand builds `go test -race <pkgs>`.

func GoToolTraceCommand

func GoToolTraceCommand(dir, traceFile, httpAddr string) ToolCommand

GoToolTraceCommand builds `go tool trace [-http=addr] <traceFile>`. The command serves a web UI instead of printing findings; the host takes the address out of its output with ParseTraceServerURL. An empty httpAddr lets go pick a port on localhost.

func GoVetCommand

func GoVetCommand(dir string, pkgs ...string) ToolCommand

GoVetCommand builds `go vet <pkgs>`.

func GovulncheckCommand

func GovulncheckCommand(dir string, pkgs ...string) ToolCommand

GovulncheckCommand builds `govulncheck <pkgs>` (text output, which is what ParseGovulncheck consumes).

func PprofTopCommand

func PprofTopCommand(dir, binary, profile string, nodeCount int) ToolCommand

PprofTopCommand builds `go tool pprof -top [-nodecount=N] [binary] <profile>`. binary may be empty: profiles written by the Go runtime already carry their symbol table, so the test binary is only needed when it does not (or when the user wants pprof to re-symbolise). nodeCount <= 0 omits -nodecount and lets pprof pick.

func StaticcheckCommand

func StaticcheckCommand(dir, checks string, pkgs ...string) ToolCommand

StaticcheckCommand builds `staticcheck [-checks=LIST] <pkgs>`. An empty checks omits the flag and leaves staticcheck on its default check set.

func (ToolCommand) WithEnv

func (c ToolCommand) WithEnv(extra ...string) ToolCommand

WithEnv returns a copy of c with extra "KEY=value" entries appended to Env. The copy is deep enough that the receiver's Env is never aliased, so a cached ToolCommand can be specialised per run — this is how RunConfigPanel's env rows reach an analyzer process.

type Uuid

type Uuid [16]byte

UUID 是128位整数, 字符串形式为 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 其中x为十六进制数字0~9或a~f(小写) 例如 123e4567-e89b-12d3-a456-426655440000 按照标准, UUID有5种格式, 其中某些值是有特定含义的, 但我们不关心这些. 我们只保证生成的UUID是合法的, 并且用作全局唯一标识.

func NewUuid

func NewUuid() (ret Uuid)

生成新的UUID

func ParseUuid

func ParseUuid(s string) (ret Uuid, err error)

解释"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" (8-4-4-4-12) 或压缩格式的Uuid

""和"0"将被识别为零值

func (Uuid) Compact

func (v Uuid) Compact() string

表示为压缩的字符串格式, 零表示为"0", 其他值表示为Base64的前22字符, 即去掉最后的"=="

func (*Uuid) GobDecode

func (this *Uuid) GobDecode(data []byte) (err error)

func (*Uuid) GobEncode

func (this *Uuid) GobEncode() ([]byte, error)

func (Uuid) IsZero

func (v Uuid) IsZero() bool

func (*Uuid) Scan

func (this *Uuid) Scan(state fmt.ScanState, verb rune) error

func (Uuid) String

func (v Uuid) String() string

"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" (8-4-4-4-12) 格式

type Value

type Value struct {
	Raw     interface{} // comparable scalar: float64 | bool | int64 | string
	Quality Quality
	Time    time.Time
}

Value is one immutable sample: payload plus provenance.

func (Value) Bool

func (v Value) Bool() bool

Bool coerces the payload to bool (lamps, valves, digital I/O).

func (Value) Float

func (v Value) Float() float64

Float coerces the payload to float64 (gauges, tanks, charts read float64).

func (Value) Int

func (v Value) Int() int64

Int coerces the payload to int64.

func (Value) String

func (v Value) String() string

String renders the payload for text/label widgets.

type Variable

type Variable struct {
	Name  string
	Type  string
	Value string

	// Kind 是 Go reflect.Kind 的名字 ("struct"/"slice"/"map"/"ptr"/...);
	// dlv 线缆上给的是数值, 这里转成名字. 空串表示 dlv 没报(或报了 Invalid).
	Kind string
	// Addr 是变量地址, 0 表示没有地址 (常量/寄存器里的值/未加载).
	Addr uint64
	// Len 是字符串/slice/map/array/chan 的元素个数 (字符串是字节数), 其它类型为 0.
	// 它是"真实长度", 可能大于 len(Children) -- LoadConfig.MaxArrayValues 会截断.
	Len int
	// Cap 是 slice 容量, 其它类型为 0.
	Cap int
	// Children 是已加载的子变量: struct 的字段 / slice 的元素 / ptr 的目标;
	// map 按 dlv 的约定是 key,value 交替排列.
	Children []Variable
}

Variable 是 dlv Eval/ListLocalVars/ListFunctionArgs 应答的投影 除 Name/Type/Value 之外带上变量树要用的元信息: Kind 决定 UI 画什么图标 / 能不能 展开, Len/Cap 让 slice/map 在不展开的情况下也能显示规模, Addr 供"跳到内存"用, Children 是已经加载回来的子变量. 注意 Children==nil 有两种含义 (没有子项 / 还没加载): 需要展开时调 LoadVariable 再要一层, 不要把 nil 当成"确定没有子项". 这就是懒展开的契约 -- 变量树默认只 加载一层, 用户点开哪个节点才为那个节点付一次 RPC.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL