ctx/internal/plugins/csharp/outline.go
Ricardo Carneiro 15dc1b6b2f feat(csharp): implement 'outline' command for single-file structural summary
Adds OutlineHandler.cs to the Roslyn helper using CSharpSyntaxTree.ParseText
and manual syntax tree traversal (no solution load required). Extracts
namespaces, types, method/property/field/event signatures and line numbers
without method bodies. Handles file-scoped namespaces, generics, records,
nested types, partial classes, top-level statements, and [Obsolete] markers.

Adds Microsoft.CodeAnalysis.CSharp 4.13.0 as the sole new NuGet dependency
(pure parser, no MSBuild coupling). Go side adds Outline() client method,
OutlineResult/Type/Member types, outline.go command, and WriteOutline formatter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 19:41:47 -03:00

69 lines
1.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package csharp
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/ricarneiro/ctx/internal/core"
"github.com/ricarneiro/ctx/internal/plugins/csharp/helper"
"github.com/spf13/cobra"
)
func outlineCmd(ctx *core.Context) *cobra.Command {
return &cobra.Command{
Use: "outline <file.cs>",
Short: "Show structural outline of a C# file (no method bodies)",
Long: `Parse a C# source file and emit its structural skeleton:
namespaces, types, method signatures, properties, fields, events.
Method bodies are omitted — reduces large files by 8090%.
Does not require a loaded solution. Works on a single file.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runOutline(ctx, args[0])
},
}
}
func runOutline(ctx *core.Context, file string) error {
abs, err := resolveFilePath(ctx.WorkDir, file)
if err != nil {
fmt.Fprintln(ctx.Stderr, err.Error())
return errExit
}
if !strings.HasSuffix(strings.ToLower(abs), ".cs") {
fmt.Fprintf(ctx.Stderr, "not a C# file: %s\n", abs)
return errExit
}
if _, err := os.Stat(abs); err != nil {
fmt.Fprintf(ctx.Stderr, "file not found: %s\n", abs)
return errExit
}
client, err := helper.NewClient()
if err != nil {
fmt.Fprintln(ctx.Stderr, err.Error())
return errExit
}
defer client.Close()
outline, err := client.Outline(abs)
if err != nil {
fmt.Fprintln(ctx.Stderr, err.Error())
return errExit
}
return WriteOutline(ctx.Stdout, outline)
}
func resolveFilePath(workDir, file string) (string, error) {
if filepath.IsAbs(file) {
return filepath.Clean(file), nil
}
return filepath.Clean(filepath.Join(workDir, file)), nil
}