feat: publish the Hime-san Beta 2 LSP

Add the standard-library language server, editor-neutral protocol contract, additive version feature discovery, and bounded security regressions while leaving the Sando runtime unchanged. Material design and implementation assistance was provided by OpenAI Codex.

Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
2026-08-12 20:31:07 -04:00
parent e3a94fd16b
commit 1082d9d61e
25 changed files with 2463 additions and 89 deletions
+30 -4
View File
@@ -18,6 +18,7 @@ import (
"gamertan.com/sandwich-hime/internal/compiler"
"gamertan.com/sandwich-hime/internal/devserver"
"gamertan.com/sandwich-hime/internal/lsp"
"gamertan.com/sandwich-hime/internal/version"
)
@@ -38,6 +39,8 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
return runCompilerCommand(ctx, args[0], args[1:], stdout, stderr, compiler.Check)
case "dev":
return runDev(ctx, args[1:], stdout, stderr)
case "lsp":
return runLSP(ctx, args[1:], os.Stdin, stdout, stderr)
case "version":
return runVersion(args[1:], stdout, stderr)
case "help", "-h", "--help":
@@ -116,10 +119,11 @@ func runVersion(args []string, stdout, stderr io.Writer) int {
return 2
}
information := struct {
Compiler string `json:"compiler"`
RuntimeABI string `json:"runtime_abi"`
Go string `json:"go"`
}{Compiler: version.Compiler, RuntimeABI: version.RuntimeABI, Go: runtime.Version()}
Compiler string `json:"compiler"`
RuntimeABI string `json:"runtime_abi"`
Go string `json:"go"`
Features []string `json:"features"`
}{Compiler: version.Compiler, RuntimeABI: version.RuntimeABI, Go: runtime.Version(), Features: []string{"lsp-stdio"}}
if *jsonOutput {
if err := json.NewEncoder(stdout).Encode(information); err != nil {
fmt.Fprintf(stderr, "himesan: encode version: %v\n", err)
@@ -131,6 +135,27 @@ func runVersion(args []string, stdout, stderr io.Writer) int {
return 0
}
func runLSP(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
flags := flag.NewFlagSet("lsp", flag.ContinueOnError)
flags.SetOutput(stderr)
stdio := flags.Bool("stdio", false, "serve Language Server Protocol JSON-RPC over stdin/stdout")
if err := flags.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
return 0
}
return 2
}
if !*stdio || flags.NArg() != 0 {
fmt.Fprintln(stderr, "himesan lsp requires exactly --stdio")
return 2
}
if err := lsp.Run(ctx, lsp.Options{Input: stdin, Output: stdout, LogOutput: stderr}); err != nil {
fmt.Fprintf(stderr, "himesan lsp: %v\n", err)
return 1
}
return 0
}
type stringList []string
func (values *stringList) String() string { return strings.Join(*values, ",") }
@@ -312,6 +337,7 @@ func printHelp(output io.Writer) {
fmt.Fprintln(output, " himesan check [--json] [paths...] validate sources and committed output without writes")
fmt.Fprintln(output, " himesan bless [--json] [paths...] friendly read-only alias for check")
fmt.Fprintln(output, " himesan dev [flags] [package] [-- app-args...] run the loopback last-good supervisor")
fmt.Fprintln(output, " himesan lsp --stdio run the read-only language server")
fmt.Fprintln(output, " himesan version [--json] print compiler and runtime ABI versions")
fmt.Fprintln(output)
fmt.Fprintln(output, "Templates use .sando; .san remains exclusively San language source.")
+12 -2
View File
@@ -30,14 +30,24 @@ func TestRunHelpVersionAndUnknownCommand(t *testing.T) {
if code := run(context.Background(), []string{"version", "--json"}, &stdout, &stderr); code != 0 {
t.Fatalf("version exit code = %d: %s", code, stderr.String())
}
var versionResult map[string]string
var versionResult struct {
Compiler string `json:"compiler"`
RuntimeABI string `json:"runtime_abi"`
Features []string `json:"features"`
}
if err := json.Unmarshal(stdout.Bytes(), &versionResult); err != nil {
t.Fatalf("version JSON: %v", err)
}
if versionResult["compiler"] == "" || versionResult["runtime_abi"] != compiler.RuntimeABI {
if versionResult.Compiler == "" || versionResult.RuntimeABI != compiler.RuntimeABI || len(versionResult.Features) != 1 || versionResult.Features[0] != "lsp-stdio" {
t.Fatalf("version result = %#v", versionResult)
}
stdout.Reset()
stderr.Reset()
if code := run(context.Background(), []string{"lsp"}, &stdout, &stderr); code != 2 {
t.Fatalf("lsp without --stdio exit code = %d, want 2", code)
}
stdout.Reset()
stderr.Reset()
if code := run(context.Background(), []string{"rebuke"}, &stdout, &stderr); code != 2 {