mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-06-05 22:23:50 +00:00
multi-fixes
This commit is contained in:
@@ -104,6 +104,39 @@ After downloading or building the binary, configure it in Claude Code:
|
|||||||
|
|
||||||
See the [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp) for more details.
|
See the [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp) for more details.
|
||||||
|
|
||||||
|
### Recommended Claude Code Configuration
|
||||||
|
|
||||||
|
#### Selective Tool Deferral
|
||||||
|
|
||||||
|
For optimal performance, keep the most frequently used tools loaded at startup and defer the rest. This follows [Anthropic's recommended practice](https://www.anthropic.com/engineering/advanced-tool-use) of loading 3-5 high-use tools immediately:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"filepuff": {
|
||||||
|
"command": "mcp-filepuff",
|
||||||
|
"args": ["-workspace", "."],
|
||||||
|
"alwaysAllow": ["file_read", "file_search", "edit_apply", "edit_preview"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This keeps `file_read`, `file_search`, `edit_apply`, and `edit_preview` immediately available while deferring less frequently used tools (`ping`, `ast_query`, `symbol_at`, `find_definition`, `find_references`).
|
||||||
|
|
||||||
|
#### System Prompt Guidance
|
||||||
|
|
||||||
|
Add the following to your `CLAUDE.md` to help Claude understand the available tool categories:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
You have access to filepuff MCP tools providing:
|
||||||
|
- File reading with AST symbol summaries (file_read)
|
||||||
|
- Fast regex search powered by ripgrep (file_search)
|
||||||
|
- Structural code pattern matching across 9+ languages (ast_query)
|
||||||
|
- LSP-powered go-to-definition, find-references, and symbol info (find_definition, find_references, symbol_at)
|
||||||
|
- AST-aware file editing with syntax validation and preview (edit_preview, edit_apply)
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Running the Server (Standalone)
|
### Running the Server (Standalone)
|
||||||
|
|||||||
+18
-5
@@ -47,9 +47,10 @@ Health check - returns pong to verify the server is running
|
|||||||
{"tool": "ping"}
|
{"tool": "ping"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** `"pong"` text string.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- Returns: "pong"
|
|
||||||
- Use to verify server connectivity
|
- Use to verify server connectivity
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -84,6 +85,8 @@ Search for text patterns in files using ripgrep. Supports regex patterns, file t
|
|||||||
{"pattern": "TODO", "ignore_case": true, "context_lines": 3}
|
{"pattern": "TODO", "ignore_case": true, "context_lines": 3}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** Results grouped by file with match context. Format: `"Found N matches in M files:"` followed by file sections, each with matching lines prefixed by `"L{line}│"` and context lines prefixed by `" │"`.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- Requires ripgrep (rg) to be installed
|
- Requires ripgrep (rg) to be installed
|
||||||
@@ -128,6 +131,8 @@ Read a file's contents with optional line range and AST symbol summary
|
|||||||
{"path": "large_file.go", "max_lines": 100}
|
{"path": "large_file.go", "max_lines": 100}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** File content with numbered lines (format: `" 12│ line text"`). When `include_ast=true`: prepends symbol summary (`"**file.go** (N lines, go)\nSymbols:\n func Name L12\n struct Config L45"`). When `symbols_only=true`: returns only the symbol summary (~95% fewer tokens). When `max_lines` is set: truncates output with `"[... N more lines omitted]"` notice.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- symbols_only mode reduces token usage by ~90-98%
|
- symbols_only mode reduces token usage by ~90-98%
|
||||||
@@ -170,6 +175,8 @@ Search for AST patterns in code files. Use code patterns with $VAR placeholders
|
|||||||
{"pattern": "function $NAME($PROPS) { $$$BODY }", "language": "javascript", "name_matches": "^[A-Z]"}
|
{"pattern": "function $NAME($PROPS) { $$$BODY }", "language": "javascript", "name_matches": "^[A-Z]"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** `"Found N match(es):"` followed by entries in format `"**file:line** (node_type)"` with code blocks and captured variables (`$NAME=value`). Returns `"No matches found."` when no results.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- $NAME captures identifiers
|
- $NAME captures identifiers
|
||||||
@@ -201,6 +208,8 @@ Get information about the symbol at a specific position in a file. Returns type,
|
|||||||
{"file": "server.go", "line": 25, "column": 10}
|
{"file": "server.go", "line": 25, "column": 10}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** `"**Symbol Information**"` followed by hover/type information from LSP, or `"**Symbol Information** (AST fallback)"` with node type and text when LSP unavailable. Returns `"No symbol information available at this position."` when nothing is found.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- Requires LSP server for full type information
|
- Requires LSP server for full type information
|
||||||
@@ -228,10 +237,11 @@ Find the definition of the symbol at a specific position. Uses LSP to locate whe
|
|||||||
{"file": "server.go", "line": 42, "column": 15}
|
{"file": "server.go", "line": 42, "column": 15}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** `"Found N definition(s):"` with entries showing `"**file:line:column**"` and a 3-line code preview with the target line marked by `">"`. Returns `"No definition found."` when the symbol has no definition.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- Requires language server for the file type
|
- Requires language server for the file type
|
||||||
- Returns file path, line, and column of definition
|
|
||||||
- Shows code preview at definition location
|
- Shows code preview at definition location
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -261,6 +271,8 @@ Find all references to the symbol at a specific position. Uses LSP to locate all
|
|||||||
{"file": "server.go", "line": 42, "column": 15, "include_declaration": false}
|
{"file": "server.go", "line": 42, "column": 15, "include_declaration": false}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** `"Found N reference(s):"` grouped by file, each showing `"**file** (count)"` with locations as `"L{line}:{column}"`. Returns `"No references found."` when no usages exist.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- Requires language server for the file type
|
- Requires language server for the file type
|
||||||
@@ -305,10 +317,10 @@ Preview an edit without applying it. Uses AST-aware editing for code files (Go,
|
|||||||
{"file": "package.json", "operation": "replace", "selector_pattern": "\\"version\\":\\\\s*\\"[^\\"]+\\"", "new_content": "\\"version\\": \\"2.0.0\\""}
|
{"file": "package.json", "operation": "replace", "selector_pattern": "\\"version\\":\\\\s*\\"[^\\"]+\\"", "new_content": "\\"version\\": \\"2.0.0\\""}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** `"**Edit Preview**"` followed by a unified diff showing proposed changes. Does not modify the file. For code files: uses AST-aware mode with syntax validation. For other files: uses text-based mode.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- Returns a diff showing proposed changes
|
|
||||||
- Does not modify the file
|
|
||||||
- Use to validate changes before applying
|
- Use to validate changes before applying
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -344,9 +356,10 @@ Apply an edit to a file. Uses AST-aware editing for code files (Go, TypeScript,
|
|||||||
{"file": "config.yaml", "operation": "replace", "selector_line": 5, "selector_line_end": 10, "new_content": "database:\\n host: production.db.example.com\\n port: 5432"}
|
{"file": "config.yaml", "operation": "replace", "selector_line": 5, "selector_line_end": 10, "new_content": "database:\\n host: production.db.example.com\\n port: 5432"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Returns:** `"**Edit Applied Successfully**"` followed by a unified diff of the changes made. For code files, validates syntax before writing — returns an error if the edit would produce invalid syntax.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- For code files: validates syntax before and after edit
|
|
||||||
- Preserves file permissions
|
- Preserves file permissions
|
||||||
- Uses atomic writes for safety
|
- Uses atomic writes for safety
|
||||||
- File locking prevents concurrent edits
|
- File locking prevents concurrent edits
|
||||||
|
|||||||
+113
-22
@@ -174,8 +174,6 @@ func (e *Engine) performASTEdit(ctx context.Context, edit *ASTEdit, apply bool)
|
|||||||
result := &EditResult{
|
result := &EditResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
Diff: diff,
|
Diff: diff,
|
||||||
OriginalContent: string(content),
|
|
||||||
NewContent: string(newContent),
|
|
||||||
Applied: false,
|
Applied: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,8 +231,6 @@ func (e *Engine) performTextEdit(_ context.Context, edit *ASTEdit, apply bool) (
|
|||||||
result := &EditResult{
|
result := &EditResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
Diff: diff,
|
Diff: diff,
|
||||||
OriginalContent: string(content),
|
|
||||||
NewContent: string(newContent),
|
|
||||||
Applied: false,
|
Applied: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,14 +564,22 @@ func indentContent(content string, indent string) string {
|
|||||||
return strings.Join(lines, "\n")
|
return strings.Join(lines, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// diffLine represents a single line in the diff with its type and content.
|
||||||
|
type diffLine struct {
|
||||||
|
op diffmatchpatch.Operation
|
||||||
|
text string // line content without trailing newline
|
||||||
|
oldN int // 1-based line number in original (0 if insert)
|
||||||
|
newN int // 1-based line number in modified (0 if delete)
|
||||||
|
}
|
||||||
|
|
||||||
// generateDiff creates a unified diff between original and modified content.
|
// generateDiff creates a unified diff between original and modified content.
|
||||||
// Uses line-level Myers diff algorithm for accurate and readable diffs.
|
// Uses line-level Myers diff algorithm and outputs a proper unified diff
|
||||||
|
// with context lines (3 before/after each change, merging close hunks).
|
||||||
func (e *Engine) generateDiff(original, modified, filename string) string {
|
func (e *Engine) generateDiff(original, modified, filename string) string {
|
||||||
dmp := e.dmp
|
dmp := e.dmp
|
||||||
|
|
||||||
// Use line-level diffing: encode each line as a single character,
|
// Use line-level diffing: encode each line as a single character,
|
||||||
// diff the encoded strings, then decode back to real lines.
|
// diff the encoded strings, then decode back to real lines.
|
||||||
// This prevents character-level diffs from splitting lines incorrectly.
|
|
||||||
chars1, chars2, lineArray := dmp.DiffLinesToChars(original, modified)
|
chars1, chars2, lineArray := dmp.DiffLinesToChars(original, modified)
|
||||||
diffs := dmp.DiffMain(chars1, chars2, false)
|
diffs := dmp.DiffMain(chars1, chars2, false)
|
||||||
diffs = dmp.DiffCharsToLines(diffs, lineArray)
|
diffs = dmp.DiffCharsToLines(diffs, lineArray)
|
||||||
@@ -583,30 +587,117 @@ func (e *Engine) generateDiff(original, modified, filename string) string {
|
|||||||
// Cleanup for readability
|
// Cleanup for readability
|
||||||
diffs = dmp.DiffCleanupSemantic(diffs)
|
diffs = dmp.DiffCleanupSemantic(diffs)
|
||||||
|
|
||||||
// Convert to unified diff format
|
// Flatten diffs into individual lines with line numbers
|
||||||
|
var lines []diffLine
|
||||||
|
oldLine := 1
|
||||||
|
newLine := 1
|
||||||
|
for _, d := range diffs {
|
||||||
|
rawLines := strings.SplitAfter(d.Text, "\n")
|
||||||
|
for _, raw := range rawLines {
|
||||||
|
if raw == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
text := strings.TrimSuffix(raw, "\n")
|
||||||
|
switch d.Type {
|
||||||
|
case diffmatchpatch.DiffEqual:
|
||||||
|
lines = append(lines, diffLine{op: d.Type, text: text, oldN: oldLine, newN: newLine})
|
||||||
|
oldLine++
|
||||||
|
newLine++
|
||||||
|
case diffmatchpatch.DiffDelete:
|
||||||
|
lines = append(lines, diffLine{op: d.Type, text: text, oldN: oldLine})
|
||||||
|
oldLine++
|
||||||
|
case diffmatchpatch.DiffInsert:
|
||||||
|
lines = append(lines, diffLine{op: d.Type, text: text, newN: newLine})
|
||||||
|
newLine++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify indices of changed lines
|
||||||
|
const contextSize = 3
|
||||||
|
var changedIndices []int
|
||||||
|
for i, l := range lines {
|
||||||
|
if l.op != diffmatchpatch.DiffEqual {
|
||||||
|
changedIndices = append(changedIndices, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(changedIndices) == 0 {
|
||||||
|
return "" // no changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build inclusion ranges: for each changed line, include contextSize lines before/after.
|
||||||
|
// Merge overlapping or adjacent ranges (gap <= 2*contextSize = 6 context lines).
|
||||||
|
type indexRange struct{ start, end int } // inclusive
|
||||||
|
var ranges []indexRange
|
||||||
|
for _, ci := range changedIndices {
|
||||||
|
rStart := ci - contextSize
|
||||||
|
if rStart < 0 {
|
||||||
|
rStart = 0
|
||||||
|
}
|
||||||
|
rEnd := ci + contextSize
|
||||||
|
if rEnd >= len(lines) {
|
||||||
|
rEnd = len(lines) - 1
|
||||||
|
}
|
||||||
|
if len(ranges) > 0 && rStart <= ranges[len(ranges)-1].end+1 {
|
||||||
|
// Merge with previous range
|
||||||
|
ranges[len(ranges)-1].end = rEnd
|
||||||
|
} else {
|
||||||
|
ranges = append(ranges, indexRange{rStart, rEnd})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit unified diff
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.WriteString(fmt.Sprintf("--- %s\n", filename))
|
buf.WriteString(fmt.Sprintf("--- %s\n", filename))
|
||||||
buf.WriteString(fmt.Sprintf("+++ %s\n", filename))
|
buf.WriteString(fmt.Sprintf("+++ %s\n", filename))
|
||||||
|
|
||||||
for _, diff := range diffs {
|
for _, r := range ranges {
|
||||||
// SplitAfter preserves the trailing \n on each line, so we can
|
// Determine hunk header line numbers
|
||||||
// distinguish real lines from a trailing empty split artifact.
|
var oldStart, oldCount, newStart, newCount int
|
||||||
lines := strings.SplitAfter(diff.Text, "\n")
|
for i := r.start; i <= r.end; i++ {
|
||||||
for _, line := range lines {
|
l := lines[i]
|
||||||
if line == "" {
|
switch l.op {
|
||||||
continue
|
case diffmatchpatch.DiffEqual:
|
||||||
|
if oldCount == 0 {
|
||||||
|
oldStart = l.oldN
|
||||||
|
}
|
||||||
|
if newCount == 0 {
|
||||||
|
newStart = l.newN
|
||||||
|
}
|
||||||
|
oldCount++
|
||||||
|
newCount++
|
||||||
|
case diffmatchpatch.DiffDelete:
|
||||||
|
if oldCount == 0 {
|
||||||
|
oldStart = l.oldN
|
||||||
|
}
|
||||||
|
if newCount == 0 {
|
||||||
|
// Set newStart from context or next available
|
||||||
|
newStart = l.oldN // approximate
|
||||||
|
}
|
||||||
|
oldCount++
|
||||||
|
case diffmatchpatch.DiffInsert:
|
||||||
|
if newCount == 0 {
|
||||||
|
newStart = l.newN
|
||||||
|
}
|
||||||
|
if oldCount == 0 {
|
||||||
|
oldStart = l.newN // approximate
|
||||||
|
}
|
||||||
|
newCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove trailing newline for display — we add our own.
|
buf.WriteString(fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", oldStart, oldCount, newStart, newCount))
|
||||||
cleanLine := strings.TrimSuffix(line, "\n")
|
|
||||||
|
|
||||||
switch diff.Type {
|
for i := r.start; i <= r.end; i++ {
|
||||||
case diffmatchpatch.DiffDelete:
|
l := lines[i]
|
||||||
buf.WriteString(fmt.Sprintf("-%s\n", cleanLine))
|
switch l.op {
|
||||||
case diffmatchpatch.DiffInsert:
|
|
||||||
buf.WriteString(fmt.Sprintf("+%s\n", cleanLine))
|
|
||||||
case diffmatchpatch.DiffEqual:
|
case diffmatchpatch.DiffEqual:
|
||||||
buf.WriteString(fmt.Sprintf(" %s\n", cleanLine))
|
buf.WriteString(fmt.Sprintf(" %s\n", l.text))
|
||||||
|
case diffmatchpatch.DiffDelete:
|
||||||
|
buf.WriteString(fmt.Sprintf("-%s\n", l.text))
|
||||||
|
case diffmatchpatch.DiffInsert:
|
||||||
|
buf.WriteString(fmt.Sprintf("+%s\n", l.text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ var DefaultServerConfigs = map[protocol.Language]ServerConfig{
|
|||||||
protocol.LangCpp: {
|
protocol.LangCpp: {
|
||||||
Command: []string{"clangd"},
|
Command: []string{"clangd"},
|
||||||
},
|
},
|
||||||
|
protocol.LangRust: {
|
||||||
|
Command: []string{"rust-analyzer"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowedLSPBinaries is a whitelist of allowed LSP server binary names.
|
// AllowedLSPBinaries is a whitelist of allowed LSP server binary names.
|
||||||
@@ -602,6 +605,8 @@ func languageToLSPID(lang protocol.Language) string {
|
|||||||
return "c"
|
return "c"
|
||||||
case protocol.LangCpp:
|
case protocol.LangCpp:
|
||||||
return "cpp"
|
return "cpp"
|
||||||
|
case protocol.LangRust:
|
||||||
|
return "rust"
|
||||||
default:
|
default:
|
||||||
return string(lang)
|
return string(lang)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ func ExtractDocComment(n *sitter.Node, content []byte, lang protocol.Language) *
|
|||||||
return extractCDocComment(n, content)
|
return extractCDocComment(n, content)
|
||||||
case protocol.LangElixir:
|
case protocol.LangElixir:
|
||||||
return extractElixirDocComment(n, content)
|
return extractElixirDocComment(n, content)
|
||||||
|
case protocol.LangRust:
|
||||||
|
return extractRustDocComment(n, content)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -551,6 +553,64 @@ func cleanPythonDocstring(doc string) string {
|
|||||||
return strings.TrimSpace(doc)
|
return strings.TrimSpace(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractRustDocComment extracts Rust documentation comments (/// style).
|
||||||
|
func extractRustDocComment(n *sitter.Node, content []byte) *DocComment {
|
||||||
|
comments := collectPrecedingComments(n, content, []string{"line_comment"})
|
||||||
|
if len(comments) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter for /// doc comments only
|
||||||
|
var docComments []*sitter.Node
|
||||||
|
for _, c := range comments {
|
||||||
|
text := GetNodeText(c, content)
|
||||||
|
trimmed := strings.TrimSpace(text)
|
||||||
|
if strings.HasPrefix(trimmed, "///") {
|
||||||
|
docComments = append(docComments, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(docComments) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts []string
|
||||||
|
var raw []string
|
||||||
|
startLine := -1
|
||||||
|
endLine := -1
|
||||||
|
|
||||||
|
for _, c := range docComments {
|
||||||
|
text := GetNodeText(c, content)
|
||||||
|
raw = append(raw, text)
|
||||||
|
|
||||||
|
if startLine == -1 {
|
||||||
|
startLine = int(c.StartPoint().Row) + 1
|
||||||
|
}
|
||||||
|
endLine = int(c.EndPoint().Row) + 1
|
||||||
|
|
||||||
|
// Clean /// prefix
|
||||||
|
cleaned := strings.TrimSpace(text)
|
||||||
|
cleaned = strings.TrimPrefix(cleaned, "///")
|
||||||
|
if len(cleaned) > 0 && cleaned[0] == ' ' {
|
||||||
|
cleaned = cleaned[1:]
|
||||||
|
}
|
||||||
|
parts = append(parts, cleaned)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DocComment{
|
||||||
|
Text: strings.Join(parts, "\n"),
|
||||||
|
Raw: strings.Join(raw, "\n"),
|
||||||
|
Style: CommentStyleDoxygen,
|
||||||
|
Tags: nil,
|
||||||
|
StartLine: startLine,
|
||||||
|
EndLine: endLine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// extractElixirDocComment extracts Elixir documentation from @doc and @moduledoc attributes.
|
// extractElixirDocComment extracts Elixir documentation from @doc and @moduledoc attributes.
|
||||||
// Elixir uses module attributes like @doc and @moduledoc for documentation.
|
// Elixir uses module attributes like @doc and @moduledoc for documentation.
|
||||||
func extractElixirDocComment(n *sitter.Node, content []byte) *DocComment {
|
func extractElixirDocComment(n *sitter.Node, content []byte) *DocComment {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/smacker/go-tree-sitter/html"
|
"github.com/smacker/go-tree-sitter/html"
|
||||||
"github.com/smacker/go-tree-sitter/javascript"
|
"github.com/smacker/go-tree-sitter/javascript"
|
||||||
"github.com/smacker/go-tree-sitter/python"
|
"github.com/smacker/go-tree-sitter/python"
|
||||||
|
"github.com/smacker/go-tree-sitter/rust"
|
||||||
"github.com/smacker/go-tree-sitter/typescript/typescript"
|
"github.com/smacker/go-tree-sitter/typescript/typescript"
|
||||||
|
|
||||||
"github.com/lukaszraczylo/mcp-filepuff/pkg/errors"
|
"github.com/lukaszraczylo/mcp-filepuff/pkg/errors"
|
||||||
@@ -117,10 +118,12 @@ func getLanguage(lang protocol.Language) (*sitter.Language, error) {
|
|||||||
return html.GetLanguage(), nil
|
return html.GetLanguage(), nil
|
||||||
case protocol.LangElixir:
|
case protocol.LangElixir:
|
||||||
return elixir.GetLanguage(), nil
|
return elixir.GetLanguage(), nil
|
||||||
|
case protocol.LangRust:
|
||||||
|
return rust.GetLanguage(), nil
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(errors.ErrInvalidLanguage, fmt.Sprintf("language %s is not supported", lang)).
|
return nil, errors.New(errors.ErrInvalidLanguage, fmt.Sprintf("language %s is not supported", lang)).
|
||||||
WithContext("language", string(lang)).
|
WithContext("language", string(lang)).
|
||||||
WithRemediation("Supported languages: Go, TypeScript, JavaScript, Python, C, C++, HTML, Vue, Elixir")
|
WithRemediation("Supported languages: Go, TypeScript, JavaScript, Python, C, C++, HTML, Vue, Elixir, Rust")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ func ExtractSymbols(tree *sitter.Tree, content []byte, lang protocol.Language, f
|
|||||||
return extractCSymbols(root, content, filename)
|
return extractCSymbols(root, content, filename)
|
||||||
case protocol.LangElixir:
|
case protocol.LangElixir:
|
||||||
return extractElixirSymbols(root, content, filename)
|
return extractElixirSymbols(root, content, filename)
|
||||||
|
case protocol.LangRust:
|
||||||
|
return extractRustSymbols(root, content, filename)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -714,6 +716,133 @@ func extractElixirProtocol(n *sitter.Node, content []byte, filename string) *pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractRustSymbols extracts symbols from Rust code.
|
||||||
|
func extractRustSymbols(root *sitter.Node, content []byte, filename string) []protocol.Symbol {
|
||||||
|
var symbols []protocol.Symbol
|
||||||
|
|
||||||
|
WalkTree(root, func(n *sitter.Node) bool {
|
||||||
|
var symbol *protocol.Symbol
|
||||||
|
|
||||||
|
switch n.Type() {
|
||||||
|
case "function_item":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content),
|
||||||
|
Kind: protocol.SymbolFunction,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "struct_item":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content),
|
||||||
|
Kind: protocol.SymbolStruct,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "enum_item":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content),
|
||||||
|
Kind: protocol.SymbolEnum,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "trait_item":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content),
|
||||||
|
Kind: protocol.SymbolTrait,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "impl_item":
|
||||||
|
symbol = extractRustImpl(n, content, filename)
|
||||||
|
case "type_item":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content),
|
||||||
|
Kind: protocol.SymbolType,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "const_item":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content),
|
||||||
|
Kind: protocol.SymbolConstant,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "static_item":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content),
|
||||||
|
Kind: protocol.SymbolVariable,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "macro_definition":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content) + " (macro)",
|
||||||
|
Kind: protocol.SymbolFunction,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "mod_item":
|
||||||
|
nameNode := n.ChildByFieldName("name")
|
||||||
|
if nameNode != nil {
|
||||||
|
symbol = &protocol.Symbol{
|
||||||
|
Name: GetNodeText(nameNode, content),
|
||||||
|
Kind: protocol.SymbolModule,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if symbol != nil {
|
||||||
|
if doc := ExtractDocComment(n, content, protocol.LangRust); doc != nil {
|
||||||
|
symbol.Doc = FormatDocComment(doc)
|
||||||
|
}
|
||||||
|
symbols = append(symbols, *symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return symbols
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractRustImpl extracts an impl block symbol from Rust code.
|
||||||
|
func extractRustImpl(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||||
|
typeNode := n.ChildByFieldName("type")
|
||||||
|
traitNode := n.ChildByFieldName("trait")
|
||||||
|
|
||||||
|
var name string
|
||||||
|
if traitNode != nil && typeNode != nil {
|
||||||
|
name = "impl " + GetNodeText(traitNode, content) + " for " + GetNodeText(typeNode, content)
|
||||||
|
} else if typeNode != nil {
|
||||||
|
name = "impl " + GetNodeText(typeNode, content)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &protocol.Symbol{
|
||||||
|
Name: name,
|
||||||
|
Kind: protocol.SymbolType,
|
||||||
|
Location: NodeLocation(n, filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// extractElixirImpl extracts a protocol implementation.
|
// extractElixirImpl extracts a protocol implementation.
|
||||||
func extractElixirImpl(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
func extractElixirImpl(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||||
// defimpl Protocol, for: Type do ... end
|
// defimpl Protocol, for: Type do ... end
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func (s *Server) handleASTQuery(ctx context.Context, request mcp.CallToolRequest
|
|||||||
// Find files to search based on language
|
// Find files to search based on language
|
||||||
exts := languageToExtensions(language)
|
exts := languageToExtensions(language)
|
||||||
if len(exts) == 0 {
|
if len(exts) == 0 {
|
||||||
return mcp.NewToolResultError(fmt.Sprintf("unsupported language: %s (supported: go, typescript, javascript, python, c, cpp, html, vue, elixir)", language)), nil
|
return mcp.NewToolResultError(fmt.Sprintf("unsupported language: %s (supported: go, typescript, javascript, python, c, cpp, html, vue, elixir, rust)", language)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var allResults []query.MatchResult
|
var allResults []query.MatchResult
|
||||||
@@ -205,6 +205,10 @@ func symbolKindIcon(kind protocol.SymbolKind) string {
|
|||||||
return "mod"
|
return "mod"
|
||||||
case protocol.SymbolPackage:
|
case protocol.SymbolPackage:
|
||||||
return "pkg"
|
return "pkg"
|
||||||
|
case protocol.SymbolEnum:
|
||||||
|
return "enum"
|
||||||
|
case protocol.SymbolTrait:
|
||||||
|
return "trait"
|
||||||
default:
|
default:
|
||||||
return "sym"
|
return "sym"
|
||||||
}
|
}
|
||||||
@@ -231,6 +235,8 @@ func languageToExtensions(language string) []string {
|
|||||||
return []string{".vue"}
|
return []string{".vue"}
|
||||||
case "elixir":
|
case "elixir":
|
||||||
return []string{".ex", ".exs"}
|
return []string{".ex", ".exs"}
|
||||||
|
case "rust":
|
||||||
|
return []string{".rs"}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-10
@@ -89,7 +89,8 @@ func (s *Server) registerTools() {
|
|||||||
// Register ping tool for health checks
|
// Register ping tool for health checks
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("ping",
|
mcp.NewTool("ping",
|
||||||
mcp.WithDescription("Health check - returns pong to verify the server is running"),
|
mcp.WithDescription("Health check - returns pong to verify the server is running.\n\n"+
|
||||||
|
"Returns: \"pong\" text string."),
|
||||||
mcp.WithReadOnlyHintAnnotation(true),
|
mcp.WithReadOnlyHintAnnotation(true),
|
||||||
),
|
),
|
||||||
s.handlePing,
|
s.handlePing,
|
||||||
@@ -99,7 +100,10 @@ func (s *Server) registerTools() {
|
|||||||
if s.searcher != nil {
|
if s.searcher != nil {
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("file_search",
|
mcp.NewTool("file_search",
|
||||||
mcp.WithDescription("Search for text patterns in files using ripgrep. Supports regex patterns, file type filtering, and context lines."),
|
mcp.WithDescription("Search for text patterns in files using ripgrep. Supports regex patterns, file type filtering, and context lines.\n\n"+
|
||||||
|
"Returns: Results grouped by file with match context. Format: \"Found N matches in M files:\" followed by file sections, "+
|
||||||
|
"each with matching lines prefixed by \"L{line}│\" and context lines prefixed by \" │\".\n\n"+
|
||||||
|
"Example: {\"pattern\": \"func.*Error\", \"file_types\": [\"go\"], \"max_results\": 20}"),
|
||||||
mcp.WithReadOnlyHintAnnotation(true),
|
mcp.WithReadOnlyHintAnnotation(true),
|
||||||
mcp.WithString("pattern",
|
mcp.WithString("pattern",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
@@ -133,7 +137,16 @@ func (s *Server) registerTools() {
|
|||||||
// Register file_read tool
|
// Register file_read tool
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("file_read",
|
mcp.NewTool("file_read",
|
||||||
mcp.WithDescription("Read a file's contents with optional line range and AST symbol summary"),
|
mcp.WithDescription("Read a file's contents with optional line range and AST symbol summary.\n\n"+
|
||||||
|
"Returns: File content with numbered lines (format: \" 12│ line text\"). "+
|
||||||
|
"When include_ast=true: prepends symbol summary (\"**file.go** (N lines, go)\\nSymbols:\\n func Name L12\\n struct Config L45\"). "+
|
||||||
|
"When symbols_only=true: returns only the symbol summary (~95% fewer tokens). "+
|
||||||
|
"When max_lines is set: truncates output with \"[... N more lines omitted]\" notice.\n\n"+
|
||||||
|
"Examples:\n"+
|
||||||
|
" Full file: {\"path\": \"main.go\"}\n"+
|
||||||
|
" With AST: {\"path\": \"main.go\", \"include_ast\": true}\n"+
|
||||||
|
" Symbols only: {\"path\": \"main.go\", \"include_ast\": true, \"symbols_only\": true}\n"+
|
||||||
|
" Line range: {\"path\": \"main.go\", \"line_start\": 10, \"line_end\": 50}"),
|
||||||
mcp.WithReadOnlyHintAnnotation(true),
|
mcp.WithReadOnlyHintAnnotation(true),
|
||||||
mcp.WithString("path",
|
mcp.WithString("path",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
@@ -161,7 +174,13 @@ func (s *Server) registerTools() {
|
|||||||
// Register ast_query tool
|
// Register ast_query tool
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("ast_query",
|
mcp.NewTool("ast_query",
|
||||||
mcp.WithDescription("Search for AST patterns in code files. Use code patterns with $VAR placeholders to match and capture code structures like functions, classes, and types."),
|
mcp.WithDescription("Search for AST patterns in code files. Use code patterns with $VAR placeholders to match and capture code structures like functions, classes, and types.\n\n"+
|
||||||
|
"Returns: \"Found N match(es):\" followed by entries in format \"**file:line** (node_type)\" with code blocks "+
|
||||||
|
"and captured variables ($NAME=value). Returns \"No matches found.\" when no results.\n\n"+
|
||||||
|
"Examples:\n"+
|
||||||
|
" Go error funcs: {\"pattern\": \"func $NAME($$$ARGS) error\", \"language\": \"go\"}\n"+
|
||||||
|
" Python classes: {\"pattern\": \"class $NAME: $$$BODY\", \"language\": \"python\"}\n"+
|
||||||
|
" Named function: {\"pattern\": \"func $NAME($$$ARGS)\", \"language\": \"go\", \"name_exact\": \"NewServer\"}"),
|
||||||
mcp.WithReadOnlyHintAnnotation(true),
|
mcp.WithReadOnlyHintAnnotation(true),
|
||||||
mcp.WithString("pattern",
|
mcp.WithString("pattern",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
@@ -169,7 +188,7 @@ func (s *Server) registerTools() {
|
|||||||
),
|
),
|
||||||
mcp.WithString("language",
|
mcp.WithString("language",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
mcp.Description("Target language: go, typescript, javascript, python, c, cpp, html, vue, elixir"),
|
mcp.Description("Target language: go, typescript, javascript, python, c, cpp, html, vue, elixir, rust"),
|
||||||
),
|
),
|
||||||
mcp.WithArray("paths",
|
mcp.WithArray("paths",
|
||||||
mcp.Description("Paths to search in (defaults to workspace root)"),
|
mcp.Description("Paths to search in (defaults to workspace root)"),
|
||||||
@@ -197,7 +216,10 @@ func (s *Server) registerTools() {
|
|||||||
// Register symbol_at tool
|
// Register symbol_at tool
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("symbol_at",
|
mcp.NewTool("symbol_at",
|
||||||
mcp.WithDescription("Get information about the symbol at a specific position in a file. Returns type, documentation, and definition location using LSP when available."),
|
mcp.WithDescription("Get information about the symbol at a specific position in a file. Returns type, documentation, and definition location using LSP when available.\n\n"+
|
||||||
|
"Returns: \"**Symbol Information**\" followed by hover/type information from LSP, or \"**Symbol Information** (AST fallback)\" "+
|
||||||
|
"with node type and text when LSP unavailable. Returns \"No symbol information available at this position.\" when nothing is found.\n\n"+
|
||||||
|
"Example: {\"file\": \"server.go\", \"line\": 45, \"column\": 6}"),
|
||||||
mcp.WithReadOnlyHintAnnotation(true),
|
mcp.WithReadOnlyHintAnnotation(true),
|
||||||
mcp.WithString("file",
|
mcp.WithString("file",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
@@ -218,7 +240,10 @@ func (s *Server) registerTools() {
|
|||||||
// Register find_definition tool
|
// Register find_definition tool
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("find_definition",
|
mcp.NewTool("find_definition",
|
||||||
mcp.WithDescription("Find the definition of the symbol at a specific position. Uses LSP to locate where a function, variable, type, etc. is defined."),
|
mcp.WithDescription("Find the definition of the symbol at a specific position. Uses LSP to locate where a function, variable, type, etc. is defined.\n\n"+
|
||||||
|
"Returns: \"Found N definition(s):\" with entries showing \"**file:line:column**\" and a 3-line code preview "+
|
||||||
|
"with the target line marked by \">\". Returns \"No definition found.\" when the symbol has no definition.\n\n"+
|
||||||
|
"Example: {\"file\": \"handler.go\", \"line\": 23, \"column\": 10}"),
|
||||||
mcp.WithReadOnlyHintAnnotation(true),
|
mcp.WithReadOnlyHintAnnotation(true),
|
||||||
mcp.WithString("file",
|
mcp.WithString("file",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
@@ -239,7 +264,10 @@ func (s *Server) registerTools() {
|
|||||||
// Register find_references tool
|
// Register find_references tool
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("find_references",
|
mcp.NewTool("find_references",
|
||||||
mcp.WithDescription("Find all references to the symbol at a specific position. Uses LSP to locate all usages of a function, variable, type, etc."),
|
mcp.WithDescription("Find all references to the symbol at a specific position. Uses LSP to locate all usages of a function, variable, type, etc.\n\n"+
|
||||||
|
"Returns: \"Found N reference(s):\" grouped by file, each showing \"**file** (count)\" with locations as "+
|
||||||
|
"\"L{line}:{column}\". Returns \"No references found.\" when no usages exist.\n\n"+
|
||||||
|
"Example: {\"file\": \"types.go\", \"line\": 5, \"column\": 6}"),
|
||||||
mcp.WithReadOnlyHintAnnotation(true),
|
mcp.WithReadOnlyHintAnnotation(true),
|
||||||
mcp.WithString("file",
|
mcp.WithString("file",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
@@ -264,7 +292,13 @@ func (s *Server) registerTools() {
|
|||||||
// Register edit tools
|
// Register edit tools
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("edit_preview",
|
mcp.NewTool("edit_preview",
|
||||||
mcp.WithDescription("Preview an edit without applying it. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++), and text-based editing for other files (Markdown, JSON, YAML, config files, etc.)."),
|
mcp.WithDescription("Preview an edit without applying it. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++, Rust), and text-based editing for other files (Markdown, JSON, YAML, config files, etc.).\n\n"+
|
||||||
|
"Returns: \"**Edit Preview**\" followed by a unified diff showing proposed changes. Does not modify the file. "+
|
||||||
|
"For code files: uses AST-aware mode with syntax validation. For other files: uses text-based mode.\n\n"+
|
||||||
|
"Examples:\n"+
|
||||||
|
" AST mode: {\"file\": \"main.go\", \"operation\": \"replace\", \"selector_kind\": \"function_declaration\", \"selector_name\": \"Hello\", \"new_content\": \"func Hello() {\\n\\treturn\\n}\"}\n"+
|
||||||
|
" Text mode: {\"file\": \"README.md\", \"operation\": \"replace\", \"selector_text\": \"## Old Header\", \"new_content\": \"## New Header\"}\n"+
|
||||||
|
" Line range: {\"file\": \"config.yaml\", \"operation\": \"replace\", \"selector_line\": 5, \"selector_line_end\": 10, \"new_content\": \"key: value\"}"),
|
||||||
mcp.WithString("file",
|
mcp.WithString("file",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
mcp.Description("Path to the file to edit"),
|
mcp.Description("Path to the file to edit"),
|
||||||
@@ -306,7 +340,13 @@ func (s *Server) registerTools() {
|
|||||||
|
|
||||||
s.mcp.AddTool(
|
s.mcp.AddTool(
|
||||||
mcp.NewTool("edit_apply",
|
mcp.NewTool("edit_apply",
|
||||||
mcp.WithDescription("Apply an edit to a file. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++) with syntax validation, and text-based editing for other files (Markdown, JSON, YAML, config files, etc.)."),
|
mcp.WithDescription("Apply an edit to a file. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++, Rust) with syntax validation, and text-based editing for other files (Markdown, JSON, YAML, config files, etc.).\n\n"+
|
||||||
|
"Returns: \"**Edit Applied Successfully**\" followed by a unified diff of the changes made. "+
|
||||||
|
"For code files, validates syntax before writing — returns an error if the edit would produce invalid syntax.\n\n"+
|
||||||
|
"Examples:\n"+
|
||||||
|
" AST mode: {\"file\": \"main.go\", \"operation\": \"replace\", \"selector_kind\": \"function_declaration\", \"selector_name\": \"Hello\", \"new_content\": \"func Hello() {\\n\\treturn\\n}\"}\n"+
|
||||||
|
" Text mode: {\"file\": \"README.md\", \"operation\": \"replace\", \"selector_text\": \"## Old Header\", \"new_content\": \"## New Header\"}\n"+
|
||||||
|
" Line range: {\"file\": \"config.yaml\", \"operation\": \"replace\", \"selector_line\": 5, \"selector_line_end\": 10, \"new_content\": \"key: value\"}"),
|
||||||
mcp.WithString("file",
|
mcp.WithString("file",
|
||||||
mcp.Required(),
|
mcp.Required(),
|
||||||
mcp.Description("Path to the file to edit"),
|
mcp.Description("Path to the file to edit"),
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ const (
|
|||||||
SymbolProperty SymbolKind = "property"
|
SymbolProperty SymbolKind = "property"
|
||||||
SymbolModule SymbolKind = "module"
|
SymbolModule SymbolKind = "module"
|
||||||
SymbolPackage SymbolKind = "package"
|
SymbolPackage SymbolKind = "package"
|
||||||
|
SymbolEnum SymbolKind = "enum"
|
||||||
|
SymbolTrait SymbolKind = "trait"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Symbol represents a code symbol (function, class, variable, etc.).
|
// Symbol represents a code symbol (function, class, variable, etc.).
|
||||||
@@ -63,6 +65,7 @@ const (
|
|||||||
LangJSON Language = "json"
|
LangJSON Language = "json"
|
||||||
LangYAML Language = "yaml"
|
LangYAML Language = "yaml"
|
||||||
LangElixir Language = "elixir"
|
LangElixir Language = "elixir"
|
||||||
|
LangRust Language = "rust"
|
||||||
LangUnknown Language = "unknown"
|
LangUnknown Language = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -92,6 +95,8 @@ func DetectLanguage(filename string) Language {
|
|||||||
return LangYAML
|
return LangYAML
|
||||||
case ".ex", ".exs":
|
case ".ex", ".exs":
|
||||||
return LangElixir
|
return LangElixir
|
||||||
|
case ".rs":
|
||||||
|
return LangRust
|
||||||
default:
|
default:
|
||||||
return LangUnknown
|
return LangUnknown
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/// A simple point in 2D space.
|
||||||
|
struct Point {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents different shapes.
|
||||||
|
enum Shape {
|
||||||
|
Circle(f64),
|
||||||
|
Rectangle(f64, f64),
|
||||||
|
Triangle(f64, f64, f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for objects that can be drawn.
|
||||||
|
trait Drawable {
|
||||||
|
fn draw(&self);
|
||||||
|
fn area(&self) -> f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drawable for Shape {
|
||||||
|
fn draw(&self) {
|
||||||
|
println!("Drawing shape");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn area(&self) -> f64 {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
/// Creates a new point.
|
||||||
|
fn new(x: f64, y: f64) -> Self {
|
||||||
|
Point { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn distance(&self, other: &Point) -> f64 {
|
||||||
|
((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type alias for a list of points.
|
||||||
|
type PointList = Vec<Point>;
|
||||||
|
|
||||||
|
/// The maximum number of points allowed.
|
||||||
|
const MAX_POINTS: usize = 1000;
|
||||||
|
|
||||||
|
static ORIGIN: Point = Point { x: 0.0, y: 0.0 };
|
||||||
|
|
||||||
|
/// A helper macro for creating points.
|
||||||
|
macro_rules! point {
|
||||||
|
($x:expr, $y:expr) => {
|
||||||
|
Point::new($x, $y)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod geometry {
|
||||||
|
pub fn hello() {
|
||||||
|
println!("Hello from geometry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let p = point!(1.0, 2.0);
|
||||||
|
println!("Point: ({}, {})", p.x, p.y);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user