pi-read-map 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/package.json +61 -0
- package/scripts/go_outline.go +239 -0
- package/scripts/python_outline.py +205 -0
- package/src/constants.ts +21 -0
- package/src/enums.ts +37 -0
- package/src/formatter.ts +414 -0
- package/src/index.ts +259 -0
- package/src/language-detect.ts +88 -0
- package/src/mapper.ts +116 -0
- package/src/mappers/c.ts +236 -0
- package/src/mappers/cpp.ts +850 -0
- package/src/mappers/csv.ts +144 -0
- package/src/mappers/ctags.ts +318 -0
- package/src/mappers/fallback.ts +162 -0
- package/src/mappers/go.ts +218 -0
- package/src/mappers/json.ts +183 -0
- package/src/mappers/jsonl.ts +135 -0
- package/src/mappers/markdown.ts +183 -0
- package/src/mappers/python.ts +138 -0
- package/src/mappers/rust.ts +886 -0
- package/src/mappers/sql.ts +195 -0
- package/src/mappers/toml.ts +191 -0
- package/src/mappers/typescript.ts +729 -0
- package/src/mappers/yaml.ts +185 -0
- package/src/types.ts +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Will Hampson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# pi-read-map
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
This pi extension augments the built-in `read` tool with structural file maps. When you open a file larger than 2,000 lines or 50 KB, the extension generates a map of every symbol and its line range. You navigate large codebases precisely instead of scanning sequentially.
|
|
6
|
+
|
|
7
|
+
## Why This Exists
|
|
8
|
+
|
|
9
|
+
**The problem:** pi sees only the first 2,000 lines of a 50,000-line source file. Ask "how does the type checker handle unions?" and the model either hallucinates or burns tokens re-reading until it finds the answer.
|
|
10
|
+
|
|
11
|
+
**The trade-off:** `pi-read-map` spends ~2,000–10,000 tokens upfront to generate a map of the entire file. The extension triggers only for files exceeding the truncation limit (>2,000 lines and >50 KB); smaller files pass through unchanged.
|
|
12
|
+
|
|
13
|
+
**The payoff:** The map stays in context. Ask "show me the merge implementation," "compare error handling in these three functions," or "what symbols exist after line 40,000?" without re-reading. The investment pays for itself when you analyze a large file beyond a single summary.
|
|
14
|
+
|
|
15
|
+
## Demo
|
|
16
|
+
|
|
17
|
+
See `pi-read-map` in action analyzing the TypeScript compiler's 54,000-line type checker:
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
https://github.com/user-attachments/assets/4408f37b-b669-453f-a588-336a5332ae90
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## What It Does
|
|
24
|
+
|
|
25
|
+
- **Generates structural maps** showing symbols, classes, functions, and their exact line ranges
|
|
26
|
+
- **Supports 16 languages** through specialized parsers: TypeScript, JavaScript, Python, Go, Rust, C, C++, SQL, JSON, JSONL, YAML, TOML, CSV, Markdown
|
|
27
|
+
- **Compresses aggressively** to ~3-5% of original file size (a 400 KB file yields an ~18 KB map)
|
|
28
|
+
- **Enforces a 20 KB budget** through progressive detail reduction
|
|
29
|
+
- **Caches maps** in memory by file path and modification time for instant re-reads
|
|
30
|
+
- **Falls back** from language-specific parsers to ctags to grep heuristics
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### From Git (Recommended)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Global install
|
|
38
|
+
pi install https://github.com/Whamp/pi-read-map
|
|
39
|
+
|
|
40
|
+
# Project-local install (adds to .pi/settings.json)
|
|
41
|
+
pi install https://github.com/Whamp/pi-read-map -l
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### From npm
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Global install
|
|
48
|
+
pi install npm:pi-read-map
|
|
49
|
+
|
|
50
|
+
# Project-local install
|
|
51
|
+
pi install npm:pi-read-map -l
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### From Local Directory
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Clone and install globally
|
|
58
|
+
pi install ./path/to/pi-read-map
|
|
59
|
+
|
|
60
|
+
# Or project-local
|
|
61
|
+
pi install ./path/to/pi-read-map -l
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### One-off Test
|
|
65
|
+
|
|
66
|
+
Try the extension without installing:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pi -e https://github.com/Whamp/pi-read-map
|
|
70
|
+
pi -e npm:pi-read-map
|
|
71
|
+
pi -e ./path/to/pi-read-map
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Verification
|
|
75
|
+
|
|
76
|
+
Start pi or run `/reload`. Then read a large file:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
read path/to/large-file.ts
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Output includes the truncated content followed by:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
[Truncated: showing first 2000 lines of 10,247 (50 KB of 412 KB)]
|
|
86
|
+
───────────────────────────────────────
|
|
87
|
+
File Map: path/to/large-file.ts
|
|
88
|
+
10,247 lines │ 412 KB │ TypeScript
|
|
89
|
+
───────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
class ProcessorConfig: [18-32]
|
|
92
|
+
class BatchProcessor: [34-890]
|
|
93
|
+
constructor(config: ProcessorConfig): [40-65]
|
|
94
|
+
async run(items: List<Item>): [67-180]
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
───────────────────────────────────────
|
|
98
|
+
Use read(path, offset=LINE, limit=N) for targeted reads.
|
|
99
|
+
───────────────────────────────────────
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Development
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm run typecheck # Type checking
|
|
106
|
+
npm run lint # Linting with oxlint
|
|
107
|
+
npm run lint:fix # Auto-fix lint issues
|
|
108
|
+
npm run format # Format with oxfmt
|
|
109
|
+
npm run format:check # Check formatting
|
|
110
|
+
npm run validate # Run all checks
|
|
111
|
+
npm run test # Unit tests
|
|
112
|
+
npm run test:watch # Watch mode
|
|
113
|
+
npm run test:integration # Integration tests
|
|
114
|
+
npm run test:e2e # End-to-end tests
|
|
115
|
+
npm run bench # Benchmarks
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Project Structure
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
src/
|
|
122
|
+
├── index.ts # Extension entry: tool registration, caching, messages
|
|
123
|
+
├── mapper.ts # Dispatcher: routes files to language mappers
|
|
124
|
+
├── formatter.ts # Budget-aware formatting with detail reduction
|
|
125
|
+
├── language-detect.ts # Maps file extensions to languages
|
|
126
|
+
├── types.ts # Shared interfaces (FileMap, FileSymbol)
|
|
127
|
+
├── enums.ts # SymbolKind, DetailLevel
|
|
128
|
+
├── constants.ts # Thresholds (2,000 lines, 50 KB, 20 KB budget)
|
|
129
|
+
└── mappers/ # Language-specific parsers
|
|
130
|
+
├── typescript.ts # ts-morph for TS/JS
|
|
131
|
+
├── python.ts # Python AST via subprocess
|
|
132
|
+
├── go.ts # Go AST via subprocess
|
|
133
|
+
├── rust.ts # tree-sitter
|
|
134
|
+
├── cpp.ts # tree-sitter for C/C++
|
|
135
|
+
├── c.ts # Regex patterns
|
|
136
|
+
├── sql.ts # Regex
|
|
137
|
+
├── json.ts # jq subprocess
|
|
138
|
+
├── jsonl.ts # Streaming parser
|
|
139
|
+
├── yaml.ts # Regex
|
|
140
|
+
├── toml.ts # Regex
|
|
141
|
+
├── csv.ts # In-process parser
|
|
142
|
+
├── markdown.ts # Regex
|
|
143
|
+
├── ctags.ts # universal-ctags fallback
|
|
144
|
+
└── fallback.ts # Grep-based final fallback
|
|
145
|
+
|
|
146
|
+
scripts/
|
|
147
|
+
├── python_outline.py # Python AST extraction
|
|
148
|
+
└── go_outline.go # Go AST extraction (compiles on first use)
|
|
149
|
+
|
|
150
|
+
tests/
|
|
151
|
+
├── unit/ # Mapper and utility tests
|
|
152
|
+
├── integration/ # Dispatcher, caching, budget enforcement
|
|
153
|
+
├── e2e/ # Real pi sessions via tmux
|
|
154
|
+
└── fixtures/ # Sample files per language
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## How It Works
|
|
158
|
+
|
|
159
|
+
The extension intercepts `read` calls and decides:
|
|
160
|
+
|
|
161
|
+
1. **Small files** (≤2,000 lines, ≤50 KB): Delegate to built-in read tool
|
|
162
|
+
2. **Targeted reads** (offset or limit provided): Delegate to built-in read tool
|
|
163
|
+
3. **Large files:**
|
|
164
|
+
- Call built-in read for the first chunk
|
|
165
|
+
- Detect language from file extension
|
|
166
|
+
- Dispatch to a mapper (language-specific → ctags → grep fallback)
|
|
167
|
+
- Format with budget enforcement
|
|
168
|
+
- Cache the map
|
|
169
|
+
- Send as a separate `file-map` message after `tool_result`
|
|
170
|
+
|
|
171
|
+
## Dependencies
|
|
172
|
+
|
|
173
|
+
**npm packages:**
|
|
174
|
+
- `ts-morph` - TypeScript AST analysis
|
|
175
|
+
- `tree-sitter` - Parser framework
|
|
176
|
+
- `tree-sitter-cpp` - C/C++ parsing
|
|
177
|
+
- `tree-sitter-rust` - Rust parsing
|
|
178
|
+
|
|
179
|
+
**System tools (optional):**
|
|
180
|
+
- `python3` - Python mapper
|
|
181
|
+
- `go` - Go mapper
|
|
182
|
+
- `jq` - JSON mapper
|
|
183
|
+
- `universal-ctags` - Language fallback
|
|
184
|
+
|
|
185
|
+
## Acknowledgments
|
|
186
|
+
|
|
187
|
+
This project was inspired by and built upon the foundation of [codemap](https://github.com/kcosr/codemap) by [kcosr](https://github.com/kcosr). Check out the original project for the ideas that made this possible.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-read-map",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pi extension that adds structural file maps for large files",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"pi": {
|
|
7
|
+
"extensions": [
|
|
8
|
+
"./src/index.ts"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"lint": "oxlint -c .oxlintrc.json src tests",
|
|
14
|
+
"lint:fix": "oxlint -c .oxlintrc.json src tests --fix",
|
|
15
|
+
"format": "oxfmt --config .oxfmtrc.jsonc src tests",
|
|
16
|
+
"format:check": "oxfmt --config .oxfmtrc.jsonc src tests --check",
|
|
17
|
+
"validate": "npm run typecheck && npm run lint && npm run format:check",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"test:integration": "vitest run tests/integration/",
|
|
21
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
22
|
+
"bench": "vitest bench"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"pi-package",
|
|
26
|
+
"pi",
|
|
27
|
+
"extension",
|
|
28
|
+
"read",
|
|
29
|
+
"file-map",
|
|
30
|
+
"code-navigation"
|
|
31
|
+
],
|
|
32
|
+
"author": "Will Hampson",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/Whamp/pi-read-map"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/Whamp/pi-read-map#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/Whamp/pi-read-map/issues"
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"tree-sitter": "0.22.4",
|
|
44
|
+
"tree-sitter-cpp": "0.23.4",
|
|
45
|
+
"tree-sitter-rust": "0.23.3",
|
|
46
|
+
"ts-morph": "27.0.2"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@factory/eslint-plugin": "^0.1.0",
|
|
50
|
+
"@mariozechner/pi-coding-agent": "^0.52.9",
|
|
51
|
+
"@sinclair/typebox": "^0.34.0",
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
|
+
"oxfmt": "^0.28.0",
|
|
54
|
+
"typescript": "^5.7.0",
|
|
55
|
+
"ultracite": "^7.1.5",
|
|
56
|
+
"vitest": "^3.0.0"
|
|
57
|
+
},
|
|
58
|
+
"overrides": {
|
|
59
|
+
"tree-sitter": "$tree-sitter"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"go/ast"
|
|
7
|
+
"go/parser"
|
|
8
|
+
"go/token"
|
|
9
|
+
"os"
|
|
10
|
+
"strings"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
type Symbol struct {
|
|
14
|
+
Name string `json:"name"`
|
|
15
|
+
Kind string `json:"kind"`
|
|
16
|
+
StartLine int `json:"startLine"`
|
|
17
|
+
EndLine int `json:"endLine"`
|
|
18
|
+
Signature string `json:"signature,omitempty"`
|
|
19
|
+
Modifiers []string `json:"modifiers,omitempty"`
|
|
20
|
+
Children []Symbol `json:"children,omitempty"`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type OutlineResult struct {
|
|
24
|
+
Package string `json:"package"`
|
|
25
|
+
Imports []string `json:"imports,omitempty"`
|
|
26
|
+
Symbols []Symbol `json:"symbols"`
|
|
27
|
+
Error string `json:"error,omitempty"`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func formatType(expr ast.Expr) string {
|
|
31
|
+
if expr == nil {
|
|
32
|
+
return ""
|
|
33
|
+
}
|
|
34
|
+
switch t := expr.(type) {
|
|
35
|
+
case *ast.Ident:
|
|
36
|
+
return t.Name
|
|
37
|
+
case *ast.SelectorExpr:
|
|
38
|
+
return formatType(t.X) + "." + t.Sel.Name
|
|
39
|
+
case *ast.StarExpr:
|
|
40
|
+
return "*" + formatType(t.X)
|
|
41
|
+
case *ast.ArrayType:
|
|
42
|
+
return "[]" + formatType(t.Elt)
|
|
43
|
+
case *ast.MapType:
|
|
44
|
+
return "map[" + formatType(t.Key) + "]" + formatType(t.Value)
|
|
45
|
+
case *ast.InterfaceType:
|
|
46
|
+
return "interface{}"
|
|
47
|
+
case *ast.StructType:
|
|
48
|
+
return "struct{}"
|
|
49
|
+
case *ast.FuncType:
|
|
50
|
+
return "func(...)"
|
|
51
|
+
case *ast.ChanType:
|
|
52
|
+
return "chan " + formatType(t.Value)
|
|
53
|
+
case *ast.Ellipsis:
|
|
54
|
+
return "..." + formatType(t.Elt)
|
|
55
|
+
default:
|
|
56
|
+
return "?"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func formatParams(fields *ast.FieldList) string {
|
|
61
|
+
if fields == nil || len(fields.List) == 0 {
|
|
62
|
+
return "()"
|
|
63
|
+
}
|
|
64
|
+
var parts []string
|
|
65
|
+
for _, f := range fields.List {
|
|
66
|
+
typeStr := formatType(f.Type)
|
|
67
|
+
if len(f.Names) == 0 {
|
|
68
|
+
parts = append(parts, typeStr)
|
|
69
|
+
} else {
|
|
70
|
+
for _, name := range f.Names {
|
|
71
|
+
parts = append(parts, name.Name+" "+typeStr)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return "(" + strings.Join(parts, ", ") + ")"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func formatResults(fields *ast.FieldList) string {
|
|
79
|
+
if fields == nil || len(fields.List) == 0 {
|
|
80
|
+
return ""
|
|
81
|
+
}
|
|
82
|
+
if len(fields.List) == 1 && len(fields.List[0].Names) == 0 {
|
|
83
|
+
return " " + formatType(fields.List[0].Type)
|
|
84
|
+
}
|
|
85
|
+
var parts []string
|
|
86
|
+
for _, f := range fields.List {
|
|
87
|
+
typeStr := formatType(f.Type)
|
|
88
|
+
if len(f.Names) == 0 {
|
|
89
|
+
parts = append(parts, typeStr)
|
|
90
|
+
} else {
|
|
91
|
+
for _, name := range f.Names {
|
|
92
|
+
parts = append(parts, name.Name+" "+typeStr)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return " (" + strings.Join(parts, ", ") + ")"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func extractSymbols(fset *token.FileSet, file *ast.File) []Symbol {
|
|
100
|
+
var symbols []Symbol
|
|
101
|
+
|
|
102
|
+
for _, decl := range file.Decls {
|
|
103
|
+
switch d := decl.(type) {
|
|
104
|
+
case *ast.GenDecl:
|
|
105
|
+
switch d.Tok {
|
|
106
|
+
case token.TYPE:
|
|
107
|
+
for _, spec := range d.Specs {
|
|
108
|
+
ts := spec.(*ast.TypeSpec)
|
|
109
|
+
sym := Symbol{
|
|
110
|
+
Name: ts.Name.Name,
|
|
111
|
+
StartLine: fset.Position(d.Pos()).Line,
|
|
112
|
+
EndLine: fset.Position(d.End()).Line,
|
|
113
|
+
}
|
|
114
|
+
switch t := ts.Type.(type) {
|
|
115
|
+
case *ast.StructType:
|
|
116
|
+
sym.Kind = "struct"
|
|
117
|
+
// Extract struct fields as children
|
|
118
|
+
if t.Fields != nil {
|
|
119
|
+
for _, field := range t.Fields.List {
|
|
120
|
+
for _, name := range field.Names {
|
|
121
|
+
sym.Children = append(sym.Children, Symbol{
|
|
122
|
+
Name: name.Name,
|
|
123
|
+
Kind: "field",
|
|
124
|
+
StartLine: fset.Position(field.Pos()).Line,
|
|
125
|
+
EndLine: fset.Position(field.End()).Line,
|
|
126
|
+
Signature: formatType(field.Type),
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
case *ast.InterfaceType:
|
|
132
|
+
sym.Kind = "interface"
|
|
133
|
+
// Extract interface methods as children
|
|
134
|
+
if t.Methods != nil {
|
|
135
|
+
for _, method := range t.Methods.List {
|
|
136
|
+
for _, name := range method.Names {
|
|
137
|
+
if ft, ok := method.Type.(*ast.FuncType); ok {
|
|
138
|
+
sym.Children = append(sym.Children, Symbol{
|
|
139
|
+
Name: name.Name,
|
|
140
|
+
Kind: "method",
|
|
141
|
+
StartLine: fset.Position(method.Pos()).Line,
|
|
142
|
+
EndLine: fset.Position(method.End()).Line,
|
|
143
|
+
Signature: formatParams(ft.Params) + formatResults(ft.Results),
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
default:
|
|
150
|
+
sym.Kind = "type"
|
|
151
|
+
}
|
|
152
|
+
symbols = append(symbols, sym)
|
|
153
|
+
}
|
|
154
|
+
case token.CONST, token.VAR:
|
|
155
|
+
kind := "variable"
|
|
156
|
+
if d.Tok == token.CONST {
|
|
157
|
+
kind = "constant"
|
|
158
|
+
}
|
|
159
|
+
for _, spec := range d.Specs {
|
|
160
|
+
vs := spec.(*ast.ValueSpec)
|
|
161
|
+
for _, name := range vs.Names {
|
|
162
|
+
if name.Name == "_" {
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
sym := Symbol{
|
|
166
|
+
Name: name.Name,
|
|
167
|
+
Kind: kind,
|
|
168
|
+
StartLine: fset.Position(vs.Pos()).Line,
|
|
169
|
+
EndLine: fset.Position(vs.End()).Line,
|
|
170
|
+
}
|
|
171
|
+
if vs.Type != nil {
|
|
172
|
+
sym.Signature = formatType(vs.Type)
|
|
173
|
+
}
|
|
174
|
+
symbols = append(symbols, sym)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
case *ast.FuncDecl:
|
|
179
|
+
sym := Symbol{
|
|
180
|
+
Name: d.Name.Name,
|
|
181
|
+
StartLine: fset.Position(d.Pos()).Line,
|
|
182
|
+
EndLine: fset.Position(d.End()).Line,
|
|
183
|
+
Signature: formatParams(d.Type.Params) + formatResults(d.Type.Results),
|
|
184
|
+
}
|
|
185
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
186
|
+
sym.Kind = "method"
|
|
187
|
+
// Add receiver info
|
|
188
|
+
recv := d.Recv.List[0]
|
|
189
|
+
recvType := formatType(recv.Type)
|
|
190
|
+
sym.Signature = "(" + recvType + ") " + sym.Name + sym.Signature
|
|
191
|
+
sym.Name = recvType + "." + d.Name.Name
|
|
192
|
+
} else {
|
|
193
|
+
sym.Kind = "function"
|
|
194
|
+
}
|
|
195
|
+
symbols = append(symbols, sym)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return symbols
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func extractImports(file *ast.File) []string {
|
|
203
|
+
var imports []string
|
|
204
|
+
for _, imp := range file.Imports {
|
|
205
|
+
path := strings.Trim(imp.Path.Value, `"`)
|
|
206
|
+
if imp.Name != nil && imp.Name.Name != "." && imp.Name.Name != "_" {
|
|
207
|
+
imports = append(imports, imp.Name.Name+" "+path)
|
|
208
|
+
} else {
|
|
209
|
+
imports = append(imports, path)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return imports
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
func main() {
|
|
216
|
+
if len(os.Args) < 2 {
|
|
217
|
+
result := OutlineResult{Error: "usage: go_outline <file.go>"}
|
|
218
|
+
json.NewEncoder(os.Stdout).Encode(result)
|
|
219
|
+
os.Exit(1)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
filePath := os.Args[1]
|
|
223
|
+
|
|
224
|
+
fset := token.NewFileSet()
|
|
225
|
+
file, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
226
|
+
if err != nil {
|
|
227
|
+
result := OutlineResult{Error: fmt.Sprintf("parse error: %v", err)}
|
|
228
|
+
json.NewEncoder(os.Stdout).Encode(result)
|
|
229
|
+
os.Exit(1)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
result := OutlineResult{
|
|
233
|
+
Package: file.Name.Name,
|
|
234
|
+
Imports: extractImports(file),
|
|
235
|
+
Symbols: extractSymbols(fset, file),
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
json.NewEncoder(os.Stdout).Encode(result)
|
|
239
|
+
}
|