File: editor.go

package info (click to toggle)
golang-github-charmbracelet-x 0.0~git20251028.0cf22f8%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,940 kB
  • sloc: sh: 124; makefile: 5
file content (106 lines) | stat: -rw-r--r-- 3,039 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Package editor provides functionality for opening files in external editors.
package editor

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"slices"
	"strings"
)

const defaultEditor = "nano"

// Option defines an editor option.
//
// An Option may act differently in some editors, or not be supported in
// some of them.
type Option func(editor, filename string) (args []string, pathInArgs bool)

// OpenAtLine opens the file at the given line number in supported editors.
//
// Deprecated: use LineNumber instead.
func OpenAtLine(n int) Option { return LineNumber(n) }

// LineNumber opens the file at the given line number in supported editors. If
// [number] is less than line 1, the file will be opened at line 1.
func LineNumber(number int) Option {
	if number < 1 {
		number = 1
	}
	plusLineEditors := []string{"vi", "vim", "nvim", "nano", "emacs", "kak", "gedit"}
	return func(editor, filename string) ([]string, bool) {
		if slices.Contains(plusLineEditors, editor) {
			return []string{fmt.Sprintf("+%d", number)}, false
		}
		if editor == "code" {
			return []string{
				"--goto",
				fmt.Sprintf("%s:%d", filename, number),
			}, true
		}
		return nil, false
	}
}

// EndOfLine opens the file at the end of the line in supported editors.
func EndOfLine() Option {
	return func(editor, _ string) (args []string, pathInArgs bool) {
		switch editor {
		case "vim", "nvim":
			return []string{"+norm! $"}, false
		}
		return nil, false
	}
}

// Cmd returns a *exec.Cmd editing the given path with $EDITOR or nano if no
// $EDITOR is set.
// Deprecated: use Command or CommandContext instead.
func Cmd(app, path string, options ...Option) (*exec.Cmd, error) {
	return CommandContext(context.Background(), app, path, options...)
}

// Command returns a *exec.Cmd editing the given path with $EDITOR or nano if
// no $EDITOR is set.
func Command(app, path string, options ...Option) (*exec.Cmd, error) {
	return CommandContext(context.Background(), app, path, options...)
}

// CommandContext returns a *exec.Cmd editing the given path with $EDITOR or nano
// if no $EDITOR is set.
func CommandContext(ctx context.Context, app, path string, options ...Option) (*exec.Cmd, error) {
	if os.Getenv("SNAP_REVISION") != "" {
		return nil, fmt.Errorf("did you install with Snap? %[1]s is sandboxed and unable to open an editor. Please install %[1]s with Go or another package manager to enable editing", app)
	}

	editor, args := getEditor()
	editorName := filepath.Base(editor)

	needsToAppendPath := true
	for _, opt := range options {
		optArgs, pathInArgs := opt(editorName, path)
		if pathInArgs {
			needsToAppendPath = false
		}
		args = append(args, optArgs...)
	}
	if needsToAppendPath {
		args = append(args, path)
	}

	return exec.CommandContext(ctx, editor, args...), nil
}

func getEditor() (string, []string) {
	editor := strings.Fields(os.Getenv("EDITOR"))
	if len(editor) > 1 {
		return editor[0], editor[1:]
	}
	if len(editor) == 1 {
		return editor[0], []string{}
	}
	return defaultEditor, []string{}
}