forked from thonny/thonny
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathterminal.py
More file actions
239 lines (194 loc) · 8.66 KB
/
Copy pathterminal.py
File metadata and controls
239 lines (194 loc) · 8.66 KB
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import os.path
import platform
import shlex
import subprocess
import sys
def run_in_terminal(cmd, cwd, env_overrides={}, keep_open=True, title: str = None):
from pystart.running import get_environment_with_overrides
env = get_environment_with_overrides(env_overrides)
if not cwd or not os.path.exists(cwd):
cwd = os.getcwd()
if sys.platform == "win32":
_run_in_terminal_in_windows(cmd, cwd, env, keep_open)
elif sys.platform == "linux":
_run_in_terminal_in_linux(cmd, cwd, env, keep_open)
elif sys.platform == "darwin":
_run_in_terminal_in_macos(cmd, cwd, env_overrides, keep_open)
else:
raise RuntimeError("Can't launch terminal in " + platform.system())
def open_system_shell(cwd, env_overrides={}):
from pystart.running import get_environment_with_overrides
env = get_environment_with_overrides(env_overrides)
if sys.platform == "darwin":
_run_in_terminal_in_macos([], cwd, env_overrides, True)
elif sys.platform == "win32":
cmd = "start " + _get_windows_terminal_command()
subprocess.Popen(cmd, cwd=cwd, env=env, shell=True)
elif sys.platform == "linux":
cmd = _get_linux_terminal_command()
subprocess.Popen(cmd, cwd=cwd, env=env, shell=True)
else:
raise RuntimeError("Can't launch terminal in " + platform.system())
def _add_to_path(directory, path):
# Always prepending to path may seem better, but this could mess up other things.
# If the directory contains only one Python distribution executables, then
# it probably won't be in path yet and therefore will be prepended.
if (
directory in path.split(os.pathsep)
or sys.platform == "win32"
and directory.lower() in path.lower().split(os.pathsep)
):
return path
else:
return directory + os.pathsep + path
def _run_in_terminal_in_windows(cmd, cwd, env, keep_open):
if keep_open:
term_cmd = _get_windows_terminal_command()
cmd_line = ["start", term_cmd, "-NoExit", "-Command"] + cmd
subprocess.Popen(cmd_line, cwd=cwd, env=env, shell=True)
else:
subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_CONSOLE, cwd=cwd, env=env)
def _run_in_terminal_in_linux(cmd, cwd, env, keep_open):
def _shellquote(s):
return subprocess.list2cmdline([s])
term_cmd = _get_linux_terminal_command()
if isinstance(cmd, list):
cmd = " ".join(map(_shellquote, cmd))
if keep_open:
# http://stackoverflow.com/a/4466566/261181
core_cmd = "{cmd}; exec bash -i".format(cmd=cmd)
in_term_cmd = "bash -c {core_cmd}".format(core_cmd=_shellquote(core_cmd))
else:
in_term_cmd = cmd
if term_cmd == "lxterminal":
# https://www.raspberrypi.org/forums/viewtopic.php?t=221490
whole_cmd = "{term_cmd} --command={in_term_cmd}".format(
term_cmd=term_cmd, in_term_cmd=_shellquote(in_term_cmd)
)
elif term_cmd == "kitty":
# Kitty doesn't support -e
whole_cmd = "{term_cmd} {in_term_cmd}".format(term_cmd=term_cmd, in_term_cmd=in_term_cmd)
elif term_cmd == "wezterm":
# Wezterm needs each argument to be quoted separately
parts = in_term_cmd.replace('"', "").split(
" ", 2
) # Split only twice to preserve the quoted command
# cd into current working directory since wezterm doesn't mantain the working directory
parts[2] = "cd " + cwd + ";" + parts[2]
in_term_cmd = " ".join(f'"{part}"' for part in parts)
whole_cmd = "{term_cmd} -e {in_term_cmd}".format(term_cmd=term_cmd, in_term_cmd=in_term_cmd)
else:
whole_cmd = "{term_cmd} -e {in_term_cmd}".format(
term_cmd=term_cmd, in_term_cmd=_shellquote(in_term_cmd)
)
if term_cmd == "terminator" and "PYTHONPATH" in env:
# it is written in Python 2 and the PYTHONPATH of Python 3 will confuse it
# https://github.com/pystart/thonny/issues/1129
del env["PYTHONPATH"]
subprocess.Popen(whole_cmd, cwd=cwd, env=env, shell=True)
def _run_in_terminal_in_macos(cmd, cwd, env_overrides, keep_open):
_shellquote = shlex.quote
cmds = "clear; cd " + _shellquote(cwd)
# osascript "tell application" won't change Terminal's env
# (at least when Terminal is already active)
# At the moment I just explicitly set some important variables
for key in env_overrides:
if env_overrides[key] is None:
cmds += "; unset " + key
else:
value = env_overrides[key]
if key == "PATH":
value = _normalize_path(value)
cmds += "; export {key}={value}".format(key=key, value=_shellquote(value))
if cmd:
if isinstance(cmd, list):
cmd = " ".join(map(_shellquote, cmd))
cmds += "; " + cmd
if not keep_open:
cmds += "; exit"
# try to shorten to avoid too long line https://github.com/pystart/thonny/issues/1529
common_prefix = os.path.normpath(sys.prefix).rstrip("/")
cmds = (
" export THOPR=" + common_prefix + " ; " + cmds.replace(common_prefix + "/", "$THOPR" + "/")
)
print(cmds)
# The script will be sent to Terminal with 'do script' command, which takes a string.
# We'll prepare an AppleScript string literal for this
# (http://stackoverflow.com/questions/10667800/using-quotes-in-a-applescript-string):
cmd_as_apple_script_string_literal = (
'"' + cmds.replace("\\", "\\\\").replace('"', '\\"').replace("$", "\\$") + '"'
)
# When Terminal is not open, then do script opens two windows.
# do script ... in window 1 would solve this, but if Terminal is already
# open, this could run the script in existing terminal (in undesirable env on situation)
# That's why I need to prepare two variations of the 'do script' command
doScriptCmd1 = """ do script %s """ % cmd_as_apple_script_string_literal
doScriptCmd2 = """ do script %s in window 1 """ % cmd_as_apple_script_string_literal
# The whole AppleScript will be executed with osascript by giving script
# lines as arguments. The lines containing our script need to be shell-quoted:
quotedCmd1 = subprocess.list2cmdline([doScriptCmd1])
quotedCmd2 = subprocess.list2cmdline([doScriptCmd2])
# Now we can finally assemble the osascript command line
cmd_line = (
"osascript"
+ """ -e 'if application "Terminal" is running then ' """
+ """ -e ' tell application "Terminal" ' """
+ """ -e """
+ quotedCmd1
+ """ -e ' activate ' """
+ """ -e ' end tell ' """
+ """ -e 'else ' """
+ """ -e ' tell application "Terminal" ' """
+ """ -e """
+ quotedCmd2
+ """ -e ' activate ' """
+ """ -e ' end tell ' """
+ """ -e 'end if ' """
)
subprocess.Popen(cmd_line, cwd=cwd, shell=True)
def _get_windows_terminal_command():
import shutil
if shutil.which("pwsh"):
# PowerShell version 6+
return "pwsh"
else:
# Windows PowerShell 5.1
return "powershell"
def _get_linux_terminal_command():
import shutil
xte = shutil.which("x-terminal-emulator")
if xte:
if os.path.realpath(xte).endswith("/lxterminal") and shutil.which("lxterminal"):
# need to know exact program, because it needs special treatment
return "lxterminal"
elif os.path.realpath(xte).endswith("/terminator") and shutil.which("terminator"):
# https://github.com/pystart/thonny/issues/1129
return "terminator"
else:
return "x-terminal-emulator"
# Older konsole didn't pass on the environment
elif shutil.which("konsole"):
if (
shutil.which("gnome-terminal")
and "gnome" in os.environ.get("DESKTOP_SESSION", "").lower()
):
return "gnome-terminal"
else:
return "konsole"
elif shutil.which("gnome-terminal"):
return "gnome-terminal"
elif shutil.which("xfce4-terminal"):
return "xfce4-terminal"
elif shutil.which("lxterminal"):
return "lxterminal"
elif shutil.which("xterm"):
return "xterm"
elif shutil.which("kitty"):
return "kitty"
elif shutil.which("wezterm"):
return "wezterm"
else:
raise RuntimeError("Don't know how to open terminal emulator")
def _normalize_path(s):
parts = s.split(os.pathsep)
return os.pathsep.join([os.path.normpath(part) for part in parts])