Skip to content

Instantly share code, notes, and snippets.

@danro danro/exec.py
Created Dec 15, 2011

Embed
What would you like to do?
Fix for the ST2 build system exec command
import sublime, sublime_plugin
import os, sys
import thread
import subprocess
import functools
class ProcessListener(object):
def on_data(self, proc, data):
pass
def on_finished(self, proc):
pass
# Encapsulates subprocess.Popen, forwarding stdout to a supplied
# ProcessListener (on a separate thread)
class AsyncProcess(object):
def __init__(self, arg_list, env, listener,
# "path" is an option in build systems
path="",
# "shell" is an options in build systems
shell=False):
self.listener = listener
self.killed = False
# Hide the console window on Windows
startupinfo = None
if os.name == "nt":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
# Set temporary PATH to locate executable in arg_list
if path:
old_path = os.environ["PATH"]
# The user decides in the build system whether he wants to append $PATH
# or tuck it at the front: "$PATH;C:\\new\\path", "C:\\new\\path;$PATH"
os.environ["PATH"] = os.path.expandvars(path).encode(sys.getfilesystemencoding())
proc_env = os.environ.copy()
proc_env.update(env)
for k, v in proc_env.iteritems():
proc_env[k] = os.path.expandvars(v).encode(sys.getfilesystemencoding())
self.proc = subprocess.Popen(arg_list, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, startupinfo=startupinfo, env=proc_env, shell=shell)
if path:
os.environ["PATH"] = old_path
if self.proc.stdout:
thread.start_new_thread(self.read_stdout, ())
if self.proc.stderr:
thread.start_new_thread(self.read_stderr, ())
def kill(self):
if not self.killed:
self.killed = True
self.proc.kill()
self.listener = None
def poll(self):
return self.proc.poll() == None
def read_stdout(self):
while True:
data = os.read(self.proc.stdout.fileno(), 2**15)
if data != "":
if self.listener:
self.listener.on_data(self, data)
else:
self.proc.stdout.close()
if self.listener:
self.listener.on_finished(self)
break
def read_stderr(self):
while True:
data = os.read(self.proc.stderr.fileno(), 2**15)
if data != "":
if self.listener:
self.listener.on_data(self, data)
else:
self.proc.stderr.close()
break
class ExecCommand(sublime_plugin.WindowCommand, ProcessListener):
def run(self, cmd = [], file_regex = "", line_regex = "", working_dir = "",
encoding = "utf-8", env = {}, quiet = False, kill = False,
# Catches "path" and "shell"
**kwargs):
if kill:
if self.proc:
self.proc.kill()
self.proc = None
self.append_data(None, "[Cancelled]")
return
if not hasattr(self, 'output_view'):
# Try not to call get_output_panel until the regexes are assigned
self.output_view = self.window.get_output_panel("exec")
# Default the to the current files directory if no working directory was given
if (working_dir == "" and self.window.active_view()
and self.window.active_view().file_name() != ""):
working_dir = os.path.dirname(self.window.active_view().file_name())
self.output_view.settings().set("result_file_regex", file_regex)
self.output_view.settings().set("result_line_regex", line_regex)
self.output_view.settings().set("result_base_dir", working_dir)
# Call get_output_panel a second time after assigning the above
# settings, so that it'll be picked up as a result buffer
self.window.get_output_panel("exec")
self.encoding = encoding
self.quiet = quiet
self.proc = None
if not self.quiet:
print "Running " + " ".join(cmd)
self.window.run_command("show_panel", {"panel": "output.exec"})
merged_env = env.copy()
if self.window.active_view():
user_env = self.window.active_view().settings().get('build_env')
if user_env:
merged_env.update(user_env)
# Change to the working dir, rather than spawning the process with it,
# so that emitted working dir relative path names make sense
if working_dir != "":
os.chdir(working_dir)
err_type = OSError
if os.name == "nt":
err_type = WindowsError
try:
# Forward kwargs to AsyncProcess
self.proc = AsyncProcess(cmd, merged_env, self, **kwargs)
except err_type as e:
self.append_data(None, str(e) + "\n")
if not self.quiet:
self.append_data(None, "[Finished]")
def is_enabled(self, kill = False):
if kill:
return hasattr(self, 'proc') and self.proc and self.proc.poll()
else:
return True
def append_data(self, proc, data):
if proc != self.proc:
# a second call to exec has been made before the first one
# finished, ignore it instead of intermingling the output.
if proc:
proc.kill()
return
try:
str = data.decode(self.encoding)
except:
str = "[Decode error - output not " + self.encoding + "]"
proc = None
# Normalize newlines, Sublime Text always uses a single \n separator
# in memory.
str = str.replace('\r\n', '\n').replace('\r', '\n')
selection_was_at_end = (len(self.output_view.sel()) == 1
and self.output_view.sel()[0]
== sublime.Region(self.output_view.size()))
self.output_view.set_read_only(False)
edit = self.output_view.begin_edit()
self.output_view.insert(edit, self.output_view.size(), str)
if selection_was_at_end:
self.output_view.show(self.output_view.size())
self.output_view.end_edit(edit)
self.output_view.set_read_only(True)
def finish(self, proc):
if not self.quiet:
self.append_data(proc, "[Finished]")
if proc != self.proc:
return
# Set the selection to the start, so that next_result will work as expected
edit = self.output_view.begin_edit()
self.output_view.sel().clear()
self.output_view.sel().add(sublime.Region(0))
self.output_view.end_edit(edit)
def on_data(self, proc, data):
sublime.set_timeout(functools.partial(self.append_data, proc, data), 0)
def on_finished(self, proc):
sublime.set_timeout(functools.partial(self.finish, proc), 0)
@danro

This comment has been minimized.

Copy link
Owner Author

danro commented Dec 15, 2011

This is a small fix for Sublime Text 2 [beta 2139] in the file Packages/Default/exec.py, which forces exec to bypass the console window when quiet is passed to your build system file. For example:

{
    "cmd": ["${file_path}/my-build-script.sh"],
    "quiet": true
}
@S0und

This comment has been minimized.

Copy link

S0und commented Sep 25, 2012

This should still work @ 2219 nightly ? Because the console is still pops up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.