diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 14f5979..f186f5d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ defaults: jobs: PythonLint: - runs-on: ubuntu-16.04 + runs-on: ubuntu-18.04 container: 'puremourning/vimspector:test' steps: - uses: actions/checkout@v2 @@ -22,7 +22,7 @@ jobs: - name: 'Run flake8' run: '$HOME/.local/bin/flake8 python3/ *.py' VimscriptLint: - runs-on: 'ubuntu-16.04' + runs-on: 'ubuntu-18.04' container: 'puremourning/vimspector:test' steps: - uses: actions/checkout@v2 @@ -32,7 +32,7 @@ jobs: run: $HOME/.local/bin/vint autoload/ compiler/ plugin/ tests/ syntax/ Linux: - runs-on: 'ubuntu-16.04' + runs-on: 'ubuntu-18.04' container: image: 'puremourning/vimspector:test' options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined @@ -156,7 +156,7 @@ jobs: # SSH_PASS: ${{ secrets.SSH_PASS }} # [V]imspector PublishRelease: - runs-on: 'ubuntu-16.04' + runs-on: 'ubuntu-18.04' needs: - Linux - MacOS diff --git a/README.md b/README.md index f8db3aa..d198292 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,10 @@ For detailed explanation of the `.vimspector.json` format, see the * [Closing debugger](#closing-debugger) * [Terminate debuggee](#terminate-debuggee) * [Debug profile configuration](#debug-profile-configuration) - * [C, C , Rust, etc.](#c-c-rust-etc) - * [C Remote debugging](#c-remote-debugging) - * [C Remote launch and attach](#c-remote-launch-and-attach) + * [C, C++, Rust, etc.](#c-c-rust-etc) + * [Data visualization / pretty printing](#data-visualization--pretty-printing) + * [C++ Remote debugging](#c-remote-debugging) + * [C++ Remote launch and attach](#c-remote-launch-and-attach) * [Rust](#rust) * [Python](#python) * [Python Remote Debugging](#python-remote-debugging) @@ -290,7 +291,7 @@ If you just want to try out vimspector without changing your vim config, there are example projects for a number of languages in `support/test`, including: * Python (`support/test/python/simple_python`) -* Go (`support/test/go/hello_world`) +* Go (`support/test/go/hello_world` and `support/test/go/name-starts-with-vowel`) * Nodejs (`support/test/node/simple`) * Chrome (`support/test/chrome/`) * etc. @@ -1176,6 +1177,38 @@ licensing. } ``` +### Data visualization / pretty printing + +Depending on the backend you need to enable pretty printing of complex types manually. + +* LLDB: Pretty printing is enabled by default + +* GDB: To enable gdb pretty printers, consider the snippet below. + It is not enough to have `set print pretty on` in your .gdbinit! + +``` +{ + "configurations": { + "Launch": { + "adapter": "vscode-cpptools", + "configuration": { + "request": "launch", + "program": "", + ... + "MIMode": "gdb" + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + } + } + } +} +``` + ### C++ Remote debugging The cpptools documentation describes how to attach cpptools to gdbserver using @@ -1368,6 +1401,8 @@ Requires: * [Delve][delve-install] installed, e.g. `go get -u github.com/go-delve/delve/cmd/dlv` * Delve to be in your PATH, or specify the `dlvToolPath` launch option +NOTE: Vimspector uses the ["legacy" vscode-go debug adapter](https://github.com/golang/vscode-go/blob/master/docs/debugging-legacy.md) rather than the "built-in" DAP support in Delve. You can track https://github.com/puremourning/vimspector/issues/186 for that. + ```json { "configurations": { @@ -1385,7 +1420,7 @@ Requires: ``` See the vscode-go docs for -[troubleshooting information](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#troubleshooting) +[troubleshooting information](https://github.com/golang/vscode-go/blob/master/docs/debugging-legacy.md#troubleshooting) ## PHP diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index 0eb394f..78c7c1b 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -223,13 +223,6 @@ function! vimspector#SetVariableValue( ... ) abort endif endfunction -function! vimspector#ReadMemory() abort - if !s:Enabled() - return - endif - py3 _vimspector_session.ReadMemory() -endfunction - function! vimspector#DeleteWatch() abort if !s:Enabled() return diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 0fa7776..acf20f2 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -7,7 +7,7 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.7.0) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) coffee-script (2.4.1) coffee-script-source diff --git a/docs/configuration.md b/docs/configuration.md index a2864b1..3d524bf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -722,7 +722,7 @@ Vimspector then orchestrates the various tools to set you up. "variables": { // Just an example of how to specify a variable manually rather than // vimspector asking for input from the user - "ServiceName": "${fileBasenameNoExtention}" + "ServiceName": "${fileBasenameNoExtension}" }, "adapter": "python-remote", diff --git a/python3/vimspector/code.py b/python3/vimspector/code.py index 5f54e1c..98aeca5 100644 --- a/python3/vimspector/code.py +++ b/python3/vimspector/code.py @@ -16,7 +16,6 @@ import vim import logging import json -import os from collections import defaultdict from vimspector import utils, terminal, signs @@ -41,7 +40,6 @@ class CodeView( object ): 'breakpoints': [] } self._current_frame = None - self._scratch_buffers = [] with utils.LetCurrentWindow( self._window ): if utils.UseWinBar(): @@ -175,10 +173,6 @@ class CodeView( object ): self.ClearBreakpoints() self.Clear() - for b in self._scratch_buffers: - utils.CleanUpHiddenBuffer( b ) - self._scratch_buffers = [] - def AddBreakpoints( self, source, breakpoints ): for breakpoint in breakpoints: source = breakpoint.get( 'source' ) or source @@ -293,32 +287,3 @@ class CodeView( object ): # FIXME: Change this tor return the PID rather than having debug_session # work that out return self._terminal.buffer_number - - - def ShowMemory( self, memoryReference, length, offset, msg ): - if not self._window.valid: - return False - - buf_name = os.path.join( '_vimspector_mem', memoryReference ) - buf = utils.BufferForFile( buf_name ) - self._scratch_buffers.append( buf ) - utils.SetUpHiddenBuffer( buf, buf_name ) - with utils.ModifiableScratchBuffer( buf ): - # TODO: The data is encoded in base64, so we need to convert that to the - # equivalent output of say xxd - data = msg.get( 'body', {} ).get( 'data', '' ) - utils.SetBufferContents( buf, [ - f'Memory Dump for Reference {memoryReference} Length: {length} bytes' - f' Offset: {offset}', - '-' * 80, - 'Offset Bytes Text', - '-' * 80, - ] ) - utils.AppendToBuffer( buf, utils.Base64ToHexDump( data ) ) - - utils.SetSyntax( '', 'xxd', buf ) - utils.JumpToWindow( self._window ) - utils.OpenFileInCurrentWindow( buf_name ) - - # TODO: Need to set up some mappings here that allow the user to browse - # around by setting the offset diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index c8c6ce8..36ad62b 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -535,45 +535,6 @@ class DebugSession( object ): def SetVariableValue( self, new_value = None, buf = None, line_num = None ): self._variablesView.SetVariableValue( new_value, buf, line_num ) - @IfConnected() - def ReadMemory( self, offset = None, buf = None, line_num = None ): - if not self._server_capabilities.get( 'supportsReadMemoryRequest' ): - utils.UserMessage( "Server does not support memory request", - error = True ) - return - - memoryReference = self._variablesView.GetMemoryReference( buf, line_num ) - if memoryReference is None: - utils.UserMessage( "Cannot find memory reference for that", - error = True ) - return - - length = utils.AskForInput( 'How much data to display? ', - default_value = '1024' ) - - if length is None: - return - - offset = utils.AskForInput( 'Location offset? ', - default_value = '0' ) - - if offset is None: - return - - - def handler( msg ): - self._codeView.ShowMemory( memoryReference, length, offset, msg ) - - self._connection.DoRequest( handler, { - 'command': 'readMemory', - 'arguments': { - 'memoryReference': memoryReference, - 'count': int( length ), - 'offset': int( offset ) - } - } ) - - @IfConnected() def AddWatch( self, expression ): self._variablesView.AddWatch( self._stackTraceView.GetCurrentFrame(), @@ -1202,8 +1163,7 @@ class DebugSession( object ): 'pathFormat': 'path', 'supportsVariableType': True, 'supportsVariablePaging': False, - 'supportsRunInTerminalRequest': True, - 'supportsMemoryReferences': True + 'supportsRunInTerminalRequest': True }, } ) diff --git a/python3/vimspector/gadgets.py b/python3/vimspector/gadgets.py index 140c9f4..02eb0e7 100644 --- a/python3/vimspector/gadgets.py +++ b/python3/vimspector/gadgets.py @@ -30,12 +30,12 @@ GADGETS = { root, gadget ), 'all': { - 'version': '0.27.0', + 'version': '1.6.0', "adapters": { "vscode-cpptools": { "name": "cppdbg", "command": [ - "${gadgetDir}/vscode-cpptools/debugAdapters/OpenDebugAD7" + "${gadgetDir}/vscode-cpptools/debugAdapters/bin/OpenDebugAD7" ], "attach": { "pidProperty": "processId", @@ -53,17 +53,17 @@ GADGETS = { 'linux': { 'file_name': 'cpptools-linux.vsix', 'checksum': - '3695202e1e75a03de18049323b66d868165123f26151f8c974a480eaf0205435', + 'c25299bcfb46b22d41aa3f125df7184e6282a35ff9fb69c47def744cb4778f55', }, 'macos': { - 'file_name': 'cpptools-osx.vsix', + 'file_name': 'cpptools-osx-arm64.vsix', 'checksum': - 'cb061e3acd7559a539e5586f8d3f535101c4ec4e8a48195856d1d39380b5cf3c', + 'ceb3e8cdaa2b5bb45af50913ddd8402089969748af8d70f5d46480408287ba6f', }, 'windows': { 'file_name': 'cpptools-win32.vsix', 'checksum': - 'aa294368ed16d48c59e49c8000e146eae5a19ad07b654efed5db8ec93b24229e', + 'ef7ac5831874a3c7dbf0feb826bfda2be579aff9b6d990622fff1d0d4ede00d1', "adapters": { "vscode-cpptools": { "name": "cppdbg", @@ -323,10 +323,10 @@ GADGETS = { '${version}/${file_name}', }, 'all': { - 'version': 'v1.16.0', - 'file_name': 'php-debug-1.16.0.vsix', + 'version': 'v1.17.0', + 'file_name': 'php-debug-1.17.0.vsix', 'checksum': - '62d210f7b87b21315c37ea10a1a5dbae376ff9f963b8f8cf33361e01413731be', + 'd0fff272503414b6696cc737bc2e18e060fdd5e5dc4bcaf38ae7373afd8d8bc9', }, 'adapters': { 'vscode-php-debug': { @@ -394,12 +394,12 @@ GADGETS = { '${version}/${file_name}', }, 'all': { - 'version': 'v1.6.4', + 'version': 'v1.6.6', }, 'macos': { - 'file_name': 'codelldb-x86_64-darwin.vsix', + 'file_name': 'codelldb-aarch64-darwin.vsix', 'checksum': - 'aa920b7b7d2ad4e9d70086355841b0b4844fb5f62cdea1296904100a1b660776', + '5adc3b9139eabdafd825bd5efc55df4424a203fb2b6087b425cd434956e7ec58', 'make_executable': [ 'adapter/codelldb', 'lldb/bin/debugserver', @@ -410,7 +410,7 @@ GADGETS = { 'linux': { 'file_name': 'codelldb-x86_64-linux.vsix', 'checksum': - '', + 'eda2cd9b3089dcc0524c273e91ffb5875fe08c930bf643739a2cd1846e1f98d6', 'make_executable': [ 'adapter/codelldb', 'lldb/bin/lldb', @@ -421,7 +421,7 @@ GADGETS = { 'windows': { 'file_name': 'codelldb-x86_64-windows.vsix', 'checksum': - '', + '8ddebe8381a3d22dc3d95139c3797fda06b5cc34aadf300e13b1c516b9da95fe', 'make_executable': [] }, 'adapters': { diff --git a/python3/vimspector/installer.py b/python3/vimspector/installer.py index f0f85a4..a81db8f 100644 --- a/python3/vimspector/installer.py +++ b/python3/vimspector/installer.py @@ -358,7 +358,8 @@ def InstallCppTools( name, root, gadget ): # It's hilarious, but the execute bits aren't set in the vsix. So they # actually have javascript code which does this. It's just a horrible horrible # hack that really is not funny. - MakeExecutable( os.path.join( extension, 'debugAdapters', 'OpenDebugAD7' ) ) + MakeExecutable( + os.path.join( extension, 'debugAdapters', 'bin', 'OpenDebugAD7' ) ) with open( os.path.join( extension, 'package.json' ) ) as f: package = json.load( f ) runtime_dependencies = package[ 'runtimeDependencies' ] diff --git a/python3/vimspector/output.py b/python3/vimspector/output.py index 8c94b44..3f0da1e 100644 --- a/python3/vimspector/output.py +++ b/python3/vimspector/output.py @@ -32,6 +32,7 @@ class TabBuffer( object ): BUFFER_MAP = { 'console': 'Console', 'stdout': 'Console', + 'output': 'Console', 'stderr': 'stderr', 'telemetry': None, } diff --git a/python3/vimspector/settings.py b/python3/vimspector/settings.py index 60e8341..89378af 100644 --- a/python3/vimspector/settings.py +++ b/python3/vimspector/settings.py @@ -54,8 +54,7 @@ DEFAULTS = { 'variables': { 'expand_collapse': [ '', '<2-LeftMouse>' ], 'delete': [ '' ], - 'set_value': [ '', '' ], - 'read_memory': [ 'm' ], + 'set_value': [ '', '' ] }, 'stack_trace': { 'expand_or_jump': [ '', '<2-LeftMouse>' ], diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 150ab95..5f836fc 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -25,9 +25,7 @@ import shlex import collections import re import typing -import base64 -from vimspector.vendor.hexdump import hexdump LOG_FILE = os.path.expanduser( os.path.join( '~', '.vimspector.log' ) ) @@ -866,8 +864,3 @@ def UseWinBar(): # Buggy neovim doesn't render correctly when the WinBar is defined: # https://github.com/neovim/neovim/issues/12689 return not int( Call( 'has', 'nvim' ) ) - - -def Base64ToHexDump( data ): - data = base64.b64decode( data ) - return list( hexdump( data, 'generator' ) ) diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index 8524d67..8dcb493 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -56,11 +56,6 @@ class Expandable: def VariablesReference( self ): assert False - @abc.abstractmethod - def MemoryReference( self ): - assert None - - class Scope( Expandable ): """Holds an expandable scope (a DAP scope dict), with expand/collapse state""" @@ -71,9 +66,6 @@ class Scope( Expandable ): def VariablesReference( self ): return self.scope.get( 'variablesReference', 0 ) - def MemoryReference( self ): - return None - def Update( self, scope ): self.scope = scope @@ -89,9 +81,6 @@ class WatchResult( Expandable ): def VariablesReference( self ): return self.result.get( 'variablesReference', 0 ) - def MemoryReference( self ): - return self.result.get( 'memoryReference' ) - def Update( self, result ): self.changed = False if self.result[ 'result' ] != result[ 'result' ]: @@ -116,9 +105,6 @@ class Variable( Expandable ): def VariablesReference( self ): return self.variable.get( 'variablesReference', 0 ) - def MemoryReference( self ): - return self.variable.get( 'memoryReference' ) - def Update( self, variable ): self.changed = False if self.variable[ 'value' ] != variable[ 'value' ]: @@ -177,10 +163,6 @@ def AddExpandMappings( mappings = None ): for mapping in utils.GetVimList( mappings, 'set_value' ): vim.command( f'nnoremap { mapping } ' ':call vimspector#SetVariableValue()' ) - for mapping in utils.GetVimList( mappings, 'read_memory' ): - vim.command( f'nnoremap { mapping } ' - ':call vimspector#ReadMemory()' ) - class VariablesView( object ): @@ -205,8 +187,6 @@ class VariablesView( object ): if utils.UseWinBar(): vim.command( 'nnoremenu 1.1 WinBar.Set ' ':call vimspector#SetVariableValue()' ) - vim.command( 'nnoremenu 1.2 WinBar.Memory ' - ':call vimspector#ReadMemory()' ) AddExpandMappings( mappings ) # Set up the "Watches" buffer in the watches_win (and create a WinBar in @@ -231,10 +211,8 @@ class VariablesView( object ): ':call vimspector#ExpandVariable()' ) vim.command( 'nnoremenu 1.3 WinBar.Delete ' ':call vimspector#DeleteWatch()' ) - vim.command( 'nnoremenu 1.4 WinBar.Set ' + vim.command( 'nnoremenu 1.1 WinBar.Set ' ':call vimspector#SetVariableValue()' ) - vim.command( 'nnoremenu 1.5 WinBar.Memory ' - ':call vimspector#ReadMemory()' ) # Set the (global!) balloon expr if supported has_balloon = int( vim.eval( "has( 'balloon_eval' )" ) ) @@ -602,14 +580,6 @@ class VariablesView( object ): }, failure_handler = failure_handler ) - def GetMemoryReference( self, buf = None, line_num = None ): - # Get a memoryReference for use in a ReadMemory request - variable, _ = self._GetVariable( buf, line_num ) - if variable is None: - return None - - return variable.MemoryReference() - def _DrawVariables( self, view, variables, indent, is_short = False ): assert indent > 0 @@ -625,12 +595,10 @@ class VariablesView( object ): value = variable.variable.get( 'value', '' ) ) else: - marker = 'm' if variable.MemoryReference() is not None else ' ' - marker += '*' if variable.changed else ' ' text = '{indent}{marker}{icon} {name} ({type_}): {value}'.format( # We borrow 1 space of indent to draw the change marker indent = ' ' * ( indent - 1 ), - marker = marker, + marker = '*' if variable.changed else ' ', icon = '+' if ( variable.IsExpandable() and not variable.IsExpanded() ) else '-', name = variable.variable.get( 'name', '' ), diff --git a/python3/vimspector/vendor/hexdump.py b/python3/vimspector/vendor/hexdump.py deleted file mode 100755 index 33b9b6b..0000000 --- a/python3/vimspector/vendor/hexdump.py +++ /dev/null @@ -1,466 +0,0 @@ -#!/usr/bin/env python -# -*- coding: latin-1 -*- - -# <-- removing this magic comment breaks Python 3.4 on Windows -""" -1. Dump binary data to the following text format: - -00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... -00000010: 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ - -It is similar to the one used by: -Scapy -00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... -00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ - -Far Manager -000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00 [hexdump] -000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 AA BB CC DD EE FF ?"3DUfwˆ™ª»ÌÝîÿ - - -2. Restore binary data from the formats above as well - as from less exotic strings of raw hex - -""" - -__version__ = '3.3' -__author__ = 'anatoly techtonik ' -__license__ = 'Public Domain' - -__history__ = \ -""" -3.3 (2015-01-22) - * accept input from sys.stdin if "-" is specified - for both dump and restore (issue #1) - * new normalize_py() helper to set sys.stdout to - binary mode on Windows - -3.2 (2015-07-02) - * hexdump is now packaged as .zip on all platforms - (on Linux created archive was tar.gz) - * .zip is executable! try `python hexdump-3.2.zip` - * dump() now accepts configurable separator, patch - by Ian Land (PR #3) - -3.1 (2014-10-20) - * implemented workaround against mysterious coding - issue with Python 3 (see revision 51302cf) - * fix Python 3 installs for systems where UTF-8 is - not default (Windows), thanks to George Schizas - (the problem was caused by reading of README.txt) - -3.0 (2014-09-07) - * remove unused int2byte() helper - * add dehex(text) helper to convert hex string - to binary data - * add 'size' argument to dump() helper to specify - length of chunks - -2.0 (2014-02-02) - * add --restore option to command line mode to get - binary data back from hex dump - * support saving test output with `--test logfile` - * restore() from hex strings without spaces - * restore() now raises TypeError if input data is - not string - * hexdump() and dumpgen() now don't return unicode - strings in Python 2.x when generator is requested - -1.0 (2013-12-30) - * length of address is reduced from 10 to 8 - * hexdump() got new 'result' keyword argument, it - can be either 'print', 'generator' or 'return' - * actual dumping logic is now in new dumpgen() - generator function - * new dump(binary) function that takes binary data - and returns string like "66 6F 72 6D 61 74" - * new genchunks(mixed, size) function that chunks - both sequences and file like objects - -0.5 (2013-06-10) - * hexdump is now also a command line utility (no - restore yet) - -0.4 (2013-06-09) - * fix installation with Python 3 for non English - versions of Windows, thanks to George Schizas - -0.3 (2013-04-29) - * fully Python 3 compatible - -0.2 (2013-04-28) - * restore() to recover binary data from a hex dump in - native, Far Manager and Scapy text formats (others - might work as well) - * restore() is Python 3 compatible - -0.1 (2013-04-28) - * working hexdump() function for Python 2 -""" - -import binascii # binascii is required for Python 3 -import sys - -# --- constants -PY3K = sys.version_info >= (3, 0) - -# --- workaround against Python consistency issues -def normalize_py(): - ''' Problem 001 - sys.stdout in Python is by default opened in - text mode, and writes to this stdout produce corrupted binary - data on Windows - - python -c "import sys; sys.stdout.write('_\n_')" > file - python -c "print(repr(open('file', 'rb').read()))" - ''' - if sys.platform == "win32": - # set sys.stdout to binary mode on Windows - import os, msvcrt - msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) - -# --- - chunking helpers -def chunks(seq, size): - '''Generator that cuts sequence (bytes, memoryview, etc.) - into chunks of given size. If `seq` length is not multiply - of `size`, the lengh of the last chunk returned will be - less than requested. - - >>> list( chunks([1,2,3,4,5,6,7], 3) ) - [[1, 2, 3], [4, 5, 6], [7]] - ''' - d, m = divmod(len(seq), size) - for i in range(d): - yield seq[i*size:(i+1)*size] - if m: - yield seq[d*size:] - -def chunkread(f, size): - '''Generator that reads from file like object. May return less - data than requested on the last read.''' - c = f.read(size) - while len(c): - yield c - c = f.read(size) - -def genchunks(mixed, size): - '''Generator to chunk binary sequences or file like objects. - The size of the last chunk returned may be less than - requested.''' - if hasattr(mixed, 'read'): - return chunkread(mixed, size) - else: - return chunks(mixed, size) -# --- - /chunking helpers - - -def dehex(hextext): - """ - Convert from hex string to binary data stripping - whitespaces from `hextext` if necessary. - """ - if PY3K: - return bytes.fromhex(hextext) - else: - hextext = "".join(hextext.split()) - return hextext.decode('hex') - -def dump(binary, size=2, sep=' '): - ''' - Convert binary data (bytes in Python 3 and str in - Python 2) to hex string like '00 DE AD BE EF'. - `size` argument specifies length of text chunks - and `sep` sets chunk separator. - ''' - hexstr = binascii.hexlify(binary) - if PY3K: - hexstr = hexstr.decode('ascii') - return sep.join(chunks(hexstr.upper(), size)) - -def dumpgen(data): - ''' - Generator that produces strings: - - '00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................' - ''' - generator = genchunks(data, 16) - for addr, d in enumerate(generator): - # 00000000: - line = '%08X: ' % (addr*16) - # 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - dumpstr = dump(d) - line += dumpstr[:8*3] - if len(d) > 8: # insert separator if needed - line += ' ' + dumpstr[8*3:] - # ................ - # calculate indentation, which may be different for the last line - pad = 2 - if len(d) < 16: - pad += 3*(16 - len(d)) - if len(d) <= 8: - pad += 1 - line += ' '*pad - - for byte in d: - # printable ASCII range 0x20 to 0x7E - if not PY3K: - byte = ord(byte) - if 0x20 <= byte <= 0x7E: - line += chr(byte) - else: - line += '.' - yield line - -def hexdump(data, result='print'): - ''' - Transform binary data to the hex dump text format: - - 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ - - [x] data argument as a binary string - [x] data argument as a file like object - - Returns result depending on the `result` argument: - 'print' - prints line by line - 'return' - returns single string - 'generator' - returns generator that produces lines - ''' - if PY3K and type(data) == str: - raise TypeError('Abstract unicode data (expected bytes sequence)') - - gen = dumpgen(data) - if result == 'generator': - return gen - elif result == 'return': - return '\n'.join(gen) - elif result == 'print': - for line in gen: - print(line) - else: - raise ValueError('Unknown value of `result` argument') - -def restore(dump): - ''' - Restore binary data from a hex dump. - [x] dump argument as a string - [ ] dump argument as a line iterator - - Supported formats: - [x] hexdump.hexdump - [x] Scapy - [x] Far Manager - ''' - minhexwidth = 2*16 # minimal width of the hex part - 00000... style - bytehexwidth = 3*16-1 # min width for a bytewise dump - 00 00 ... style - - result = bytes() if PY3K else '' - if type(dump) != str: - raise TypeError('Invalid data for restore') - - text = dump.strip() # ignore surrounding empty lines - for line in text.split('\n'): - # strip address part - addrend = line.find(':') - if 0 < addrend < minhexwidth: # : is not in ascii part - line = line[addrend+1:] - line = line.lstrip() - # check dump type - if line[2] == ' ': # 00 00 00 ... type of dump - # check separator - sepstart = (2+1)*7+2 # ('00'+' ')*7+'00' - sep = line[sepstart:sepstart+3] - if sep[:2] == ' ' and sep[2:] != ' ': # ...00 00 00 00... - hexdata = line[:bytehexwidth+1] - elif sep[2:] == ' ': # ...00 00 | 00 00... - Far Manager - hexdata = line[:sepstart] + line[sepstart+3:bytehexwidth+2] - else: # ...00 00 00 00... - Scapy, no separator - hexdata = line[:bytehexwidth] - line = hexdata - result += dehex(line) - return result - - -def runtest(logfile=None): - '''Run hexdump tests. Requires hexfile.bin to be in the same - directory as hexdump.py itself''' - - class TeeOutput(object): - def __init__(self, stream1, stream2): - self.outputs = [stream1, stream2] - - # -- methods from sys.stdout / sys.stderr - def write(self, data): - for stream in self.outputs: - if PY3K: - if 'b' in stream.mode: - data = data.encode('utf-8') - stream.write(data) - stream.flush() - - def tell(self): - raise IOError - - def flush(self): - for stream in self.outputs: - stream.flush() - # --/ sys.stdout - - if logfile: - openlog = open(logfile, 'wb') - # copy stdout and stderr streams to log file - savedstd = sys.stderr, sys.stdout - sys.stderr = TeeOutput(sys.stderr, openlog) - sys.stdout = TeeOutput(sys.stdout, openlog) - - - def echo(msg, linefeed=True): - sys.stdout.write(msg) - if linefeed: - sys.stdout.write('\n') - - expected = '''\ -00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... -00000010: 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........\ -''' - - # get path to hexfile.bin - # this doesn't work from .zip - # import os.path as osp - # hexfile = osp.dirname(osp.abspath(__file__)) + '/hexfile.bin' - # this doesn't work either - # hexfile = osp.dirname(sys.modules[__name__].__file__) + '/hexfile.bin' - # this works - import pkgutil - bin = pkgutil.get_data('hexdump', 'data/hexfile.bin') - - # varios length of input data - hexdump(b'zzzz'*12) - hexdump(b'o'*17) - hexdump(b'p'*24) - hexdump(b'q'*26) - # allowable character set filter - hexdump(b'line\nfeed\r\ntest') - hexdump(b'\x00\x00\x00\x5B\x68\x65\x78\x64\x75\x6D\x70\x5D\x00\x00\x00\x00' - b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\x0A\xBB\xCC\xDD\xEE\xFF') - print('---') - # dumping file-like binary object to screen (default behavior) - hexdump(bin) - print('return output') - hexout = hexdump(bin, result='return') - assert hexout == expected, 'returned hex didn\'t match' - print('return generator') - hexgen = hexdump(bin, result='generator') - assert next(hexgen) == expected.split('\n')[0], 'hex generator 1 didn\'t match' - assert next(hexgen) == expected.split('\n')[1], 'hex generator 2 didn\'t match' - - # binary restore test - bindata = restore( -''' -00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... -00000010: 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........ -''') - echo('restore check ', linefeed=False) - assert bin == bindata, 'restore check failed' - echo('passed') - - far = \ -''' -000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00 [hexdump] -000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 0A BB CC DD EE FF ?"3DUfwˆ™ª»ÌÝîÿ -''' - echo('restore far format ', linefeed=False) - assert bin == restore(far), 'far format check failed' - echo('passed') - - scapy = '''\ -00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... -00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........ -''' - echo('restore scapy format ', linefeed=False) - assert bin == restore(scapy), 'scapy format check failed' - echo('passed') - - if not PY3K: - assert restore('5B68657864756D705D') == '[hexdump]', 'no space check failed' - assert dump('\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e' - else: - assert restore('5B68657864756D705D') == b'[hexdump]', 'no space check failed' - assert dump(b'\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e' - - print('---[test file hexdumping]---') - - import os - import tempfile - hexfile = tempfile.NamedTemporaryFile(delete=False) - try: - hexfile.write(bin) - hexfile.close() - hexdump(open(hexfile.name, 'rb')) - finally: - os.remove(hexfile.name) - if logfile: - sys.stderr, sys.stdout = savedstd - openlog.close() - - -def main(): - from optparse import OptionParser - parser = OptionParser(usage=''' - %prog [binfile|-] - %prog -r hexfile - %prog --test [logfile]''', version=__version__) - parser.add_option('-r', '--restore', action='store_true', - help='restore binary from hex dump') - parser.add_option('--test', action='store_true', help='run hexdump sanity checks') - - options, args = parser.parse_args() - - if options.test: - if args: - runtest(logfile=args[0]) - else: - runtest() - elif not args or len(args) > 1: - parser.print_help() - sys.exit(-1) - else: - ## dump file - if not options.restore: - # [x] memory effective dump - if args[0] == '-': - if not PY3K: - hexdump(sys.stdin) - else: - hexdump(sys.stdin.buffer) - else: - hexdump(open(args[0], 'rb')) - - ## restore file - else: - # prepare input stream - if args[0] == '-': - instream = sys.stdin - else: - if PY3K: - instream = open(args[0]) - else: - instream = open(args[0], 'rb') - - # output stream - # [ ] memory efficient restore - if PY3K: - sys.stdout.buffer.write(restore(instream.read())) - else: - # Windows - binary mode for sys.stdout to prevent data corruption - normalize_py() - sys.stdout.write(restore(instream.read())) - -if __name__ == '__main__': - main() - -# [x] file restore from command line utility -# [ ] write dump with LF on Windows for consistency -# [ ] encoding param for hexdump()ing Python 3 str if anybody requests that - -# [ ] document chunking API -# [ ] document hexdump API -# [ ] blog about sys.stdout text mode problem on Windows diff --git a/run_tests b/run_tests index 441acb0..201ec1b 100755 --- a/run_tests +++ b/run_tests @@ -21,7 +21,7 @@ out_fd=1 while [ -n "$1" ]; do case "$1" in - "--basedir") + "--basedir"|"--base-dir"|"--test-base") shift SetBaseDir $1 shift @@ -36,7 +36,7 @@ while [ -n "$1" ]; do INSTALL=$1 shift ;; - "--update") + "--update"|"--upgrade") UPDATE=1 shift ;; diff --git a/support/custom_ui_vimrc b/support/custom_ui_vimrc index a8812cb..e76c6ee 100644 --- a/support/custom_ui_vimrc +++ b/support/custom_ui_vimrc @@ -54,7 +54,7 @@ function s:SetUpTerminal() let padding = 4 let cols = max( [ min( [ &columns - left_bar - code - padding, 80 ] ), 10 ] ) call win_gotoid( terminal_win ) - execute cols . 'wincmd |' + execute string(cols) . 'wincmd |' endfunction function! s:CustomiseWinBar() diff --git a/support/gadget_upgrade/README.md b/support/gadget_upgrade/README.md new file mode 100644 index 0000000..9ae3d7f --- /dev/null +++ b/support/gadget_upgrade/README.md @@ -0,0 +1,8 @@ +# Manually updating shipped gadgets + +Download the gadget files manuall from their official source into this dir. +Run `./checksum.py ` to get the checksums. + +Update ../../python3/vimspector/gadgets.py with the new version and the +checksums. + diff --git a/support/gadget_upgrade/checksum.py b/support/gadget_upgrade/checksum.py new file mode 100755 index 0000000..d0c1404 --- /dev/null +++ b/support/gadget_upgrade/checksum.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import hashlib +import sys + + +def GetChecksumSHA254( file_path ): + with open( file_path, 'rb' ) as existing_file: + return hashlib.sha256( existing_file.read() ).hexdigest() + + +for arg in sys.argv[ 1: ]: + print( f"{ arg } = { GetChecksumSHA254( arg ) }" ) diff --git a/support/test/bash/.vimspector.json b/support/test/bash/.vimspector.json new file mode 100644 index 0000000..a1be1b9 --- /dev/null +++ b/support/test/bash/.vimspector.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://puremourning.github.io/vimspector/schema/vimspector.schema.json", + "configurations": { + "Run Current Script": { + "adapter": "vscode-bash", + "autoselect": false, + "configuration": { + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "args": [ "*${args}" ] + } + } + } +} diff --git a/support/test/go/name-starts-with-vowel/.vimspector.json b/support/test/go/name-starts-with-vowel/.vimspector.json new file mode 100644 index 0000000..ffcfc93 --- /dev/null +++ b/support/test/go/name-starts-with-vowel/.vimspector.json @@ -0,0 +1,29 @@ +{ + "configurations": { + "run-cmd": { + "adapter": "vscode-go", + "configuration": { + "request": "launch", + "program": "${workspaceRoot}/cmd/namestartswithvowel/main.go", + "mode": "debug", + "dlvToolPath": "$HOME/go/bin/dlv", + "dlvLoadConfig": { + "maxArrayValues": 1000, + "maxStringLen": 1000 + } + } + }, + "test-current-file": { + "adapter": "vscode-go", + "configuration": { + "request": "launch", + "mode": "test", + "program": "${fileDirname}", + "cwd": "${fileDirname}", + "dlvToolPath": "$GOPATH/bin/dlv", + "env": {}, + "args": [] + } + } + } +} diff --git a/support/test/go/name-starts-with-vowel/README.md b/support/test/go/name-starts-with-vowel/README.md new file mode 100644 index 0000000..fec967e --- /dev/null +++ b/support/test/go/name-starts-with-vowel/README.md @@ -0,0 +1,33 @@ +# Purpose + +This example comes with two example vimspector configs for the Go programming language. + +1) `run-cmd` will launch the main programme under `cmd/namestartswithvowel`. +1) `test-current-file` will run the tests in the current file in debug mode. + +## Example use-cases + +### run-cmd + +* Open `cmd/namestartswithvowel/main.go` +* Add a breakpoint somewhere within the programme +* Start the debugger (`:call vimspector#Continue()` or your relevant keymapping) +* Select the first launch configuration (`1: run-cmd`) + +### test-current-file + +* Open `internal/vowels/vowels_test.go` +* Add a breakpoint somewhere within the test +* Start the debugger (`:call vimspector#Continue()` or your relevant keymapping) +* Select the second launch configuration (`2: test-current-file`) + +## Additional Configuration + +There are two additional configuration options specified under `run-cmd`; these parameters configure the maximum string/array size to be shown while debugging. + +``` +"dlvLoadConfig": { + "maxArrayValues": 1000, + "maxStringLen": 1000 +} +``` diff --git a/support/test/go/name-starts-with-vowel/cmd/namestartswithvowel/main.go b/support/test/go/name-starts-with-vowel/cmd/namestartswithvowel/main.go new file mode 100644 index 0000000..c160aea --- /dev/null +++ b/support/test/go/name-starts-with-vowel/cmd/namestartswithvowel/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + + "example.com/internal/vowels" +) + +func main() { + names := []string{"Simon", "Bob", "Jennifer", "Amy", "Duke", "Elizabeth"} + + for _, n := range names { + if vowels.NameStartsWithVowel(n) { + fmt.Printf("%s starts with a vowel!\n", n) + continue + } + + fmt.Printf("%s does not start with a vowel!\n", n) + } +} diff --git a/support/test/go/name-starts-with-vowel/go.mod b/support/test/go/name-starts-with-vowel/go.mod new file mode 100644 index 0000000..3070734 --- /dev/null +++ b/support/test/go/name-starts-with-vowel/go.mod @@ -0,0 +1,3 @@ +module example.com + +go 1.16 diff --git a/support/test/go/name-starts-with-vowel/internal/vowels/vowels.go b/support/test/go/name-starts-with-vowel/internal/vowels/vowels.go new file mode 100644 index 0000000..4e76480 --- /dev/null +++ b/support/test/go/name-starts-with-vowel/internal/vowels/vowels.go @@ -0,0 +1,9 @@ +package vowels + +import "strings" + +func NameStartsWithVowel(name string) bool { + s := strings.Split(strings.ToLower(name), "") + + return s[0] == "a" || s[0] == "e" || s[0] == "i" || s[0] == "o" || s[0] == "u" +} diff --git a/support/test/go/name-starts-with-vowel/internal/vowels/vowels_test.go b/support/test/go/name-starts-with-vowel/internal/vowels/vowels_test.go new file mode 100644 index 0000000..e0d5773 --- /dev/null +++ b/support/test/go/name-starts-with-vowel/internal/vowels/vowels_test.go @@ -0,0 +1,30 @@ +package vowels + +import ( + "fmt" + "testing" +) + +func TestNameStartsWithVowel(t *testing.T) { + testCases := []struct { + input string + expectedOutput bool + }{ + { + input: "Simon", + expectedOutput: false, + }, + { + input: "Andy", + expectedOutput: true, + }, + } + for _, tt := range testCases { + t.Run(fmt.Sprintf("%s should product %t", tt.input, tt.expectedOutput), func(t *testing.T) { + out := NameStartsWithVowel(tt.input) + if out != tt.expectedOutput { + t.Errorf("%s produced %t, when %t was expected", tt.input, out, tt.expectedOutput) + } + }) + } +} diff --git a/tests/testdata/cpp/simple/.vimspector.json b/tests/testdata/cpp/simple/.vimspector.json index 0dca061..48ce801 100644 --- a/tests/testdata/cpp/simple/.vimspector.json +++ b/tests/testdata/cpp/simple/.vimspector.json @@ -12,7 +12,7 @@ "externalConsole": false, "stopAtEntry": true, "stopOnEntry": true, - "MImode": "${VIMSPECTOR_MIMODE}" + "MIMode": "${VIMSPECTOR_MIMODE}" }, "breakpoints": { "exception": { @@ -33,7 +33,7 @@ "externalConsole": false, "stopAtEntry": false, "stopOnEntry": false, - "MImode": "${VIMSPECTOR_MIMODE}" + "MIMode": "${VIMSPECTOR_MIMODE}" }, "breakpoints": { "exception": { @@ -55,7 +55,7 @@ "externalConsole": false, "stopAtEntry": false, "stopOnEntry": false, - "MImode": "${VIMSPECTOR_MIMODE}" + "MIMode": "${VIMSPECTOR_MIMODE}" }, "breakpoints": { "exception": { @@ -82,7 +82,7 @@ "configuration": { "request": "launch", "program": "${workspaceRoot}/${fileBasenameNoExtension}", - "MImode": "${VIMSPECTOR_MIMODE}", + "MIMode": "${VIMSPECTOR_MIMODE}", "externalConsole": false, "args": [ "CALCULATED_LIST", "${CALCULATED_LIST}", diff --git a/tests/variables.test.vim b/tests/variables.test.vim index 59ca2c0..c00fb7f 100644 --- a/tests/variables.test.vim +++ b/tests/variables.test.vim @@ -194,6 +194,7 @@ function! Test_ExpandVariables() \ [ \ '- Scope: Locals', \ ' *+ t (Test): {...}', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -219,6 +220,7 @@ function! Test_ExpandVariables() \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -237,6 +239,7 @@ function! Test_ExpandVariables() \ ' - c (char): 0 ''\\0\{1,3}''', \ ' - fffff (float): 0', \ ' + another_test (AnotherTest):\( {...}\)\?', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -253,6 +256,7 @@ function! Test_ExpandVariables() \ [ \ '- Scope: Locals', \ ' + t (Test): {...}', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -267,6 +271,7 @@ function! Test_ExpandVariables() \ [ \ '- Scope: Locals', \ ' + t (Test): {...}', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -286,6 +291,7 @@ function! Test_ExpandVariables() \ ' \*- c (char): 99 ''c''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -302,6 +308,7 @@ function! Test_ExpandVariables() \ assert_equal( \ [ \ '+ Scope: Locals', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -316,6 +323,7 @@ function! Test_ExpandVariables() \ assert_equal( \ [ \ '+ Scope: Locals', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -331,6 +339,7 @@ function! Test_ExpandVariables() \ assert_equal( \ [ \ '+ Scope: Locals', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -846,6 +855,7 @@ function! Test_SetVariableValue_Local() \ [ \ '- Scope: Locals', \ ' *+ t (Test): {...}', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -871,6 +881,7 @@ function! Test_SetVariableValue_Local() \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -897,6 +908,7 @@ EOF \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -916,6 +928,7 @@ EOF \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -935,6 +948,7 @@ EOF \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1,