From 309d4c08f9f55a355ac781cb0a3d7a185fe1bd71 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Date: Tue, 3 Mar 2026 14:15:57 +0530 Subject: [PATCH 1/3] feat(editor): implement platform-specific argument parsing for editors - Added `parseEditorArgs` function for Windows and POSIX systems to handle command line arguments for text editors. - Created `editor_windows.go` and `editor_unix.go` files to separate platform-specific implementations. - Introduced tests in `editor_windows_test.go` and `editor_unix_test.go` to ensure correct parsing behavior across different scenarios. - This change enhances compatibility with various text editors by correctly interpreting command line arguments, improving user experience. --- internal/prompt/editor.go | 4 +- internal/prompt/editor_unix.go | 14 +++++ internal/prompt/editor_unix_test.go | 73 ++++++++++++++++++++++++++ internal/prompt/editor_windows.go | 44 ++++++++++++++++ internal/prompt/editor_windows_test.go | 73 ++++++++++++++++++++++++++ 5 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 internal/prompt/editor_unix.go create mode 100644 internal/prompt/editor_unix_test.go create mode 100644 internal/prompt/editor_windows.go create mode 100644 internal/prompt/editor_windows_test.go diff --git a/internal/prompt/editor.go b/internal/prompt/editor.go index 153601ad7..be9f6c4ee 100644 --- a/internal/prompt/editor.go +++ b/internal/prompt/editor.go @@ -7,8 +7,6 @@ import ( "os/exec" "runtime" - "github.com/kballard/go-shellquote" - "github.com/auth0/auth0-cli/internal/iostream" ) @@ -40,7 +38,7 @@ type editorPrompt struct { // openFile opens filename in the preferred text editor, resolving the // arguments with editor specific logic. func (p *editorPrompt) openFile(filename string, infoFn func()) error { - args, err := shellquote.Split(p.cmd) + args, err := parseEditorArgs(p.cmd) if err != nil { return err } diff --git a/internal/prompt/editor_unix.go b/internal/prompt/editor_unix.go new file mode 100644 index 000000000..5c52308ad --- /dev/null +++ b/internal/prompt/editor_unix.go @@ -0,0 +1,14 @@ +//go:build !windows +// +build !windows + +package prompt + +import ( + "github.com/kballard/go-shellquote" +) + +// parseEditorArgs parses POSIX shell-style command line arguments +// into a slice of strings suitable for exec.Command. +func parseEditorArgs(cmd string) ([]string, error) { + return shellquote.Split(cmd) +} diff --git a/internal/prompt/editor_unix_test.go b/internal/prompt/editor_unix_test.go new file mode 100644 index 000000000..84d380ffc --- /dev/null +++ b/internal/prompt/editor_unix_test.go @@ -0,0 +1,73 @@ +//go:build !windows +// +build !windows + +package prompt + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseEditorArgs(t *testing.T) { + tests := []struct { + name string + cmd string + expected []string + wantErr bool + }{ + { + name: "simple editor", + cmd: "vim", + expected: []string{"vim"}, + }, + { + name: "editor with flag", + cmd: "code --wait", + expected: []string{"code", "--wait"}, + }, + { + name: "editor with multiple flags", + cmd: "subl --wait --new-window", + expected: []string{"subl", "--wait", "--new-window"}, + }, + { + name: "path with spaces in double quotes", + cmd: `"/usr/local/bin/my editor" --wait`, + expected: []string{"/usr/local/bin/my editor", "--wait"}, + }, + { + name: "path with spaces in single quotes", + cmd: `'/usr/local/bin/my editor' --wait`, + expected: []string{"/usr/local/bin/my editor", "--wait"}, + }, + { + name: "path without spoaces or quotes", + cmd: `/usr/local/bin/myeditor --wait`, + expected: []string{"/usr/local/bin/myeditor", "--wait"}, + }, + { + name: "empty command", + cmd: ``, + expected: []string{}, + }, + { + name: "unterminated quote", + cmd: `"unterminated`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args, err := parseEditorArgs(tt.cmd) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.expected, args) + }) + } +} diff --git a/internal/prompt/editor_windows.go b/internal/prompt/editor_windows.go new file mode 100644 index 000000000..9852aa4fd --- /dev/null +++ b/internal/prompt/editor_windows.go @@ -0,0 +1,44 @@ +//go:build windows +// +build windows + +package prompt + +import ( + "fmt" + "syscall" + "unsafe" +) + +// parseEditorArgs parses Windows-style command line arguments +// into a slice of strings suitable for exec.Command. +// Uses the Windows CommandLineToArgvW API to correctly handle +// backslash path separators and quoted paths with spaces. +func parseEditorArgs(cmd string) ([]string, error) { + if cmd == "" { + return []string{}, nil + } + + // Convert the Go string (UTF-8) to a UTF-16 pointer, as required by Windows APIs. + utf16Cmd, err := syscall.UTF16PtrFromString(cmd) + if err != nil { + return nil, fmt.Errorf("failed to encode editor command: %w", err) + } + + // Use the Windows CommandLineToArgvW API to split the command string into arguments. + // This correctly handles Windows path separators (\) and quoted paths with spaces. + var argc int32 + argv, err := syscall.CommandLineToArgv(utf16Cmd, &argc) + if err != nil { + return nil, fmt.Errorf("failed to parse editor command %q: %w", cmd, err) + } + // argv is allocated by the Windows API and must be freed with LocalFree. + defer syscall.LocalFree(syscall.Handle(unsafe.Pointer(argv))) + + // Convert each UTF-16 encoded argument back to a Go string. + args := make([]string, argc) + for i := range args { + args[i] = syscall.UTF16ToString((*argv[i])[:]) + } + + return args, nil +} diff --git a/internal/prompt/editor_windows_test.go b/internal/prompt/editor_windows_test.go new file mode 100644 index 000000000..f847ab17f --- /dev/null +++ b/internal/prompt/editor_windows_test.go @@ -0,0 +1,73 @@ +//go:build windows +// +build windows + +package prompt + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseEditorArgs(t *testing.T) { + tests := []struct { + name string + cmd string + expected []string + wantErr bool + }{ + { + name: "simple editor", + cmd: "notepad", + expected: []string{"notepad"}, + }, + { + name: "editor with flag", + cmd: "code --wait", + expected: []string{"code", "--wait"}, + }, + { + name: "windows path with backslashes", + cmd: `C:\Windows\notepad.exe`, + expected: []string{`C:\Windows\notepad.exe`}, + }, + { + name: "quoted path with spaces", + cmd: `"C:\Program Files\Notepad++\notepad++.exe" --wait`, + expected: []string{`C:\Program Files\Notepad++\notepad++.exe`, "--wait"}, + }, + { + name: "quoted path with spaces and multiple flags", + cmd: `"C:\Program Files\Microsoft VS Code\code.exe" --wait --new-window`, + expected: []string{`C:\Program Files\Microsoft VS Code\code.exe`, "--wait", "--new-window"}, + }, + { + name: "path without spaces or quotes", + cmd: `C:\tools\vim.exe -u C:\Users\me\.vimrc`, + expected: []string{`C:\tools\vim.exe`, "-u", `C:\Users\me\.vimrc`}, + }, + { + name: "empty command", + cmd: ``, + expected: []string{}, + }, + { + name: "unterminated quote", + cmd: `"unterminated`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args, err := parseEditorArgs(tt.cmd) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.expected, args) + }) + } +} From 9645ec71e4f434681a50fde60a3f01b334b9fc69 Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Fri, 6 Mar 2026 13:26:29 +0530 Subject: [PATCH 2/3] docs(README): update default editor instructions for Windows and macOS - Clarified the default text editor settings for Linux/macOS and Windows. - Added examples for setting the `EDITOR` environment variable in PowerShell and Command Prompt on Windows. - Ensured instructions are clear for both terminal and non-terminal editors. --- README.md | 27 ++++++++++++++++++++++---- internal/prompt/editor_windows_test.go | 5 ----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2cdbb024b..b07656701 100644 --- a/README.md +++ b/README.md @@ -188,11 +188,10 @@ Select **y** to proceed with your default tenant, or **N** to choose a different ## Customization -To change the text editor used for editing templates, rules, and actions, set the environment variable `EDITOR` to your -preferred editor. If choosing a non-terminal editor, ensure that the command starts the editor and waits for the files -to be closed before returning. +The default text editor is `vim` on Linux/macOS and `notepad` on Windows. To change that for editing templates, rules, and actions, set the environment variable `EDITOR` to your +preferred editor. If choosing a non-terminal editor, ensure that the command starts the editor and waits for the files to be closed before returning. -Examples: +### Linux / macOS ```shell # Uses vscode with the --wait flag. @@ -208,6 +207,26 @@ export EDITOR="nano" export EDITOR="vim" ``` +### Windows + +```powershell +# PowerShell (current session). +$env:EDITOR = "code --wait" +$env:EDITOR = '"C:\Program Files\Notepad++\notepad++.exe" --wait' +$env:EDITOR = '"C:\Program Files\Microsoft VS Code\code.exe" --wait --new-window' + +# PowerShell (persistent, across sessions). +[System.Environment]::SetEnvironmentVariable("EDITOR", "code --wait", "User") +``` + +```cmd +REM Command Prompt (current session). +set EDITOR=code --wait + +REM Command Prompt (persistent, across sessions). +setx EDITOR "code --wait" +``` + ## Anonymized Analytics Disclosure Anonymized data points are collected during the use of this CLI. This data includes the CLI version, operating system, timestamp, and other technical details that do not personally identify you. diff --git a/internal/prompt/editor_windows_test.go b/internal/prompt/editor_windows_test.go index f847ab17f..bcf6a9ef4 100644 --- a/internal/prompt/editor_windows_test.go +++ b/internal/prompt/editor_windows_test.go @@ -52,11 +52,6 @@ func TestParseEditorArgs(t *testing.T) { cmd: ``, expected: []string{}, }, - { - name: "unterminated quote", - cmd: `"unterminated`, - wantErr: true, - }, } for _, tt := range tests { From 0e25a421c690065b03c07b86e4433e465d4260d6 Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Fri, 6 Mar 2026 15:41:26 +0530 Subject: [PATCH 3/3] docs(README): update default editor paths for Windows PowerShell - Updated the Notepad++ and Visual Studio Code paths to a more generic format. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b07656701..efe1e24a4 100644 --- a/README.md +++ b/README.md @@ -212,8 +212,8 @@ export EDITOR="vim" ```powershell # PowerShell (current session). $env:EDITOR = "code --wait" -$env:EDITOR = '"C:\Program Files\Notepad++\notepad++.exe" --wait' -$env:EDITOR = '"C:\Program Files\Microsoft VS Code\code.exe" --wait --new-window' +$env:EDITOR = '"C:\Path To\bin\code" --wait' +$env:EDITOR = '"C:\Path To\notepad++.exe" --wait' # PowerShell (persistent, across sessions). [System.Environment]::SetEnvironmentVariable("EDITOR", "code --wait", "User")