diff --git a/README b/README deleted file mode 100644 index 721054a..0000000 --- a/README +++ /dev/null @@ -1,59 +0,0 @@ - -================= -LLDB Vim Frontend -================= - -Prerequisites -------------- - -This plugin is known to work with the following flavours of Vim: - - * Linux (tested on Ubuntu 12.04/12.10): - * vim/gvim (from vim-gnome package version 7.3) - - * Mac OS X (tested on Mountain Lion) - * Vim command-line (7.3 from Xcode) - * MacVim 7.3 - -To install the plugin, ensure you have - * a working version of lldb on your path, or the environment variable LLDB - pointing to the lldb binary you would like to use. - * a python-enabled vim (check with ":python print 2") - - -Installation ------------- - -1) Install the Vim pathogen plugin (it keeps installed plugins organized): - - https://github.com/tpope/vim-pathogen - - Or, for the impatient: - -mkdir -p ~/.vim/autoload ~/.vim/bundle; \ -curl -Sso ~/.vim/autoload/pathogen.vim \ - https://raw.github.com/tpope/vim-pathogen/master/autoload/pathogen.vim - -2) Symlink (or copy) ~/.vim/bundle/vim-lldb to this directory: - -ln -sf /utils/vim-lldb ~/.vim/bundle/vim-lldb - -3) Update your help-tags. Start vim, do: - - :Helptags - -4) Have fun! - - -Usage/Getting Help ------------------- -All LLDB commands (with tab-completion) can be accessed in Vim's -command mode. Try it out by typing: - -:L - -There are several sources of help available: - -:help lldb -- Documentation for this plugin -:Lhelp -- LLDB's built-in help system (i.e lldb 'help' command) -:Lscript help (lldb) -- Complete LLDB Python API reference diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa3a7d2 --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +vim-lldb +======== + +LLDB debugging in Vim. + + +Installation +------------ + +### Using Vim's native package manager is recommended + +See `:help packages` for details + +### Using [vim-plug](https://github.com/junegunn/vim-plug) + +```vim +Plug 'lldb-tools/vim-lldb' +``` + +### Using [vundle](https://github.com/VundleVim/Vundle.Vim) + +```vim +Plugin 'lldb-tools/vim-lldb' +``` + +System Requirements +------------------- + +- Vim 8.2 or above +- Have [Python or Python 3 support enabled in Vim](#verifying-python-support)* + + +vim-lldb Commands +-------- + +| Command | List | +| --- | --- | +| `:help lldb` | plugin specific documentation | +| `:Lhelp` | LLDB's built-in help system (i.e lldb 'help' command) | +| `:Lscript help (lldb)` | Complete LLDB Python API reference | +| `:L` | tab completion through all LLDB commands | + + + +LLDB Commands +------------- + +All LLDB commands are available through `:L`. Using lldb's documentation at `:Lhelp` along with `:L` tab completion for all available LLDB commands is a good place to start. Remember to prepend all commands with `:L`. +For example: + +```vim +" set a target file +:Ltarget ./path/to/file +" set a breakpoint under cursor +:Lbr +" run debugger +:Lrun +" get help for continue command +:Lhelp continue +``` + +Example commands: + + +| Command | Function | +| --- | --- | +| `:Ltarget file` | specify target file | +| `:Lsettings set target.input-path ` | specify file input (exec < file) | +| `:Lbr` | set breakpoint under cursor | +| `:Lrun` | run | +| `:Lstep` | source level single step in current thread | +| `:Lnext` | source level single step over in current thread | +| `:Lthread step-in` | instruction level single step in current thread | +| `:Lthread step-over` | instruction level single step-over in current thread | +| `Lcontinue` | Continue execution of all threads in the current process. | +| `:Lfinish` | step out of currently selected frame | +| `:Lthread return `| return immediately from currently selected frame with optional return value | +| `:Lthread select 1`| select thread 1 as default thread for subsequent commands | +| `:Lbt all` | thread backtrace all | +| `:Lfr v` | show args and local vars for current frame | +| `:Lfr v -f x bar` | show contents of variable `bar` formatted as hex | +| `:Lfr v -f b bar` | same as above with binary formatting | +| `:Lregister read` | show the general purpose registers for current thread | +| `:Lregister read rax rsp` | show the contents of rax, rsp | +| `:Lregister write rax 123` | write `123` into rax | +| `:Ldisassemble --name main` | disassemble any functions named `main` | +| `:Ldisassemble --line` | disassemble current source line for current frame | +| `:Ldisassemble --mixed` | disassemble with mixed mode | + + + +For a complete list of commands, see [gdb to lldb map](https://lldb.llvm.org/use/map.html) + + +Customization +------------- + +### Global options + + +```vim +" add custom path to lldb +let g:lldb_path="/absolute/path/to/lldb" +``` + +```vim +" enable lldb, default is 1 {enable}, 0 {disable} +let g:lldb_enable = 1 +``` + +```vim +" set lldb to async, default is 1 {async}, 0 {sync} +let g:lldb_async = 1 +``` + +```vim +" set lldb console output color +:hi lldb_output ctermfg=green ctermbg=NONE guifg=green guibg=NONE +" set breakpoint color +:hi lldb_breakpoint ctermfg=white ctermbg=DarkGray guifg=white guibg=DarkGray +``` + + +Verifying Python Support +------------------------ + +This plugin leverages the `LLDB` module which requires Python support in Vim. Vim's Python version must match `LLDB`'s Python interpreter version exactly. + +To verify Vim's Python support, run: + + vim --version | grep python + +The output must contain either `+python` or `+python3` indicating support for Python 2 or Python 3, respectively. It is recommended to use Vim compiled with Python 3 as many Vim plugins only support this option. + +If Vim warns that it is unable to load vim-lldb on launch, there may be mismatched versions of Python running between `LLDB` and Vim's Python interpreter. Versions must match exactly. To verify Vim's exact Python version, launch Vim and run: + + :pyx << EOF + import sys + print(sys.version) + EOF + + " verify this version matches lldb's output below + 3.7.6 (default, ...) + + + +Verify LLDB's version of Python by launching the Python interpreter in LLDB: + + $> lldb -b -o "script import sys; print(sys.version)" + 3.7.6 (default, ...) + + +If Python versions are mismatched, either recompile Vim to match the exact version as LLDB or vice-versa. See [lldb caveats](https://lldb.llvm.org/resources/caveats.html) for details. + +See [Customization](#customization) for specifying lldb path in `vimrc`. + + diff --git a/plugin/lldb.vim b/plugin/lldb.vim index ac5cfe3..b79c70f 100644 --- a/plugin/lldb.vim +++ b/plugin/lldb.vim @@ -1,23 +1,89 @@ " Vim script glue code for LLDB integration +" + +if !has('pythonx') + call confirm('ERROR: This Vim installation does not have python support. lldb debugging is disabled.') + finish +elseif (has('python3')) + " prefer Python 3 over 2 + let s:lldb_python_version = 3 +elseif (has('python')) + let s:lldb_python_version = "" +endif + +if(v:version < 801) + call confirm('ERROR: lldb requires vim > v8.1.0. lldb debugging is disabled.') + finish +endif + +if (exists("g:loaded_lldb") || (exists("g:lldb_enable") && g:lldb_enable == 0)) + finish +endif +let g:loaded_lldb = 1 + +let s:keepcpo = &cpo +set cpo&vim + +" read in custom options from vimrc +let s:lldb_custom_path = "" +let s:lldb_async = 1 " async by default +let s:default_panes = [] + +if (exists("g:lldb_path")) + let s:lldb_custom_path = g:lldb_path +endif + +if (exists("g:lldb_default_panes")) + let s:lldb_default_panes = g:lldb_default_panes +endif +if (exists("g:lldb_enable_async") && g:lldb_enable_async == 0) + let s:lldb_async = 0 +endif + +function! s:Highlight() + if !hlexists("lldb_output") + :hi lldb_output ctermfg=NONE ctermbg=NONE guifg=NONE guibg=NONE + endif + if !hlexists("lldb_breakpoint") + :hi lldb_breakpoint ctermfg=NONE ctermbg=NONE guifg=NONE guibg=NONE + endif + if !hlexists("lldb_pc_active") + :hi lldb_pc_active ctermfg=White ctermbg=Blue guifg=White guibg=Blue + endif + if !hlexists("lldb_pc_inactive") + :hi lldb_pc_inactive ctermfg=NONE ctermbg=LightGray guifg=NONE guibg=LightGray + endif + if !hlexists("lldb_changed") + :hi lldb_changed ctermfg=DarkGreen ctermbg=White guifg=DarkGreen guibg=White + endif + if !hlexists("lldb_selected") + :hi lldb_selected ctermfg=LightYellow ctermbg=DarkGray guifg=LightYellow guibg=DarkGray + endif +endfunction + + +let s:script_dir = resolve(expand(":p:h")) function! s:FindPythonScriptDir() - for dir in pathogen#split(&runtimepath) - let searchstr = "python-vim-lldb" - let candidates = pathogen#glob_directories(dir . "/" . searchstr) - if len(candidates) > 0 - return candidates[0] - endif - endfor - return -endfunction() + let base_dir = fnamemodify(s:script_dir, ':h') + return base_dir . "/python-vim-lldb" +endfunction function! s:InitLldbPlugin() - if has('python') == 0 - call confirm('ERROR: This Vim installation does not have python support. lldb.vim will not work.') + + " Setup the python interpreter path + let vim_lldb_pydir = s:FindPythonScriptDir() + execute 'pyx import sys; sys.path.append("' . vim_lldb_pydir . '")' + " if import fails, lldb_disabled is set + execute 'pyxfile ' . vim_lldb_pydir . '/plugin.py' + + if(exists("s:lldb_disabled")) return endif - + + let g:vim_lldb_pydir = vim_lldb_pydir + " Key-Bindings " FIXME: choose sensible keybindings for: " - process: start, interrupt, continue, continue-to-cursor @@ -28,124 +94,157 @@ function! s:InitLldbPlugin() map :Lbreakpoint endif - " - " Setup the python interpreter path - " - let vim_lldb_pydir = s:FindPythonScriptDir() - execute 'python import sys; sys.path.append("' . vim_lldb_pydir . '")' - " " Register :L " The LLDB CommandInterpreter provides tab-completion in Vim's command mode. " FIXME: this list of commands, at least partially should be auto-generated " + " " Window show/hide commands - command -complete=custom,s:CompleteWindow -nargs=1 Lhide python ctrl.doHide('') - command -complete=custom,s:CompleteWindow -nargs=0 Lshow python ctrl.doShow('') + command -complete=custom,s:CompleteWindow -nargs=1 Lhide pyx ctrl.doHide('') + command -complete=custom,s:CompleteWindow -nargs=0 Lshow pyx ctrl.doShow('') " Launching convenience commands (no autocompletion) - command -nargs=* Lstart python ctrl.doLaunch(True, '') - command -nargs=* Lrun python ctrl.doLaunch(False, '') - command -nargs=1 Lattach python ctrl.doAttach('') - command -nargs=0 Ldetach python ctrl.doDetach() + command -nargs=* Lstart pyx ctrl.doLaunch(True, '') + command -nargs=* Lrun pyx ctrl.doLaunch(False, '') + command -nargs=1 Lattach pyx ctrl.doAttach('') + command -nargs=0 Ldetach pyx ctrl.doDetach() " Regexp-commands: because vim's command mode does not support '_' or '-' " characters in command names, we omit them when creating the :L " equivalents. - command -complete=custom,s:CompleteCommand -nargs=* Lregexpattach python ctrl.doCommand('_regexp-attach', '') - command -complete=custom,s:CompleteCommand -nargs=* Lregexpbreak python ctrl.doCommand('_regexp-break', '') - command -complete=custom,s:CompleteCommand -nargs=* Lregexpbt python ctrl.doCommand('_regexp-bt', '') - command -complete=custom,s:CompleteCommand -nargs=* Lregexpdown python ctrl.doCommand('_regexp-down', '') - command -complete=custom,s:CompleteCommand -nargs=* Lregexptbreak python ctrl.doCommand('_regexp-tbreak', '') - command -complete=custom,s:CompleteCommand -nargs=* Lregexpdisplay python ctrl.doCommand('_regexp-display', '') - command -complete=custom,s:CompleteCommand -nargs=* Lregexpundisplay python ctrl.doCommand('_regexp-undisplay', '') - command -complete=custom,s:CompleteCommand -nargs=* Lregexpup python ctrl.doCommand('_regexp-up', '') - - command -complete=custom,s:CompleteCommand -nargs=* Lapropos python ctrl.doCommand('apropos', '') - command -complete=custom,s:CompleteCommand -nargs=* Lbacktrace python ctrl.doCommand('bt', '') - command -complete=custom,s:CompleteCommand -nargs=* Lbreakpoint python ctrl.doBreakpoint('') - command -complete=custom,s:CompleteCommand -nargs=* Lcommand python ctrl.doCommand('command', '') - command -complete=custom,s:CompleteCommand -nargs=* Ldisassemble python ctrl.doCommand('disassemble', '') - command -complete=custom,s:CompleteCommand -nargs=* Lexpression python ctrl.doCommand('expression', '') - command -complete=custom,s:CompleteCommand -nargs=* Lhelp python ctrl.doCommand('help', '') - command -complete=custom,s:CompleteCommand -nargs=* Llog python ctrl.doCommand('log', '') - command -complete=custom,s:CompleteCommand -nargs=* Lplatform python ctrl.doCommand('platform','') - command -complete=custom,s:CompleteCommand -nargs=* Lplugin python ctrl.doCommand('plugin', '') - command -complete=custom,s:CompleteCommand -nargs=* Lprocess python ctrl.doProcess('') - command -complete=custom,s:CompleteCommand -nargs=* Lregister python ctrl.doCommand('register', '') - command -complete=custom,s:CompleteCommand -nargs=* Lscript python ctrl.doCommand('script', '') - command -complete=custom,s:CompleteCommand -nargs=* Lsettings python ctrl.doCommand('settings','') - command -complete=custom,s:CompleteCommand -nargs=* Lsource python ctrl.doCommand('source', '') - command -complete=custom,s:CompleteCommand -nargs=* Ltype python ctrl.doCommand('type', '') - command -complete=custom,s:CompleteCommand -nargs=* Lversion python ctrl.doCommand('version', '') - command -complete=custom,s:CompleteCommand -nargs=* Lwatchpoint python ctrl.doCommand('watchpoint', '') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpattach pyx ctrl.doCommand('_regexp-attach', '') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpbreak pyx ctrl.doCommand('_regexp-break', '') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpbt pyx ctrl.doCommand('_regexp-bt', '') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpdown pyx ctrl.doCommand('_regexp-down', '') + command -complete=custom,s:CompleteCommand -nargs=* Lregexptbreak pyx ctrl.doCommand('_regexp-tbreak', '') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpdisplay pyx ctrl.doCommand('_regexp-display', '') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpundisplay pyx ctrl.doCommand('_regexp-undisplay', '') + command -complete=custom,s:CompleteCommand -nargs=* Lregexpup pyx ctrl.doCommand('_regexp-up', '') + + command -complete=custom,s:CompleteCommand -nargs=* Lapropos pyx ctrl.doCommand('apropos', '') + command -complete=custom,s:CompleteCommand -nargs=* Lbacktrace pyx ctrl.doCommand('bt', '') + command -complete=custom,s:CompleteCommand -nargs=* Lbreakpoint pyx ctrl.doBreakpoint('') + command -complete=custom,s:CompleteCommand -nargs=* Lcommand pyx ctrl.doCommand('command', '') + command -complete=custom,s:CompleteCommand -nargs=* Ldisassemble pyx ctrl.doCommand('disassemble', '') + command -complete=custom,s:CompleteCommand -nargs=* Lexpression pyx ctrl.doCommand('expression', '') + command -complete=custom,s:CompleteCommand -nargs=* Lhelp pyx ctrl.doCommand('help', '') + command -complete=custom,s:CompleteCommand -nargs=* Llog pyx ctrl.doCommand('log', '') + command -complete=custom,s:CompleteCommand -nargs=* Lplatform pyx ctrl.doCommand('platform','') + command -complete=custom,s:CompleteCommand -nargs=* Lplugin pyx ctrl.doCommand('plugin', '') + command -complete=custom,s:CompleteCommand -nargs=* Lprocess pyx ctrl.doProcess('') + command -complete=custom,s:CompleteCommand -nargs=* Lregister pyx ctrl.doCommand('register', '') + command -complete=custom,s:CompleteCommand -nargs=* Lscript pyx ctrl.doCommand('script', '') + command -complete=custom,s:CompleteCommand -nargs=* Lsettings pyx ctrl.doCommand('settings','') + command -complete=custom,s:CompleteCommand -nargs=* Lsource pyx ctrl.doCommand('source', '') + command -complete=custom,s:CompleteCommand -nargs=* Ltype pyx ctrl.doCommand('type', '') + command -complete=custom,s:CompleteCommand -nargs=* Lversion pyx ctrl.doCommand('version', '') + command -complete=custom,s:CompleteCommand -nargs=* Lwatchpoint pyx ctrl.doCommand('watchpoint', '') " Convenience (shortcut) LLDB commands - command -complete=custom,s:CompleteCommand -nargs=* Lprint python ctrl.doCommand('print', vim.eval("s:CursorWord('')")) - command -complete=custom,s:CompleteCommand -nargs=* Lpo python ctrl.doCommand('po', vim.eval("s:CursorWord('')")) - command -complete=custom,s:CompleteCommand -nargs=* LpO python ctrl.doCommand('po', vim.eval("s:CursorWORD('')")) - command -complete=custom,s:CompleteCommand -nargs=* Lbt python ctrl.doCommand('bt', '') + command -complete=custom,s:CompleteCommand -nargs=* Lprint pyx ctrl.doCommand('print', vim.eval("s:CursorWord('')")) + command -complete=custom,s:CompleteCommand -nargs=* Lpo pyx ctrl.doCommand('po', vim.eval("s:CursorWord('')")) + command -complete=custom,s:CompleteCommand -nargs=* LpO pyx ctrl.doCommand('po', vim.eval("s:CursorWORD('')")) + command -complete=custom,s:CompleteCommand -nargs=* Lbt pyx ctrl.doCommand('bt', '') " Frame/Thread-Selection (commands that also do an Uupdate but do not " generate events in LLDB) - command -complete=custom,s:CompleteCommand -nargs=* Lframe python ctrl.doSelect('frame', '') - command -complete=custom,s:CompleteCommand -nargs=? Lup python ctrl.doCommand('up', '', print_on_success=False, goto_file=True) - command -complete=custom,s:CompleteCommand -nargs=? Ldown python ctrl.doCommand('down', '', print_on_success=False, goto_file=True) - command -complete=custom,s:CompleteCommand -nargs=* Lthread python ctrl.doSelect('thread', '') + command -complete=custom,s:CompleteCommand -nargs=* Lframe pyx ctrl.doSelect('frame', '') + command -complete=custom,s:CompleteCommand -nargs=? Lup pyx ctrl.doCommand('up', '', print_on_success=False, goto_file=True) + command -complete=custom,s:CompleteCommand -nargs=? Ldown pyx ctrl.doCommand('down', '', print_on_success=False, goto_file=True) + command -complete=custom,s:CompleteCommand -nargs=* Lthread pyx ctrl.doSelect('thread', '') - command -complete=custom,s:CompleteCommand -nargs=* Ltarget python ctrl.doTarget('') + command -complete=custom,s:CompleteCommand -nargs=* Ltarget pyx ctrl.doTarget('') " Continue - command -complete=custom,s:CompleteCommand -nargs=* Lcontinue python ctrl.doContinue() + command -complete=custom,s:CompleteCommand -nargs=* Lcontinue pyx ctrl.doContinue() " Thread-Stepping (no autocompletion) - command -nargs=0 Lstepinst python ctrl.doStep(StepType.INSTRUCTION) - command -nargs=0 Lstepinstover python ctrl.doStep(StepType.INSTRUCTION_OVER) - command -nargs=0 Lstepin python ctrl.doStep(StepType.INTO) - command -nargs=0 Lstep python ctrl.doStep(StepType.INTO) - command -nargs=0 Lnext python ctrl.doStep(StepType.OVER) - command -nargs=0 Lfinish python ctrl.doStep(StepType.OUT) + command -nargs=0 Lstepinst pyx ctrl.doStep(StepType.INSTRUCTION) + command -nargs=0 Lstepinstover pyx ctrl.doStep(StepType.INSTRUCTION_OVER) + command -nargs=0 Lstepin pyx ctrl.doStep(StepType.INTO) + command -nargs=0 Lstep pyx ctrl.doStep(StepType.INTO) + command -nargs=0 Lnext pyx ctrl.doStep(StepType.OVER) + command -nargs=0 Lfinish pyx ctrl.doStep(StepType.OUT) + + " Bind/Unbind + command -bar -bang Lunbind call s:UnbindCursorFromLLDB() + command -bar -bang Lbind call s:BindCursorToLLDB() + + call s:ServiceLLDBEventQueue() +endfunction + + +" @TODO move this and other binding functions to /autoload +function! s:ServiceLLDBEventQueue() " hack: service the LLDB event-queue when the cursor moves " FIXME: some threaded solution would be better...but it " would have to be designed carefully because Vim's APIs are non threadsafe; " use of the vim module **MUST** be restricted to the main thread. - command -nargs=0 Lrefresh python ctrl.doRefresh() - autocmd CursorMoved * :Lrefresh - autocmd CursorHold * :Lrefresh - autocmd VimLeavePre * python ctrl.doExit() + command -nargs=0 Lrefresh pyx ctrl.doRefresh() + call s:BindCursorToLLDB() +endfunction + + +function! s:BindCursorToLLDB() + augroup bindtocursor + autocmd! + autocmd CursorMoved * :Lrefresh + autocmd CursorHold * :Lrefresh + autocmd VimLeavePre * pyx ctrl.doExit() + augroup end +endfunction + + +function! s:UnbindCursorFromLLDB() + augroup bindtocursor + autocmd! + augroup end + echo "vim-LLDB: unbound cursor" +endfunction - execute 'pyfile ' . vim_lldb_pydir . '/plugin.py' -endfunction() function! s:CompleteCommand(A, L, P) - python << EOF +pyx << EOF a = vim.eval("a:A") l = vim.eval("a:L") p = vim.eval("a:P") returnCompleteCommand(a, l, p) EOF -endfunction() +endfunction function! s:CompleteWindow(A, L, P) - python << EOF +pyx << EOF a = vim.eval("a:A") l = vim.eval("a:L") p = vim.eval("a:P") returnCompleteWindow(a, l, p) EOF -endfunction() +endfunction " Returns cword if search term is empty function! s:CursorWord(term) return empty(a:term) ? expand('') : a:term -endfunction() +endfunction " Returns cleaned cWORD if search term is empty function! s:CursorWORD(term) " Will strip all non-alphabetic characters from both sides return empty(a:term) ? substitute(expand(''), '^\A*\(.\{-}\)\A*$', '\1', '') : a:term -endfunction() +endfunction + +augroup VimLLDB + autocmd! + au ColorScheme * call s:Highlight() +augroup END + call s:InitLldbPlugin() + +call s:Highlight() + + +let &cpo = s:keepcpo +unlet s:keepcpo diff --git a/python-vim-lldb/import_lldb.py b/python-vim-lldb/import_lldb.py index 41cb42b..bba1f56 100644 --- a/python-vim-lldb/import_lldb.py +++ b/python-vim-lldb/import_lldb.py @@ -1,18 +1,19 @@ # Locate and load the lldb python module +import vim import os import sys def import_lldb(): """ Find and import the lldb modules. This function tries to find the lldb module by: - 1. Simply by doing "import lldb" in case the system python installation is aware of lldb. If that fails, - 2. Executes the lldb executable pointed to by the LLDB environment variable (or if unset, the first lldb - on PATH") with the -P flag to determine the PYTHONPATH to set. If the lldb executable returns a valid - path, it is added to sys.path and the import is attempted again. If that fails, 3. On Mac OS X the - default Xcode 4.5 installation path. - """ + 1. "import lldb" => in case the Vim's python installation is aware of lldb. If that fails, + 2. "s:lldb_path" => check if lldb_path is set in vimrc, if so, update and use full path as `lldb` below + 3. "lldb -P" => exec the lldb executable pointed to by the LLDB environment variable (or if unset, the first lldb on PATH") with the -P flag to determine the PYTHONPATH to set. If the lldb executable returns a valid + path, it is added to sys.path and the import is attempted again. If that fails, + 4. On Mac OS X the default Xcode 4.5 installation path. +""" # Try simple 'import lldb', in case of a system-wide install or a # pre-configured PYTHONPATH @@ -25,9 +26,15 @@ def import_lldb(): # Allow overriding default path to lldb executable with the LLDB # environment variable lldb_executable = 'lldb' + if 'LLDB' in os.environ and os.path.exists(os.environ['LLDB']): lldb_executable = os.environ['LLDB'] + # vimrc overrides environ ${LLDB} + vimrc_lldb_path = vim.eval('s:lldb_custom_path') + if vimrc_lldb_path != "": + lldb_executable = vimrc_lldb_path + # Try using builtin module location support ('lldb -P') from subprocess import check_output, CalledProcessError try: @@ -36,12 +43,14 @@ def import_lldb(): "%s -P" % lldb_executable, shell=True, - stderr=fnull).strip() + stderr=fnull).strip().decode("utf-8") + if not os.path.exists(lldb_minus_p_path): # lldb -P returned invalid path, probably too old pass else: sys.path.append(lldb_minus_p_path) + # print("DEBUG: importing from sys.path as lldb: %s"% lldb_minus_p_path) import lldb return True except CalledProcessError: @@ -51,10 +60,12 @@ def import_lldb(): # Unable to import lldb module from path returned by `lldb -P` pass - # On Mac OS X, use the try the default path to XCode lldb module + # On Mac OS X, try the default path to Xcode lldb module if "darwin" in sys.platform: - xcode_python_path = "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/Current/Resources/Python/" + xcode_path_modifier = vim.eval("s:lldb_python_version") + xcode_python_path = "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/Current/Resources/Python%s/"% xcode_path_modifier sys.path.append(xcode_python_path) + try: import lldb return True @@ -65,7 +76,4 @@ def import_lldb(): return False if not import_lldb(): - import vim - vim.command( - 'redraw | echo "%s"' % - " Error loading lldb module; vim-lldb will be disabled. Check LLDB installation or set LLDB environment variable.") + vim.command("let s:lldb_disabled=1") diff --git a/python-vim-lldb/lldb_controller.py b/python-vim-lldb/lldb_controller.py index 0e59cc5..ebc55bb 100644 --- a/python-vim-lldb/lldb_controller.py +++ b/python-vim-lldb/lldb_controller.py @@ -10,15 +10,17 @@ import sys import lldb import vim +from utility import * from vim_ui import UI + # ================================================= # Convert some enum value to its string counterpart # ================================================= # Shamelessly copy/pasted from lldbutil.py in the test suite - +# current to lldb@v11 see lldb.py ~223 for values def state_type_to_str(enum): """Returns the stateType string given an enum.""" if enum == lldb.eStateInvalid: @@ -78,6 +80,14 @@ def __init__(self): self.load_dependent_modules = True self.dbg = lldb.SBDebugger.Create() + # during step/continue do not return from function until process stops + # async is enabled by default, but overridden in vimrc g:lldb_enable_async + vimrc_lldb_async = vim.eval('s:lldb_async') + if (vimrc_lldb_async == 0): + self.dbg.SetAsync(False) + else: + self.dbg.SetAsync(True) + self.commandInterpreter = self.dbg.GetCommandInterpreter() self.ui = UI() @@ -314,6 +324,9 @@ def getCommandResult(self, command, command_args): return (result.Succeeded(), result.GetOutput() if result.Succeeded() else result.GetError()) + # check if ci.CommandExists(command) before exec + # may need this check in the future if auto-generating commands + # currently they are expicitly whitelisted in lldb.vim so we are ok def doCommand( self, command, @@ -325,12 +338,18 @@ def doCommand( if success: self.ui.update(self.target, "", self, goto_file) if len(output) > 0 and print_on_success: - print(output) + output = escape_ansi(output.encode("utf-8", "replace")) + # vim uses "''" to escape single quotes + output = str(output.decode("utf-8")).replace("'", "''") + vim.command('echohl lldb_output') + vim.command("let g:lldb_msg='%s'" % output) + vim.command('echo lldb_msg') + vim.command('echohl None') else: sys.stderr.write(output) def getCommandOutput(self, command, command_args=""): - """ runs cmd in the command interpreter andreturns (status, result) """ + """ runs cmd in the command interpreter and returns (status, result) """ result = lldb.SBCommandReturnObject() cmd = "%s %s" % (command, command_args) self.commandInterpreter.HandleCommand(cmd, result) @@ -399,17 +418,11 @@ def returnCompleteCommand(a, l, p): def returnCompleteWindow(a, l, p): """ Returns a "\n"-separated string with possible completion results for commands that expect a window name parameter (like hide/show). - FIXME: connect to ctrl.ui instead of hardcoding the list here """ separator = "\n" - results = [ - 'breakpoints', - 'backtrace', - 'disassembly', - 'locals', - 'threads', - 'registers'] + results = ul.defaultPanes vim.command('return "%s%s"' % (separator.join(results), separator)) global ctrl + ctrl = LLDBController() diff --git a/python-vim-lldb/plugin.py b/python-vim-lldb/plugin.py index c59546c..b2e4483 100644 --- a/python-vim-lldb/plugin.py +++ b/python-vim-lldb/plugin.py @@ -9,7 +9,7 @@ import vim except ImportError: sys.stderr.write( - "Unable to load vim/lldb module. Check lldb is on the path is available (or LLDB is set) and that script is invoked inside Vim with :pyfile") + "Unable to load vim/lldb module, vim-lldb is disabled. Check lldb is available on path with `lldb -P` and codesigned or set lldb_path in .vimrc. See README for setup help.") pass else: # Everthing went well, so use import to start the plugin controller diff --git a/python-vim-lldb/utility.py b/python-vim-lldb/utility.py new file mode 100644 index 0000000..9f03a06 --- /dev/null +++ b/python-vim-lldb/utility.py @@ -0,0 +1,9 @@ +from re import compile, VERBOSE + +# 7/8-bit C1 ANSI sequences +ansi_escape = compile( + br'(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])' +) + +def escape_ansi(line): + return ansi_escape.sub(b'', bytes(line)) diff --git a/python-vim-lldb/vim_panes.py b/python-vim-lldb/vim_panes.py index b0c804d..6999b81 100644 --- a/python-vim-lldb/vim_panes.py +++ b/python-vim-lldb/vim_panes.py @@ -37,6 +37,8 @@ import lldb import vim +from utility import * + import sys # ============================================================== @@ -231,6 +233,7 @@ class VimPane(object): SELECTED_HIGHLIGHT_NAME_GUI = 'Cursor' SELECTED_HIGHLIGHT_NAME_TERM = 'lldb_selected' + SELECTED_HIGHLIGHT_COLOUR_TERM = 'darkblue' MSG_NO_TARGET = "Target does not exist." @@ -368,7 +371,9 @@ def write(self, msg): """ replace buffer with msg""" self.prepare() - msg = str(msg.encode("utf-8", "replace")).split('\n') + msg = escape_ansi(msg.encode("utf-8", "replace")) + msg = str(msg.decode("utf-8")).split('\n') + try: self.buffer.append(msg) vim.command("execute \"normal ggdd\"") @@ -519,8 +524,11 @@ def format_register(self, reg): def get_frame_content(self, frame): """ Returns a list of key-value pairs ("name", "value") of registers in frame """ + # print("frame.getRegisters: %s"% frame.GetRegisters()) result = [] for register_sets in frame.GetRegisters(): + # print("reg set: %s"% register_sets.GetName()) + # hack the register group name into the list of registers... result.append((" = = %s =" % register_sets.GetName(), "")) diff --git a/python-vim-lldb/vim_signs.py b/python-vim-lldb/vim_signs.py index 94f79ec..8510e2e 100644 --- a/python-vim-lldb/vim_signs.py +++ b/python-vim-lldb/vim_signs.py @@ -3,12 +3,15 @@ import vim - class VimSign(object): SIGN_TEXT_BREAKPOINT_RESOLVED = "B>" SIGN_TEXT_BREAKPOINT_UNRESOLVED = "b>" SIGN_TEXT_PC = "->" - SIGN_HIGHLIGHT_COLOUR_PC = 'darkblue' + SIGN_HIGHLIGHT_COLOUR_PC = 'darkblue' # default + SIGN_BREAKPOINT="lldb_breakpoint" + SIGN_PC_ACTIVE="lldb_pc_active" + SIGN_PC_INACTIVE="lldb_pc_inactive" + SIGN_DEFAULT_NAME = 'lldb_highlight' # can be overriden from .vimrc # unique sign id (for ':[sign/highlight] define) sign_id = 1 @@ -19,34 +22,28 @@ class VimSign(object): # Map of {(sign_text, highlight_colour) --> sign_name} defined_signs = {} - def __init__(self, sign_text, buffer, line_number, highlight_colour=None): + def __init__(self, sign_text, buffer, line_number, highlight_name=SIGN_DEFAULT_NAME): """ Define the sign and highlight (if applicable) and show the sign. """ # Get the sign name, either by defining it, or looking it up in the map # of defined signs - key = (sign_text, highlight_colour) + key = (sign_text, highlight_name) if key not in VimSign.defined_signs: - name = self.define(sign_text, highlight_colour) + name = self.define(sign_text, highlight_name) else: name = VimSign.defined_signs[key] self.show(name, buffer.number, line_number) pass - def define(self, sign_text, highlight_colour): + def define(self, sign_text, highlight_name): """ Defines sign and highlight (if highlight_colour is not None). """ sign_name = "sign%d" % VimSign.name_id - if highlight_colour is None: - vim.command("sign define %s text=%s" % (sign_name, sign_text)) - else: - self.highlight_name = "highlight%d" % VimSign.name_id - vim.command( - "highlight %s ctermbg=%s guibg=%s" % - (self.highlight_name, highlight_colour, highlight_colour)) - vim.command( - "sign define %s text=%s linehl=%s texthl=%s" % - (sign_name, sign_text, self.highlight_name, self.highlight_name)) - VimSign.defined_signs[(sign_text, highlight_colour)] = sign_name + self.highlight_name = "highlight%d" % VimSign.name_id + vim.command("sign define %s text=%s linehl=%s texthl=%s" % + (sign_name, sign_text, highlight_name, highlight_name)) + + VimSign.defined_signs[(sign_text, self.highlight_name)] = sign_name VimSign.name_id += 1 return sign_name @@ -66,16 +63,14 @@ class BreakpointSign(VimSign): def __init__(self, buffer, line_number, is_resolved): txt = VimSign.SIGN_TEXT_BREAKPOINT_RESOLVED if is_resolved else VimSign.SIGN_TEXT_BREAKPOINT_UNRESOLVED - super(BreakpointSign, self).__init__(txt, buffer, line_number) + super(BreakpointSign, self).__init__(txt, buffer, line_number, VimSign.SIGN_BREAKPOINT) class PCSign(VimSign): def __init__(self, buffer, line_number, is_selected_thread): - super( - PCSign, - self).__init__( + super( PCSign, self).__init__( VimSign.SIGN_TEXT_PC, buffer, line_number, - VimSign.SIGN_HIGHLIGHT_COLOUR_PC if is_selected_thread else None) + VimSign.SIGN_PC_ACTIVE if is_selected_thread else VimSign.SIGN_PC_INACTIVE) diff --git a/python-vim-lldb/vim_ui.py b/python-vim-lldb/vim_ui.py index bc5b6a4..617798f 100644 --- a/python-vim-lldb/vim_ui.py +++ b/python-vim-lldb/vim_ui.py @@ -53,8 +53,10 @@ def __init__(self): def activate(self): """ Activate UI: display default set of panes """ + self.paneCol.prepare(self.defaultPanes) + def get_user_buffers(self, filter_name=None): """ Returns a list of buffers that are not a part of the LLDB UI. That is, they are not contained in the PaneLayout object self.paneCol.