turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

pty.go · 149 lines · 5.1 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 20h ago1package terminal
2
3import (
4 "errors"
5 "os"
6 "runtime"
7)
8
9// ErrUnsupported is returned by Start on a platform where this editor cannot
10// open a pseudo-terminal.
11//
12// It is not a failure of the editor: everything else goes on working, and only
13// terminal windows are unavailable. Linux, macOS and Windows all have one;
14// anything else does not, yet.
15var ErrUnsupported = errors.New("terminal: not supported on " + runtime.GOOS + " yet")
16
17// Session is a shell running in its own pseudo-terminal.
18//
19// Reading from it gives whatever the shell has written; writing to it is what
20// the shell reads as its input. Both are safe to use from separate goroutines,
21// which is how a terminal window is driven: one goroutine reads, the main one
22// writes.
23//
24// session, err := terminal.Start(terminal.Options{Dir: ".", Width: 80, Height: 24})
25// if err != nil {
26// return err
27// }
28// defer session.Close()
29// go io.Copy(parser, session)
30type Session struct {
31 child child
32}
33
34// child is the platform's half of a session: the process on the far side of
35// the pseudo-terminal, and the two ends of it this side holds.
36//
37// Unix has one implementation over /dev/ptmx and an exec.Cmd; Windows has one
38// over a pseudo-console and a process created by hand, because Go's exec
39// cannot attach a process to a pseudo-console. The Session above is the same
40// on both, which is what lets the emulator and the view know nothing about
41// which one they are talking to.
42type child interface {
43 // Read returns what the shell has written; io.EOF once it has gone.
44 Read(b []byte) (int, error)
45 // Write sends input to the shell.
46 Write(b []byte) (int, error)
47 // Resize tells the shell its terminal has changed size.
48 Resize(width, height int) error
49 // Close ends the shell and releases everything held for it.
50 Close() error
51 // Command returns the name of the program that was started.
52 Command() string
53}
54
55// Options say what shell to run and how big its terminal is.
56type Options struct {
57 // Shell is the program to run. Empty means the user's own shell: $SHELL on
58 // Unix, %COMSPEC% on Windows.
59 Shell string
60 // Args are the arguments to give it. Empty means none, which is how an
61 // interactive shell is started; ["-c", "go test ./..."] is how one command
62 // is run instead — or ["/S", "/C", "go test ./..."] for cmd.exe.
63 Args []string
64 // Dir is the directory to start in. Empty means the current one.
65 Dir string
66 // Width and Height are the size of the terminal, in cells.
67 Width int
68 Height int
69 // Env is the environment for the shell. Empty means the editor's own,
70 // with TERM set to what this emulator implements.
71 Env []string
72}
73
74// TermName is what the shell is told its terminal is.
75//
76// It has to be a terminal the emulator can live up to: promising xterm-256color
77// and then not understanding what a program sends back is worse than promising
78// less.
79const TermName = "xterm-256color"
80
81// Start runs a shell in a new pseudo-terminal.
82//
83// On a platform without support it returns ErrUnsupported, which callers are
84// expected to report rather than treat as a failure.
85func Start(options Options) (*Session, error) {
86 c, err := startChild(options)
87 if err != nil {
88 return nil, err
89 }
90 return &Session{child: c}, nil
91}
92
93// shellOrDefault returns the shell to run: the one asked for, or the user's
94// own, which each platform names in its own way.
95func shellOrDefault(shell string) string {
96 if shell != "" {
97 return shell
98 }
99 return defaultShell()
100}
101
102// environment returns the environment for the shell, with TERM set to the
103// terminal this emulator actually implements.
104//
105// Inheriting the editor's own TERM would tell the shell about the *outer*
106// terminal, which may promise capabilities this window does not have.
107func environment(env []string) []string {
108 if env == nil {
109 env = os.Environ()
110 }
111
112 out := make([]string, 0, len(env)+1)
113 for _, entry := range env {
114 if !hasPrefix(entry, "TERM=") {
115 out = append(out, entry)
116 }
117 }
118 return append(out, "TERM="+TermName)
119}
120
121// hasPrefix reports whether a string starts with a prefix.
122func hasPrefix(s, prefix string) bool {
123 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
124}
125
126// Read returns what the shell has written. It blocks until there is something,
127// and returns io.EOF once the shell has gone.
128func (s *Session) Read(b []byte) (int, error) { return s.child.Read(b) }
129
130// Write sends input to the shell, as typing at it would.
131func (s *Session) Write(b []byte) (int, error) { return s.child.Write(b) }
132
133// Resize tells the shell its terminal has changed size, which is what makes a
134// full-screen program repaint at the new one.
135func (s *Session) Resize(width, height int) error {
136 return s.child.Resize(max(width, 1), max(height, 1))
137}
138
139// Close ends the session: the pseudo-terminal is closed, which the shell sees
140// as its input ending, and the process is waited for so that it leaves nothing
141// behind.
142//
143// A shell that does not take the hint is killed. Closing must not be able to
144// hang the editor.
145func (s *Session) Close() error { return s.child.Close() }
146
147// Command returns the name of the shell that was started, without its path,
148// which is what a window title wants.
149func (s *Session) Command() string { return s.child.Command() }