Remove build system, add search for remedy file, remove all in one

Cleaning up the plugin a bit, seems like going the build system
route was not worth it. Minimal gains, a lot of additional mental overhead.
As much as I love sublime the plugin api is not great for more complicated
things.

Instead by default plugin seeks for a nearby remedy file, hopefully this
will help a bit.
This commit is contained in:
Krzosa Karol
2024-01-25 11:39:50 +01:00
parent f0dabdd978
commit 3acca1409a
6 changed files with 79 additions and 313 deletions

View File

@@ -4,7 +4,7 @@
"command": "remedy_launch",
},
{
"caption": "RemedyBG: Goto the sublime cursor in remedy",
"caption": "RemedyBG: Goto to cursor in remedy",
"command": "remedy_go_to_cursor",
},
{
@@ -35,8 +35,4 @@
"caption": "RemedyBG: Add selection or word under cursor to watch window",
"command": "remedy_add_to_watch",
},
{
"caption": "RemedyBG: All in one, this is for the convienient mouse + keyboard usage",
"command": "remedy_all_in_one",
},
]

View File

@@ -3,5 +3,6 @@
{ "keys": ["f5"], "command": "remedy_start_debugging" },
{ "keys": ["shift+f5"], "command": "remedy_stop_debugging" },
{ "keys": ["f9"], "command": "remedy_set_breakpoint" },
{ "keys": ["ctrl+f9"], "command": "remedy_set_conditional_breakpoint" },
{ "keys": ["ctrl+shift+f5"], "command": "remedy_restart_debugging" },
]

View File

@@ -1,11 +1,7 @@
### RemedyBG debugger integration with Sublime Text
I frequently find that nothing beats the Visual Studio workflow.
You just click Ctrl+F10 and you are inspecting the code you have just written.
This package seeks to recreate that experience in Sublime Text but without
the buggy and slow Visual Studio, thanks to a much better debugger that is RemedyBG
and it's user API!
I frequently find that nothing at the present moment beats the Visual Studio workflow. You just click Ctrl+F10 and you are inspecting the code you have just written. This package seeks to recreate that experience in Sublime Text but without the buggy and slow Visual Studio, thanks to a much better debugger that is RemedyBG and it's user API!
You can buy the debugger here:
@@ -24,57 +20,25 @@ git clone https://github.com/krzosa/Sublime_RemedyBG
Optional:
- If remedybg is not on your path or has different name, change remedy_executable in your personal sublime settings, look at ```Remedy.sublime-settings``` for syntax reference.
- If remedybg is not on your path or has a different name, change remedy_executable in your personal sublime settings, look at ```Remedy.sublime-settings``` for syntax reference.
- Setup visual studio developer's prompt or ```vcvarsall.bat```, look at vcvarsall section in readme.
### Usage
By default plugin binds to standard debugger hotkeys. You can edit them in your personal sublime keybindings, look at ```Default.sublime-keymap``` for syntax reference.
- Ctrl + F10: Run to cursor
- F5: Start debugging
- Shift + F5: Stop debugging
- F9: Set breakpoint
- Ctrl + Shift + F5 - Restart debugging
- `Ctrl + F10`: Run to cursor
- `F5`: Start debugging
- `Shift + F5`: Stop debugging
- `F9`: Add breakpoint
- `Ctrl + F9`: Add conditional breakpoint
- `Ctrl + Shift + F5`: Restart debugging
### Bonus: Build before debugging
Sadly Sublime doesn't allow for querying of currently chosen build system.
Neither does it allow for effective hook into the builtin ```build``` command
with custom arguments as such this package needs to emulate the ```build``` command.
To make it so that you can build before debugging you need to: firstly change
```Remedy.sublime-settings```, secondly you need to have
a project / project file. That project file needs a build system, if there
is only one build system, everything is going to work. If there are more,
you will need to add a field called ```remedy_build_system```, here is an example:
```
{
"folders":
[
{
"path": "."
}
],
"remedy_target": "this_is_optional/main.exe",
"remedy_build_system": "first",
"build_systems":
[
{
"name": "first",
"shell_cmd": "build.bat"
}
]
}
```
### Bonus: Setting up Microsoft compiler enviroment with ```vcvarsall.bat```
If you are developing using remedybg it seems pretty obvious that you would want access to the Microsoft compiler so additionally the package is shipping with the ```setup_vsvars.py```.
If you are developing using remedybg it seems pretty obvious that you would want access to the Microsoft compiler so the package is shipping with ```setup_vsvars.py```.
1. Copy content of ```setup_vcvarsall``` to your ```User``` folder for normal build commands or to ```Sublime_RemedyBG``` dir for build before debugging.
2. You need to update the path to your vcvarsall inside your global sublime settings/preferences, use ```Remedy.sublime-settings``` for reference.
If you want vcvars for both remedy_build and normal sublime build, you will need to have 2 copies, one in remedy folder and the other in user folder. You need 2 copies because it seems that sublime heavily sandboxes packages from eachother so this package cannot influence the global enviroment. If anyone has any ideas how to make it global I would be happy to hear them.
1. Copy content of ```setup_vcvarsall``` to your ```User``` folder.
2. You need to provide path to your vcvarsall inside your global sublime settings/preferences, use ```Remedy.sublime-settings``` for reference.
### Credits

View File

@@ -1,14 +1,6 @@
{
// NOTE to build_before_debugging
// You need a project and a build system inside that project if you want build_before_debugging,
// Sublime API doesnt allow for querying of the selected build system, I need to essentially emulate that process.
// Look here to figure out the project format: https://www.sublimetext.com/docs/projects.html
// Additionally you need a field called "remedy_build_system" to signal which
// build system was chosen if you have more then 1 build system.
"build_before_debugging": true,
"stop_debugging_on_build_command": true,
"executable": "remedybg.exe",
"search_for_remedy_file_in_current_dir": true,
"vc_vars_cmd": "C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Auxiliary/Build/vcvarsall.bat",
"vc_vars_arch": "amd64",
"output_debug_strings_to_console": true,
}

315
remedy.py
View File

@@ -1,26 +1,39 @@
"""
CREDITS
* septag - plugin is based on his 10x plugin https://github.com/slynch8/10x/blob/main/PythonScripts/RemedyBG/RemedyBG.py
"""
import subprocess
import os, io, ctypes
import sublime
import sublime_plugin
from Default.exec import ExecCommand
import subprocess, os, io, ctypes
import sublime, sublime_plugin
import win32pipe, win32file, pywintypes, win32api
from .remedy_api import *
def get_remedy_variable(name, default):
settings = sublime.load_settings("Preferences.sublime-settings")
result = settings.get(name)
if result != None:
return result
settings = sublime.load_settings("Remedy.sublime-settings")
result = settings.get(name)
if result != None:
return result
return default
def get_current_dir(window):
vars = window.extract_variables()
result = vars.get("project_path")
if result == None:
result = vars.get("folder")
if result == None:
result = vars.get("file_path")
if result == None:
sublime.message_dialog("RemedyBG: Trying to launch but cant figure out starting directory, open a file or project")
return None
return result
class RemedyInstance:
def __init__(self):
self.cmd_pipe = None
self.event_pipe = None
self.process = None
self.servername = ""
self.breakpoints = {}
def begin_command(self, cmd):
cmd_buffer = io.BytesIO()
@@ -46,6 +59,13 @@ class RemedyInstance:
sublime.message_dialog('RemedyBG: ' + str(cmd) + ' failed')
return out_buffer, result_code
def set_bring_to_foreground_on_suspended(self, enabled = True):
buff = self.begin_command(COMMAND_SET_BRING_TO_FOREGROUND_ON_SUSPENDED)
buff.write(ctypes.c_uint8(enabled))
buff, result_code = self.end_command(buff)
if result_code != 1:
print("set_bring_to_foreground_on_suspended")
def add_breakpoint_at_filename_line(self, view, filename, line, region, expr = None):
buff = self.begin_command(COMMAND_ADD_BREAKPOINT_AT_FILENAME_LINE)
buff.write(ctypes.c_uint16(len(filename)))
@@ -58,29 +78,6 @@ class RemedyInstance:
buff.write(ctypes.c_uint16(0))
buff, result_code = self.end_command(buff)
if result_code == 1:
bp_id = int.from_bytes(buff.read(4), 'little')
key = filename + ":" + str(line)
self.breakpoints[key] = {"id": bp_id, "view": view}
view.add_regions(key, [region], scope="region.redish", icon="circle")
def delete_breakpoint(self, filename, line):
key = filename + ":" + str(line)
id = self.breakpoints.get(key)
if id:
id = id
buff = self.begin_command(COMMAND_DELETE_BREAKPOINT)
buff.write(ctypes.c_uint32(id["id"]))
buff, result_code = self.end_command(buff)
self.breakpoints.pop(key)
id["view"].erase_regions(key)
def toggle_breakpoint(self, view, filename, line, region):
key = filename + ":" + str(line)
if key in self.breakpoints.keys():
self.delete_breakpoint(filename, line)
else:
self.add_breakpoint_at_filename_line(view, filename, line, region)
def run_to_file_at_line(self, filename, line):
buff = self.begin_command(COMMAND_RUN_TO_FILE_AT_LINE)
@@ -114,40 +111,12 @@ class RemedyInstance:
return int.from_bytes(buff.read(4), 'little')
return 0
def get_breakpoint_locations(self, bp_id):
if self.cmd_pipe is None:
return 0
cmd_buffer = io.BytesIO()
cmd_buffer.write(ctypes.c_uint16(COMMAND_GET_BREAKPOINT_LOCATIONS))
cmd_buffer.write(ctypes.c_uint32(bp_id))
try:
out_data = win32pipe.TransactNamedPipe(self.cmd_pipe, cmd_buffer.getvalue(), 8192, None)
except pywintypes.error as pipe_error:
print('RemedyBG', pipe_error)
self.close()
return ('', 0)
out_buffer = io.BytesIO(out_data[1])
result_code = int.from_bytes(out_buffer.read(2), 'little')
if result_code == 1:
num_locs = int.from_bytes(out_buffer.read(2), 'little')
# TODO: do we have several locations for a single breakpoint ?
if num_locs > 0:
address = int.from_bytes(out_buffer.read(8), 'little')
module_name = out_buffer.read(int.from_bytes(out_buffer.read(2), 'little')).decode('utf-8')
filename = out_buffer.read(int.from_bytes(out_buffer.read(2), 'little')).decode('utf-8')
line_num = int.from_bytes(out_buffer.read(4), 'little')
return (filename, line_num)
else:
return ('', 0)
else:
return ('', 0)
def send_command(self, cmd):
buff = self.begin_command(cmd)
if cmd == COMMAND_START_DEBUGGING:
buff.write(ctypes.c_uint8(0))
buff, result_code = self.end_command(buff)
return buff, result_code
def stop_debugging(self):
if self.get_target_state() != TARGETSTATE_NONE:
@@ -170,10 +139,6 @@ class RemedyInstance:
self.process.kill()
self.process = None
for k,v in self.breakpoints.items():
v["view"].erase_region(k)
self.breakpoints = {}
print("RemedyBG: Connection closed")
def try_launching(self):
@@ -191,41 +156,34 @@ class RemedyInstance:
def figure_out_target_and_launch(self):
self.window = sublime.active_window()
current_dir = get_current_dir(self.window)
remedy_target = None
if remedy_target == None:
project = self.window.project_data()
if project and project.get("remedy_target"):
remedy_target = project.get("remedy_target")
remedy_target = sublime.expand_variables(remedy_target, self.window.extract_variables())
search_for_remedy_file = get_remedy_variable("search_for_remedy_file_in_current_dir", True)
if search_for_remedy_file:
for it in os.listdir(current_dir):
if it.endswith("rdbg"):
remedy_target = current_dir + "/" + it
if remedy_target:
self.launch(remedy_target)
return
if remedy_target == None:
vars = self.window.extract_variables()
self.current_dir = vars.get("project_path")
if self.current_dir == None:
self.current_dir = vars.get("folder")
if self.current_dir == None:
self.current_dir = vars.get("file_path")
if self.current_dir == None:
sublime.message_dialog("RemedyBG: Trying to launch but cant figure out starting directory, open a file or project")
return
if current_dir == None: return
self.current_dir = current_dir
self.filelist = os.listdir(self.current_dir)
def walk_the_user_to_executable(item_index):
if item_index == -1:
return
item = self.filelist[item_index]
item_path = self.current_dir + "/" + item
if os.path.isdir(item_path):
self.current_dir += "/" + item
self.filelist = os.listdir(self.current_dir)
self.window.show_quick_panel(self.filelist, walk_the_user_to_executable)
elif os.path.isfile(item_path):
self.launch(item_path)
self.window.show_quick_panel(self.filelist, walk_the_user_to_executable)
self.filelist = os.listdir(self.current_dir)
def walk_the_user_to_executable(item_index):
if item_index == -1:
return
item = self.filelist[item_index]
item_path = self.current_dir + "/" + item
if os.path.isdir(item_path):
self.current_dir += "/" + item
self.filelist = os.listdir(self.current_dir)
self.window.show_quick_panel(self.filelist, walk_the_user_to_executable)
elif os.path.isfile(item_path):
self.launch(item_path)
self.window.show_quick_panel(self.filelist, walk_the_user_to_executable)
def launch(self, target):
try:
@@ -305,38 +263,7 @@ class RemedyInstance:
def process_event(self, event_buffer, event_type):
if event_type == EVENTTYPE_EXIT_PROCESS:
exit_code = int.from_bytes(event_buffer.read(4), 'little')
print('RemedyBG: Debugging terminated with exit code:', exit_code)
elif event_type == EVENTTYPE_OUTPUT_DEBUG_STRING and self.settings.get("output_debug_strings_to_console", False):
text = event_buffer.read(int.from_bytes(event_buffer.read(2), 'little')).decode('utf-8')
print(text.strip())
i = 0
while i < 3000:
i += 1
event_buffer, event_type = self.get_event()
if event_type == EVENTTYPE_OUTPUT_DEBUG_STRING:
text = event_buffer.read(int.from_bytes(event_buffer.read(2), 'little')).decode('utf-8')
print(text.strip())
else:
self.process_event(event_buffer, event_type)
break
elif event_type == EVENTTYPE_BREAKPOINT_ADDED: # @todo: The problem here is that we need to figure out a view to which the marker is going to be bound
pass
# bp_id = int.from_bytes(event_buffer.read(4), 'little')
# filename, line = self.get_breakpoint_locations(bp_id)
# if filename != "":
# key = filename + ":" + str(line) # @copy_paste
# self.breakpoints[key] = bp_id
# view.add_regions(key, [region], scope="region.redish", icon="circle")
elif event_type == EVENTTYPE_BREAKPOINT_REMOVED:
bp_id = int.from_bytes(event_buffer.read(4), 'little')
key = None
for k,v in self.breakpoints.items():
if v["id"] == bp_id:
key = k
if key:
v = self.breakpoints[key]
v["view"].erase_regions(key)
self.breakpoints.pop(key)
#print('RemedyBG: Debugging terminated with exit code:', exit_code)
def get_event(self):
try:
@@ -370,115 +297,11 @@ class RemedyInstance:
def breakpoint_on_cursor(self, view):
filename, line, cursor = RemedyInstance.filename_and_line()
self.toggle_breakpoint(view, filename, line, sublime.Region(cursor))
self.add_breakpoint_at_filename_line(view, filename, line, sublime.Region(cursor))
remedy_instance = RemedyInstance()
def get_remedy_variable(var, default):
settings = sublime.load_settings("Remedy.sublime-settings")
result = settings.get(var)
if result == None: result = default
return result
def get_build_system(window):
project = window.project_data()
build = None
if project:
bs = project.get("build_systems")
rbs = project.get("remedy_build_system")
if bs:
if len(bs) == 1:
build = bs[0]
elif rbs:
for i in bs:
if rbs == i["name"]:
build = i
break
# if build == None:
# settings = sublime.load_settings("Preferences.sublime-settings")
# bs = settings.get("remedy_chosen_build_system")
# if bs:
# sublime.
return project, build
def should_build_before_debugging(window):
build_before = get_remedy_variable("build_before_debugging", False)
if build_before:
project, build = get_build_system(window)
if project == None or build == None:
build_before = False
return build_before
class RemedyBuildCommand(ExecCommand):
def run(self, **kwargs):
self.command = kwargs.get("command")
if self.command == None:
sublime.message_dialog("RemedyBG: remedy_build expects a command, one of [run_to_cursor, start_debugging, goto_cursor]\n\nexample :: \"args\":{\"command\": \"run_to_cursor\"}")
project, build = get_build_system(self.window)
if build == None:
sublime.error_message("""
RemedyBG: You need a project and a build system inside that project to call this function,
Sublime API doesnt allow for querying the selected build system.
Look here to figure out the project format: https://www.sublimetext.com/docs/projects.html
Additionally you need a field called "remedy_build_system" to signal which
build system was chosen
""")
return
if remedy_instance.try_launching():
return
self.window.run_command("save_all")
kwargs = {
"cmd": build.get("cmd", None),
"shell_cmd": build.get("shell_cmd", None),
"file_regex": build.get("file_regex", ""),
"line_regex": build.get("line_regex", ""),
"working_dir": build.get("working_dir", ""),
"encoding": build.get("encoding", "utf-8"),
"env": build.get("env", {}),
"quiet": build.get("quiet", False),
"kill": build.get("kill", False),
"kill_previous": build.get("kill_previous", False),
"update_annotations_only": build.get("update_annotations_only", False),
"word_wrap": build.get("word_wrap", True),
"syntax": build.get("syntax", "Packages/Text/Plain text.tmLanguage"),
}
variables = self.window.extract_variables()
for key in ["cmd", "shell_cmd", "file_regex", "line_regex", "working_dir"]:
if kwargs.get(key) != None:
kwargs[key] = sublime.expand_variables(kwargs[key], variables)
for key in os.environ.keys():
if key not in kwargs["env"]:
kwargs["env"][key] = os.environ[key]
super().run(**kwargs)
def on_finished(self, proc):
super().on_finished(proc)
if proc == self.proc and proc.killed == False and proc.exit_code() == 0:
errs = self.output_view.find_all_results()
if len(errs) == 0:
if self.command == "run_to_cursor":
remedy_instance.run_to_cursor()
elif self.command == "start_debugging":
remedy_instance.send_command(COMMAND_START_DEBUGGING)
elif self.command == "goto_cursor":
remedy_instance.goto_cursor()
else: # @warning: While adding here also need to change error message !!!!
sublime.message_dialog("RemedyBG: Unrecognized command =", self.command)
class RemedyLaunchCommand(sublime_plugin.WindowCommand):
def run(self):
remedy_instance.figure_out_target_and_launch()
@@ -489,10 +312,7 @@ class RemedyStartDebuggingCommand(sublime_plugin.WindowCommand):
state = remedy_instance.get_target_state()
if state == TARGETSTATE_NONE:
if should_build_before_debugging(self.window):
self.window.run_command("remedy_build", {"command": "start_debugging"})
else:
remedy_instance.send_command(COMMAND_START_DEBUGGING)
remedy_instance.send_command(COMMAND_START_DEBUGGING)
elif state == TARGETSTATE_SUSPENDED:
remedy_instance.send_command(COMMAND_CONTINUE_EXECUTION)
@@ -504,16 +324,15 @@ class RemedyStopDebuggingCommand(sublime_plugin.WindowCommand):
class RemedyRestartDebuggingCommand(sublime_plugin.WindowCommand):
def run(self):
if remedy_instance.try_launching(): return
remedy_instance.send_command(COMMAND_RESTART_DEBUGGING)
state = remedy_instance.get_target_state()
if state == TARGETSTATE_EXECUTING or state == TARGETSTATE_SUSPENDED:
remedy_instance.send_command(COMMAND_RESTART_DEBUGGING)
class RemedyRunToCursorCommand(sublime_plugin.TextCommand):
def run(self, edit):
if remedy_instance.try_launching(): return
window = sublime.active_window()
if should_build_before_debugging(sublime.active_window()):
window.run_command("remedy_build", {"command": "run_to_cursor"})
else:
remedy_instance.run_to_cursor()
remedy_instance.run_to_cursor()
class RemedyGotoCursorCommand(sublime_plugin.TextCommand):
def run(self, edit):
@@ -554,13 +373,5 @@ class RemedyAddToWatchCommand(sublime_plugin.TextCommand):
remedy_instance.add_watch(self.view.substr(region_cursor))
class RemedyOnBuildCommand(sublime_plugin.EventListener):
def on_window_command(self, window, command_name, args):
if remedy_instance.is_connected() == False:
return
if command_name in ["build", "remedy_build"]:
if get_remedy_variable("stop_debugging_on_build_command", False):
remedy_instance.stop_debugging()
def plugin_unloaded():
remedy_instance.close()

View File

@@ -3,6 +3,7 @@ TARGETSTATE_SUSPENDED = 2
TARGETSTATE_EXECUTING = 3
COMMAND_BRING_DEBUGGER_TO_FOREGROUND = 50
COMMAND_SET_BRING_TO_FOREGROUND_ON_SUSPENDED = 53
COMMAND_SET_WINDOW_POS = 51
COMMAND_GET_WINDOW_POS = 52
COMMAND_COMMAND_EXIT_DEBUGGER = 75
@@ -25,6 +26,7 @@ COMMAND_STEP_OVER_BY_LINE = 309
COMMAND_STEP_OUT = 311,
COMMAND_CONTINUE_EXECUTION = 312
COMMAND_RUN_TO_FILE_AT_LINE = 313
COMMAND_GET_BREAKPOINTS = 600
COMMAND_GET_BREAKPOINT_LOCATIONS = 601
COMMAND_ADD_BREAKPOINT_AT_FILENAME_LINE = 604
COMMAND_UPDATE_BREAKPOINT_LINE = 608