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.

windows.go · 133 lines · 4.4 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 20h ago1package terminal
2
3// The pure half of the Windows support: the shapes Windows wants things in,
4// built here with no Windows API so that they can be tested on any platform.
5// pty_windows.go hands them to the API.
6
7import (
8 "strings"
9 "unicode/utf16"
10)
11
12// windowsFallbackShell is what runs when COMSPEC is unset, which it never is
13// on a working Windows: cmd.exe is found through PATH.
14const windowsFallbackShell = "cmd.exe"
15
16// windowsShell returns the shell Windows says is the user's — the value of
17// COMSPEC, which is cmd.exe on every Windows since NT — or the fallback when
18// the variable is empty.
19func windowsShell(comspec string) string {
20 if comspec != "" {
21 return comspec
22 }
23 return windowsFallbackShell
24}
25
26// environmentBlock encodes an environment the way CreateProcess wants it: each
27// entry as UTF-16, ended by a NUL, the whole block ended by a second NUL.
28//
29// An empty environment is two NULs, which CreateProcess reads as "no
30// variables" rather than as "inherit mine" — the latter is a nil block, which
31// this never returns, because the caller has always set TERM.
32func environmentBlock(env []string) []uint16 {
33 if len(env) == 0 {
34 return []uint16{0, 0}
35 }
36 block := make([]uint16, 0, 64*len(env)+2)
37 for _, entry := range env {
38 block = append(block, utf16.Encode([]rune(entry))...)
39 block = append(block, 0)
40 }
41 return append(block, 0)
42}
43
44// windowsCommandLine composes the single command-line string Windows starts a
45// process from, since Windows has no argv: the program receives one string and
46// splits it by its own rules.
47//
48// cmd.exe's rules are its own and are not the C runtime's. Given `/S /C`, it
49// strips the first and last quote of what follows and hands the rest to its
50// parser verbatim, so the command is wrapped in exactly one pair of quotes and
51// nothing inside it is escaped — a `"` inside the command has to reach cmd.exe
52// as a `"`, and the C-runtime spelling `\"` would reach it as a backslash and
53// a quote. Every other program gets the C-runtime quoting that Go's own exec
54// uses, which is what a Go, Node or Python program on the other end expects.
55func windowsCommandLine(program string, args []string) string {
56 if isCmdExe(program) && len(args) >= 1 && strings.EqualFold(args[0], "/S") {
57 return cmdExeCommandLine(program, args)
58 }
59
60 parts := make([]string, 0, 1+len(args))
61 parts = append(parts, escapeArgument(program))
62 for _, arg := range args {
63 parts = append(parts, escapeArgument(arg))
64 }
65 return strings.Join(parts, " ")
66}
67
68// isCmdExe reports whether a program path names cmd.exe, whatever its case
69// and wherever it is.
70func isCmdExe(program string) bool {
71 name := program
72 if at := strings.LastIndexAny(name, `\/`); at >= 0 {
73 name = name[at+1:]
74 }
75 return strings.EqualFold(name, "cmd.exe") || strings.EqualFold(name, "cmd")
76}
77
78// cmdExeCommandLine writes `"cmd.exe" /S /C "command"`: the switches verbatim,
79// and the command — the last argument — inside one pair of quotes that /S
80// tells cmd.exe to strip.
81func cmdExeCommandLine(program string, args []string) string {
82 switches := args[:len(args)-1]
83 command := args[len(args)-1]
84
85 parts := []string{`"` + program + `"`}
86 parts = append(parts, switches...)
87 parts = append(parts, `"`+command+`"`)
88 return strings.Join(parts, " ")
89}
90
91// escapeArgument quotes one argument the way the C runtime's argv parser
92// undoes it: quotes when it holds a space, a tab or a quote, `\"` for a quote,
93// and doubled backslashes only where they precede a quote or end the argument.
94// It is the rule syscall.EscapeArg follows, written here because that function
95// exists only on Windows and this one has to be testable everywhere.
96func escapeArgument(arg string) string {
97 if arg == "" {
98 return `""`
99 }
100 if !strings.ContainsAny(arg, " \t\"") {
101 return arg
102 }
103
104 var b strings.Builder
105 b.WriteByte('"')
106 backslashes := 0
107 for i := 0; i < len(arg); i++ {
108 switch arg[i] {
109 case '\\':
110 backslashes++
111 continue
112 case '"':
113 b.WriteString(strings.Repeat(`\`, 2*backslashes+1))
114 b.WriteByte('"')
115 default:
116 b.WriteString(strings.Repeat(`\`, backslashes))
117 b.WriteByte(arg[i])
118 }
119 backslashes = 0
120 }
121 b.WriteString(strings.Repeat(`\`, 2*backslashes))
122 b.WriteByte('"')
123 return b.String()
124}
125
126// programName returns the last element of a Windows or Unix path, which is
127// what a window title shows for the shell.
128func programName(program string) string {
129 if at := strings.LastIndexAny(program, `\/`); at >= 0 {
130 return program[at+1:]
131 }
132 return program
133}