From d8a5143662e60769a89e734b6fb6d93e40154495 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 27 Feb 2025 13:18:55 +1100 Subject: [PATCH 1/3] feat: open cmd --- cmd/ftl/cmd_open.go | 121 ++++++++++++++++++++++++++++++++ cmd/ftl/main.go | 1 + internal/terminal/predictors.go | 1 + 3 files changed, 123 insertions(+) create mode 100644 cmd/ftl/cmd_open.go diff --git a/cmd/ftl/cmd_open.go b/cmd/ftl/cmd_open.go new file mode 100644 index 0000000000..fa9e902263 --- /dev/null +++ b/cmd/ftl/cmd_open.go @@ -0,0 +1,121 @@ +package main + +import ( + "context" + "fmt" + "strconv" + "strings" + + "connectrpc.com/connect" + "github.com/alecthomas/kong" + ftlv1 "github.com/block/ftl/backend/protos/xyz/block/ftl/v1" + "github.com/block/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" + "github.com/block/ftl/common/reflection" + "github.com/block/ftl/common/schema" + "github.com/block/ftl/internal/exec" + "github.com/block/ftl/internal/log" + "github.com/block/ftl/internal/projectconfig" +) + +type openCmd struct { + Ref reflection.Ref `arg:"" help:"Language of the module to create." placeholder:"MODULE.ITEM" predictor:"decls"` + Editor string `arg:"" help:"Editor to open the file with." enum:"auto,vscode,intellij" default:"auto"` + + TerminalProgram string `help:"Terminal program this command is running in which can influence 'auto' editor" env:"TERM_PROGRAM" hidden:""` + TerminalEmulator string `help:"Terminal emulator can influence 'auto' editor" env:"TERMINAL_EMULATOR" hidden:""` +} + +func (i openCmd) Run(ctx context.Context, ktctx *kong.Context, client ftlv1connect.AdminServiceClient, pc projectconfig.Config) error { + ref := i.Ref.ToSchema() + resp, err := client.GetSchema(ctx, connect.NewRequest(&ftlv1.GetSchemaRequest{})) + if err != nil { + return fmt.Errorf("could not get schema: %w", err) + } + sch, err := mergedSchemaFromResp(resp.Msg) + if err != nil { + return err + } + decl, ok := sch.Resolve(ref).Get() + if !ok { + return fmt.Errorf("could not find %q", ref) + } + if decl.Position().Filename == "" { + return fmt.Errorf("could not find location of %q", ref) + } + if i.Editor == "auto" { + if i.TerminalProgram == "vscode" { + i.Editor = "vscode" + } else if strings.Contains(i.TerminalEmulator, "JetBrains") { + i.Editor = "intellij" + } else { + return fmt.Errorf(`could not auto choose default editor, use one of the following values: + vscode + intellij`) + } + } + switch i.Editor { + case "vscode": + return openVisualStudioCode(ctx, decl.Position(), pc.Root()) + case "intellij": + return openIntelliJ(ctx, decl.Position(), pc.Root()) + default: + // TODO: print out valid editors + return fmt.Errorf("unsupported editor %q", i.Editor) + } +} + +// mergedSchemaFromResp parses the schema from the response and applies any changesets. +// modules that are removed by a changeset stay in the schema. +func mergedSchemaFromResp(resp *ftlv1.GetSchemaResponse) (*schema.Schema, error) { + sch, err := schema.FromProto(resp.Schema) + if err != nil { + return nil, fmt.Errorf("could not parse schema: %w", err) + } + for _, cs := range resp.Changesets { + fmt.Printf("Considering changeset %q\n", cs.Key) + + for _, module := range cs.Modules { + moduleSch, err := schema.ModuleFromProto(module) + if err != nil { + return nil, fmt.Errorf("could not parse module: %w", err) + } + + var found bool + for i, m := range sch.Modules { + if m.Name != module.Name { + continue + } + sch.Modules[i] = moduleSch + found = true + break + } + if !found { + sch.Modules = append(sch.Modules, moduleSch) + } + } + } + return sch, nil +} + +func openVisualStudioCode(ctx context.Context, pos schema.Position, projectRoot string) error { + // TODO: schema filepath is has a root of `ftl/modulename`. Replace it with module root + path := pos.Filename + ":" + fmt.Sprint(pos.Line) + if pos.Column > 0 { + path += ":" + fmt.Sprint(pos.Column) + } + err := exec.Command(ctx, log.Debug, ".", "code", projectRoot, "--goto", path).RunBuffered(ctx) + if err != nil { + return fmt.Errorf("could not open visual studio code: %w", err) + } + return nil +} + +func openIntelliJ(ctx context.Context, pos schema.Position, projectRoot string) error { + // TODO: schema filepath is has a root of `ftl/modulename`. Replace it with module root + // TODO: if idea is not available, explain how to activate it + err := exec.Command(ctx, log.Debug, ".", "idea", projectRoot, "--line", strconv.Itoa(pos.Line), "--column", strconv.Itoa(pos.Column), pos.Filename).RunBuffered(ctx) + if err != nil { + return fmt.Errorf("could not open IntelliJ IDEA: %w", err) + } + return nil +} diff --git a/cmd/ftl/main.go b/cmd/ftl/main.go index 5a993b8ec3..b11b78179b 100644 --- a/cmd/ftl/main.go +++ b/cmd/ftl/main.go @@ -69,6 +69,7 @@ type SharedCLI struct { Goose gooseCmd `cmd:"" help:"Run a goose command."` Mysql mySQLCmd `cmd:"" help:"Manage MySQL databases."` Postgres postgresCmd `cmd:"" help:"Manage PostgreSQL databases."` + Open openCmd `cmd:"" help:"Open a file in the editor at the location of a schema declaration."` } type CLI struct { diff --git a/internal/terminal/predictors.go b/internal/terminal/predictors.go index 4728e19ab4..75c19aaeea 100644 --- a/internal/terminal/predictors.go +++ b/internal/terminal/predictors.go @@ -11,6 +11,7 @@ import ( func Predictors(view *schemaeventsource.View) map[string]complete.Predictor { return map[string]complete.Predictor{ + "decls": &declPredictor[schema.Decl]{view: *view}, "verbs": &declPredictor[*schema.Verb]{view: *view}, "configs": &declPredictor[*schema.Config]{view: *view}, "secrets": &declPredictor[*schema.Secret]{view: *view}, From d229d87db8c533de49768175e5fa2e34f4d0af84 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 27 Feb 2025 05:24:04 +0000 Subject: [PATCH 2/3] chore(autofmt): Automated formatting --- cmd/ftl/cmd_open.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/ftl/cmd_open.go b/cmd/ftl/cmd_open.go index fa9e902263..6d1e7f40e2 100644 --- a/cmd/ftl/cmd_open.go +++ b/cmd/ftl/cmd_open.go @@ -8,6 +8,7 @@ import ( "connectrpc.com/connect" "github.com/alecthomas/kong" + ftlv1 "github.com/block/ftl/backend/protos/xyz/block/ftl/v1" "github.com/block/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" "github.com/block/ftl/common/reflection" From 5a6f1f9f9cea24dc23b281d3a8692e60d4295b11 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Fri, 28 Feb 2025 11:54:48 +1100 Subject: [PATCH 3/3] more --- cmd/ftl/cmd_open.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/ftl/cmd_open.go b/cmd/ftl/cmd_open.go index 6d1e7f40e2..a2d46af842 100644 --- a/cmd/ftl/cmd_open.go +++ b/cmd/ftl/cmd_open.go @@ -32,6 +32,7 @@ func (i openCmd) Run(ctx context.Context, ktctx *kong.Context, client ftlv1conne if err != nil { return fmt.Errorf("could not get schema: %w", err) } + // merge changesets into schema so we get the latest decl positions sch, err := mergedSchemaFromResp(resp.Msg) if err != nil { return err @@ -120,3 +121,7 @@ func openIntelliJ(ctx context.Context, pos schema.Position, projectRoot string) } return nil } + +func filePathForPosition(pos schema.Position, modulePath string) string { + // TODO: implement +}