├── VERSION ├── CHANGELOG ├── autoload ├── python │ ├── do.pyc │ ├── async.pyc │ ├── buffer.pyc │ ├── command.pyc │ ├── logger.pyc │ ├── utils.pyc │ ├── window.pyc │ ├── rendering.pyc │ ├── async.py │ ├── window.py │ ├── buffer.py │ ├── rendering.py │ ├── utils.py │ └── do.py └── do.vim ├── syntax ├── do_output.vim └── do_command_window.vim ├── LICENSE ├── plugin └── do.vim ├── README └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | === vim-do 0.1.0 / 2015-12-10 2 | 3 | Features: 4 | * First release 5 | -------------------------------------------------------------------------------- /autoload/python/do.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-scripts/vim-do/HEAD/autoload/python/do.pyc -------------------------------------------------------------------------------- /autoload/python/async.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-scripts/vim-do/HEAD/autoload/python/async.pyc -------------------------------------------------------------------------------- /autoload/python/buffer.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-scripts/vim-do/HEAD/autoload/python/buffer.pyc -------------------------------------------------------------------------------- /autoload/python/command.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-scripts/vim-do/HEAD/autoload/python/command.pyc -------------------------------------------------------------------------------- /autoload/python/logger.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-scripts/vim-do/HEAD/autoload/python/logger.pyc -------------------------------------------------------------------------------- /autoload/python/utils.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-scripts/vim-do/HEAD/autoload/python/utils.pyc -------------------------------------------------------------------------------- /autoload/python/window.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-scripts/vim-do/HEAD/autoload/python/window.pyc -------------------------------------------------------------------------------- /autoload/python/rendering.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-scripts/vim-do/HEAD/autoload/python/rendering.pyc -------------------------------------------------------------------------------- /syntax/do_output.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: Vim Debugger Watch Window 3 | " Maintainer: Jon Cairns 4 | " Latest Revision: 2 August 2012 5 | 6 | if exists("b:current_syntax") 7 | finish 8 | endif 9 | 10 | syn region doCommandHeader start=+^=+ end=+=$+ 11 | syn match doCommandTitle '\zs\[\(command\|status\|time\|pid\)\]\ze' 12 | syn region doCommandStderr start=+E>+ end=+$+ 13 | 14 | hi def link doCommandHeader Type 15 | hi def link doCommandTitle Title 16 | hi def link doCommandStderr Error 17 | -------------------------------------------------------------------------------- /syntax/do_command_window.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: Vim Debugger Watch Window 3 | " Maintainer: Jon Cairns 4 | " Latest Revision: 2 August 2012 5 | 6 | if exists("b:current_syntax") 7 | finish 8 | endif 9 | 10 | syn region doCommandWindowHeader start=+^=+ end=+=$+ 11 | syn match doCommandWindowDivider '|' 12 | syn match doCommandWindowTitle '[A-Z]\{2,}' 13 | syn match doCommandWindowPid '\s\d\{5,}\s' 14 | 15 | 16 | hi def link doCommandWindowHeader Type 17 | hi def link doCommandWindowDivider Type 18 | hi def link doCommandWindowTitle Title 19 | hi def link doCommandWindowPid Number 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 Jon Cairns 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /plugin/do.vim: -------------------------------------------------------------------------------- 1 | " Do: Run shell commands asynchronously and show the output in Vim 2 | " 3 | " Script Info {{{ 4 | "============================================================================= 5 | " Copyright: Copyright (C) 2015 Jon Cairns 6 | " Licence: The MIT Licence (see LICENCE file) 7 | " Name Of File: do.vim 8 | " Description: Run shell commands asynchronously and show the output in Vim 9 | " Maintainer: Jon Cairns 10 | " Version: 0.0.1 11 | " Usage: Use :help Do for information on how to configure and use 12 | " this script, or visit the Github page http://github.com/joonty/vim-do. 13 | " 14 | "============================================================================= 15 | " }}} 16 | 17 | if !has("python") 18 | finish 19 | endif 20 | 21 | command! -nargs=* Do call do#Execute() 22 | command! -nargs=* DoQuietly call do#Execute(, 1) 23 | command! -range DoThis call do#ExecuteSelection() 24 | command! DoAgain call do#ExecuteAgain() 25 | command! Doing call do#ToggleCommandWindow() 26 | command! Done call do#ToggleCommandWindow() 27 | 28 | " Looking for the rest? Check the autoload directory :D 29 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is a mirror of http://www.vim.org/scripts/script.php?script_id=5282 2 | 3 | For the full README visit the GitHub repo: https://github.com/joonty/vim-do. 4 | 5 | == Description 6 | 7 | The aim of this plugin is to provide a quick and simple way of running shell commands in the background, and displaying the results (output, exit status, etc) in a Vim buffer as the process is running. That means you can run several commands, carry on working in Vim, and swap between the processes at will or just keep them running hidden in the background. 8 | 9 | It works nicely for both short- and long-running processes, and the great thing is that you don't have to swap between a terminal and Vim to run commands or see their output. 10 | 11 | == Features 12 | 13 | After installing (see below) you're given the :Do vim command, which allows you to run any shell command: 14 | 15 | :Do echo "hi" 16 | :Do rake 17 | :Do find / 18 | 19 | Oops, that last command is gonna take a while... 20 | 21 | But don't worry, it's asynchronous! A new buffer will pop up while it's running and show the output. If you close it, it will keep running until the command finishes naturally. You can run as many commands at the same time as your computer can handle. 22 | 23 | For more information, continue reading on the GitHub README: https://github.com/joonty/vim-do. 24 | 25 | -------------------------------------------------------------------------------- /autoload/python/async.py: -------------------------------------------------------------------------------- 1 | import Queue 2 | import threading 3 | import subprocess 4 | import shlex 5 | import select 6 | from utils import log 7 | import os 8 | 9 | class AsyncProcessReader(threading.Thread): 10 | def __init__(self, process, output_q): 11 | self.__process = process 12 | self.__output_q = output_q 13 | threading.Thread.__init__(self) 14 | 15 | def run(self): 16 | pid = self.__process.pid 17 | 18 | log("Checking output") 19 | for (stdout, stderr) in self._readfds(): 20 | self.__output_q.put_nowait((pid, None, stdout, stderr)) 21 | log("Collected all output") 22 | 23 | self.__process.wait() 24 | log("Finished with %i" % self.__process.returncode) 25 | self.__output_q.put_nowait((pid, self.__process.returncode, None, None)) 26 | 27 | def _readfds(self): 28 | fds = [self.__process.stdout.fileno(), self.__process.stderr.fileno()] 29 | streams = [self.__process.stdout, self.__process.stderr] 30 | 31 | while self.__process.poll() is None: 32 | fdsin, _, _ = select.select(fds, [], []) 33 | for fd in fdsin: 34 | output = [None, None] 35 | ind = fds.index(fd) 36 | stream = streams[ind] 37 | s = stream.readline() 38 | if len(s) > 0: 39 | output[ind] = s 40 | yield output 41 | 42 | 43 | class ProcessPool: 44 | def __init__(self): 45 | self.__threads = [] 46 | self.__output_q = Queue.Queue(0) 47 | 48 | def execute(self, cmd): 49 | subproc = subprocess.Popen(cmd, shell=True, 50 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 51 | 52 | thread = AsyncProcessReader(subproc, self.__output_q) 53 | thread.start() 54 | self.__threads.append(thread) 55 | return subproc.pid 56 | 57 | def any_running(self): 58 | self.cleanup() 59 | return len(self.__threads) == 0 60 | 61 | def get_outputs(self): 62 | if self.__output_q.empty(): 63 | return [] 64 | 65 | results = [] 66 | try: 67 | for result in iter(self.__output_q.get_nowait, None): 68 | results.append(result) 69 | except Queue.Empty: 70 | pass 71 | 72 | return results 73 | 74 | def cleanup(self): 75 | self.__threads = [t for t in self.__threads if t.is_alive()] 76 | 77 | def stop(self): 78 | if self.any_running(): 79 | for t in self.__threads: 80 | t.join(1000) 81 | 82 | -------------------------------------------------------------------------------- /autoload/python/window.py: -------------------------------------------------------------------------------- 1 | from buffer import * 2 | from utils import log 3 | 4 | class Window(): 5 | name = "WINDOW" 6 | creation_count = 0 7 | 8 | def __init__(self): 9 | self._buffer = HiddenBuffer() 10 | self.is_open = False 11 | self._buffernr = None 12 | 13 | def toggle(self, open_cmd): 14 | if self.is_open: 15 | self.destroy() 16 | else: 17 | self.create(open_cmd) 18 | 19 | def mark_as_closed(self): 20 | self.destroy() 21 | 22 | def getbuffernr(self): 23 | if self._buffernr is None: 24 | self._buffernr = int(vim.eval("buffer_number('%s')" % self.name)) 25 | return self._buffernr 26 | 27 | def getwinnr(self): 28 | return int(vim.eval("bufwinnr('%s')" % self.name)) 29 | 30 | def set_height(self, height): 31 | height = int(height) 32 | minheight = int(vim.eval("&winminheight")) 33 | if height < minheight: 34 | height = minheight 35 | if height <= 0: 36 | height = 1 37 | self.command('set winheight=%i' % height) 38 | 39 | def write(self, msg, overwrite = False): 40 | return self._buffer.write(msg, overwrite) 41 | 42 | def overwrite(self, msg, lineno, allowEmpty = False): 43 | return self._buffer.overwrite(msg, lineno, allowEmpty) 44 | 45 | def delete(self, start_line, end_line = None): 46 | self._buffer.delete(start_line, end_line) 47 | 48 | def line_at(self, line): 49 | return self._buffer.line(line) 50 | 51 | def create(self, open_cmd): 52 | """ create window """ 53 | if self.is_open: 54 | return 55 | vim.command('silent %s %s' %(open_cmd, self.name)) 56 | vim.command("setlocal buftype=nofile modifiable "+ \ 57 | "winfixheight winfixwidth") 58 | existing_content = self._buffer.contents() 59 | self._buffer = VimBuffer(vim.buffers[self.getbuffernr()]) 60 | self._buffer.replace(existing_content) 61 | self.is_open = True 62 | self.creation_count += 1 63 | 64 | self.on_create() 65 | 66 | def destroy(self, wipeout = True): 67 | """ destroy window """ 68 | if not self.is_open: 69 | return 70 | self.on_destroy() 71 | self.is_open = False 72 | self._buffer = HiddenBuffer(self._buffer.contents()) 73 | self._buffernr = None 74 | if wipeout and int(vim.eval('buffer_exists("%s")' % self.name)) == 1: 75 | vim.command('bwipeout %s' % self.name) 76 | log("Wiped out buffer %s" % self.name) 77 | 78 | def clean(self): 79 | """ clean all data in buffer """ 80 | self._buffer.clean() 81 | 82 | def command(self, cmd): 83 | """ go to my window & execute command """ 84 | winnr = self.getwinnr() 85 | if winnr != int(vim.eval("winnr()")): 86 | vim.command(str(winnr) + 'wincmd w') 87 | vim.command(str(cmd)) 88 | 89 | def on_create(self): 90 | pass 91 | 92 | def on_destroy(self): 93 | pass 94 | 95 | class ProcessWindow(Window): 96 | name = "DoProcess" 97 | 98 | def on_create(self): 99 | if self.creation_count == 1: 100 | cmd = 'silent! au BufWinLeave %s' % self.name 101 | cmd += ' call do#MarkProcessWindowAsClosed()' 102 | vim.command(cmd) 103 | 104 | self.command("setlocal syntax=do_output buftype=nofile modifiable "+ \ 105 | "winfixheight winfixwidth") 106 | 107 | 108 | class CommandWindow(Window): 109 | name = "DoCommands" 110 | def on_create(self): 111 | if self.creation_count == 1: 112 | cmd = 'silent! au BufWinLeave %s' % self.name 113 | cmd += ' call do#MarkCommandWindowAsClosed()' 114 | vim.command(cmd) 115 | 116 | self.command('setlocal syntax=do_command_window') 117 | self.command('nnoremap '+\ 118 | ':call do#ShowProcessFromCommandWindow()') 119 | -------------------------------------------------------------------------------- /autoload/python/buffer.py: -------------------------------------------------------------------------------- 1 | import vim 2 | 3 | class VimBuffer: 4 | def __init__(self, buffer): 5 | self._buffer = buffer 6 | 7 | def replace(self, content): 8 | self._buffer[:] = content 9 | 10 | def line(self, number): 11 | return self._buffer[number] 12 | 13 | def write(self, msg, overwrite): 14 | last_line = len(self._buffer) 15 | 16 | if isinstance(msg, list): 17 | to_write = msg 18 | else: 19 | to_write = str(msg).split('\n') 20 | 21 | if len(to_write) == 1 and to_write[0] == "": 22 | return (last_line, last_line) 23 | 24 | if overwrite or self.is_empty(): 25 | self._buffer[:] = to_write 26 | else: 27 | self._buffer.append(to_write) 28 | 29 | return (last_line, last_line + len(to_write)) 30 | 31 | def overwrite(self, msg, lineno, allowEmpty): 32 | """ insert into current position in buffer""" 33 | if not msg and allowEmpty == False: 34 | return 35 | 36 | if isinstance(msg, list): 37 | to_write = msg 38 | else: 39 | to_write = str(msg).split('\n') 40 | 41 | lstart = lineno - 1 42 | lend = lstart + len(to_write) 43 | self._buffer[lstart:lend] = to_write 44 | 45 | return (lstart, lend) 46 | 47 | def delete(self, start_line, end_line = None): 48 | try: 49 | if not end_line: 50 | end_line = start_line + 1 51 | self._buffer[end_line] 52 | remaining_buffer = self._buffer[end_line:] 53 | del self._buffer[start_line:] 54 | self._buffer.append(remaining_buffer) 55 | except IndexError: 56 | del self._buffer[start_line:] 57 | 58 | def contents(self): 59 | return self._buffer[:] 60 | 61 | def clean(self): 62 | self._buffer[:] = [] 63 | 64 | def is_empty(self): 65 | if len(self._buffer) == 1 and len(self._buffer[0]) == 0: 66 | return True 67 | else: 68 | return False 69 | 70 | class HiddenBuffer: 71 | def __init__(self, buffer = []): 72 | self._buffer = buffer[:] 73 | 74 | def line(self, number): 75 | return self._buffer[number] 76 | 77 | def replace(self, contents): 78 | self._buffer[:] = contents[:] 79 | 80 | def write(self, msg, overwrite): 81 | last_line = len(self._buffer) 82 | 83 | if isinstance(msg, list): 84 | to_write = msg 85 | else: 86 | to_write = str(msg).split('\n') 87 | 88 | if len(to_write) == 1 and to_write[0] == "": 89 | return (last_line, last_line) 90 | 91 | to_write = str(msg).split('\n') 92 | 93 | if overwrite or self.is_empty(): 94 | self._buffer[:] = to_write 95 | else: 96 | self._buffer.extend(to_write) 97 | 98 | return (last_line, last_line + len(to_write)) 99 | 100 | def overwrite(self, msg, lineno, allowEmpty): 101 | """ insert into current position in buffer""" 102 | if not msg and allowEmpty == False: 103 | return 104 | 105 | if isinstance(msg, list): 106 | to_write = msg 107 | else: 108 | to_write = str(msg).split('\n') 109 | last_line = len(self._buffer) 110 | 111 | lstart = lineno - 1 112 | lend = lstart + len(to_write) 113 | self._buffer[lstart:lend] = to_write 114 | 115 | return (lstart, lend) 116 | 117 | def delete(self, start_line, end_line = None): 118 | try: 119 | if not end_line: 120 | end_line = start_line + 1 121 | self._buffer[start_line:end_line] = [] 122 | except IndexError: 123 | del self._buffer[start_line:] 124 | 125 | def clean(self): 126 | self._buffer[:] = [] 127 | 128 | def contents(self): 129 | return self._buffer[:] 130 | 131 | def is_empty(self): 132 | return not self._buffer 133 | 134 | 135 | -------------------------------------------------------------------------------- /autoload/python/rendering.py: -------------------------------------------------------------------------------- 1 | import window 2 | from utils import Options, log 3 | 4 | class ProcessRenderer: 5 | def __init__(self): 6 | self.__command_window = window.CommandWindow() 7 | self.__command_window.write(CommandWindowHeaderFormat()) 8 | self.__command_window_line_maps = {} 9 | self.__command_window_line_map_order = [] 10 | 11 | self.__process_window = window.ProcessWindow() 12 | self.__process_window_output_line = 0 13 | self.__process_window_process = None 14 | 15 | def get_pid_by_line_number(self, lineno): 16 | try: 17 | # Account for header 18 | return self.__command_window_line_map_order[lineno - 4] 19 | except IndexError: 20 | return None 21 | 22 | def add_process(self, process, quiet): 23 | if not quiet and Options.auto_show_process_window(): 24 | self.show_process(process) 25 | 26 | (first_line, _) = self.__command_window.write(CommandWindowProcessFormat(process)) 27 | self.__command_window_line_maps[process.get_pid()] = first_line + 1 28 | self.__command_window_line_map_order.append(process.get_pid()) 29 | 30 | def show_process(self, process): 31 | log("showing process output: %s" % process.get_pid()) 32 | self.__process_window_process = process 33 | 34 | self.__process_window.clean() 35 | self.__process_window_output_line = 0 36 | self.__process_window.create(Options.new_process_window_command()) 37 | 38 | self.__process_window.write(ProcessWindowHeaderFormat(process)) 39 | self.__write_output(process.output().all()) 40 | 41 | def __write_output(self, output): 42 | (first, last) = self.__process_window.write(output) 43 | self.__process_window_output_line += last - first 44 | 45 | def update_process(self, process): 46 | self.__command_window.overwrite(CommandWindowProcessFormat(process), 47 | self.__command_window_line_maps[process.get_pid()], 48 | True) 49 | 50 | if self.__process_window_process == process: 51 | log("updating process output: %s, %s" 52 | %(process.get_pid(),process.get_status())) 53 | self.__write_output(process.output().from_line(self.__process_window_output_line)) 54 | 55 | self.__process_window.overwrite(ProcessWindowHeaderFormat(process), 56 | 1, True) 57 | 58 | 59 | def toggle_command_window(self): 60 | self.__command_window.toggle("rightbelow 7new") 61 | 62 | def destroy_command_window(self): 63 | self.__command_window.destroy() 64 | 65 | def destroy_process_window(self): 66 | self.__process_window.destroy() 67 | 68 | 69 | class ProcessWindowHeaderFormat: 70 | def __init__(self, process): 71 | self.__process = process 72 | 73 | def __str__(self): 74 | values = (self.__process.get_command(), 75 | self.__process.get_status(), 76 | self.__formatted_time(), 77 | self.__process.get_pid()) 78 | max_length = max(map(len, values)) + 12 79 | 80 | title = "=" * max_length + "\n" 81 | title += " [command] %s\n" % values[0] 82 | title += " [status] %s\n" % values[1] 83 | title += " [time] %s\n" % values[2] 84 | title += " [pid] %s\n" % values[3] 85 | title += "=" * max_length 86 | return title 87 | 88 | def __formatted_time(self): 89 | time = self.__process.get_time() 90 | if time > 1000.0: 91 | time = round(time / 1000.0, 2) 92 | unit = "s" 93 | else: 94 | unit = "ms" 95 | return "{:,}".format(time) + unit 96 | 97 | 98 | class CommandWindowHeaderFormat: 99 | 100 | def __str__(self): 101 | return ''' 102 | ============================================================================= 103 | PID | COMMAND | STATUS 104 | ============================================================================= 105 | '''[1:-1] 106 | 107 | 108 | class CommandWindowProcessFormat: 109 | def __init__(self, process): 110 | self.__process = process 111 | 112 | def __str__(self): 113 | s = "" 114 | cmd = self.__process.get_command() 115 | cmd = cmd if len(cmd) <= 30 else cmd[:27] + "..." 116 | s += " %-7s | %-51s | %s" %(self.__process.get_pid(), cmd, 117 | self.__process.get_status()) 118 | return s 119 | 120 | -------------------------------------------------------------------------------- /autoload/python/utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import os 4 | import vim 5 | 6 | class Options: 7 | instance = None 8 | 9 | def __init__(self): 10 | self.refresh_key = vim.eval('do#get("do_refresh_key")') 11 | self.update_time = int(vim.eval("do#get('do_update_time')")) 12 | self.new_process_window_command = vim.eval('do#get("do_new_process_window_command")') 13 | self.auto_show_process_window = bool(int(vim.eval('do#get("do_auto_show_process_window")'))) 14 | self.check_interval = int(vim.eval("do#get('do_check_interval')")) 15 | 16 | if self.check_interval < 500: 17 | self.check_interval = 500 18 | 19 | @classmethod 20 | def reload(cls): 21 | cls.instance = Options() 22 | 23 | @classmethod 24 | def inst(cls): 25 | """Get the Options instance. 26 | """ 27 | if cls.instance is None: 28 | cls.reload() 29 | return cls.instance 30 | 31 | @classmethod 32 | def refresh_key(cls): 33 | return cls.inst().refresh_key 34 | 35 | @classmethod 36 | def check_interval(cls): 37 | return cls.inst().check_interval 38 | 39 | @classmethod 40 | def update_time(cls): 41 | return cls.inst().update_time 42 | 43 | @classmethod 44 | def new_process_window_command(cls): 45 | return cls.inst().new_process_window_command 46 | 47 | @classmethod 48 | def auto_show_process_window(cls): 49 | return cls.inst().auto_show_process_window 50 | 51 | class Logger: 52 | """ Abstract class for all logger implementations. 53 | 54 | Concrete classes will log messages using various methods, 55 | e.g. write to a file. 56 | """ 57 | 58 | (ERROR,INFO,DEBUG) = (0,1,2) 59 | TYPES = ("ERROR","Info","Debug") 60 | debug_level = ERROR 61 | 62 | def __init__(self, debug_level): 63 | pass 64 | 65 | def log(self, string, level): 66 | """ Log a message """ 67 | pass 68 | 69 | def shutdown(self): 70 | """ Action to perform when closing the logger """ 71 | pass 72 | 73 | def time(self): 74 | """ Get a nicely formatted time string """ 75 | return time.strftime("%a %d %Y %H:%M:%S", \ 76 | time.localtime()) 77 | 78 | def format(self, string, level): 79 | display_level = self.TYPES[level] 80 | """ Format the error message in a standard way """ 81 | return "- [%s] {%s} %s" %(display_level, self.time(), str(string)) 82 | 83 | 84 | class FileLogger(Logger): 85 | """ Log messages to a window. 86 | 87 | The window object is passed in on construction, but 88 | only created if a message is written. 89 | """ 90 | def __init__(self, debug_level, filename): 91 | self.filename = os.path.expanduser(filename) 92 | self.f = None 93 | self.debug_level = int(debug_level) 94 | 95 | def __open(self): 96 | try: 97 | self.f = open(self.filename,'w') 98 | except IOError, e: 99 | raise LogError("Invalid file name '%s' for log file: %s" \ 100 | %(self.filename, str(e))) 101 | except: 102 | raise LogError("Error using file '%s' as a log file: %s" \ 103 | %(self.filename, sys.exc_info()[0])) 104 | 105 | 106 | def shutdown(self): 107 | if self.f is not None: 108 | self.f.close() 109 | 110 | def log(self, string, level): 111 | if level > self.debug_level: 112 | return 113 | if self.f is None: 114 | self.__open() 115 | self.f.write(\ 116 | self.format(string,level)+"\n") 117 | self.f.flush() 118 | 119 | class Log: 120 | 121 | loggers = {} 122 | 123 | def __init__(self,string,level = Logger.INFO): 124 | Log.log(string,level) 125 | 126 | @classmethod 127 | def log(cls, string, level = Logger.INFO): 128 | for k, l in cls.loggers.iteritems(): 129 | l.log(string,level) 130 | 131 | @classmethod 132 | def set_logger(cls, logger): 133 | k = logger.__class__.__name__ 134 | if k in cls.loggers: 135 | cls.loggers[k].shutdown() 136 | cls.loggers[k] = logger 137 | 138 | @classmethod 139 | def remove_logger(cls, type): 140 | if type in cls.loggers: 141 | cls.loggers[type].shutdown() 142 | return True 143 | else: 144 | print "Failed to find logger %s in list of loggers" % type 145 | return False 146 | 147 | @classmethod 148 | def shutdown(cls): 149 | for k, l in cls.loggers.iteritems(): 150 | l.shutdown() 151 | cls.loggers = {} 152 | 153 | class LogError(Exception): 154 | pass 155 | 156 | def log(string, level = Logger.INFO): 157 | Log.log(string, level) 158 | -------------------------------------------------------------------------------- /autoload/python/do.py: -------------------------------------------------------------------------------- 1 | import os 2 | import inspect 3 | import sys 4 | directory = os.path.dirname(inspect.getfile(inspect.currentframe())) 5 | sys.path.append(directory) 6 | 7 | import rendering 8 | import async 9 | import window 10 | import vim 11 | import time 12 | import string 13 | import signal 14 | from utils import * 15 | 16 | class Do: 17 | def __init__(self): 18 | self.__process_pool = async.ProcessPool() 19 | self.__processes = ProcessCollection() 20 | self.__process_renderer = rendering.ProcessRenderer() 21 | self.__au_assigned = False 22 | self.__last_check = time.time() * 1000 23 | 24 | def __del__(self): 25 | self.stop() 26 | 27 | def execute(self, cmd, quiet = False): 28 | pid = self.__process_pool.execute(cmd) 29 | log("Started command with pid %i: %s" %(pid, cmd)) 30 | process = self.__processes.add(cmd, pid) 31 | self.__process_renderer.add_process(process, quiet) 32 | 33 | self.__assign_autocommands() 34 | self.check() 35 | 36 | def reload_options(self): 37 | Options.reload() 38 | 39 | def toggle_command_window(self): 40 | self.__process_renderer.toggle_command_window() 41 | 42 | def mark_command_window_as_closed(self): 43 | self.__process_renderer.destroy_command_window() 44 | 45 | def mark_process_window_as_closed(self): 46 | try: 47 | self.__process_renderer.destroy_process_window() 48 | except Exception, e: 49 | log("Error: %s" % str(e)) 50 | 51 | def show_process_from_command_window(self): 52 | lineno = vim.current.window.cursor[0] 53 | pid = self.__process_renderer.get_pid_by_line_number(lineno) 54 | process = self.__processes.get_by_pid(pid) 55 | if process is not None: 56 | self.__process_renderer.show_process(process) 57 | 58 | def check(self): 59 | log("check()") 60 | if (1000 * time.time()) - self.__last_check > Options.check_interval(): 61 | self.check_now() 62 | self.__last_check = time.time() * 1000 63 | 64 | def check_now(self): 65 | log("Checking background threads output") 66 | outputs = self.__process_pool.get_outputs() 67 | changed_processes = set() 68 | for output in outputs: 69 | if output[1] is not None: 70 | log("Process %s has finished with exit status %s" 71 | %(output[0], output[1])) 72 | process = self.__processes.update(*output) 73 | changed_processes.add(process) 74 | 75 | for process in changed_processes: 76 | self.__process_renderer.update_process(process) 77 | 78 | self.__process_pool.cleanup() 79 | if self.__processes.all_finished(): 80 | log("All background threads completed") 81 | self.__unassign_autocommands() 82 | else: 83 | s = 'feedkeys("\\%s")' % Options.refresh_key() 84 | log(s) 85 | vim.eval(s) 86 | 87 | def enable_logger(self, path): 88 | Log.set_logger(FileLogger(Logger.DEBUG, path)) 89 | 90 | def stop(self): 91 | self.__processes.kill_all() 92 | self.__process_pool.stop() 93 | 94 | def __assign_autocommands(self): 95 | if self.__au_assigned: 96 | return 97 | log("Assigning autocommands for background checking") 98 | vim.command('call do#AssignAutocommands()') 99 | self.__au_assigned = True 100 | 101 | def __unassign_autocommands(self): 102 | log("Unassigning autocommands") 103 | vim.command('call do#UnassignAutocommands()') 104 | self.__au_assigned = False 105 | 106 | 107 | class ProcessCollection: 108 | def __init__(self): 109 | self.__processes = {} 110 | 111 | def add(self, command, pid): 112 | process = Process(command, pid) 113 | self.__processes[pid] = process 114 | return process 115 | 116 | def get_by_pid(self, pid): 117 | return next((p for p in self.__processes.values() if p.get_pid() == pid), None) 118 | 119 | def update(self, pid, exit_status, stdout, stderr): 120 | process = self.__processes[pid] 121 | if process is not None: 122 | if exit_status is not None: 123 | process.mark_as_complete(exit_status) 124 | if stdout or stderr: 125 | process.output().append(stdout, stderr) 126 | return process 127 | 128 | def all_finished(self): 129 | return len(self.get_running()) == 0 130 | 131 | def get_running(self): 132 | return filter(lambda p: p.is_running(), self.__processes.values()) 133 | 134 | def kill_all(self): 135 | for process in self.get_running(): 136 | process.kill() 137 | 138 | class Process: 139 | def __init__(self, command, pid): 140 | self.__command = command 141 | self.__pid = str(pid) 142 | self.__start_time = time.time() 143 | self.__output = Output() 144 | self.__exit_code = None 145 | self.__time = None 146 | 147 | def mark_as_complete(self, exit_code): 148 | self.__exit_code = str(exit_code) 149 | self.__time = round((time.time() - self.__start_time) * 1000) 150 | 151 | def has_finished(self): 152 | return self.__exit_code is not None 153 | 154 | def is_running(self): 155 | return not self.has_finished() 156 | 157 | def get_pid(self): 158 | return self.__pid 159 | 160 | def get_status(self): 161 | if self.__exit_code is None: 162 | return "Running" 163 | else: 164 | return "exited <%s>" % self.__exit_code 165 | 166 | def get_command(self): 167 | return self.__command 168 | 169 | def get_time(self): 170 | if self.__time: 171 | return self.__time 172 | else: 173 | return round((time.time() - self.__start_time) * 1000) 174 | 175 | def output(self): 176 | return self.__output 177 | 178 | def name(self): 179 | return "DoOutput(%s)" % self.__pid 180 | 181 | def kill(self): 182 | try: 183 | os.kill(int(self.__pid), signal.SIGTERM) 184 | except: 185 | pass 186 | 187 | 188 | class Output: 189 | def __init__(self): 190 | self.__output = [] 191 | 192 | def all(self): 193 | return self.__output 194 | 195 | def __len__(self): 196 | return len(self.__output) 197 | 198 | def from_line(self, line): 199 | return self.__output[line:] 200 | 201 | def append(self, stdout, stderr): 202 | if stdout is not None: 203 | self.__output.append(stdout) 204 | if stderr is not None: 205 | self.__output.append("E> %s" % stderr) 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Do for Vim 2 | 3 | ## Run asynchronous shell commands and display the output 4 | 5 | ![animation showing vim-do in action](http://www.mediafire.com/convkey/7734/1o4gb6rpgde2ycy9g.jpg?size_id=5) 6 | 7 | The aim of this plugin is to provide a quick and simple way of running shell commands in the background, and displaying the results (output, exit status, etc) in a Vim buffer as the process is running. That means you can run several commands, carry on working in Vim, and swap between the processes at will or just keep them running hidden in the background. 8 | 9 | It works nicely for both short- and long-running processes, and the great thing is that you don't have to swap between a terminal and Vim to run commands or see their output. 10 | 11 | ## Features 12 | 13 | After installing (see below) you're given the `:Do` vim command, which allows you to run any shell command: 14 | 15 | ```vim 16 | :Do echo "hi" 17 | :Do rake 18 | :Do find / 19 | ``` 20 | 21 | Oops, that last command is gonna take a while... 22 | 23 | But don't worry, it's asynchronous! A new buffer will pop up while it's running and show the output. If you close it, it will keep running until the command finishes naturally. You can run as many commands at the same time as your computer can handle. 24 | 25 | ### Overview of all commands 26 | 27 | ```vim 28 | " Execute a command in the background 29 | :Do 30 | 31 | " Execute a command in the background without opening the process output window 32 | :DoQuietly 33 | 34 | " Show all processes (running and finished) 35 | :Doing 36 | 37 | " Alias for :Doing 38 | :Done 39 | 40 | " Execute the command under visual selection 41 | :'<,'>DoThis 42 | ``` 43 | 44 | ### View running/finished processes and swap between them 45 | 46 | You can see running and finished processes with the `:Doing` or `:Done` commands (different names for the same thing). It looks something like this: 47 | 48 | ![the command window](http://www.mediafire.com/convkey/319f/plvj08c83s030qjzg.jpg) 49 | 50 | Pressing `` (the `Enter` key) on a line will open the process window which gives more detail and shows the output (standard out and standard error). 51 | 52 | ### Automatically updating output 53 | 54 | While a process is running and the process window is open, the output from the process will automatically be written to the buffer. This is achieved with a combination of python threads, io select and Vim's autocommands. 55 | 56 | ### Re-run the last command 57 | 58 | After running any command, run it again with the command `:DoAgain`. 59 | 60 | ### Run a command without the process window popping up 61 | 62 | Execute a command with the `:DoQuietly` vim command instead of `:Do`, and it won't open the process window. Alternatively, you can turn it off permanently by setting `g:do_auto_show_process_window = 0`. 63 | 64 | You can still view information and output for a process by opening the command window (`:Doing` or `:Done`) and selecting the process. 65 | 66 | ### Replacement for :make 67 | 68 | If you're used to running `:make` and using `set makeprg=some\ command`, then you can use `:Do` as a replacement. Running `:Do` without any arguments will automatically run whichever command has been set by `makeprg`. 69 | 70 | ```vim 71 | :Do 72 | " Runs make 73 | :set makeprg=rake 74 | :Do 75 | " Runs rake 76 | ``` 77 | 78 | ### Run the command under selection 79 | 80 | If you want to run a command selected under the cursor (in visual select mode), you can use the command `:'<,'>DoThis`. 81 | 82 | ## Install 83 | 84 | **Requires Vim compiled with Python 2.4+ support** 85 | 86 | ### Classic 87 | 88 | Clone or download a tarball of the plugin and move its content in your 89 | `~/.vim/` directory. 90 | 91 | Your `~/.vim/plugin/` directory should now contain vdebug.vim and a directory 92 | called "python". 93 | 94 | ### Using git and Pathogen 95 | 96 | Clone this repository in your `~/.vim/bundle` directory 97 | 98 | ### Using vundle 99 | 100 | Add this to your `~/.vimrc` file: 101 | 102 | ```vim 103 | Bundle 'joonty/vim-do.git' 104 | ``` 105 | 106 | Then, from the command line, run: 107 | 108 | ```bash 109 | vim +BundleInstall +qall 110 | ``` 111 | 112 | ## Configuration 113 | 114 | Here are the available configuration options: 115 | 116 | * `g:check_interval`: vim-do checks running processes for output and exit codes, and this value sets how often it will allow these checks to be made, in milliseconds (default 1000). 117 | * `g:do_new_buffer_command_prefix`: when a process starts, a new window will open with the default command `:new`. This prefix will be added before the `new`, so, for example, you can change it to a vertical split by setting this to "vertical". 118 | * `g:do_new_buffer_size`: set the size of the process window, no default. 119 | * `g:do_refresh_key`: this should be set to a key combination _that you don't want to use_, as it's used to trigger Vim's autocommands, but shouldn't actually do anything. By default it's set to `` (Control-B), which may conflict with other plugins. If it does, change it to another key combination that you don't ever use. 120 | * `g:do_update_time`: used to change vim's `updatetime` setting, which determines how quickly vim-do will check and update running processes after you stop typing any keys, in milliseconds (default 500). 121 | 122 | If you change an option after vim-do has loaded you'll need to tell it to reload the options. You can do this with the function `do#ReloadOptions()`, i.e.: 123 | 124 | ```vim 125 | :let g:do_auto_show_process_window = 0 126 | :call do#ReloadOptions() 127 | ``` 128 | 129 | ## How does it work? 130 | 131 | Vim is single-threaded, which is obviously a big challenge in running commands asynchronously. However, Vim almost always comes with Python support, which is multithreaded. With some trickery, we can use Python's threading to run processes and get Vim to periodically check these threads for the process' state. 132 | 133 | When you run a command with `:Do `, a new Python thread is created to run the process and capture the standard output/error line-by-line. This thread will run until the process finishes. 134 | 135 | The challenge with this is getting Vim to keep going back to these threads and check their output, and ultimately their exit status when they eventually finish. I used autocommands, particularly `CursorHold` and `CursorHoldI`. These are used to trigger a Vim command or function when the user stops typing (in normal and insert mode respectively). This is great - I can get Vim to check my process threads and give the appearance of this being done in a completely multithreaded way. But this command only gets triggered once. 136 | 137 | So to get round this, after checking the process threads I also send a dummy keystroke, which is mapped to a command that does nothing. This tricks Vim into thinking that the user has typed something, and triggers the `CursorHold` or `CursorHoldI` autocommands again. That means even if you're not typing anything, there are circular, periodic checks of the process threads. These autocommands are cleared when all the process threads finish, so it doesn't keep running Vim functions periodically when not needed. 138 | 139 | If you understood this, then yay. If not, who cares? You can still use vim-do without knowing any of it. I wrote it down so I wouldn't forget in 3 months (weeks) time. 140 | 141 | ## License 142 | 143 | This is released under the MIT license. 144 | -------------------------------------------------------------------------------- /autoload/do.vim: -------------------------------------------------------------------------------- 1 | " Exit when already loaded (or "compatible" mode set) 2 | if exists("g:do_loaded") || &cp 3 | finish 4 | endif 5 | 6 | " Vars used by this script, don't change 7 | let g:do_loaded = 1 8 | let s:existing_update_time = &updatetime 9 | let s:previous_command = "" 10 | 11 | " Configuration vars 12 | let s:do_check_interval = 500 13 | let s:do_new_process_window_command = "new" 14 | let s:do_refresh_key = "" 15 | let s:do_update_time = 500 16 | let s:do_auto_show_process_window = 1 17 | 18 | " Load Python script 19 | if filereadable($VIMRUNTIME."/plugin/python/do.py") 20 | pyfile $VIMRUNTIME/plugin/do.py 21 | elseif filereadable($HOME."/.vim/plugin/python/do.py") 22 | pyfile $HOME/.vim/plugin/python/do.py 23 | else 24 | " when we use pathogen for instance 25 | let $CUR_DIRECTORY=expand(":p:h") 26 | 27 | if filereadable($CUR_DIRECTORY."/python/do.py") 28 | pyfile $CUR_DIRECTORY/python/do.py 29 | else 30 | call confirm('vdebug.vim: Unable to find do.py. Place it in either your home vim directory or in the Vim runtime directory.', 'OK') 31 | endif 32 | endif 33 | 34 | "" 35 | " Fetch a scoped value of an option 36 | " 37 | " Determine a value of an option based on user configuration or pre-configured 38 | " defaults. A user can configure an option by defining it as a buffer variable 39 | " or as a global (buffer vars override globals). Default value can be provided 40 | " by defining a script variable for the whole file or a function local (local 41 | " vars override script vars). When all else fails, falls back the supplied 42 | " default value, if one is supplied. 43 | " 44 | " @param string option Scope-less name of the option 45 | " @param mixed a:1 An option default value for the option 46 | " 47 | function! do#get(option, ...) 48 | for l:scope in ['b', 'g', 'l', 's'] 49 | if exists(l:scope . ':' . a:option) 50 | return eval(l:scope . ':' . a:option) 51 | endif 52 | endfor 53 | 54 | if a:0 > 0 55 | return a:1 56 | endif 57 | 58 | call do#error('Invalid or undefined option: ' . a:option) 59 | endfunction 60 | 61 | "" 62 | " Show user an error message 63 | " 64 | " Pre-format supplied message as an Error and display it to the user. All 65 | " messages are saved to message-history and are accessible via `:messages`. 66 | " 67 | " @param string message A message to be displayed to the user 68 | " 69 | function! do#error(message) 70 | echohl Error | echomsg a:message | echohl None 71 | endfunction 72 | 73 | "" 74 | " Execute the last command again. 75 | " 76 | " See do#Execute(). 77 | " 78 | function! do#ExecuteAgain() 79 | if empty(s:previous_command) 80 | call do#error("You cannot execute the previous command when no previous command exists!") 81 | else 82 | call do#Execute(s:previous_command) 83 | endif 84 | endfunction 85 | 86 | "" 87 | " Reload all do options set by `g:do_...` 88 | " 89 | " Do will cache option values for performance reasons, but calling this 90 | " function will reload them. 91 | " 92 | function! do#ReloadOptions() 93 | python do_async.reload_options() 94 | endfunction 95 | 96 | "" 97 | " Execute a shell command asynchronously. 98 | " 99 | " If a command string is supplied, this will be executed. If no argument (or 100 | " an empty string) is supplied, it will default to using the command set by 101 | " the vim setting "makeprg", which defaults to `make`. 102 | " 103 | " Any special file modifiers will get expanded, such as "%". This allows you 104 | " to run commands like "test %", where "%" will be expanded to the current 105 | " file name. 106 | " 107 | " @param string command (optional) The command to run, defaults to &makeprg 108 | " 109 | function! do#Execute(command, ...) 110 | if a:0 > 0 111 | let l:quiet = a:1 112 | else 113 | let l:quiet = 0 114 | end 115 | let l:command = a:command 116 | if empty(l:command) 117 | let l:command = &makeprg 118 | endif 119 | let l:command = Strip(join(map(split(l:command, '\ze[<%#]'), 'expand(v:val)'), '')) 120 | if empty(l:command) 121 | call do#error("Supplied command is empty") 122 | else 123 | let s:previous_command = l:command 124 | python <<_EOF_ 125 | do_async.execute(vim.eval("l:command"), int(vim.eval("l:quiet")) == 1) 126 | _EOF_ 127 | endif 128 | endfunction 129 | 130 | 131 | "" 132 | " Execute a shell command asynchronously, from the current visually selected text. 133 | " 134 | " See do#Execute() for more information. 135 | " 136 | function! do#ExecuteSelection() 137 | let l:command = s:getVisualSelection() 138 | call do#Execute(l:command) 139 | endfunction 140 | 141 | "" 142 | " Enable the file logger for debugging purposes. 143 | " 144 | " @param string file_path The path to the file to write log information 145 | " 146 | function! do#EnableLogger(file_path) 147 | python <<_EOF_ 148 | do_async.enable_logger(vim.eval("a:file_path")) 149 | _EOF_ 150 | endfunction 151 | 152 | "" 153 | " Show or hide the command window. 154 | " 155 | " The command window details currently running and finished processes. 156 | " 157 | function! do#ToggleCommandWindow() 158 | python do_async.toggle_command_window() 159 | endfunction 160 | 161 | "" 162 | " A callback for when the command window is closed. 163 | " 164 | " Executed automatically via an autocommand. 165 | " 166 | function! do#MarkCommandWindowAsClosed() 167 | python do_async.mark_command_window_as_closed() 168 | endfunction 169 | 170 | "" 171 | " A callback for when the process window is closed. 172 | " 173 | " Executed automatically via an autocommand. 174 | " 175 | function! do#MarkProcessWindowAsClosed() 176 | python do_async.mark_process_window_as_closed() 177 | endfunction 178 | 179 | "" 180 | " Trigger selection of a process in the command window. 181 | " 182 | function! do#ShowProcessFromCommandWindow() 183 | python do_async.show_process_from_command_window() 184 | endfunction 185 | 186 | "" 187 | " Do nothing. 188 | " 189 | " Used in do#AssignAutocommands() 190 | " 191 | function! do#nop() 192 | endfunction 193 | 194 | "" 195 | " Assign auto commands that are used after a command has started execution. 196 | " 197 | " This combination of auto commands should cover most cases of the user being 198 | " idle or using vim. The updatetime is set to that defined by the option 199 | " g:do_update_time, which is typically more regular than the default. 200 | " 201 | " Autocommands are added in a group, for easy removal. 202 | " 203 | function! do#AssignAutocommands() 204 | execute "nnoremap " . do#get("do_refresh_key") . " :call do#nop()" 205 | execute "inoremap " . do#get("do_refresh_key") . ' :call do#nop()' 206 | augroup vim_do 207 | au CursorHold * python do_async.check() 208 | au CursorHoldI * python do_async.check() 209 | au CursorMoved * python do_async.check() 210 | au CursorMovedI * python do_async.check() 211 | au FocusGained * python do_async.check() 212 | au FocusLost * python do_async.check() 213 | augroup END 214 | let &updatetime=do#get("do_update_time") 215 | endfunction 216 | 217 | "" 218 | " Remove all autocommands set by do#AssignAutocommands(). 219 | " 220 | " Also reset the updatetime to what it was before assigning the autocommands. 221 | " 222 | function! do#UnassignAutocommands() 223 | au! vim_do 224 | let &updatetime=s:existing_update_time 225 | endfunction 226 | 227 | " PRIVATE FUNCTIONS 228 | " ----------------- 229 | 230 | " Strip whitespace from input strings. 231 | " 232 | " @param string input_string The string which requires whitespace stripping 233 | " 234 | function! Strip(input_string) 235 | return substitute(a:input_string, '^\s*\(.\{-}\)\s*$', '\1', '') 236 | endfunction 237 | 238 | " Thanks to http://stackoverflow.com/a/6271254/1087866 239 | function! s:getVisualSelection() 240 | " Why is this not a built-in Vim script function?! 241 | let [lnum1, col1] = getpos("'<")[1:2] 242 | let [lnum2, col2] = getpos("'>")[1:2] 243 | let lines = getline(lnum1, lnum2) 244 | let lines[-1] = lines[-1][: col2 - (&selection == 'inclusive' ? 1 : 2)] 245 | let lines[0] = lines[0][col1 - 1:] 246 | return join(lines, "\n") 247 | endfunction 248 | 249 | " Initialize do 250 | python do_async = Do() 251 | autocmd VimLeavePre * python do_async.stop() 252 | --------------------------------------------------------------------------------