diff --git a/.flake8 b/.flake8 index 1accc5b1..f63765b0 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,3 @@ [flake8] max-line-length = 88 -ignore = E501, E203, W503 # line length, whitespace before ':', line break before binary operator +ignore = E501, E203, W503, E402, E231 # line length, whitespace before ':', line break before binary operator, module level import not at top of file diff --git a/.github/workflows/build_executable.yml b/.github/workflows/build_executable.yml index 36cbd6dc..d57977f8 100644 --- a/.github/workflows/build_executable.yml +++ b/.github/workflows/build_executable.yml @@ -12,13 +12,13 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, macos-latest] python-version: [3.8] include: - os: ubuntu-latest buildname: linux - - os: windows-latest - buildname: windows + # - os: windows-latest + # buildname: windows - os: macos-latest buildname: mac steps: @@ -35,7 +35,7 @@ jobs: run: | nox --non-interactive --session build_executable_${{ matrix.buildname }} - name: Upload ${{ matrix.buildname }} executable - if: github.ref == 'refs/heads/master' + # if: github.ref == 'refs/heads/master' uses: actions/upload-artifact@v1 with: name: gdbgui_${{ matrix.buildname }} diff --git a/.gitignore b/.gitignore index 2ebb68d0..40e33454 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,5 @@ yarn-error.log venv .vscode site -build.js -build.js.map +gdbgui/static/js/* __pycache__ diff --git a/.vulture_whitelist.py b/.vulture_whitelist.py new file mode 100644 index 00000000..c3ed3d2a --- /dev/null +++ b/.vulture_whitelist.py @@ -0,0 +1,3 @@ +_.sessions # unused attribute (noxfile.py:7) +_.reuse_existing_virtualenvs # unused attribute (noxfile.py:6) +_.secret_key # unused attribute (gdbgui/backend.py:104) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a5c5fea..4f7fe8a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,20 @@ # gdbgui release history ## development +**Breaking Changes** +* Removed support for Windows +* Replaced `--gdb` flag with `--gdb-cmd`. The `--gdb-cmd` argument specifies the gdb executable as well as all arguments you wish to pass to gdb at startup, for example `--gdb-cmd "gdb -nx"`. The existing `-g` argument is an alias for `--gdb-cmd`. +* Removed `--rr` flag. Use `--gdb-cmd rr replay` instead. +* Removed deprecated and hidden `--hide-gdbgui-upgrades` argument. It will now raise an error. + +**Additional Changes** +* Replaced single terminal on frontend with three terminals: an interactive xterm terminal running gdb, a gdbgui console for diagnostic messages, and a terminal connected to the inferior application being debugged. +* Updates to the dashboard +* Add ability to specify gdb command from the browser. This can now be accomplished from the dashboard. * Removed gdbgui binaries from source control. They can now be downloaded as artifacts of [releases](https://github.com/cs01/gdbgui/releases). +* [documentation] Fix bug when generating md5 checksum for binary releases +* Remove "shutdown" button in UI + ## 0.13.2.1 * No end user changes. This release builds the gdbgui executables with GitHub actions. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 412d8678..bd56f120 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,9 @@ Thanks for your interest in contributing to gdbgui! If your change is small, go ahead and submit a pull request. If it is substantial, create a GitHub issue to discuss it before making the change. -## Development Instructions +## Dependencies -gdbgui uses [nox](https://github.com/theacodes/nox) to automate various tasks. You will need it installed on your system before continuing. +1.) [nox](https://github.com/theacodes/nox) is used to automate various tasks. You will need it installed on your system before continuing. You can install it with pipx (recommended): ``` @@ -15,72 +15,33 @@ or pip: > pip install --user nox ``` -### Step 1: Compile JavaScript Code +2.) [yarn](https://yarnpkg.com/) is used for managing JavaScript files -gdbgui compiles JavaScript source code into a single .js file. - -Note that `yarn` can be replaced with `npm`: - -First install JavaScript dependencies: -```bash -yarn install +## Developing +Development can be done with one simple step: ``` - -To watch JavaScript files for changes and build non-optimized code for each change, use -``` -yarn start +> nox -s develop ``` +This will install all Python and JavaScript dependencies, and build and watch Python and JavaScript files for changes, automatically reloading as things are changed. -This is useful for iterating on changes. - -To build once for production-optimized code, you can run -``` -yarn build -``` - -### Step 2: Run Server +Make sure you [turn your cache off](https://www.technipages.com/google-chrome-how-to-completely-disable-cache) so that changes made locally are reflected in the page. +## Running and Adding tests ```bash -git clone https://github.com/cs01/gdbgui -cd gdbgui -nox -s develop-3.7 # replace with desired Python version -source .nox/develop-3-7/bin/activate # replace with desired Python version +> nox ``` -You are now in a virtual environment with gdbgui's dependencies installed. When finished, type `deactivate` to leave the virtual environment. +runs all applicable tests and linting. -```bash -python -m gdbgui --debug +Python tests are in `gdbgui/tests`. They are run as part of the above command, but can be run with ``` - -The `--debug` flag: - -1. Automatically reloads the server when it detects changes you've made -1. Adds a new component at the bottom of the right sidebar called "gdb machine interface output" that displays the raw gdb mi output to help you debug. -1. Displays all changes to state data in the browser's developer console, such as `rendered_source_file_fullname null -> /home/chad/git/gdbgui/examples/hello.c` - - -### Step 3: Make your changes - -Open the browser to view gdbgui. Refresh the page as you make changes to JavaScript code. - -!!! Note - - Make sure you have caching turned off in your browser. In Chrome, for example, this is a setting in the developer console. - -### Step 4: Run and Add tests - -To continue, you must have nox installed. - -```bash -nox +> nox -s python_tests ``` -runs gdbgui unit tests. - -If you have changed any Python code, add new tests to `gdbgui/tests/test_app.py` as necessary. - -JavaScript tests are minimal, so you will have to manually excercise any code paths that may be affected. +JavaScript tests are in `gdbgui/src/js/tests`. They are run as part of the above command, but can be run with +``` +> nox -s js_tests +``` ## Documentation diff --git a/MANIFEST.in b/MANIFEST.in index 430c880e..05a0e704 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,10 @@ include README.md include LICENSE graft gdbgui +# these files are built and must be included in distribution +# but shouldn't be included in git repository since they +# are generated +graft gdbgui/static/js prune examples prune downloads @@ -16,6 +20,7 @@ exclude mypy.ini exclude .eslintrc.json exclude .coveragerc exclude .flake8 +exclude .vulture_whitelist.py exclude .prettierrc.js exclude jest.config.js exclude make_executable.py @@ -29,3 +34,5 @@ exclude yarn.lock exclude noxfile.py exclude CHANGELOG.md exclude CONTRIBUTING.md +exclude postcss.config.js +exclude tailwind.config.js diff --git a/README.md b/README.md index b79fdaaf..2cd3add6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

-A modern, browser-based frontend to gdb (gnu debugger) +A browser-based frontend to gdb (gnu debugger)

diff --git a/docs/examples.md b/docs/examples.md index 836deb5c..05695a1e 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -14,33 +14,34 @@ gdbgui set the inferior program, pass argument, set a breakpoint at main ``` -gdbgui "./myprogram myarg -myflag" +gdbgui --args ./myprogram myarg -myflag ``` -alternate way to do the same thing (note the lack of quotes) ``` -gdbgui --args ./myprogram myarg command -myflag +gdbgui "./myprogram myarg -myflag" ``` use gdb binary not on your $PATH ``` -gdbgui -g build/mygdb +gdbgui --gdb-cmd build/mygdb ``` -run on port 8080 instead of the default port +Pass arbitrary arguments directly to gdb when it is launched ``` -gdbgui --port 8080 +gdbgui --gdb-cmd="gdb -x gdbcmds.txt" ``` -Pass arbitrary arguments directly to gdb when it is launched +run on port 8080 instead of the default port ``` -gdbgui --gdb-args="-x gdbcmds.txt" +gdbgui --port 8080 ``` + + run on a server and host on 0.0.0.0. Accessible to the outside world as long as port 80 is not blocked. ``` @@ -63,13 +64,13 @@ gdbgui -r --auth --key private.key --cert host.cert Use Mozilla's [record and replay](https://rr-project.org) (rr) debugging supplement to gdb. rr lets your record a program (usually with a hard-to-reproduce bug in it), then deterministically replay it as many times as you want. You can even step forwards and backwards. ``` -gdbgui --rr +gdbgui --gdb-cmd "rr replay" ``` Use recording other than the most recent one ``` -gdbgui --rr RECORDED_DIRECTORY +gdbgui --gdb-cmd "rr replay RECORDED_DIRECTORY" ``` Don't automatically open the browser when launching diff --git a/docs/gettingstarted.md b/docs/gettingstarted.md index 4a255429..ce4a068e 100644 --- a/docs/gettingstarted.md +++ b/docs/gettingstarted.md @@ -1,9 +1,13 @@ -Now that you have `gdbgui` installed, all you need to do is run +Before running `gdbgui`, you should compile your program with debug symbols and a lower level of optimization, so code isn't optimized out before runtime. To include debug symbols with `gcc` use `-ggdb`, with `rustc` use `-g`. To disable most optimizations in `gcc` use the `-O0` flag, with `rustc` use `-O`. + +For more details, consult your compiler's documentation or a search engine. + +Now that you have `gdbgui` installed and your program compiled with debug symbols, all you need to do is run ``` gdbgui ``` -which will start gdbgui's server and open a new tab in your browser. That tab contains a fully functional frontend running `gdb`! +This will start gdbgui's server and open a new tab in your browser. That tab contains a fully functional frontend running `gdb`! You can see gdbgui in action on [YouTube](https://www.youtube.com/channel/UCUCOSclB97r9nd54NpXMV5A). @@ -32,10 +36,3 @@ The following keyboard shortcuts are available when the focus is not in an input * Up: u or up arrow * Next Instruction: m * Step Instruction: , - - -## Debugging Faults - -If your program exits unexpectedly from something like a SEGFAULT, gdbgui displays a button in the console to re-enter the state the program was in when it exited. This allows you to inspect the stack, the line on which the program exited, memory, variables, registers, etc. - -![](https://raw.githubusercontent.com/cs01/gdbgui/master/screenshots/SIGSEV.png) diff --git a/docs/guides.md b/docs/guides.md index c4d5cc95..41d02fe6 100644 --- a/docs/guides.md +++ b/docs/guides.md @@ -7,8 +7,9 @@ Remember, these guides, like gdbgui, are **open source** and can be edited by yo After downloading gdbgui, you can launch it like so: * `gdbgui` (or whatever the binary name is, i.e. `gdbgui_0.10.0.0`) -* `gdbgui "./mybinary -myarg value -flag1 -flag2"` (note the quotes around the arguments) -* `gdbgui --args "./mybinary -myarg value -flag1 -flag2"` (note the quotes around the arguments) +* `gdbgui --args ./mybinary -myarg value -flag1 -flag2` + +Make sure the program you want to debug was compiled with debug symbols. See the getting started section for more details. A new tab in your browser will open with gdbgui in it. If a browser tab did not open, navigate to the ip/port that gdbgui is being served on (i.e. http://localhost:5000). @@ -42,9 +43,11 @@ cd myprog cargo build ``` -You can start debugging with a simple +You can start debugging with -`gdbgui target/debug/myprog` +``` +gdbgui --args target/debug/myprog +``` There are a couple of small difficulties. @@ -72,11 +75,10 @@ You can load this into gdb with the following command (changed as appropriate): symbol-file /Users/user/git/gdbgui/examples/rust/target/debug/deps/hello-486956f9dde465e5 ``` -2.) The GDB pretty-printing macros that Rust ships with. GDB can't find these by default, -which makes it print the message +2.) The GDB pretty-printing macros that Rust ships with. GDB can't find these by default, which makes it print the message ``` -warning: Missing auto-load script at offset 0 in section .debug_gdb_scripts of file /home/phil/temp/myprog/target/debug/myprog. +warning: Missing auto-load script at offset 0 in section .debug_gdb_scripts of file /home/temp/myprog/target/debug/myprog. Use `info auto-load python-scripts [REGEXP]' to list them. ``` @@ -106,7 +108,7 @@ Then you can launch `gdb` or `gdbgui` and connect to it. In `gdbgui`, use the dr Read more at the [gdbserver homepage](https://sourceware.org/gdb/onlinedocs/gdb/Server.html). -If the machine gdbgui is running on and the target being debugged have different architectures, make sure gdb is built properly. See [] +If the machine gdbgui is running on and the target being debugged have different architectures, make sure gdb is built properly (see `Remote Debugging Between Different Architectures`). ## Remote Debugging Between Different Architectures diff --git a/docs/index.md b/docs/index.md index cf9d0a31..b0c3aec7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,7 +3,7 @@

-A modern, browser-based frontend to gdb (gnu debugger) +A browser-based frontend to gdb (gnu debugger)

diff --git a/docs/installation.md b/docs/installation.md index 72ae967a..45aed249 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -94,6 +94,8 @@ instructions](http://andresabino.com/2015/04/14/codesign-gdb-on-mac-os-x-yosemit ### Windows Dependencies +Note that windows is only supported for gdbgui versions less than 0.14. + - [Python 3](https://www.python.org/downloads/windows/) - gdb, make, gcc diff --git a/docs/screenshots.md b/docs/screenshots.md index fbcd7cd9..4a7b77b2 100644 --- a/docs/screenshots.md +++ b/docs/screenshots.md @@ -2,7 +2,7 @@ ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/gdbgui2.png) Enter the binary and args just as you'd call them on the command line. -Binary is restored when gdbgui is opened at a later time. +The binary is restored when gdbgui is opened at a later time. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/load_binary_and_args.png) @@ -11,7 +11,7 @@ Next, Step, Return, Next Instruction, Step Instruction. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/controls.png) -If using an Intel CPU and running Linux, gdbgui also works in conjunction with [rr](http://rr-project.org/) to let you debug in reverse. +If the environment supports reverse debugging, such as when using an Intel CPU and running Linux and debugging with [rr](http://rr-project.org/), gdbgui allows you to debug in reverse. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/reverse_debugging.png) ## Stack/Threads @@ -22,13 +22,10 @@ pointing and clicking. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/stack_and_threads.png) -## Send Signal to Inferior +## Send Signal to Inferior (debugged) Process Choose from any signal your OS supports to send to the inferior. For example, to mock `CTRL+C` in plain gdb, you can send `SIGINT` to interrupt the inferior process. If the inferior process is hung for some reason, you can send `SIGKILL`, etc. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/send_signal.png) -Signals are also recognized by `gdbgui`, and a button is presented to let you step back into the program and inspect the program's state in case it exits unexpectedly. -![image](https://github.com/cs01/gdbgui/raw/master/screenshots/SIGSEV.png) - ## Source Code View source, assembly, add breakpoints. All symbols used to compile the diff --git a/examples/c/hello.c b/examples/c/hello.c index 984da5a6..c4e0340a 100644 --- a/examples/c/hello.c +++ b/examples/c/hello.c @@ -1,21 +1,23 @@ #include #include - -void say_something(const char* str) +void say_something(const char *str) { printf("%s\n", str); } -struct mystruct_t { +struct mystruct_t +{ int value; char letter; char *string; - struct { + struct + { double dbl; } substruct; /* named sub-struct */ - struct { + struct + { float fp; }; /* anonymous struct */ @@ -27,8 +29,8 @@ struct mystruct_t { }; }; - -int main(int argc, char **argv) { +int main(int argc, char **argv) +{ printf("Hello World\n"); int retval = 1; @@ -46,11 +48,13 @@ int main(int argc, char **argv) { s.unionint = 0; s.uniondouble = 1.0; - for (int i = 0; i < 2; i++) { + for (int i = 0; i < 2; i++) + { printf("i is %d\n", i); } - if (!strcmp(s.string, "pass")) { + if (!strcmp(s.string, "pass")) + { retval = 0; } diff --git a/examples/c/input.c b/examples/c/input.c new file mode 100644 index 00000000..e3bde8e3 --- /dev/null +++ b/examples/c/input.c @@ -0,0 +1,11 @@ +#include +#include + +int main(int argc, char **argv) +{ + char name[20]; + printf("Hello. What's your name?\n"); + fgets(name, 20, stdin); + printf("Hi there, %s", name); + return 0; +} diff --git a/examples/c/makefile b/examples/c/makefile index f3ecd392..34800b93 100644 --- a/examples/c/makefile +++ b/examples/c/makefile @@ -1,17 +1,21 @@ -GDBGUI=../../gdbgui/backend.py +ROOT:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) hello: hello.c gcc hello.c -o hello_c.a -std=c99 -g - $(GDBGUI) hello_c.a + @echo Run with gdbgui: gdbgui --args $(ROOT)/hello_c.a + +input: input.c + gcc input.c -o input.a -std=c99 -g + @echo Run with gdbgui: gdbgui --args $(ROOT)/input.a debug_segfault: debug_segfault.c gcc debug_segfault.c -g -o debug_segfault.a -std=c99 - $(GDBGUI) debug_segfault.a + @echo Run with gdbgui: gdbgui --args $(ROOT)/debug_segfault.a threads: threads.c gcc threads.c -o threads.a -std=c99 -lpthread -g - $(GDBGUI) threads.a + @echo Run with gdbgui: gdbgui --args $(ROOT)/threads.a sleeper: sleeper.c gcc sleeper.c -o sleeper.a -std=c99 -g - $(GDBGUI) sleeper.a + @echo Run with gdbgui: gdbgui --args $(ROOT)/sleeper.a diff --git a/examples/cpp/makefile b/examples/cpp/makefile index 15cee550..c2963fb5 100644 --- a/examples/cpp/makefile +++ b/examples/cpp/makefile @@ -2,16 +2,16 @@ GDBGUI=../../gdbgui/backend.py hello: hello.cpp g++ hello.cpp -o hello_cpp.a -std=c++11 -g - $(GDBGUI) hello_cpp.a + @echo Run with gdbgui: gdbgui --args hello_cpp.a linked_list: linked_list.cpp g++ linked_list.cpp -o linked_list_cpp.a -std=c++11 -g - $(GDBGUI) linked_list_cpp.a + @echo Run with gdbgui: gdbgui --args linked_list_cpp.a smart_ptr_demo: smart_ptr_demo.cpp g++ smart_ptr_demo.cpp -o smart_ptr_demo_cpp.a -std=c++11 -g - $(GDBGUI) smart_ptr_demo_cpp.a + @echo Run with gdbgui: gdbgui --args smart_ptr_demo_cpp.a sin: sin.cpp g++ sin.cpp -o sin_cpp.a -std=c++11 -g - $(GDBGUI) sin_cpp.a + @echo Run with gdbgui: gdbgui --args sin_cpp.a diff --git a/examples/fortran/makefile b/examples/fortran/makefile index 4e0cd0b9..2a35a0e2 100644 --- a/examples/fortran/makefile +++ b/examples/fortran/makefile @@ -2,4 +2,4 @@ GDBGUI=../../gdbgui/backend.py array_demo: fortran_array.f90 gfortran fortran_array.f90 -o array_f90.a -g - $(GDBGUI) array_f90.a + @echo Run with gdbgui: gdbgui --args array_f90.a diff --git a/examples/golang/makefile b/examples/golang/makefile index e24c490e..2095b14a 100644 --- a/examples/golang/makefile +++ b/examples/golang/makefile @@ -2,4 +2,4 @@ GDBGUI=../../gdbgui/backend.py hello: hello.go go build -o hello_go.a -gccgoflags "-w" hello.go - $(GDBGUI) hello_go.a + @echo Run with gdbgui: gdbgui --args hello_go.a diff --git a/gdbgui/VERSION.txt b/gdbgui/VERSION.txt index d087633b..2b3d2e2d 100644 --- a/gdbgui/VERSION.txt +++ b/gdbgui/VERSION.txt @@ -1 +1 @@ -0.13.2.1 +0.14.0.0 \ No newline at end of file diff --git a/gdbgui/backend.py b/gdbgui/backend.py index e20bc962..f7f73a4d 100755 --- a/gdbgui/backend.py +++ b/gdbgui/backend.py @@ -12,16 +12,14 @@ import os import platform import re -import shlex import signal import socket +import shlex import sys import traceback import webbrowser -from distutils.spawn import find_executable from functools import wraps - -import pygdbmi # type: ignore +from typing import Dict, List, Optional from flask import ( Flask, Response, @@ -34,13 +32,11 @@ ) from flask_compress import Compress # type: ignore from flask_socketio import SocketIO, emit # type: ignore -from pygdbmi.gdbcontroller import NoGdbProcessError # type: ignore from pygments.lexers import get_lexer_for_filename # type: ignore from gdbgui import __version__, htmllistformatter -from gdbgui.statemanager import StateManager +from gdbgui.sessionmanager import SessionManager, DebugSession -pyinstaller_env_var_base_dir = "_MEIPASS" pyinstaller_base_dir = getattr(sys, "_MEIPASS", None) using_pyinstaller = pyinstaller_base_dir is not None if using_pyinstaller: @@ -70,6 +66,7 @@ def get_ssl_context(private_key, certificate): # noqa logger = logging.getLogger(__name__) logger.setLevel(logging.WARNING) +logging.basicConfig(format="(%(asctime)s) %(msg)s") class ColorFormatter(logging.Formatter): @@ -101,12 +98,10 @@ def format(self, record): app.config["initial_binary_and_args"] = [] app.config["gdb_path"] = DEFAULT_GDB_EXECUTABLE -app.config["gdb_cmd_file"] = None +app.config["gdb_command"] = None app.config["TEMPLATES_AUTO_RELOAD"] = True -app.config["LLDB"] = False # assume false, okay to change later app.config["project_home"] = None app.config["remap_sources"] = {} -app.config["rr"] = False app.secret_key = binascii.hexlify(os.urandom(24)).decode("utf-8") @@ -119,13 +114,13 @@ def csrf_protect_all_post_and_cross_origin_requests(): logger.warning("Received cross origin request. Aborting") abort(403) if request.method in ["POST", "PUT"]: - token = session.get("csrf_token") - if token == request.form.get("csrf_token"): + server_token = session.get("csrf_token") + if server_token == request.form.get("csrf_token"): return success - - elif token == request.environ.get("HTTP_X_CSRFTOKEN"): + elif server_token == request.environ.get("HTTP_X_CSRFTOKEN"): + return success + elif request.json and server_token == request.json.get("csrf_token"): return success - else: logger.warning("Received invalid csrf token. Aborting") abort(403) @@ -171,12 +166,12 @@ def add_csrf_token_to_session(): session["csrf_token"] = binascii.hexlify(os.urandom(20)).decode("utf-8") -socketio = SocketIO() -_state = StateManager(app.config) +socketio = SocketIO(manage_session=False) +manager = SessionManager(app.config) def setup_backend( - serve=True, + *, host=DEFAULT_HOST, port=DEFAULT_PORT, debug=False, @@ -185,10 +180,8 @@ def setup_backend( testing=False, private_key=None, certificate=None, - LLDB=False, ): """Run the server of the gdb gui""" - app.config["LLDB"] = LLDB kwargs = {} ssl_context = get_ssl_context(private_key, certificate) @@ -240,7 +233,10 @@ def setup_backend( b = webbrowser.get(browsername) if browsername else webbrowser b.open(url_with_prefix) else: - print(colorize("View gdbgui at %s%s:%d" % (protocol, url[0], url[1]))) + print(colorize(f"View gdbgui at {protocol}{url[0]}:{url[1]}")) + print( + colorize(f"View gdbgui dashboard at {protocol}{url[0]}:{url[1]}/dashboard") + ) print("exit gdbgui by pressing CTRL+C") @@ -251,45 +247,13 @@ def setup_backend( port=int(port), host=host, extra_files=get_extra_files(), - **kwargs + **kwargs, ) except KeyboardInterrupt: # Process was interrupted by ctrl+c on keyboard, show message pass -def verify_gdb_exists(gdb_path): - if find_executable(gdb_path) is None: - pygdbmi.printcolor.print_red( - 'gdb executable "%s" was not found. Verify the executable exists, or that it is a directory on your $PATH environment variable.' - % gdb_path - ) - if USING_WINDOWS: - print( - 'Install gdb (package name "mingw32-gdb") using MinGW (https://sourceforge.net/projects/mingw/files/Installer/mingw-get-setup.exe/download), then ensure gdb is on your "Path" environement variable: Control Panel > System Properties > Environment Variables > System Variables > Path' - ) - else: - print('try "sudo apt-get install gdb" for Linux or "brew install gdb"') - sys.exit(1) - elif "lldb" in gdb_path.lower() and "lldb-mi" not in app.config["gdb_path"].lower(): - pygdbmi.printcolor.print_red( - 'gdbgui cannot use the standard lldb executable. You must use an executable with "lldb-mi" in its name.' - ) - sys.exit(1) - - -def dbprint(*args): - """print only if app.debug is truthy""" - if app and app.debug: - if USING_WINDOWS: - print("DEBUG: " + " ".join(args)) - - else: - CYELLOW2 = "\33[93m" - NORMAL = "\033[0m" - print(CYELLOW2 + "DEBUG: " + " ".join(args) + NORMAL) - - def colorize(text): if IS_A_TTY and not USING_WINDOWS: return "\033[1;32m" + text + "\x1b[0m" @@ -300,6 +264,15 @@ def colorize(text): @socketio.on("connect", namespace="/gdb_listener") def client_connected(): + """Connect a websocket client to a debug session + + This is the main intial connection. + + Depending on the arguments passed, the client will connect + to an existing debug session, or create a new one. + A message is a emitted back to the client with details on + the debug session that was created or connected to. + """ if is_cross_origin(request): logger.warning("Received cross origin request. Aborting") abort(403) @@ -321,40 +294,107 @@ def client_connected(): ) return - # see if user wants to connect to existing gdb pid desired_gdbpid = int(request.args.get("gdbpid", 0)) - - payload = _state.connect_client(request.sid, desired_gdbpid) - logger.info( - 'Client websocket connected in async mode "%s", id %s' - % (socketio.async_mode, request.sid) - ) - - # tell the client browser tab which gdb pid is a dedicated to it - emit("gdb_pid", payload) + try: + if desired_gdbpid: + # connect to exiting debug session + debug_session = manager.connect_client_to_debug_session( + desired_gdbpid=desired_gdbpid, client_id=request.sid + ) + emit( + "debug_session_connection_event", + { + "ok": True, + "started_new_gdb_process": False, + "pid": debug_session.pid, + "message": f"Connected to existing gdb process {desired_gdbpid}", + }, + ) + else: + # start new debug session + gdb_command = request.args.get("gdb_command", app.config["gdb_command"]) + mi_version = request.args.get("mi_version", "mi2") + debug_session = manager.add_new_debug_session( + gdb_command=gdb_command, mi_version=mi_version, client_id=request.sid + ) + emit( + "debug_session_connection_event", + { + "ok": True, + "started_new_gdb_process": True, + "message": f"Started new gdb process, pid {debug_session.pid}", + "pid": debug_session.pid, + }, + ) + except Exception as e: + emit( + "debug_session_connection_event", + {"message": f"Failed to establish gdb session: {e}", "ok": False}, + ) # Make sure there is a reader thread reading. One thread reads all instances. - if _state.gdb_reader_thread is None: - _state.gdb_reader_thread = socketio.start_background_task( - target=read_and_forward_gdb_output + if manager.gdb_reader_thread is None: + manager.gdb_reader_thread = socketio.start_background_task( + target=read_and_forward_gdb_and_pty_output ) logger.info("Created background thread to read gdb responses") +@socketio.on("pty_interaction", namespace="/gdb_listener") +def pty_interaction(message): + """Write a character to the user facing pty""" + debug_session = manager.debug_session_from_client_id(request.sid) + if not debug_session: + emit( + "error_running_gdb_command", + {"message": f"no gdb session available for client id {request.sid}"}, + ) + return + + try: + data = message.get("data") + pty_name = data.get("pty_name") + if pty_name == "user_pty": + pty = debug_session.pty_for_gdb + elif pty_name == "program_pty": + pty = debug_session.pty_for_debugged_program + else: + raise ValueError(f"Unknown pty: {pty_name}") + + action = data.get("action") + if action == "write": + key = data["key"] + pty.write(key) + elif action == "set_winsize": + pty.set_winsize(data["rows"], data["cols"]) + else: + raise ValueError(f"Unknown action {action}") + except Exception: + err = traceback.format_exc() + logger.error(err) + emit("error_running_gdb_command", {"message": err}) + + @socketio.on("run_gdb_command", namespace="/gdb_listener") -def run_gdb_command(message): - """ - Endpoint for a websocket route. - Runs a gdb command. - Responds only if an error occurs when trying to write the command to - gdb - """ - controller = _state.get_controller_from_client_id(request.sid) - if controller is not None: +def run_gdb_command(message: Dict[str, str]): + """Write commands to gdbgui's gdb mi pty""" + client_id = request.sid # type: ignore + debug_session = manager.debug_session_from_client_id(client_id) + if not debug_session: + emit("error_running_gdb_command", {"message": "no session"}) + return + pty_mi = debug_session.pygdbmi_controller + if pty_mi is not None: try: # the command (string) or commands (list) to run - cmd = message["cmd"] - controller.write(cmd, read_response=False) + cmds = message["cmd"] + for cmd in cmds: + pty_mi.write( + cmd + "\n", + timeout_sec=0, + raise_error_on_timeout=False, + read_response=False, + ) except Exception: err = traceback.format_exc() @@ -384,7 +424,7 @@ def send_msg_to_clients(client_ids, msg, error=False): def remove_gdb_controller(): gdbpid = int(request.form.get("gdbpid")) - orphaned_client_ids = _state.remove_gdb_controller_by_pid(gdbpid) + orphaned_client_ids = manager.remove_debug_session_by_pid(gdbpid) num_removed = len(orphaned_client_ids) send_msg_to_clients( @@ -404,7 +444,7 @@ def remove_gdb_controller(): @socketio.on("disconnect", namespace="/gdb_listener") def client_disconnected(): """do nothing if client disconnects""" - _state.disconnect_client(request.sid) + manager.disconnect_client(request.sid) logger.info("Client websocket disconnected, id %s" % (request.sid)) @@ -413,28 +453,28 @@ def test_disconnect(): print("Client websocket disconnected", request.sid) -def read_and_forward_gdb_output(): +def read_and_forward_gdb_and_pty_output(): """A task that runs on a different thread, and emits websocket messages of gdb responses""" while True: socketio.sleep(0.05) - controllers_to_remove = [] - controller_items = _state.controller_to_client_ids.items() - for controller, client_ids in controller_items: + debug_sessions_to_remove = [] + for debug_session, client_ids in manager.debug_session_to_client_ids.items(): try: try: - response = controller.get_gdb_response( + response = debug_session.pygdbmi_controller.get_gdb_response( timeout_sec=0, raise_error_on_timeout=False ) - except NoGdbProcessError: + + except Exception: response = None send_msg_to_clients( client_ids, "The underlying gdb process has been killed. This tab will no longer function as expected.", error=True, ) - controllers_to_remove.append(controller) + debug_sessions_to_remove.append(debug_session) if response: for client_id in client_ids: @@ -454,12 +494,45 @@ def read_and_forward_gdb_output(): except Exception: logger.error(traceback.format_exc()) - for controller in controllers_to_remove: - _state.remove_gdb_controller(controller) + debug_sessions_to_remove += check_and_forward_pty_output() + for debug_session in set(debug_sessions_to_remove): + manager.remove_debug_session(debug_session) -def server_error(obj): - return jsonify(obj), 500 +def check_and_forward_pty_output() -> List[DebugSession]: + debug_sessions_to_remove = [] + for debug_session, client_ids in manager.debug_session_to_client_ids.items(): + try: + response = debug_session.pty_for_gdb.read() + if response is not None: + for client_id in client_ids: + socketio.emit( + "user_pty_response", + response, + namespace="/gdb_listener", + room=client_id, + ) + + response = debug_session.pty_for_debugged_program.read() + if response is not None: + for client_id in client_ids: + socketio.emit( + "program_pty_response", + response, + namespace="/gdb_listener", + room=client_id, + ) + except Exception as e: + debug_sessions_to_remove.append(debug_session) + for client_id in client_ids: + socketio.emit( + "fatal_server_error", + {"message": str(e)}, + namespace="/gdb_listener", + room=client_id, + ) + logger.error(e, exc_info=True) + return debug_sessions_to_remove def client_error(obj): @@ -522,35 +595,28 @@ def wrapper(*args, **kwargs): @authenticate def gdbgui(): """Render the main gdbgui interface""" - interpreter = "lldb" if app.config["LLDB"] else "gdb" gdbpid = request.args.get("gdbpid", 0) - initial_gdb_user_command = request.args.get("initial_gdb_user_command", "") - + gdb_command = request.args.get("gdb_command", app.config["gdb_command"]) add_csrf_token_to_session() THEMES = ["monokai", "light"] - # fmt: off initial_data = { "csrf_token": session["csrf_token"], "gdbgui_version": __version__, "gdbpid": gdbpid, - "initial_gdb_user_command": initial_gdb_user_command, - "interpreter": interpreter, + "gdb_command": gdb_command, "initial_binary_and_args": app.config["initial_binary_and_args"], "project_home": app.config["project_home"], "remap_sources": app.config["remap_sources"], - "rr": app.config["rr"], "themes": THEMES, "signals": SIGNAL_NAME_TO_OBJ, "using_windows": USING_WINDOWS, } - # fmt: on return render_template( "gdbgui.html", version=__version__, debug=app.debug, - interpreter=interpreter, initial_data=initial_data, themes=THEMES, ) @@ -607,40 +673,32 @@ def dashboard(): GdbController instance""" return render_template( "dashboard.html", - processes=_state.get_dashboard_data(), + gdbgui_sessions=manager.get_dashboard_data(), csrf_token=session["csrf_token"], + default_command=app.config["gdb_command"], ) -@app.route("/shutdown", methods=["GET"]) +@app.route("/dashboard_data", methods=["GET"]) @authenticate -def shutdown_webview(): - add_csrf_token_to_session() - return render_template( - "shutdown.html", debug=app.debug, csrf_token=session["csrf_token"] - ) - - -@app.route("/help") -def help(): - return redirect("https://github.com/cs01/gdbgui/blob/master/HELP.md") - +def dashboard_data(): + return jsonify(manager.get_dashboard_data()) -@app.route("/_shutdown", methods=["POST"]) -def _shutdown(): - try: - _state.exit_all_gdb_processes() - except Exception: - logger.error("failed to exit gdb subprocces") - logger.error(traceback.format_exc()) - pid = os.getpid() - if app.debug: - os.kill(pid, signal.SIGINT) +@app.route("/kill_session", methods=["PUT"]) +@authenticate +def kill_session(): + pid = request.json.get("gdbpid") + if pid: + manager.remove_debug_session_by_pid(pid) + return jsonify({"success": True}) else: - socketio.stop() + return Response("Missing required parameter: gdbpid", 401,) - return jsonify({}) + +@app.route("/help") +def help_route(): + return redirect("https://github.com/cs01/gdbgui/blob/master/HELP.md") @app.route("/get_last_modified_unix_sec", methods=["GET"]) @@ -765,7 +823,9 @@ def get_gdbgui_auth_user_credentials(auth_file, user, password): def get_parser(): - parser = argparse.ArgumentParser(description=__doc__) + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) gdb_group = parser.add_argument_group(title="gdb settings") args_group = parser.add_mutually_exclusive_group() @@ -775,36 +835,26 @@ def get_parser(): gdb_group.add_argument( "-g", - "--gdb", - help="Path to debugger. Default: %s" % DEFAULT_GDB_EXECUTABLE, + "--gdb-cmd", + help=""" + gdb binary and arguments to run. If passing arguments, + enclose in quotes. + If using rr, it should be specified here with + 'rr replay'. + Examples: gdb, /path/to/gdb, 'gdb --command=FILE -ix', 'rr replay' + + """, default=DEFAULT_GDB_EXECUTABLE, ) - gdb_group.add_argument( - "--gdb-args", - help=( - "Arguments passed directly to gdb when gdb is invoked. " - 'For example,--gdb-args="--nx --tty=/dev/ttys002"' - ), - default="", - ) - gdb_group.add_argument( - "--rr", - action="store_true", - help=( - "Use `rr replay` instead of gdb. Replays last recording by default. " - "Replay arbitrary recording by passing recorded directory as an argument. " - "i.e. gdbgui /recorded/dir --rr. See http://rr-project.org/." - ), - ) network.add_argument( "-p", "--port", - help="The port on which gdbgui will be hosted. Default: %s" % DEFAULT_PORT, + help="The port on which gdbgui will be hosted", default=DEFAULT_PORT, ) network.add_argument( "--host", - help="The host ip address on which gdbgui serve. Default: %s" % DEFAULT_HOST, + help="The host ip address on which gdbgui serve", default=DEFAULT_HOST, ) network.add_argument( @@ -847,7 +897,7 @@ def get_parser(): help=( "Replace compile-time source paths to local source paths. " "Pass valid JSON key/value pairs." - 'i.e. --remap-sources=\'{"/buildmachine": "/home/chad"}\'' + 'i.e. --remap-sources=\'{"/buildmachine": "/current/machine"}\'' ), ) other.add_argument( @@ -856,11 +906,6 @@ def get_parser(): ) other.add_argument("-v", "--version", help="Print version", action="store_true") - other.add_argument( - "--hide-gdbgui-upgrades", - help=argparse.SUPPRESS, # deprecated. left so calls to gdbgui don't break - action="store_true", - ) other.add_argument( "-n", "--no-browser", @@ -879,22 +924,20 @@ def get_parser(): "Pass this flag when debugging gdbgui itself to automatically reload the server when changes are detected", action="store_true", ) - args_group.add_argument( - "cmd", + "debug_program", nargs="?", - type=lambda prog: [prog], - help="The executable file and any arguments to pass to it." + help="The executable file you wish to debug, and any arguments to pass to it." " To pass flags to the binary, wrap in quotes, or use --args instead." " Example: gdbgui ./mybinary [other-gdbgui-args...]" " Example: gdbgui './mybinary myarg -flag1 -flag2' [other gdbgui args...]", - default=[], + default=None, ) args_group.add_argument( "--args", nargs=argparse.REMAINDER, - help="Specify the executable file and any arguments to pass to it. All arguments are" - " taken literally, so if used, this must be the last argument" + help="Specify the executable file you wish to debug and any arguments to pass to it. All arguments are" + " taken literally, so if used, this must be the last argument. This can also be specified later in the frontend." " passed to gdbgui." " Example: gdbgui [...] --args ./mybinary myarg -flag1 -flag2", default=[], @@ -902,28 +945,33 @@ def get_parser(): return parser +def get_initial_binary_and_args( + user_supplied_args: List[str], debug_program_and_args: Optional[str] +) -> List[str]: + if debug_program_and_args: + # passed via positional + return shlex.split(debug_program_and_args) + else: + # passed via --args + return user_supplied_args + + def main(): """Entry point from command line""" parser = get_parser() args = parser.parse_args() - - if args.debug: - logger.setLevel(logging.NOTSET) - if args.version: print(__version__) return - cmd = args.cmd or args.args - if args.no_browser and args.browser: print("Cannot specify no-browser and browser. Must specify one or the other.") exit(1) - app.config["initial_binary_and_args"] = cmd - app.config["gdb_args"] = shlex.split(args.gdb_args) - app.config["rr"] = args.rr - app.config["gdb_path"] = args.gdb + app.config["gdb_command"] = args.gdb_cmd + app.config["initial_binary_and_args"] = get_initial_binary_and_args( + args.args, args.debug_program + ) app.config["gdbgui_auth_user_credentials"] = get_gdbgui_auth_user_credentials( args.auth_file, args.user, args.password ) @@ -938,7 +986,6 @@ def main(): print(e) exit(1) - verify_gdb_exists(app.config["gdb_path"]) if args.remote: args.host = "0.0.0.0" args.no_browser = True @@ -948,7 +995,7 @@ def main(): "accessible IP address. See gdbgui --help." ) - if warn_startup_with_shell_off(platform.platform().lower(), args.gdb_args): + if warn_startup_with_shell_off(platform.platform().lower(), args.gdb_cmd): logger.warning( "You may need to set startup-with-shell off when running on a mac. i.e.\n" " gdbgui --gdb-args='--init-eval-command=\"set startup-with-shell off\"'\n" @@ -957,7 +1004,6 @@ def main(): ) setup_backend( - serve=True, host=args.host, port=int(args.port), debug=bool(args.debug), @@ -968,7 +1014,7 @@ def main(): ) -def warn_startup_with_shell_off(platform, gdb_args): +def warn_startup_with_shell_off(platform: str, gdb_args: str): """return True if user may need to turn shell off if mac OS version is 16 (sierra) or higher, may need to set shell off due to os's security requirements diff --git a/gdbgui/ptylib.py b/gdbgui/ptylib.py new file mode 100644 index 00000000..7f2443c1 --- /dev/null +++ b/gdbgui/ptylib.py @@ -0,0 +1,83 @@ +import os + +USING_WINDOWS = os.name == "nt" +if USING_WINDOWS: + raise RuntimeError( + "Windows is not supported at this time. " + + "Versions lower than 0.14.x. are Windows compatible." + ) +import pty +import select +import struct +import signal +from typing import Optional +import shlex +import termios +import fcntl + + +class Pty: + max_read_bytes = 1024 * 20 + + def __init__(self, *, cmd: Optional[str] = None, echo: bool = True): + if cmd: + (child_pid, fd) = pty.fork() + if child_pid == 0: + # this is the child process fork. + # anything printed here will show up in the pty, including the output + # of this subprocess + def sigint_handler(_sig, _frame): + # prevent SIGINT (ctrl+c) from exiting + # the whole program + pass + + signal.signal(signal.SIGINT, sigint_handler) + args = shlex.split(cmd) + os.execvp(args[0], args) + + else: + # this is the parent process fork. + # store child fd and pid + self.stdin = fd + self.stdout = fd + self.pid = child_pid + else: + (master, slave) = pty.openpty() + self.stdin = master + self.stdout = master + self.name = os.ttyname(slave) + self.set_echo(echo) + + def set_echo(self, echo_on: bool) -> None: + (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(self.stdin) + if echo_on: + lflag = lflag & termios.ECHO # type: ignore + else: + lflag = lflag & ~termios.ECHO # type: ignore + termios.tcsetattr( + self.stdin, + termios.TCSANOW, + [iflag, oflag, cflag, lflag, ispeed, ospeed, cc], + ) + + def set_winsize(self, rows: int, cols: int): + xpix = 0 + ypix = 0 + winsize = struct.pack("HHHH", rows, cols, xpix, ypix) + if self.stdin is None: + raise RuntimeError("fd stdin not assigned") + fcntl.ioctl(self.stdin, termios.TIOCSWINSZ, winsize) + + def read(self) -> Optional[str]: + if self.stdout is None: + return "done" + timeout_sec = 0 + (data_to_read, _, _) = select.select([self.stdout], [], [], timeout_sec) + if data_to_read: + response = os.read(self.stdout, self.max_read_bytes).decode() + return response + return None + + def write(self, data: str): + edata = data.encode() + os.write(self.stdin, edata) diff --git a/gdbgui/sessionmanager.py b/gdbgui/sessionmanager.py new file mode 100644 index 00000000..8e987091 --- /dev/null +++ b/gdbgui/sessionmanager.py @@ -0,0 +1,171 @@ +import logging +import traceback +from typing import Any, Dict, List, Optional, Set +from pygdbmi.IoManager import IoManager +from collections import defaultdict +from .ptylib import Pty +import os +import datetime +import signal + +logger = logging.getLogger(__name__) + + +class DebugSession: + def __init__( + self, + *, + pygdbmi_controller: IoManager, + pty_for_gdbgui: Pty, + pty_for_gdb: Pty, + pty_for_debugged_program: Pty, + command: str, + mi_version: str, + pid: int, + ): + self.command = command + self.pygdbmi_controller = pygdbmi_controller + self.pty_for_gdbgui = pty_for_gdbgui + self.pty_for_gdb = pty_for_gdb + self.pty_for_debugged_program = pty_for_debugged_program + self.mi_version = mi_version + self.pid = pid + self.start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.client_ids: Set[str] = set() + + def terminate(self): + if self.pid: + try: + os.kill(self.pid, signal.SIGKILL) + except Exception as e: + logger.error(f"Failed to kill pid {self.pid}: {str(e)}") + + self.pygdbmi_controller = None + + def to_dict(self): + return { + "pid": self.pid, + "start_time": self.start_time, + "command": self.command, + "c2": "hi", + "client_ids": list(self.client_ids), + } + + def add_client(self, client_id: str): + self.client_ids.add(client_id) + + def remove_client(self, client_id: str): + self.client_ids.discard(client_id) + if len(self.client_ids) == 0: + self.terminate() + + +class SessionManager(object): + def __init__(self, app_config: Dict[str, Any]): + self.debug_session_to_client_ids: Dict[DebugSession, List[str]] = defaultdict( + list + ) # key is controller, val is list of client ids + + self.gdb_reader_thread = None + self.config = app_config + + def connect_client_to_debug_session( + self, *, desired_gdbpid: int, client_id: str + ) -> DebugSession: + debug_session = self.debug_session_from_pid(desired_gdbpid) + + if not debug_session: + raise ValueError(f"No existing gdb process with pid {desired_gdbpid}") + debug_session.add_client(client_id) + self.debug_session_to_client_ids[debug_session].append(client_id) + return debug_session + + def add_new_debug_session( + self, *, gdb_command: str, mi_version: str, client_id: str + ) -> DebugSession: + pty_for_debugged_program = Pty() + pty_for_gdbgui = Pty(echo=False) + gdbgui_startup_cmds = [ + f"new-ui {mi_version} {pty_for_gdbgui.name}", + f"set inferior-tty {pty_for_debugged_program.name}", + ] + # instead of writing to the pty after it starts, add startup + # commands to gdb. This allows gdb to be run as sudo and prompt for a + # password, for example. + gdbgui_startup_cmds_str = " ".join([f"-ex='{c}'" for c in gdbgui_startup_cmds]) + pty_for_gdb = Pty(cmd=f"{gdb_command} {gdbgui_startup_cmds_str}") + + pid = pty_for_gdb.pid + debug_session = DebugSession( + pygdbmi_controller=IoManager( + os.fdopen(pty_for_gdbgui.stdin, mode="wb", buffering=0), + os.fdopen(pty_for_gdbgui.stdout, mode="rb", buffering=0), + None, + ), + pty_for_gdbgui=pty_for_gdbgui, + pty_for_gdb=pty_for_gdb, + pty_for_debugged_program=pty_for_debugged_program, + command=gdb_command, + mi_version=mi_version, + pid=pid, + ) + debug_session.add_client(client_id) + self.debug_session_to_client_ids[debug_session] = [client_id] + return debug_session + + def remove_debug_session_by_pid(self, gdbpid: int) -> List[str]: + debug_session = self.debug_session_from_pid(gdbpid) + if debug_session: + orphaned_client_ids = self.remove_debug_session(debug_session) + else: + logger.info(f"could not find debug session with gdb pid {gdbpid}") + orphaned_client_ids = [] + return orphaned_client_ids + + def remove_debug_session(self, debug_session: DebugSession) -> List[str]: + logger.info(f"Removing debug session for pid {debug_session.pid}") + try: + debug_session.terminate() + except Exception: + logger.error(traceback.format_exc()) + orphaned_client_ids = self.debug_session_to_client_ids.pop(debug_session, []) + return orphaned_client_ids + + def remove_debug_sessions_with_no_clients(self) -> None: + to_remove = [] + for debug_session, _ in self.debug_session_to_client_ids.items(): + if len(debug_session.client_ids) == 0: + to_remove.append(debug_session) + for debug_session in to_remove: + self.remove_debug_session(debug_session) + + def get_pid_from_debug_session(self, debug_session: DebugSession) -> Optional[int]: + if debug_session and debug_session.pid: + return debug_session.pid + return None + + def debug_session_from_pid(self, pid: int) -> Optional[DebugSession]: + for debug_session in self.debug_session_to_client_ids: + this_pid = self.get_pid_from_debug_session(debug_session) + if this_pid == pid: + return debug_session + return None + + def debug_session_from_client_id(self, client_id: str) -> Optional[DebugSession]: + for debug_session, client_ids in self.debug_session_to_client_ids.items(): + if client_id in client_ids: + return debug_session + return None + + def get_dashboard_data(self) -> List[DebugSession]: + return [ + debug_session.to_dict() + for debug_session in self.debug_session_to_client_ids.keys() + ] + + def disconnect_client(self, client_id: str): + for debug_session, client_ids in self.debug_session_to_client_ids.items(): + if client_id in client_ids: + client_ids.remove(client_id) + debug_session.remove_client(client_id) + self.remove_debug_sessions_with_no_clients() diff --git a/gdbgui/src/js/Actions.js b/gdbgui/src/js/Actions.ts similarity index 78% rename from gdbgui/src/js/Actions.js rename to gdbgui/src/js/Actions.ts index 36c50150..9a3a00eb 100644 --- a/gdbgui/src/js/Actions.js +++ b/gdbgui/src/js/Actions.ts @@ -1,9 +1,9 @@ import { store } from "statorgfc"; -import GdbApi from "./GdbApi.jsx"; -import SourceCode from "./SourceCode.jsx"; -import Locals from "./Locals.jsx"; -import Memory from "./Memory.jsx"; -import constants from "./constants.js"; +import GdbApi from "./GdbApi"; +import SourceCode from "./SourceCode"; +import Locals from "./Locals"; +import Memory from "./Memory"; +import constants from "./constants"; import React from "react"; void React; // using jsx implicity uses React @@ -32,8 +32,11 @@ const Actions = { constants.source_code_selection_states.PAUSED_FRAME ); store.set("paused_on_frame", frame); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'fullname' does not exist on type '{}'. store.set("fullname_to_render", frame.fullname); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'line' does not exist on type '{}'. store.set("line_of_source_to_flash", parseInt(frame.line)); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'addr' does not exist on type '{}'. store.set("current_assembly_address", frame.addr); store.set("source_code_infinite_scrolling", false); SourceCode.make_current_line_visible(); @@ -54,35 +57,43 @@ const Actions = { refresh_state_for_gdb_pause: function() { GdbApi.run_gdb_command(GdbApi._get_refresh_state_for_pause_cmds()); }, - execute_console_command: function(command) { + execute_console_command: function(command: any) { if (store.get("refresh_state_after_sending_console_command")) { GdbApi.run_command_and_refresh_state(command); } else { GdbApi.run_gdb_command(command); } }, + onConsoleCommandRun: function() { + if (store.get("refresh_state_after_sending_console_command")) { + GdbApi.run_gdb_command(GdbApi._get_refresh_state_for_pause_cmds()); + } + }, clear_console: function() { store.set("gdb_console_entries", []); }, - add_console_entries: function(entries, type) { - if (!_.isArray(entries)) { + add_console_entries: function(entries: any, type: any) { + if (type === constants.console_entry_type.STD_OUT) { + // ignore + return; + } + if (!Array.isArray(entries)) { entries = [entries]; } - const typed_entries = entries.map(entry => { - return { type: type, value: entry }; - }); - - const previous_entries = store.get("gdb_console_entries"); - const MAX_NUM_ENTRIES = 1000; - const new_entries = previous_entries.concat(typed_entries); - if (new_entries.length > MAX_NUM_ENTRIES) { - new_entries.splice(0, new_entries.length - MAX_NUM_ENTRIES); + const pty = store.get("gdbguiPty"); + if (pty) { + entries.forEach((data: any) => { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'colorTypeMap' does not exist on type 'Re... Remove this comment to see the full error message + pty.write(constants.colorTypeMap[type] ?? constants.xtermColors["reset"]); + pty.writeln(data); + pty.write(constants.xtermColors["reset"]); + }); + } else { + console.error("Pty not available. New entries are:", entries); } - - store.set("gdb_console_entries", new_entries); }, - add_gdb_response_to_console(mi_obj) { + add_gdb_response_to_console(mi_obj: any) { if (!mi_obj) { return; } @@ -120,12 +131,12 @@ const Actions = { toggle_modal_visibility() { store.set("show_modal", !store.get("show_modal")); }, - show_modal(header, body) { + show_modal(header: any, body: any) { store.set("modal_header", header); store.set("modal_body", body); store.set("show_modal", true); }, - set_gdb_binary_and_arguments(binary, args) { + set_gdb_binary_and_arguments(binary: any, args: any) { // remove list of source files associated with the loaded binary since we're loading a new one store.set("source_file_paths", []); store.set("language", "c_family"); @@ -135,7 +146,7 @@ const Actions = { GdbApi.run_gdb_command(cmds); GdbApi.get_inferior_binary_last_modified_unix_sec(binary); }, - connect_to_gdbserver(user_input) { + connect_to_gdbserver(user_input: any) { // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation store.set("source_file_paths", []); store.set("language", "c_family"); @@ -162,7 +173,7 @@ const Actions = { } GdbApi.run_gdb_command(cmds); }, - attach_to_process(user_input) { + attach_to_process(user_input: any) { // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation GdbApi.run_gdb_command(`-target-attach ${user_input}`); }, @@ -170,12 +181,12 @@ const Actions = { store.set("source_file_paths", []); GdbApi.run_gdb_command("-file-list-exec-source-files"); }, - view_file(fullname, line) { + view_file(fullname: any, line: any) { store.set("fullname_to_render", fullname); store.set("source_code_infinite_scrolling", false); Actions.set_line_state(line); }, - set_line_state(line) { + set_line_state(line: any) { store.set("source_code_infinite_scrolling", false); store.set( "source_code_selection_state", @@ -192,30 +203,19 @@ const Actions = { } store.set("cached_source_files", cached_source_files); }, - update_max_lines_of_code_to_fetch(new_value) { + update_max_lines_of_code_to_fetch(new_value: any) { if (new_value <= 0) { new_value = constants.default_max_lines_of_code_to_fetch; } store.set("max_lines_of_code_to_fetch", new_value); localStorage.setItem("max_lines_of_code_to_fetch", JSON.stringify(new_value)); }, - show_upgrade_modal() { - const body = ( -

- This feature is only available in gdbgui pro. -

- window.open("http://gdbgui.com")} className="btn btn-primary"> - Upgrade now - -

- ); - Actions.show_modal("upgrade to pro for this feature", body); - }, - send_signal(signal_name, pid) { + send_signal(signal_name: any, pid: any) { $.ajax({ beforeSend: function(xhr) { xhr.setRequestHeader( "x-csrftoken", + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. initial_data.csrf_token ); /* global initial_data */ }, @@ -232,6 +232,7 @@ const Actions = { error: function(response) { if (response.responseJSON && response.responseJSON.message) { Actions.add_console_entries( + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. _.escape(response.responseJSON.message), constants.console_entry_type.STD_ERR ); diff --git a/gdbgui/src/js/BinaryLoader.jsx b/gdbgui/src/js/BinaryLoader.tsx similarity index 76% rename from gdbgui/src/js/BinaryLoader.jsx rename to gdbgui/src/js/BinaryLoader.tsx index 33c9c59a..0bc187fc 100644 --- a/gdbgui/src/js/BinaryLoader.jsx +++ b/gdbgui/src/js/BinaryLoader.tsx @@ -1,8 +1,8 @@ import React from "react"; -import constants from "./constants.js"; -import Actions from "./Actions.js"; -import Util from "./Util.js"; -import ToolTipTourguide from "./ToolTipTourguide.jsx"; +import constants from "./constants"; +import Actions from "./Actions"; +import Util from "./Util"; +import ToolTipTourguide from "./ToolTipTourguide"; const TARGET_TYPES = { file: "file", @@ -11,29 +11,38 @@ const TARGET_TYPES = { target_download: "target_download" }; +type State = any; + /** * The BinaryLoader component allows the user to select their binary * and specify inputs */ -class BinaryLoader extends React.Component { - constructor(props) { +class BinaryLoader extends React.Component<{}, State> { + constructor(props: {}) { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); this.state = { past_binaries: [], + // @ts-expect-error ts-migrate(2339) FIXME: Property 'initial_user_input' does not exist on ty... Remove this comment to see the full error message user_input: props.initial_user_input.join(" "), + // @ts-expect-error ts-migrate(2339) FIXME: Property 'initial_user_input' does not exist on ty... Remove this comment to see the full error message initial_set_target_app: props.initial_user_input.length, // if user supplied initial binary, load it immediately target_type: TARGET_TYPES.file }; try { + // @ts-expect-error ts-migrate(2542) FIXME: Index signature in type 'Readonly' only permi... Remove this comment to see the full error message this.state.past_binaries = _.uniq( + // @ts-expect-error ts-migrate(2345) FIXME: Type 'null' is not assignable to type 'string'. JSON.parse(localStorage.getItem("past_binaries")) ); if (!this.state.user_input) { let most_recent_binary = this.state.past_binaries[0]; + // @ts-expect-error ts-migrate(2542) FIXME: Index signature in type 'Readonly' only permi... Remove this comment to see the full error message this.state.user_input = most_recent_binary; } } catch (err) { + // @ts-expect-error ts-migrate(2542) FIXME: Index signature in type 'Readonly' only permi... Remove this comment to see the full error message this.state.past_binaries = []; } } @@ -120,6 +129,7 @@ class BinaryLoader extends React.Component { /> - {this.state.past_binaries.map((b, i) => ( + {this.state.past_binaries.map((b: any, i: any) => ( ))} @@ -167,12 +178,13 @@ class BinaryLoader extends React.Component { this.set_target_app(); } } - onkeyup_user_input(e) { + onkeyup_user_input(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { this.set_target_app(); } } - onchange_user_inpu(e) { + onchange_user_inpu(e: any) { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. if (initial_data.using_windows) { // replace backslashes with forward slashes when using windows this.setState({ user_input: e.target.value.replace(/\\/g, "/") }); @@ -184,12 +196,15 @@ class BinaryLoader extends React.Component { this.set_target_app(); } // save to list of binaries used that autopopulates the input dropdown - _add_user_input_to_history(binary_and_args) { - _.remove(this.state.past_binaries, i => i === binary_and_args); + _add_user_input_to_history(binary_and_args: any) { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. + _.remove(this.state.past_binaries, (i: any) => i === binary_and_args); this.state.past_binaries.unshift(binary_and_args); // add to beginning this.setState({ past_binaries: this.state.past_binaries }); + // @ts-expect-error ts-migrate(2345) FIXME: Type 'never[]' is not assignable to type 'string'. localStorage.setItem("past_binaries", JSON.stringify(this.state.past_binaries) || []); + // @ts-expect-error ts-migrate(2345) FIXME: Type 'null' is not assignable to type 'string'. let num_gdbgui_sessions = parseInt(localStorage.getItem("num_gdbgui_sessions")); if (isNaN(num_gdbgui_sessions)) { num_gdbgui_sessions = 0; @@ -201,10 +216,10 @@ class BinaryLoader extends React.Component { * @param {string} user_input raw input from user * @return {Object} { the binary (string) and arguments (array) parsed from user input } */ - _parse_binary_and_args_from_user_input(user_input) { + _parse_binary_and_args_from_user_input(user_input: any) { let list_of_params = Util.string_to_array_safe_quotes(user_input), binary = "", - args = [], + args: any = [], len = list_of_params.length; if (len === 1) { binary = list_of_params[0]; @@ -215,9 +230,9 @@ class BinaryLoader extends React.Component { return { binary: binary, args: args.join(" ") }; } set_target_app() { - let user_input = _.trim(this.state.user_input); + let user_input = this.state.user_input.trim(); - if (_.trim(user_input) === "") { + if (user_input === "") { Actions.add_console_entries( "input cannot be empty", constants.console_entry_type.GDBGUI_OUTPUT diff --git a/gdbgui/src/js/Breakpoints.jsx b/gdbgui/src/js/Breakpoints.tsx similarity index 76% rename from gdbgui/src/js/Breakpoints.jsx rename to gdbgui/src/js/Breakpoints.tsx index 6b189343..9ab482e4 100644 --- a/gdbgui/src/js/Breakpoints.jsx +++ b/gdbgui/src/js/Breakpoints.tsx @@ -1,47 +1,57 @@ import React from "react"; import { store } from "statorgfc"; -import GdbApi from "./GdbApi.jsx"; -import Actions from "./Actions.js"; -import Util from "./Util.js"; -import FileOps from "./FileOps.jsx"; +import GdbApi from "./GdbApi"; +import Actions from "./Actions"; +import Util from "./Util"; +import FileOps from "./FileOps"; import { FileLink } from "./Links"; -import constants from "./constants.js"; +import constants from "./constants"; const BreakpointSourceLineCache = { _cache: {}, - get_line: function(fullname, linenum) { + get_line: function(fullname: any, linenum: any) { if ( + // @ts-expect-error ts-migrate(7053) FIXME: Property 'fullname' does not exist on type '{}'. BreakpointSourceLineCache._cache["fullname"] !== undefined && + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. _.isString(BreakpointSourceLineCache._cache["fullname"][linenum]) ) { + // @ts-expect-error ts-migrate(7053) FIXME: Property 'fullname' does not exist on type '{}'. return BreakpointSourceLineCache._cache["fullname"][linenum]; } return null; }, - add_line: function(fullname, linenum, escaped_text) { + add_line: function(fullname: any, linenum: any, escaped_text: any) { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (!_.isObject(BreakpointSourceLineCache._cache["fullname"])) { + // @ts-expect-error ts-migrate(7053) FIXME: Property 'fullname' does not exist on type '{}'. BreakpointSourceLineCache._cache["fullname"] = {}; } + // @ts-expect-error ts-migrate(7053) FIXME: Property 'fullname' does not exist on type '{}'. BreakpointSourceLineCache._cache["fullname"][linenum] = escaped_text; } }; -class Breakpoint extends React.Component { - constructor(props) { +type BreakpointState = any; + +class Breakpoint extends React.Component<{}, BreakpointState> { + constructor(props: {}) { super(props); this.state = { breakpoint_condition: "", editing_breakpoint_condition: false }; } - get_source_line(fullname, linenum) { + get_source_line(fullname: any, linenum: any) { // if we have the source file cached, we can display the line of text const MAX_CHARS_TO_SHOW_FROM_SOURCE = 40; let line = null; if (BreakpointSourceLineCache.get_line(fullname, linenum)) { line = BreakpointSourceLineCache.get_line(fullname, linenum); + // @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2. } else if (FileOps.line_is_cached(fullname, linenum)) { let syntax_highlighted_line = FileOps.get_line_from_file(fullname, linenum); + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. line = _.trim(Util.get_text_from_html(syntax_highlighted_line)); if (line.length > MAX_CHARS_TO_SHOW_FROM_SOURCE) { @@ -59,7 +69,7 @@ class Breakpoint extends React.Component { } return "(file not cached)"; } - get_delete_jsx(bkpt_num_to_delete) { + get_delete_jsx(bkpt_num_to_delete: any) { return (
); } - get_num_times_hit(bkpt) { + get_num_times_hit(bkpt: any) { if ( bkpt.times === undefined || // E.g. 'bkpt' is a child breakpoint bkpt.times == 0 @@ -86,24 +96,25 @@ class Breakpoint extends React.Component { return `${bkpt.times} hits`; } } - on_change_bkpt_cond(e) { + on_change_bkpt_cond(e: any) { this.setState({ breakpoint_condition: e.target.value, editing_breakpoint_condition: true }); } - on_key_up_bktp_cond(number, e) { + on_key_up_bktp_cond(number: any, e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { this.setState({ editing_breakpoint_condition: false }); Breakpoints.set_breakpoint_condition(e.target.value, number); } } - on_break_cond_click(e) { + on_break_cond_click(e: any) { this.setState({ editing_breakpoint_condition: true }); } render() { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'bkpt' does not exist on type 'Readonly<{... Remove this comment to see the full error message let b = this.props.bkpt, checked = b.enabled === "y" ? "checked" : "", source_line = this.get_source_line(b.fullname_to_display, b.line); @@ -219,8 +230,7 @@ class Breakpoint extends React.Component { style={{ width: "100%", fontSize: "0.9em", - borderWidth: "1px", - borderColor: "black" + borderWidth: "0px" }} className="lighttext table-condensed" > @@ -229,6 +239,7 @@ class Breakpoint extends React.Component { Breakpoints.enable_or_disable_bkpt(checked, b.number)} /> @@ -252,12 +263,15 @@ class Breakpoint extends React.Component { class Breakpoints extends React.Component { constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["breakpoints"]); } render() { let breakpoints_jsx = []; for (let b of store.get("breakpoints")) { + // @ts-expect-error ts-migrate(2322) FIXME: Property 'bkpt' does not exist on type 'IntrinsicA... Remove this comment to see the full error message breakpoints_jsx.push(); } @@ -267,37 +281,37 @@ class Breakpoints extends React.Component { return no breakpoints; } } - static enable_or_disable_bkpt(checked, bkpt_num) { + static enable_or_disable_bkpt(checked: any, bkpt_num: any) { if (checked) { GdbApi.run_gdb_command([`-break-disable ${bkpt_num}`, GdbApi.get_break_list_cmd()]); } else { GdbApi.run_gdb_command([`-break-enable ${bkpt_num}`, GdbApi.get_break_list_cmd()]); } } - static set_breakpoint_condition(condition, bkpt_num) { + static set_breakpoint_condition(condition: any, bkpt_num: any) { GdbApi.run_gdb_command([ `-break-condition ${bkpt_num} ${condition}`, GdbApi.get_break_list_cmd() ]); } - static remove_breakpoint_if_present(fullname, line) { + static remove_breakpoint_if_present(fullname: any, line: any) { if (Breakpoints.has_breakpoint(fullname, line)) { let number = Breakpoints.get_breakpoint_number(fullname, line); let cmd = [GdbApi.get_delete_break_cmd(number), GdbApi.get_break_list_cmd()]; GdbApi.run_gdb_command(cmd); } } - static add_or_remove_breakpoint(fullname, line) { + static add_or_remove_breakpoint(fullname: any, line: any) { if (Breakpoints.has_breakpoint(fullname, line)) { Breakpoints.remove_breakpoint_if_present(fullname, line); } else { Breakpoints.add_breakpoint(fullname, line); } } - static add_breakpoint(fullname, line) { + static add_breakpoint(fullname: any, line: any) { GdbApi.run_gdb_command(GdbApi.get_insert_break_cmd(fullname, line)); } - static has_breakpoint(fullname, line) { + static has_breakpoint(fullname: any, line: any) { let bkpts = store.get("breakpoints"); for (let b of bkpts) { if (b.fullname === fullname && b.line == line) { @@ -306,7 +320,7 @@ class Breakpoints extends React.Component { } return false; } - static get_breakpoint_number(fullname, line) { + static get_breakpoint_number(fullname: any, line: any) { let bkpts = store.get("breakpoints"); for (let b of bkpts) { if (b.fullname === fullname && b.line == line) { @@ -315,31 +329,31 @@ class Breakpoints extends React.Component { } console.error(`could not find breakpoint for ${fullname}:${line}`); } - static delete_breakpoint(breakpoint_number) { + static delete_breakpoint(breakpoint_number: any) { GdbApi.run_gdb_command([ GdbApi.get_delete_break_cmd(breakpoint_number), GdbApi.get_break_list_cmd() ]); } - static get_breakpoint_lines_for_file(fullname) { + static get_breakpoint_lines_for_file(fullname: any) { return store .get("breakpoints") - .filter(b => b.fullname_to_display === fullname && b.enabled === "y") - .map(b => parseInt(b.line)); + .filter((b: any) => b.fullname_to_display === fullname && b.enabled === "y") + .map((b: any) => parseInt(b.line)); } - static get_disabled_breakpoint_lines_for_file(fullname) { + static get_disabled_breakpoint_lines_for_file(fullname: any) { return store .get("breakpoints") - .filter(b => b.fullname_to_display === fullname && b.enabled !== "y") - .map(b => parseInt(b.line)); + .filter((b: any) => b.fullname_to_display === fullname && b.enabled !== "y") + .map((b: any) => parseInt(b.line)); } - static get_conditional_breakpoint_lines_for_file(fullname) { + static get_conditional_breakpoint_lines_for_file(fullname: any) { return store .get("breakpoints") - .filter(b => b.fullname_to_display === fullname && b.cond !== undefined) - .map(b => parseInt(b.line)); + .filter((b: any) => b.fullname_to_display === fullname && b.cond !== undefined) + .map((b: any) => parseInt(b.line)); } - static save_breakpoints(payload) { + static save_breakpoints(payload: any) { store.set("breakpoints", []); if (payload && payload.BreakpointTable && payload.BreakpointTable.body) { for (let breakpoint of payload.BreakpointTable.body) { @@ -347,7 +361,7 @@ class Breakpoints extends React.Component { } } } - static save_breakpoint(breakpoint) { + static save_breakpoint(breakpoint: any) { let bkpt = Object.assign({}, breakpoint); bkpt.is_parent_breakpoint = bkpt.addr === "(MULTIPLE)"; diff --git a/gdbgui/src/js/ControlButtons.jsx b/gdbgui/src/js/ControlButtons.tsx similarity index 80% rename from gdbgui/src/js/ControlButtons.jsx rename to gdbgui/src/js/ControlButtons.tsx index bbc6b758..d9c36ea8 100644 --- a/gdbgui/src/js/ControlButtons.jsx +++ b/gdbgui/src/js/ControlButtons.tsx @@ -1,12 +1,16 @@ import React from "react"; -import Actions from "./Actions.js"; -import GdbApi from "./GdbApi.jsx"; +import Actions from "./Actions"; +import GdbApi from "./GdbApi"; import { store } from "statorgfc"; -class ControlButtons extends React.Component { +type State = any; + +class ControlButtons extends React.Component<{}, State> { constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["gdb_pid"]); } render() { @@ -30,6 +34,7 @@ class ControlButtons extends React.Component { type="button" title={ "Continue until breakpoint is hit or inferior program exits keyboard shortcut: c" + + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. (initial_data.rr ? ". shift + c for reverse." : "") } className={btn_class} @@ -40,7 +45,7 @@ class ControlButtons extends React.Component {
); } else { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. val = _.isString(obj.value) ? Memory.make_addrs_into_links_react(obj.value) : obj.value; } return val; } - static change_radix(obj) { + static change_radix(obj: any) { if (obj._radix === 16) { obj._radix = 2; } else { @@ -281,10 +310,10 @@ class GdbVariable extends React.Component { * Get ul for a variable with or without children */ _get_ul_for_var( - expression, - mi_obj, - expr_type, - is_root, + expression: any, + mi_obj: any, + expr_type: any, + is_root: any, plus_or_minus = "", child_tree = "", numchild = 0 @@ -320,6 +349,7 @@ class GdbVariable extends React.Component { if (mi_obj.can_plot && mi_obj.show_plot) { // dots are not allowed in the dom as id's. replace with '-'. let id = mi_obj.dom_id_for_plot; + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. plot_button = ( ); + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. plot_content =
; } else if (mi_obj.can_plot && !mi_obj.show_plot) { + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. plot_button = ( {_.trim(mi_obj.type) || ""}
@@ -363,12 +396,12 @@ class GdbVariable extends React.Component { ); } - static _get_full_path(obj) { + static _get_full_path(obj: any) { if (!obj) { return ""; } - function update_path(path, obj) { + function update_path(path: any, obj: any) { let potential_addition = obj.expression || obj.exp; if ( potential_addition === "public" || @@ -399,13 +432,13 @@ class GdbVariable extends React.Component { } return path; } - static create_variable(expression, expr_type) { + static create_variable(expression: any, expr_type: any) { VarCreator.create_variable(expression, expr_type); } - static gdb_created_root_variable(r) { + static gdb_created_root_variable(r: any) { VarCreator.created_variable(r); } - static gdb_variable_fetch_failed(r) { + static gdb_variable_fetch_failed(r: any) { VarCreator.fetch_failed(r); } /** @@ -414,7 +447,7 @@ class GdbVariable extends React.Component { * object * @param r (object): gdb mi object */ - static gdb_created_children_variables(r) { + static gdb_created_children_variables(r: any) { // example reponse payload: // "payload": { // "has_more": "0", @@ -451,7 +484,7 @@ class GdbVariable extends React.Component { let parent_obj = GdbVariable.get_obj_from_gdb_var_name(expressions, parent_name); if (parent_obj) { // prepare all the child objects we received for local storage - let children = r.payload.children.map(child_obj => + let children = r.payload.children.map((child_obj: any) => GdbVariable.prepare_gdb_obj_for_storage(child_obj, parent_obj) ); // save these children as a field to their parent @@ -477,7 +510,7 @@ class GdbVariable extends React.Component { * @param obj (object): mi object returned from gdb * @param expr_type (str): type of expression being created (see store creation for documentation) */ - static prepare_gdb_obj_for_storage(obj, parent) { + static prepare_gdb_obj_for_storage(obj: any, parent: any) { let new_obj = Object.assign({}, obj); // obj was copied, now add some additional fields used by gdbgui @@ -523,7 +556,7 @@ class GdbVariable extends React.Component { GdbVariable._update_radix_values(new_obj); // mutates new_obj return new_obj; } - static _update_numeric_properties(obj) { + static _update_numeric_properties(obj: any) { let value = obj.value; if (obj.value.startsWith("0x")) { value = parseInt(obj.value, 16); @@ -533,7 +566,7 @@ class GdbVariable extends React.Component { obj.can_plot = obj.is_numeric && obj.expr_type === "expr"; obj.is_int = obj.is_numeric ? obj._float_value % 1 === 0 : false; } - static _update_radix_values(obj) { + static _update_radix_values(obj: any) { if (obj.is_int) { obj._int_value_decimal = parseInt(obj.value); if (obj._radix < 2 || obj._radix > 36) { @@ -551,7 +584,7 @@ class GdbVariable extends React.Component { * function render a plot on an existing element * @param obj: object to make a plot for */ - static _make_plot(obj) { + static _make_plot(obj: any) { let id = "#" + obj.dom_id_for_plot, // this div should have been created already jq = $(id), data = [], @@ -564,6 +597,7 @@ class GdbVariable extends React.Component { } // make the plot + // @ts-expect-error ts-migrate(2339) FIXME: Property 'plot' does not exist on type 'JQueryStat... Remove this comment to see the full error message $.plot( jq, [ @@ -602,7 +636,7 @@ class GdbVariable extends React.Component { * If so, update the dom accordingly * @param obj: expression object to plot (may have children to plot too) */ - static plot_var_and_children(obj) { + static plot_var_and_children(obj: any) { if (obj.show_plot) { GdbVariable._make_plot(obj); } @@ -610,7 +644,7 @@ class GdbVariable extends React.Component { GdbVariable.plot_var_and_children(child); } } - static fetch_and_show_children_for_var(gdb_var_name) { + static fetch_and_show_children_for_var(gdb_var_name: any) { let expressions = store.get("expressions"); let obj = GdbVariable.get_obj_from_gdb_var_name(expressions, gdb_var_name); // mutate object by reference @@ -624,7 +658,7 @@ class GdbVariable extends React.Component { // already have child data, re-render will occur from event dispatch } } - static hide_children_in_ui(gdb_var_name) { + static hide_children_in_ui(gdb_var_name: any) { let expressions = store.get("expressions"), obj = GdbVariable.get_obj_from_gdb_var_name(expressions, gdb_var_name); if (obj) { @@ -632,10 +666,10 @@ class GdbVariable extends React.Component { store.set("expressions", expressions); } } - static click_toggle_children_visibility(gdb_variable_name) { + static click_toggle_children_visibility(gdb_variable_name: any) { GdbVariable._toggle_children_visibility(gdb_variable_name); } - static _toggle_children_visibility(gdb_var_name) { + static _toggle_children_visibility(gdb_var_name: any) { // get data object, which has field that says whether its expanded or not let obj = GdbVariable.get_obj_from_gdb_var_name( store.get("expressions"), @@ -655,7 +689,7 @@ class GdbVariable extends React.Component { console.error("developer error - expected to find gdb variable object"); } } - static click_toggle_plot(gdb_var_name) { + static click_toggle_plot(gdb_var_name: any) { let expressions = store.get("expressions"), // get data object, which has field that says whether its expanded or not obj = GdbVariable.get_obj_from_gdb_var_name(expressions, gdb_var_name); @@ -665,7 +699,7 @@ class GdbVariable extends React.Component { } } static get_update_cmds() { - function _get_cmds_for_obj(obj) { + function _get_cmds_for_obj(obj: any) { let cmds = [`-var-update --all-values ${obj.name}`]; for (let child of obj.children) { cmds = cmds.concat(_get_cmds_for_obj(child)); @@ -673,13 +707,13 @@ class GdbVariable extends React.Component { return cmds; } - let cmds = []; + let cmds: any = []; for (let obj of store.get("expressions")) { cmds = cmds.concat(_get_cmds_for_obj(obj)); } return cmds; } - static handle_changelist(changelist_array) { + static handle_changelist(changelist_array: any) { for (let changelist of changelist_array) { let expressions = store.get("expressions"), obj = GdbVariable.get_obj_from_gdb_var_name(expressions, changelist.name); @@ -690,7 +724,7 @@ class GdbVariable extends React.Component { ChildVarFetcher.fetch_children(changelist["name"], obj.expr_type); } if ("new_children" in changelist) { - let new_children = changelist.new_children.map(child_obj => + let new_children = changelist.new_children.map((child_obj: any) => GdbVariable.prepare_gdb_obj_for_storage(child_obj, obj) ); obj.children = obj.children.concat(new_children); @@ -708,10 +742,10 @@ class GdbVariable extends React.Component { } } } - static click_draw_tree_gdb_variable(gdb_variable) { + static click_draw_tree_gdb_variable(gdb_variable: any) { store.set("root_gdb_tree_var", gdb_variable); } - static delete_gdb_variable(gdbvar) { + static delete_gdb_variable(gdbvar: any) { // delete locally GdbVariable._delete_local_gdb_var_data(gdbvar); // delete in gdb too @@ -721,15 +755,16 @@ class GdbVariable extends React.Component { * Delete local copy of gdb variable (all its children are deleted too * since they are stored as fields in the object) */ - static _delete_local_gdb_var_data(gdb_var_name) { + static _delete_local_gdb_var_data(gdb_var_name: any) { let expressions = store.get("expressions"); - _.remove(expressions, v => v.name === gdb_var_name); + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. + _.remove(expressions, (v: any) => v.name === gdb_var_name); store.set("expressions", expressions); } /** * Locally save the variable to our cached variables */ - static save_new_expression(expression, expr_type, obj) { + static save_new_expression(expression: any, expr_type: any, obj: any) { let new_obj = GdbVariable.prepare_gdb_obj_for_storage(obj, null); new_obj.expression = expression; let expressions = store.get("expressions"); @@ -739,7 +774,7 @@ class GdbVariable extends React.Component { /** * Get child variable with a particular name */ - static get_child_with_name(children, name) { + static get_child_with_name(children: any, name: any) { for (let child of children) { if (child.name === name) { return child; @@ -747,14 +782,16 @@ class GdbVariable extends React.Component { } return undefined; } - static get_root_name_from_gdbvar_name(gdb_var_name) { + static get_root_name_from_gdbvar_name(gdb_var_name: any) { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isString(gdb_var_name)) { return gdb_var_name.split(".")[0]; } else { return ""; } } - static get_child_names_from_gdbvar_name(gdb_var_name) { + static get_child_names_from_gdbvar_name(gdb_var_name: any) { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isString(gdb_var_name)) { return gdb_var_name.split(".").slice(1, gdb_var_name.length); } else { @@ -768,13 +805,13 @@ class GdbVariable extends React.Component { * @param gdb_var_name: gdb variable name to find corresponding cached object. Can have dot notation * @return: object if found, or undefined if not found */ - static get_obj_from_gdb_var_name(expressions, gdb_var_name) { + static get_obj_from_gdb_var_name(expressions: any, gdb_var_name: any) { // gdb provides names in dot notation // let gdb_var_names = gdb_var_name.split('.'), let top_level_var_name = GdbVariable.get_root_name_from_gdbvar_name(gdb_var_name), children_names = GdbVariable.get_child_names_from_gdbvar_name(gdb_var_name); - let objs = expressions.filter(v => v.name === top_level_var_name); + let objs = expressions.filter((v: any) => v.name === top_level_var_name); if (objs.length === 1) { // we found our top level object diff --git a/gdbgui/src/js/GdbguiModal.jsx b/gdbgui/src/js/GdbguiModal.tsx similarity index 77% rename from gdbgui/src/js/GdbguiModal.jsx rename to gdbgui/src/js/GdbguiModal.tsx index c857eb68..611a66d2 100644 --- a/gdbgui/src/js/GdbguiModal.jsx +++ b/gdbgui/src/js/GdbguiModal.tsx @@ -1,10 +1,15 @@ import React from "react"; -import Actions from "./Actions.js"; +import Actions from "./Actions"; import { store } from "statorgfc"; -class Modal extends React.Component { +type State = any; + +class Modal extends React.Component<{}, State> { + fullscreen_node: any; constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["show_modal", "modal_body", "modal_header"]); } render() { diff --git a/gdbgui/src/js/GlobalEvents.js b/gdbgui/src/js/GlobalEvents.ts similarity index 81% rename from gdbgui/src/js/GlobalEvents.js rename to gdbgui/src/js/GlobalEvents.ts index efad962e..842b57b9 100644 --- a/gdbgui/src/js/GlobalEvents.js +++ b/gdbgui/src/js/GlobalEvents.ts @@ -2,18 +2,19 @@ * Setup global DOM events */ -import constants from "./constants.js"; -import GdbApi from "./GdbApi.jsx"; +import constants from "./constants"; +import GdbApi from "./GdbApi"; const GlobalEvents = { init: function() { - window.onkeydown = function(e) { + window.onkeydown = function(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { // when pressing enter in an input, don't redirect entire page! e.preventDefault(); } }; $("body").on("keydown", GlobalEvents.body_keydown); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'tooltip' does not exist on type 'JQuery<... Remove this comment to see the full error message $('[data-toggle="tooltip"]').tooltip(); window.onbeforeunload = () => @@ -23,7 +24,7 @@ const GlobalEvents = { * keyboard shortcuts to interact with gdb. * enabled only when key is depressed on a target that is NOT an input. */ - body_keydown: function(e) { + body_keydown: function(e: any) { let modifier = e.altKey || e.ctrlKey || e.metaKey; if (e.target.nodeName !== "INPUT" && !modifier) { @@ -44,6 +45,7 @@ const GlobalEvents = { GdbApi.click_next_instruction_button(e.shiftKey); } else if (e.keyCode === constants.COMMA_BUTTON_NUM) { GdbApi.click_step_instruction_button(e.shiftKey); + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. } else if (initial_data.rr && e.keyCode === constants.LEFT_BUTTON_NUM) { // reverse GdbApi.click_next_button(true); diff --git a/gdbgui/src/js/HoverVar.jsx b/gdbgui/src/js/HoverVar.tsx similarity index 67% rename from gdbgui/src/js/HoverVar.jsx rename to gdbgui/src/js/HoverVar.tsx index 52751ba1..8d6a7c62 100644 --- a/gdbgui/src/js/HoverVar.jsx +++ b/gdbgui/src/js/HoverVar.tsx @@ -5,8 +5,8 @@ import React from "react"; import { store } from "statorgfc"; -import constants from "./constants.js"; -import GdbVariable from "./GdbVariable.jsx"; +import constants from "./constants"; +import GdbVariable from "./GdbVariable"; class HoverVar extends React.Component { static enter_timeout = undefined; // debounce fetching the expression @@ -14,7 +14,10 @@ class HoverVar extends React.Component { static left = 0; static top = 0; + obj: any; + constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // when hovering over a potential variable @@ -28,10 +31,11 @@ class HoverVar extends React.Component { $("body").on("mouseenter", "#hovervar", HoverVar.mouseover_hover_window); $("body").on("mouseleave", "#hovervar", HoverVar.mouseout_hover_window); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["expressions"]); } render() { - let hover_objs = store.get("expressions").filter(o => o.expr_type === "hover"), + let hover_objs = store.get("expressions").filter((o: any) => o.expr_type === "hover"), obj; if (Array.isArray(hover_objs) && hover_objs.length === 1) { obj = hover_objs[0]; @@ -45,8 +49,10 @@ class HoverVar extends React.Component { backgroundColor: "white" }; return ( + // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type '"absolute... Remove this comment to see the full error message
no variable hovered
; } } - static mouseover_variable(e) { + static mouseover_variable(e: any) { HoverVar.clear_hover_state(); let rect = e.target.getBoundingClientRect(), @@ -69,28 +75,31 @@ class HoverVar extends React.Component { HoverVar.top = rect.bottom; const WAIT_TIME_SEC = 0.5; + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Timeout' is not assignable to type 'undefine... Remove this comment to see the full error message HoverVar.enter_timeout = setTimeout(() => { if (store.get("inferior_program") === constants.inferior_states.paused) { let ignore_errors = true; + // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 3. GdbVariable.create_variable(var_name, "hover", ignore_errors); } }, WAIT_TIME_SEC * 1000); } - static mouseout_variable(e) { + static mouseout_variable(e: any) { void e; const WAIT_TIME_SEC = 0.1; + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Timeout' is not assignable to type 'undefine... Remove this comment to see the full error message HoverVar.exit_timeout = setTimeout(() => { HoverVar.clear_hover_state(); }, WAIT_TIME_SEC * 1000); } - static mouseover_hover_window(e) { + static mouseover_hover_window(e: any) { void e; // Mouse went from hovering over variable name in source code to // hovering over the window showing the contents of the variable. // Don't remove the window in this case. clearTimeout(HoverVar.exit_timeout); } - static mouseout_hover_window(e) { + static mouseout_hover_window(e: any) { void e; HoverVar.clear_hover_state(); } @@ -99,8 +108,8 @@ class HoverVar extends React.Component { clearTimeout(HoverVar.exit_timeout); let exprs_objs_to_remove = store .get("expressions") - .filter(obj => obj.expr_type === "hover"); - exprs_objs_to_remove.map(obj => GdbVariable.delete_gdb_variable(obj.name)); + .filter((obj: any) => obj.expr_type === "hover"); + exprs_objs_to_remove.map((obj: any) => GdbVariable.delete_gdb_variable(obj.name)); } } diff --git a/gdbgui/src/js/InferiorProgramInfo.jsx b/gdbgui/src/js/InferiorProgramInfo.tsx similarity index 75% rename from gdbgui/src/js/InferiorProgramInfo.jsx rename to gdbgui/src/js/InferiorProgramInfo.tsx index 09f64619..a5a8ff67 100644 --- a/gdbgui/src/js/InferiorProgramInfo.jsx +++ b/gdbgui/src/js/InferiorProgramInfo.tsx @@ -1,10 +1,13 @@ import React from "react"; -import Actions from "./Actions.js"; +import Actions from "./Actions"; import { store } from "statorgfc"; -class InferiorProgramInfo extends React.Component { +type State = any; + +class InferiorProgramInfo extends React.Component<{}, State> { constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); this.get_li_for_signal = this.get_li_for_signal.bind(this); this.get_dropdown = this.get_dropdown.bind(this); @@ -12,29 +15,35 @@ class InferiorProgramInfo extends React.Component { selected_signal: "SIGINT", other_pid: "" }; + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["inferior_pid", "gdb_pid"]); } - get_li_for_signal(s, signal_key) { + get_li_for_signal(s: any, signal_key: any) { let onclick = function() { let obj = {}; + // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message obj[signal_key] = s; + // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message this.setState(obj); }.bind(this); return (
  • + {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'signals' does not exist on type 'Readonl... Remove this comment to see the full error message */} {`${s} (${this.props.signals[s]})`}
  • ); } - get_signal_choices(signal_key) { + get_signal_choices(signal_key: any) { let signals = []; // push SIGINT and SIGKILL to top + // @ts-expect-error ts-migrate(2339) FIXME: Property 'signals' does not exist on type 'Readonl... Remove this comment to see the full error message for (let s in this.props.signals) { if (s === "SIGKILL" || s === "SIGINT") { signals.push(this.get_li_for_signal(s, signal_key)); } } + // @ts-expect-error ts-migrate(2339) FIXME: Property 'signals' does not exist on type 'Readonl... Remove this comment to see the full error message for (let s in this.props.signals) { if (s !== "SIGKILL" && s !== "SIGINT") { signals.push(this.get_li_for_signal(s, signal_key)); diff --git a/gdbgui/src/js/InitialStoreData.js b/gdbgui/src/js/InitialStoreData.ts similarity index 77% rename from gdbgui/src/js/InitialStoreData.js rename to gdbgui/src/js/InitialStoreData.ts index 53c2e288..79df95b7 100644 --- a/gdbgui/src/js/InitialStoreData.js +++ b/gdbgui/src/js/InitialStoreData.ts @@ -1,6 +1,6 @@ /* global initial_data */ /* global debug */ -import constants from "./constants.js"; +import constants from "./constants"; /** * The initial store data. Keys cannot be added after initialization. @@ -9,13 +9,16 @@ import constants from "./constants.js"; */ const initial_store_data = { // environment + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'debug'. debug: debug, // if gdbgui is run in debug mode - interpreter: initial_data.interpreter, // either 'gdb' or 'llvm' + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. gdbgui_version: initial_data.gdbgui_version, latest_gdbgui_version: "(not fetched)", gdb_version: undefined, // this is parsed from gdb's output gdb_version_array: [], // this is parsed from gdb's output gdb_pid: undefined, + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. + gdb_command: initial_data.gdb_command, can_fetch_register_values: true, // set to false if using Rust and gdb v7.12.x (see https://github.com/cs01/gdbgui/issues/64) show_settings: false, @@ -31,7 +34,9 @@ const initial_store_data = { textarea_to_copy_to_clipboard: {}, // will be replaced with textarea dom node // preferences + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. themes: initial_data.themes, + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. current_theme: localStorage.getItem("theme") || initial_data.themes[0], highlight_source_code: true, // get saved boolean to highlight source code max_lines_of_code_to_fetch: constants.default_max_lines_of_code_to_fetch, @@ -39,6 +44,7 @@ const initial_store_data = { pretty_print: true, // whether gdb should "pretty print" variables. There is an option for this in Settings refresh_state_after_sending_console_command: true, // If true, send commands to refresh GUI store after each command is sent from console + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'debug'. show_all_sent_commands_in_console: debug, // show all sent commands if in debug mode inferior_program: constants.inferior_states.unknown, @@ -100,13 +106,18 @@ const initial_store_data = { gdb_console_entries: [], + // if we try to write something before the websocket is connected, store it here + queuedGdbCommands: [], + show_filesystem: false, - middle_panes_split_obj: {} + middle_panes_split_obj: {}, + gdbguiPty: null }; -function get_stored(key, default_val) { +function get_stored(key: any, default_val: any) { try { if (localStorage.hasOwnProperty(key)) { + // @ts-expect-error ts-migrate(2345) FIXME: Type 'null' is not assignable to type 'string'. let cached = JSON.parse(localStorage.getItem(key)); if (typeof cached === typeof default_val) { return cached; @@ -122,12 +133,16 @@ function get_stored(key, default_val) { // restore saved localStorage data for (let key in initial_store_data) { + // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message let default_val = initial_store_data[key]; + // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message initial_store_data[key] = get_stored(key, default_val); } if (localStorage.hasOwnProperty("max_lines_of_code_to_fetch")) { + // @ts-expect-error ts-migrate(2345) FIXME: Type 'null' is not assignable to type 'string'. let savedval = JSON.parse(localStorage.getItem("max_lines_of_code_to_fetch")); + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isInteger(savedval) && savedval > 0) { initial_store_data["max_lines_of_code_to_fetch"] = savedval; } diff --git a/gdbgui/src/js/Links.tsx b/gdbgui/src/js/Links.tsx index d8505275..da72d1bc 100644 --- a/gdbgui/src/js/Links.tsx +++ b/gdbgui/src/js/Links.tsx @@ -1,4 +1,4 @@ -import Actions from "./Actions.js"; +import Actions from "./Actions"; import * as React from "react"; import CopyToClipboard from "./CopyToClipboard"; import MemoryLink from "./MemoryLink"; @@ -41,7 +41,7 @@ export class FileLink extends React.Component { > {this.props.file} {sep} - {line} + {line > 0 ? line : ""} diff --git a/gdbgui/src/js/Locals.jsx b/gdbgui/src/js/Locals.tsx similarity index 69% rename from gdbgui/src/js/Locals.jsx rename to gdbgui/src/js/Locals.tsx index a3b95d9c..22c3be8d 100644 --- a/gdbgui/src/js/Locals.jsx +++ b/gdbgui/src/js/Locals.tsx @@ -5,18 +5,21 @@ import React from "react"; import { store } from "statorgfc"; -import GdbVariable from "./GdbVariable.jsx"; +import GdbVariable from "./GdbVariable"; class Locals extends React.Component { constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["expressions", "locals"]); } render() { let content = []; + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let sorted_local_objs = _.sortBy( store.get("locals"), - unsorted_obj => unsorted_obj.name + (unsorted_obj: any) => unsorted_obj.name ); for (let local of sorted_local_objs) { @@ -24,6 +27,7 @@ class Locals extends React.Component { if (obj) { content.push( obj.expr_type === "local"); - exprs_objs_to_remove.map(obj => GdbVariable.delete_gdb_variable(obj.name)); + .filter((obj: any) => obj.expr_type === "local"); + exprs_objs_to_remove.map((obj: any) => GdbVariable.delete_gdb_variable(obj.name)); } static clear() { store.set("locals", []); Locals.clear_autocreated_exprs(); } - static save_locals(locals) { - let locals_with_meta = locals.map(local => { + static save_locals(locals: any) { + let locals_with_meta = locals.map((local: any) => { // add field to local local.can_be_expanded = Locals.can_local_be_expanded(local) ? true : false; return local; }); store.set("locals", locals_with_meta); } - static can_local_be_expanded(local) { + static can_local_be_expanded(local: any) { // gdb returns list of locals. We may want to turn that local into a GdbVariable // to explore its children if ("value" in local) { diff --git a/gdbgui/src/js/Memory.jsx b/gdbgui/src/js/Memory.tsx similarity index 87% rename from gdbgui/src/js/Memory.jsx rename to gdbgui/src/js/Memory.tsx index 60745686..0383e9fe 100644 --- a/gdbgui/src/js/Memory.jsx +++ b/gdbgui/src/js/Memory.tsx @@ -6,20 +6,25 @@ */ import { store } from "statorgfc"; -import GdbApi from "./GdbApi.jsx"; -import constants from "./constants.js"; -import ReactTable from "./ReactTable.jsx"; +import GdbApi from "./GdbApi"; +import constants from "./constants"; +import ReactTable from "./ReactTable"; +// @ts-expect-error ts-migrate(2691) FIXME: An import path cannot end with a '.tsx' extension.... Remove this comment to see the full error message import MemoryLink from "./MemoryLink.tsx"; import Actions from "./Actions"; import React from "react"; -class Memory extends React.Component { +type State = any; + +class Memory extends React.Component<{}, State> { static MAX_ADDRESS_DELTA_BYTES = 1000; static DEFAULT_ADDRESS_DELTA_BYTES = 31; static DEFAULT_BYTES_PER_LINE = 8; constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "memory_cache", "start_addr", @@ -114,6 +119,7 @@ class Memory extends React.Component { ]); } + // @ts-expect-error ts-migrate(2769) FIXME: Type 'string' is not assignable to type 'never'. return ; } render() { @@ -164,12 +170,12 @@ class Memory extends React.Component {
    ); } - static keypress_on_input(e) { + static keypress_on_input(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { Memory.fetch_memory_from_state(); } } - static set_inputs_from_address(addr) { + static set_inputs_from_address(addr: any) { // set inputs in DOM store.set("start_addr", "0x" + parseInt(addr, 16).toString(16)); store.set( @@ -180,7 +186,9 @@ class Memory extends React.Component { } static get_gdb_commands_from_state() { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let start_addr = parseInt(_.trim(store.get("start_addr")), 16), + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. end_addr = parseInt(_.trim(store.get("end_addr")), 16); if (!window.isNaN(start_addr) && window.isNaN(end_addr)) { @@ -188,6 +196,7 @@ class Memory extends React.Component { } let cmds = []; + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isInteger(start_addr) && end_addr) { if (start_addr > end_addr) { end_addr = start_addr + Memory.DEFAULT_ADDRESS_DELTA_BYTES; @@ -232,6 +241,7 @@ class Memory extends React.Component { static click_read_preceding_memory() { // update starting value, then re-fetch let NUM_ROWS = 3; + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let start_addr = parseInt(_.trim(store.get("start_addr")), 16), byte_offset = store.get("bytes_per_line") * NUM_ROWS; store.set("start_addr", "0x" + (start_addr - byte_offset).toString(16)); @@ -241,6 +251,7 @@ class Memory extends React.Component { static click_read_more_memory() { // update ending value, then re-fetch let NUM_ROWS = 3; + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let end_addr = parseInt(_.trim(store.get("end_addr")), 16), byte_offset = store.get("bytes_per_line") * NUM_ROWS; store.set("end_addr", "0x" + (end_addr + byte_offset).toString(16)); @@ -251,7 +262,7 @@ class Memory extends React.Component { * @param text: string to convert address-like text into clickable components * return react component */ - static make_addrs_into_links_react(text) { + static make_addrs_into_links_react(text: any) { let matches = text.match(/(0x[\d\w]+)/g); if (text && matches && matches.length) { let addr = matches[0]; @@ -274,7 +285,7 @@ class Memory extends React.Component { } } - static add_value_to_cache(hex_str, hex_val) { + static add_value_to_cache(hex_str: any, hex_val: any) { // strip leading zeros off address provided by gdb // i.e. 0x000123 turns to // 0x123 diff --git a/gdbgui/src/js/MemoryLink.tsx b/gdbgui/src/js/MemoryLink.tsx index bac8e860..f75e720f 100644 --- a/gdbgui/src/js/MemoryLink.tsx +++ b/gdbgui/src/js/MemoryLink.tsx @@ -1,11 +1,13 @@ import * as React from "react"; -import Memory from "./Memory.jsx"; +import Memory from "./Memory"; -type Props = { +type OwnProps = { addr: string; style?: React.CSSProperties; }; +type Props = OwnProps & typeof MemoryLink.defaultProps; + class MemoryLink extends React.Component { render() { // turn 0x00000000000000 into 0x0 diff --git a/gdbgui/src/js/MiddleLeft.jsx b/gdbgui/src/js/MiddleLeft.tsx similarity index 70% rename from gdbgui/src/js/MiddleLeft.jsx rename to gdbgui/src/js/MiddleLeft.tsx index 0ab35f1b..b3ae19c2 100644 --- a/gdbgui/src/js/MiddleLeft.jsx +++ b/gdbgui/src/js/MiddleLeft.tsx @@ -3,11 +3,15 @@ */ import React from "react"; -import SourceCode from "./SourceCode.jsx"; -import FileOps from "./FileOps.jsx"; +import SourceCode from "./SourceCode"; +import FileOps from "./FileOps"; class MiddleLeft extends React.Component { + fetch_more_at_top_timeout: any; + onscroll_timeout: any; + source_code_container_node: any; constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); this.onscroll_container = this.onscroll_container.bind(this); this.onscroll_timeout = null; @@ -25,6 +29,7 @@ class MiddleLeft extends React.Component { ); } componentDidMount() { + // @ts-expect-error ts-migrate(2322) FIXME: Type 'JQuery' is not assignable to ty... Remove this comment to see the full error message SourceCode.el_code_container = $("#code_container"); // todo: no jquery if (this.source_code_container_node) { @@ -43,6 +48,7 @@ class MiddleLeft extends React.Component { let fetching_for_top = false; // don't fetch for more at bottom and top at same time if (SourceCode.view_more_top_node) { let { is_visible } = SourceCode.is_source_line_visible( + // @ts-expect-error ts-migrate(2769) FIXME: Argument of type 'null' is not assignable to param... Remove this comment to see the full error message $(SourceCode.view_more_top_node) ); if (is_visible) { @@ -53,6 +59,7 @@ class MiddleLeft extends React.Component { if (!fetching_for_top && SourceCode.view_more_bottom_node) { let { is_visible } = SourceCode.is_source_line_visible( + // @ts-expect-error ts-migrate(2769) FIXME: Argument of type 'null' is not assignable to param... Remove this comment to see the full error message $(SourceCode.view_more_bottom_node) ); if (is_visible) { diff --git a/gdbgui/src/js/ReactTable.jsx b/gdbgui/src/js/ReactTable.jsx deleted file mode 100644 index 742daf79..00000000 --- a/gdbgui/src/js/ReactTable.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; - -class TableRow extends React.Component { - get_tds() { - let tds = []; - for (let i in this.props.data) { - tds.push({this.props.data[i]}); - } - return tds; - } - - render() { - return {this.get_tds()}; - } -} - -class ReactTable extends React.Component { - static defaultProps = { header: [] }; - render_row(row_data, i) { - return ; - } - - render_head() { - let ths = [], - i = 0; - for (let th_data of this.props.header) { - ths.push({th_data}); - i++; - } - return ths; - } - - render() { - let classes = ["table", "table-condensed"].concat(this.props.classes); - return ( - - - {this.render_head()} - - {this.props.data.map(this.render_row)} -
    - ); - } -} - -export default ReactTable; diff --git a/gdbgui/src/js/ReactTable.tsx b/gdbgui/src/js/ReactTable.tsx new file mode 100644 index 00000000..9476f1bb --- /dev/null +++ b/gdbgui/src/js/ReactTable.tsx @@ -0,0 +1,54 @@ +import React from "react"; + +class TableRow extends React.Component { + className: any; + get_tds() { + let tds = []; + // @ts-expect-error ts-migrate(2339) FIXME: Property 'data' does not exist on type 'Readonly<{... Remove this comment to see the full error message + for (let i in this.props.data) { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'data' does not exist on type 'Readonly<{... Remove this comment to see the full error message + tds.push({this.props.data[i]}); + } + return tds; + } + + render() { + return {this.get_tds()}; + } +} + +class ReactTable extends React.Component { + static defaultProps = { header: [] }; + render_row(row_data: any, i: any) { + // @ts-expect-error ts-migrate(2769) FIXME: Property 'data' does not exist on type 'IntrinsicA... Remove this comment to see the full error message + return ; + } + + render_head() { + let ths = [], + i = 0; + // @ts-expect-error ts-migrate(2339) FIXME: Property 'header' does not exist on type 'Readonly... Remove this comment to see the full error message + for (let th_data of this.props.header) { + ths.push({th_data}); + i++; + } + return ths; + } + + render() { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'classes' does not exist on type 'Readonl... Remove this comment to see the full error message + let classes = ["table", "table-condensed"].concat(this.props.classes); + return ( + // @ts-expect-error ts-migrate(2339) FIXME: Property 'style' does not exist on type 'Readonly<... Remove this comment to see the full error message + + + {this.render_head()} + + {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'data' does not exist on type 'Readonly<{... Remove this comment to see the full error message */} + {this.props.data.map(this.render_row)} +
    + ); + } +} + +export default ReactTable; diff --git a/gdbgui/src/js/Registers.jsx b/gdbgui/src/js/Registers.tsx similarity index 70% rename from gdbgui/src/js/Registers.jsx rename to gdbgui/src/js/Registers.tsx index 18abba62..e423928a 100644 --- a/gdbgui/src/js/Registers.jsx +++ b/gdbgui/src/js/Registers.tsx @@ -4,19 +4,23 @@ import React from "react"; import { store } from "statorgfc"; -import constants from "./constants.js"; -import ReactTable from "./ReactTable.jsx"; -import Memory from "./Memory.jsx"; -import GdbApi from "./GdbApi.jsx"; -import register_descriptions from "./register_descriptions.js"; +import constants from "./constants"; +import ReactTable from "./ReactTable"; +import Memory from "./Memory"; +import GdbApi from "./GdbApi"; +import register_descriptions from "./register_descriptions"; const MAX_REGISTER_NAME_FETCH_COUNT = 5; let register_name_fetch_count = 0, - register_name_fetch_timeout = null; + register_name_fetch_timeout: any = null; -class Registers extends React.Component { +type State = any; + +class Registers extends React.Component<{}, State> { constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "inferior_program", "previous_register_values", @@ -26,7 +30,15 @@ class Registers extends React.Component { ]); } static get_update_cmds() { - let cmds = []; + let cmds: any = []; + if ( + [constants.inferior_states.paused, constants.inferior_states.running].indexOf( + // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'boolean' is not assignable to pa... Remove this comment to see the full error message + store.get("inferior_program") > -1 + ) + ) { + return cmds; + } if (store.get("can_fetch_register_values") === true) { if (store.get("register_names").length === 0) { if (register_name_fetch_count <= MAX_REGISTER_NAME_FETCH_COUNT) { @@ -48,9 +60,12 @@ class Registers extends React.Component { } return cmds; } - static cache_register_names(names) { + static cache_register_names(names: any) { // filter out non-empty names - store.set("register_names", names.filter(name => name)); + store.set( + "register_names", + names.filter((name: any) => name) + ); } static clear_register_name_cache() { store.set("register_names", []); @@ -90,16 +105,19 @@ class Registers extends React.Component { for (let i in register_names) { let name = register_names[i], - obj = _.find(register_values, v => v["number"] === i), + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. + obj = _.find(register_values, (v: any) => v["number"] === i), hex_val_raw = "", disp_hex_val = "", disp_dec_val = "", + // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message register_description = register_descriptions[name] || ""; if (obj && obj.value) { hex_val_raw = obj["value"]; - let old_obj = _.find(prev_register_values, v => v["number"] === i), + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. + let old_obj = _.find(prev_register_values, (v: any) => v["number"] === i), old_hex_val_raw, changed = false; if (old_obj) { @@ -120,7 +138,9 @@ class Registers extends React.Component { if (changed) { name = {name}; + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. disp_hex_val = {disp_hex_val}; + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. disp_dec_val = {disp_dec_val}; } } @@ -135,6 +155,7 @@ class Registers extends React.Component { return ( diff --git a/gdbgui/src/js/RightSidebar.jsx b/gdbgui/src/js/RightSidebar.tsx similarity index 59% rename from gdbgui/src/js/RightSidebar.jsx rename to gdbgui/src/js/RightSidebar.tsx index 07b12601..e77dbb03 100644 --- a/gdbgui/src/js/RightSidebar.jsx +++ b/gdbgui/src/js/RightSidebar.tsx @@ -5,35 +5,47 @@ import React from "react"; -import Breakpoints from "./Breakpoints.jsx"; -import constants from "./constants.js"; -import Expressions from "./Expressions.jsx"; -import GdbMiOutput from "./GdbMiOutput.jsx"; -import InferiorProgramInfo from "./InferiorProgramInfo.jsx"; -import Locals from "./Locals.jsx"; -import Memory from "./Memory.jsx"; -import Registers from "./Registers.jsx"; -import Tree from "./Tree.js"; -import Threads from "./Threads.jsx"; -import ToolTipTourguide from "./ToolTipTourguide.jsx"; - -let onmouseup_in_parent_callbacks = [], - onmousemove_in_parent_callbacks = []; +import Breakpoints from "./Breakpoints"; +import constants from "./constants"; +import Expressions from "./Expressions"; +import GdbMiOutput from "./GdbMiOutput"; +import InferiorProgramInfo from "./InferiorProgramInfo"; +import Locals from "./Locals"; +import Memory from "./Memory"; +import Registers from "./Registers"; +import Tree from "./Tree"; +import Threads from "./Threads"; +import ToolTipTourguide from "./ToolTipTourguide"; + +let onmouseup_in_parent_callbacks: any = [], + onmousemove_in_parent_callbacks: any = []; let onmouseup_in_parent_callback = function() { + // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'fn' implicitly has an 'any' type. onmouseup_in_parent_callbacks.map(fn => fn()); }; -let onmousemove_in_parent_callback = function(e) { +let onmousemove_in_parent_callback = function(e: any) { + // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'fn' implicitly has an 'any' type. onmousemove_in_parent_callbacks.map(fn => { fn(e); }); }; -class Collapser extends React.Component { +type OwnCollapserState = any; + +type CollapserState = OwnCollapserState & typeof Collapser.defaultProps; + +class Collapser extends React.Component<{}, CollapserState> { static defaultProps = { collapsed: false, id: "" }; - constructor(props) { + _height_when_clicked: any; + _page_y_orig: any; + _resizing: any; + collapser_box_node: any; + constructor(props: {}) { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); this.state = { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'collapsed' does not exist on type '{}'. collapsed: props.collapsed, autosize: true, height_px: null, // if an integer, force height to this value @@ -51,7 +63,7 @@ class Collapser extends React.Component { toggle_visibility() { this.setState({ collapsed: !this.state.collapsed }); } - onmousedown_resizer(e) { + onmousedown_resizer(e: any) { this._resizing = true; this._page_y_orig = e.pageY; this._height_when_clicked = this.collapser_box_node.clientHeight; @@ -59,7 +71,7 @@ class Collapser extends React.Component { onmouseup_resizer() { this._resizing = false; } - onmousemove_resizer(e) { + onmousemove_resizer(e: any) { if (this._resizing) { let dh = e.pageY - this._page_y_orig; this.setState({ @@ -79,6 +91,7 @@ class Collapser extends React.Component { let reset_size_button = ""; if (!this.state.autosize) { + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. reset_size_button = (
    + {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'title' does not exist on type 'Readonly<... Remove this comment to see the full error message */} {this.props.title}
    ... Remove this comment to see the full error message id={this.props.id} style={style} ref={n => (this.collapser_box_node = n)} > + {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'content' does not exist on type 'Readonl... Remove this comment to see the full error message */} {this.props.content}
    @@ -152,8 +170,11 @@ class RightSidebar extends React.Component { fontSize: "1em" }, mi_output = ""; + // @ts-expect-error ts-migrate(2339) FIXME: Property 'debug' does not exist on type 'Readonly<... Remove this comment to see the full error message if (this.props.debug) { + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. mi_output = ( + // @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message } /> ); } @@ -165,6 +186,7 @@ class RightSidebar extends React.Component { onMouseMove={onmousemove_in_parent_callback} > @@ -191,11 +213,15 @@ class RightSidebar extends React.Component { step_num={5} /> + {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> + {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> + {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> @@ -215,12 +241,17 @@ class RightSidebar extends React.Component {
    } /> + {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> + {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> } /> + {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> {mi_output} diff --git a/gdbgui/src/js/Settings.jsx b/gdbgui/src/js/Settings.tsx similarity index 86% rename from gdbgui/src/js/Settings.jsx rename to gdbgui/src/js/Settings.tsx index 8bdcbb7b..cc99c3d8 100644 --- a/gdbgui/src/js/Settings.jsx +++ b/gdbgui/src/js/Settings.tsx @@ -1,14 +1,19 @@ import { store } from "statorgfc"; -import Actions from "./Actions.js"; -import ToolTip from "./ToolTip.jsx"; +import Actions from "./Actions"; +import ToolTip from "./ToolTip"; import React from "react"; /** * Settings modal when clicking the gear icon */ class Settings extends React.Component { + max_source_file_lines_input: any; + save_button: any; + settings_node: any; constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "debug", "current_theme", @@ -26,11 +31,11 @@ class Settings extends React.Component { this ); } - static toggle_key(key) { + static toggle_key(key: any) { store.set(key, !store.get(key)); localStorage.setItem(key, JSON.stringify(store.get(key))); } - static get_checkbox_row(store_key, text) { + static get_checkbox_row(store_key: any, text: any) { return ( @@ -63,6 +68,7 @@ class Settings extends React.Component { onClick={() => { let new_value = parseInt(this.max_source_file_lines_input.value); Actions.update_max_lines_of_code_to_fetch(new_value); + // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '1' is not assignable to paramete... Remove this comment to see the full error message ToolTip.show_tooltip_on_node("saved!", this.save_button, 1); }} > @@ -108,7 +114,7 @@ class Settings extends React.Component { localStorage.setItem("theme", e.currentTarget.value); }} > - {store.get("themes").map(t => ( + {store.get("themes").map((t: any) => ( ))} diff --git a/gdbgui/src/js/SourceCode.jsx b/gdbgui/src/js/SourceCode.tsx similarity index 86% rename from gdbgui/src/js/SourceCode.jsx rename to gdbgui/src/js/SourceCode.tsx index 74dd5335..018c7fab 100644 --- a/gdbgui/src/js/SourceCode.jsx +++ b/gdbgui/src/js/SourceCode.tsx @@ -4,14 +4,16 @@ import { store } from "statorgfc"; import React from "react"; -import FileOps from "./FileOps.jsx"; -import Breakpoints from "./Breakpoints.jsx"; -import Memory from "./Memory.jsx"; +import FileOps from "./FileOps"; +import Breakpoints from "./Breakpoints"; +import Memory from "./Memory"; import MemoryLink from "./MemoryLink"; -import constants from "./constants.js"; -import Actions from "./Actions.js"; +import constants from "./constants"; +import Actions from "./Actions"; -class SourceCode extends React.Component { +type State = any; + +class SourceCode extends React.Component<{}, State> { static el_code_container = null; // todo: no jquery static el_code_container_node = null; static code_container_node = null; @@ -19,7 +21,9 @@ class SourceCode extends React.Component { static view_more_bottom_node = null; constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "fullname_to_render", "cached_source_files", @@ -148,20 +152,20 @@ class SourceCode extends React.Component { } } } - click_gutter(line_num) { + click_gutter(line_num: any) { Breakpoints.add_or_remove_breakpoint(this.state.fullname_to_render, line_num); } _get_source_line( - source, - line_should_flash, - is_gdb_paused_on_this_line, - line_num_being_rendered, - has_bkpt, - has_disabled_bkpt, - has_conditional_bkpt, - assembly_for_line, - paused_addr + source: any, + line_should_flash: any, + is_gdb_paused_on_this_line: any, + line_num_being_rendered: any, + has_bkpt: any, + has_disabled_bkpt: any, + has_conditional_bkpt: any, + assembly_for_line: any, + paused_addr: any ) { let row_class = ["srccode"]; @@ -219,7 +223,7 @@ class SourceCode extends React.Component { ); } - get_linenum_td(linenum, gutter_cls = "") { + get_linenum_td(linenum: any, gutter_cls = "") { return ( {`(${assm.opcodes})`}
    ) : ( @@ -258,6 +262,7 @@ class SourceCode extends React.Component { ); return ( + {/* @ts-expect-error ts-migrate(2769) FIXME: Property 'fontFamily' is missing in type '{ paddin... Remove this comment to see the full error message */} {asterisk} {opcodes /* i.e. mov */} {instruction} @@ -272,7 +277,7 @@ class SourceCode extends React.Component { ); } - _get_assm_row(key, assm, paused_addr) { + _get_assm_row(key: any, assm: any, paused_addr: any) { return ( @@ -282,7 +287,7 @@ class SourceCode extends React.Component { ); } - is_gdb_paused_on_this_line(line_num_being_rendered, line_gdb_is_paused_on) { + is_gdb_paused_on_this_line(line_num_being_rendered: any, line_gdb_is_paused_on: any) { if (this.state.paused_on_frame) { return ( line_num_being_rendered === line_gdb_is_paused_on && @@ -292,8 +297,9 @@ class SourceCode extends React.Component { return false; } } - get_view_more_tr(fullname, linenum, node_key) { + get_view_more_tr(fullname: any, linenum: any, node_key: any) { return ( + // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message (SourceCode[node_key] = el)}> ); } - get_end_of_file_tr(linenum) { + get_end_of_file_tr(linenum: any) { return ( @@ -318,7 +324,12 @@ class SourceCode extends React.Component { ); } - get_line_nums_to_render(source_code_obj, start_linenum, line_to_flash, end_linenum) { + get_line_nums_to_render( + source_code_obj: any, + start_linenum: any, + line_to_flash: any, + end_linenum: any + ) { let start_linenum_to_render = start_linenum; let end_linenum_to_render = end_linenum; let linenum = start_linenum; @@ -346,13 +357,13 @@ class SourceCode extends React.Component { return { start_linenum_to_render, end_linenum_to_render }; } get_body_source_and_assm( - fullname, - source_code_obj, - assembly, - paused_addr, - start_linenum, - end_linenum, - num_lines_in_file + fullname: any, + source_code_obj: any, + assembly: any, + paused_addr: any, + start_linenum: any, + end_linenum: any, + num_lines_in_file: any ) { let body = []; @@ -444,7 +455,7 @@ class SourceCode extends React.Component { return body; } - get_body_assembly_only(assm_array, paused_addr) { + get_body_assembly_only(assm_array: any, paused_addr: any) { let body = [], i = 0; for (let assm of assm_array) { @@ -464,13 +475,15 @@ class SourceCode extends React.Component { static make_current_line_visible() { return SourceCode._make_jq_selector_visible($("#scroll_to_line")); } - static is_source_line_visible(jq_selector) { + static is_source_line_visible(jq_selector: any) { if (jq_selector.length !== 1) { // make sure something is selected before trying to scroll to it throw "Unexpected jquery selector"; } + // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. let top_of_container = SourceCode.el_code_container.position().top, + // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. height_of_container = SourceCode.el_code_container.height(), bottom_of_container = top_of_container + height_of_container, top_of_line = jq_selector.position().top, @@ -490,7 +503,7 @@ class SourceCode extends React.Component { * Used to jump around to various lines * returns true on success */ - static _make_jq_selector_visible(jq_selector) { + static _make_jq_selector_visible(jq_selector: any) { if (jq_selector.length === 1) { // make sure something is selected before trying to scroll to it const { @@ -504,6 +517,7 @@ class SourceCode extends React.Component { // line is out of view, scroll so it's in the middle of the table const time_to_scroll = 0; let scroll_top = top_of_line - (top_of_table + height_of_container / 2); + // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. SourceCode.el_code_container.animate({ scrollTop: scroll_top }, time_to_scroll); } return true; diff --git a/gdbgui/src/js/SourceCodeHeading.jsx b/gdbgui/src/js/SourceCodeHeading.tsx similarity index 69% rename from gdbgui/src/js/SourceCodeHeading.jsx rename to gdbgui/src/js/SourceCodeHeading.tsx index 51f4223a..9b4b3b3e 100644 --- a/gdbgui/src/js/SourceCodeHeading.jsx +++ b/gdbgui/src/js/SourceCodeHeading.tsx @@ -1,12 +1,16 @@ import React from "react"; -import constants from "./constants.js"; +import constants from "./constants"; import { store } from "statorgfc"; import { FileLink } from "./Links"; -import FileOps from "./FileOps.jsx"; +import FileOps from "./FileOps"; -class SourceCodeHeading extends React.Component { +type State = any; + +class SourceCodeHeading extends React.Component<{}, State> { constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "fullname_to_render", "paused_on_frame", @@ -31,6 +35,7 @@ class SourceCodeHeading extends React.Component { this.state.fullname_to_render && FileOps.get_source_file_obj_from_cache(this.state.fullname_to_render) ) { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. num_lines = FileOps.get_num_lines_in_file(this.state.fullname_to_render); } return ( diff --git a/gdbgui/src/js/SourceFileAutocomplete.jsx b/gdbgui/src/js/SourceFileAutocomplete.tsx similarity index 78% rename from gdbgui/src/js/SourceFileAutocomplete.jsx rename to gdbgui/src/js/SourceFileAutocomplete.tsx index d1e3115b..8d7d4ff9 100644 --- a/gdbgui/src/js/SourceFileAutocomplete.jsx +++ b/gdbgui/src/js/SourceFileAutocomplete.tsx @@ -1,8 +1,8 @@ import { store } from "statorgfc"; -import constants from "./constants.js"; -import Actions from "./Actions.js"; -import Util from "./Util.js"; -import FileOps from "./FileOps.jsx"; +import constants from "./constants"; +import Actions from "./Actions"; +import Util from "./Util"; +import FileOps from "./FileOps"; import React from "react"; /** @@ -14,11 +14,16 @@ import React from "react"; const help_text = "Enter file path to view, press enter"; /* global Awesomplete */ class SourceFileAutocomplete extends React.Component { + awesomeplete_input: any; + html_input: any; constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'subscribeToKeys' does not exist on type ... Remove this comment to see the full error message store.subscribeToKeys(["source_file_paths"], this.store_change_callback.bind(this)); } store_change_callback() { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (!_.isEqual(this.awesomeplete_input._list, store.get("source_file_paths"))) { this.awesomeplete_input.list = store.get("source_file_paths"); } @@ -49,8 +54,9 @@ class SourceFileAutocomplete extends React.Component { ); } - keyup_source_file_input(e) { + keyup_source_file_input(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let user_input = _.trim(e.currentTarget.value); if (user_input.length === 0) { @@ -60,6 +66,7 @@ class SourceFileAutocomplete extends React.Component { let fullname, default_line = 0, line; + // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message [fullname, line] = Util.parse_fullname_and_line(user_input, default_line); FileOps.user_select_file_to_view(fullname, line); } else if (store.get("source_file_paths").length === 0) { @@ -86,18 +93,19 @@ class SourceFileAutocomplete extends React.Component { componentDidMount() { // initialize list of source files // TODO maybe use a pre-built React component for this + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'Awesomplete'. this.awesomeplete_input = new Awesomplete("#source_file_input", { minChars: 0, maxItems: 10000, list: [], // standard sort algorithm (the default Awesomeplete sort is weird) - sort: (a, b) => { + sort: (a: any, b: any) => { return a < b ? -1 : 1; } }); // perform action when an item is selected - this.html_input.addEventListener("awesomplete-selectcomplete", function(e) { + this.html_input.addEventListener("awesomplete-selectcomplete", function(e: any) { let fullname = e.currentTarget.value; FileOps.user_select_file_to_view(fullname, 1); }); diff --git a/gdbgui/src/js/StatusBar.jsx b/gdbgui/src/js/StatusBar.tsx similarity index 78% rename from gdbgui/src/js/StatusBar.jsx rename to gdbgui/src/js/StatusBar.tsx index aa93d549..0dc609d6 100644 --- a/gdbgui/src/js/StatusBar.jsx +++ b/gdbgui/src/js/StatusBar.tsx @@ -1,11 +1,13 @@ import React from "react"; -import Util from "./Util.js"; +import Util from "./Util"; import { store } from "statorgfc"; +type State = any; + /** * Component to render a status message with optional error/warning label */ -class StatusBar extends React.Component { +class StatusBar extends React.Component<{}, State> { render() { if (this.state.waiting_for_response) { return ; diff --git a/gdbgui/src/js/Terminals.tsx b/gdbgui/src/js/Terminals.tsx new file mode 100644 index 00000000..d4062bec --- /dev/null +++ b/gdbgui/src/js/Terminals.tsx @@ -0,0 +1,198 @@ +import React from "react"; +import GdbApi from "./GdbApi"; +import { Terminal } from "xterm"; +import { FitAddon } from "xterm-addon-fit"; +import { store } from "statorgfc"; +import "xterm/css/xterm.css"; +import constants from "./constants"; +import Actions from "./Actions"; + +function customKeyEventHandler(config: { + pty_name: string; + pty: Terminal; + canPaste: boolean; + pidStoreKey: string; +}) { + return async (e: KeyboardEvent): Promise => { + if (!(e.type === "keydown")) { + return true; + } + if (e.shiftKey && e.ctrlKey) { + const key = e.key.toLowerCase(); + if (key === "c") { + const toCopy = config.pty.getSelection(); + navigator.clipboard.writeText(toCopy); + config.pty.focus(); + return false; + } else if (key === "v") { + if (!config.canPaste) { + return false; + } + const toPaste = await navigator.clipboard.readText(); + + GdbApi.getSocket().emit("pty_interaction", { + data: { pty_name: config.pty_name, key: toPaste, action: "write" } + }); + return false; + } + } + return true; + }; +} +export class Terminals extends React.Component { + userPtyRef: React.RefObject; + programPtyRef: React.RefObject; + gdbguiPtyRef: React.RefObject; + constructor(props: any) { + super(props); + this.userPtyRef = React.createRef(); + this.programPtyRef = React.createRef(); + this.gdbguiPtyRef = React.createRef(); + this.terminal = this.terminal.bind(this); + } + + terminal(ref: React.RefObject) { + let className = " bg-black p-0 m-0 h-full align-baseline "; + return ( +
    +
    +
    + ); + } + render() { + let terminalsClass = "w-full h-full relative grid grid-cols-3 "; + return ( +
    + {this.terminal(this.userPtyRef)} + {/* */} + {this.terminal(this.gdbguiPtyRef)} + {this.terminal(this.programPtyRef)} +
    + ); + } + + componentDidMount() { + const fitAddon = new FitAddon(); + const programFitAddon = new FitAddon(); + const gdbguiFitAddon = new FitAddon(); + + const userPty = new Terminal({ + cursorBlink: true, + macOptionIsMeta: true, + scrollback: 9999 + }); + userPty.loadAddon(fitAddon); + userPty.open(this.userPtyRef.current); + userPty.writeln(`running command: ${store.get("gdb_command")}`); + userPty.writeln(""); + userPty.attachCustomKeyEventHandler( + // @ts-expect-error + customKeyEventHandler({ + pty_name: "user_pty", + pty: userPty, + canPaste: true, + pidStoreKey: "gdb_pid" + }) + ); + GdbApi.getSocket().on("user_pty_response", function(data: string) { + userPty.write(data); + }); + userPty.onKey((data, ev) => { + GdbApi.getSocket().emit("pty_interaction", { + data: { pty_name: "user_pty", key: data.key, action: "write" } + }); + if (data.domEvent.code === "Enter") { + Actions.onConsoleCommandRun(); + } + }); + + const programPty = new Terminal({ + cursorBlink: true, + macOptionIsMeta: true, + scrollback: 9999 + }); + programPty.loadAddon(programFitAddon); + programPty.open(this.programPtyRef.current); + programPty.attachCustomKeyEventHandler( + // @ts-expect-error + customKeyEventHandler({ + pty_name: "program_pty", + pty: programPty, + canPaste: true, + pidStoreKey: "inferior_pid" + }) + ); + programPty.write(constants.xtermColors.grey); + programPty.write( + "Program output -- Programs being debugged are connected to this terminal. " + + "You can read output and send input to the program from here." + ); + programPty.writeln(constants.xtermColors.reset); + GdbApi.getSocket().on("program_pty_response", function(pty_response: string) { + programPty.write(pty_response); + }); + programPty.onKey((data, ev) => { + GdbApi.getSocket().emit("pty_interaction", { + data: { pty_name: "program_pty", key: data.key, action: "write" } + }); + }); + + const gdbguiPty = new Terminal({ + cursorBlink: false, + macOptionIsMeta: true, + scrollback: 9999, + disableStdin: true + // theme: { background: "#888" } + }); + gdbguiPty.write(constants.xtermColors.grey); + gdbguiPty.writeln("gdbgui output (read-only)"); + gdbguiPty.writeln( + "Copy/Paste available in all terminals with ctrl+shift+c, ctrl+shift+v" + ); + gdbguiPty.write(constants.xtermColors.reset); + + gdbguiPty.attachCustomKeyEventHandler( + // @ts-expect-error + customKeyEventHandler({ pty_name: "unused", pty: gdbguiPty, canPaste: false }) + ); + + gdbguiPty.loadAddon(gdbguiFitAddon); + gdbguiPty.open(this.gdbguiPtyRef.current); + // gdbguiPty is written to elsewhere + store.set("gdbguiPty", gdbguiPty); + + const interval = setInterval(() => { + fitAddon.fit(); + programFitAddon.fit(); + gdbguiFitAddon.fit(); + const socket = GdbApi.getSocket(); + + if (socket.disconnected) { + return; + } + socket.emit("pty_interaction", { + data: { + pty_name: "user_pty", + rows: userPty.rows, + cols: userPty.cols, + action: "set_winsize" + } + }); + + socket.emit("pty_interaction", { + data: { + pty_name: "program_pty", + rows: programPty.rows, + cols: programPty.cols, + action: "set_winsize" + } + }); + }, 2000); + + setTimeout(() => { + fitAddon.fit(); + programFitAddon.fit(); + gdbguiFitAddon.fit(); + }, 0); + } +} diff --git a/gdbgui/src/js/Threads.jsx b/gdbgui/src/js/Threads.tsx similarity index 77% rename from gdbgui/src/js/Threads.jsx rename to gdbgui/src/js/Threads.tsx index ed754f16..1649c7cc 100644 --- a/gdbgui/src/js/Threads.jsx +++ b/gdbgui/src/js/Threads.tsx @@ -1,23 +1,26 @@ import React from "react"; -import ReactTable from "./ReactTable.jsx"; +import ReactTable from "./ReactTable"; import { store } from "statorgfc"; -import GdbApi from "./GdbApi.jsx"; -import Memory from "./Memory.jsx"; +import GdbApi from "./GdbApi"; +import Memory from "./Memory"; import { FileLink } from "./Links"; import MemoryLink from "./MemoryLink"; class FrameArguments extends React.Component { - render_frame_arg(frame_arg) { + render_frame_arg(frame_arg: any) { return [frame_arg.name, frame_arg.value]; } render() { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'args' does not exist on type 'Readonly<{... Remove this comment to see the full error message let frame_args = this.props.args; + // @ts-expect-error ts-migrate(2339) FIXME: Property 'args' does not exist on type 'Readonly<{... Remove this comment to see the full error message if (!this.props.args) { frame_args = []; } return ( @@ -25,9 +28,13 @@ class FrameArguments extends React.Component { } } -class Threads extends React.Component { +type ThreadsState = any; + +class Threads extends React.Component<{}, ThreadsState> { constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "threads", "current_thread_id", @@ -36,11 +43,11 @@ class Threads extends React.Component { ]); } - static select_thread_id(thread_id) { + static select_thread_id(thread_id: any) { GdbApi.select_thread_id(thread_id); } - static select_frame(framenum) { + static select_frame(framenum: any) { store.set("selected_frame_num", framenum); store.set("line_of_source_to_flash", null); store.set("make_current_line_visible", true); @@ -76,6 +83,7 @@ class Threads extends React.Component { } content.push(Threads.get_thread_header(thread, is_current_thread_being_rendered)); content.push( + // @ts-expect-error ts-migrate(2769) FIXME: Type 'string' is not assignable to type 'never'. {content}; } - static get_stack_for_thread(cur_frame, stack_data, is_current_thread_being_rendered) { + static get_stack_for_thread( + cur_frame: any, + stack_data: any, + is_current_thread_being_rendered: any + ) { // each thread provides only the frame that it's paused on (cur_frame). // we also have the output of `-stack-list-frames` (stack_data), which // is the full stack of the selected thread @@ -103,7 +115,7 @@ class Threads extends React.Component { return [cur_frame]; } - static get_thread_header(thread, is_current_thread_being_rendered) { + static get_thread_header(thread: any, is_current_thread_being_rendered: any) { let selected, cls = ""; if (is_current_thread_being_rendered) { @@ -146,11 +158,11 @@ class Threads extends React.Component { ); } static get_frame_row( - frame, - is_selected_frame, - thread_id, - is_current_thread_being_rendered, - frame_num + frame: any, + is_selected_frame: any, + thread_id: any, + is_current_thread_being_rendered: any, + frame_num: any ) { let onclick; let classes = []; @@ -183,15 +195,16 @@ class Threads extends React.Component {
    , , , + // @ts-expect-error ts-migrate(2769) FIXME: Property 'args' does not exist on type 'IntrinsicA... Remove this comment to see the full error message ]; } static get_row_data_for_stack( - stack, - selected_frame_num, - thread_id, - is_current_thread_being_rendered + stack: any, + selected_frame_num: any, + thread_id: any, + is_current_thread_being_rendered: any ) { let row_data = []; let frame_num = 0; @@ -215,7 +228,7 @@ class Threads extends React.Component { } return row_data; } - static update_stack(stack) { + static update_stack(stack: any) { store.set("stack", stack); store.set("paused_on_frame", stack[store.get("selected_frame_num") || 0]); store.set( @@ -226,7 +239,7 @@ class Threads extends React.Component { store.set("current_assembly_address", store.get("paused_on_frame").addr); store.set("make_current_line_visible", true); } - set_thread_id(id) { + set_thread_id(id: any) { store.set("current_thread_id", parseInt(id)); } } diff --git a/gdbgui/src/js/ToolTip.jsx b/gdbgui/src/js/ToolTip.tsx similarity index 67% rename from gdbgui/src/js/ToolTip.jsx rename to gdbgui/src/js/ToolTip.tsx index d972d85e..c7ee8adb 100644 --- a/gdbgui/src/js/ToolTip.jsx +++ b/gdbgui/src/js/ToolTip.tsx @@ -2,8 +2,11 @@ import React from "react"; import { store } from "statorgfc"; class ToolTip extends React.Component { + timeout: any; constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["tooltip"]); this.timeout = null; } @@ -15,7 +18,7 @@ class ToolTip extends React.Component { content: null }); } - static show_tooltip_on_node(content, node, show_for_n_sec = null) { + static show_tooltip_on_node(content: any, node: any, show_for_n_sec = null) { store.set("tooltip", { hidden: false, show_for_n_sec: show_for_n_sec, @@ -23,7 +26,8 @@ class ToolTip extends React.Component { content: content }); } - static show_copied_tooltip_on_node(node) { + static show_copied_tooltip_on_node(node: any) { + // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '1' is not assignable to paramete... Remove this comment to see the full error message ToolTip.show_tooltip_on_node("copied!", node, 1); } render() { @@ -41,6 +45,7 @@ class ToolTip extends React.Component { : 0, left = rect.x - horizontal_buffer + "px", top = rect.y + tooltip.node.offsetHeight + "px"; + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isInteger(tooltip.show_for_n_sec)) { this.timeout = setTimeout(ToolTip.hide_tooltip, tooltip.show_for_n_sec * 1000); } @@ -54,6 +59,7 @@ class ToolTip extends React.Component { border: "1px solid", position: "fixed", padding: "5px", + // @ts-expect-error ts-migrate(2322) FIXME: Type '"121"' is not assignable to type '"-moz-init... Remove this comment to see the full error message zIndex: "121" }} > diff --git a/gdbgui/src/js/ToolTipTourguide.jsx b/gdbgui/src/js/ToolTipTourguide.tsx similarity index 55% rename from gdbgui/src/js/ToolTipTourguide.jsx rename to gdbgui/src/js/ToolTipTourguide.tsx index 675a43d5..a1716d14 100644 --- a/gdbgui/src/js/ToolTipTourguide.jsx +++ b/gdbgui/src/js/ToolTipTourguide.tsx @@ -1,14 +1,19 @@ import React from "react"; -import Util from "./Util.js"; +import Util from "./Util"; import { store } from "statorgfc"; -class ToolTipTourguide extends React.Component { - constructor(props) { +type State = any; + +class ToolTipTourguide extends React.Component<{}, State> { + constructor(props: {}) { super(props); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'position' does not exist on type '{}'. if (!props.position && !(props.top && props.left)) { console.warn("did not receive position"); } + // @ts-expect-error ts-migrate(2551) FIXME: Property 'ref' does not exist on type 'ToolTipTour... Remove this comment to see the full error message this.ref = React.createRef(); + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "tour_guide_step", "num_tour_guide_steps", @@ -35,13 +40,15 @@ class ToolTipTourguide extends React.Component { Util.persist_value_for_key("show_tour_guide"); } componentDidUpdate() { + // @ts-expect-error ts-migrate(2551) FIXME: Property 'ref' does not exist on type 'ToolTipTour... Remove this comment to see the full error message if (this.state.show_tour_guide && this.ref.current) { // need to ensure absolute position is respected by setting parent to // relative + // @ts-expect-error ts-migrate(2551) FIXME: Property 'ref' does not exist on type 'ToolTipTour... Remove this comment to see the full error message this.ref.current.parentNode.style.position = "relative"; } } - get_position(position_name) { + get_position(position_name: any) { let top, left; switch (position_name) { case "left": @@ -70,6 +77,7 @@ class ToolTipTourguide extends React.Component { left = "50%"; break; default: + // @ts-expect-error ts-migrate(2339) FIXME: Property 'position' does not exist on type 'Readon... Remove this comment to see the full error message console.warn("invalid position " + this.props.position); top = "100%"; left = "50%"; @@ -80,26 +88,33 @@ class ToolTipTourguide extends React.Component { render() { if (!this.state.show_tour_guide) { return null; + // @ts-expect-error ts-migrate(2339) FIXME: Property 'step_num' does not exist on type 'Readon... Remove this comment to see the full error message } else if (this.props.step_num !== this.state.tour_guide_step) { return null; } let top, left; + // @ts-expect-error ts-migrate(2339) FIXME: Property 'top' does not exist on type 'Readonly<{}... Remove this comment to see the full error message if (this.props.top && this.props.left) { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'top' does not exist on type 'Readonly<{}... Remove this comment to see the full error message top = this.props.top; + // @ts-expect-error ts-migrate(2339) FIXME: Property 'left' does not exist on type 'Readonly<{... Remove this comment to see the full error message left = this.props.left; } else { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'position' does not exist on type 'Readon... Remove this comment to see the full error message [top, left] = this.get_position(this.props.position); } + // @ts-expect-error ts-migrate(2339) FIXME: Property 'step_num' does not exist on type 'Readon... Remove this comment to see the full error message let is_last_step = this.props.step_num + 1 === this.state.num_tour_guide_steps, dismiss = is_last_step ? null : ( - dismiss + Dismiss ); return (
    + {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'content' does not exist on type 'Readonl... Remove this comment to see the full error message */} {this.props.content}

    + {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'step_num' does not exist on type 'Readon... Remove this comment to see the full error message */} {this.props.step_num + 1} of {this.state.num_tour_guide_steps}

    {dismiss} - {is_last_step ? "Let's Do This!" : "next"} + {is_last_step ? "Finish" : "Next"}

    ); diff --git a/gdbgui/src/js/TopBar.jsx b/gdbgui/src/js/TopBar.tsx similarity index 88% rename from gdbgui/src/js/TopBar.jsx rename to gdbgui/src/js/TopBar.tsx index cba6d54e..c35a964a 100644 --- a/gdbgui/src/js/TopBar.jsx +++ b/gdbgui/src/js/TopBar.tsx @@ -1,40 +1,23 @@ import React from "react"; import { store } from "statorgfc"; -import BinaryLoader from "./BinaryLoader.jsx"; -import ControlButtons from "./ControlButtons.jsx"; -import Settings from "./Settings.jsx"; -import SourceCodeHeading from "./SourceCodeHeading.jsx"; -import ToolTipTourguide from "./ToolTipTourguide.jsx"; -import FileOps from "./FileOps.jsx"; -import GdbApi from "./GdbApi.jsx"; -import Actions from "./Actions.js"; -import constants from "./constants.js"; -import Util from "./Util.js"; +import BinaryLoader from "./BinaryLoader"; +import ControlButtons from "./ControlButtons"; +import Settings from "./Settings"; +import SourceCodeHeading from "./SourceCodeHeading"; +import ToolTipTourguide from "./ToolTipTourguide"; +import FileOps from "./FileOps"; +import GdbApi from "./GdbApi"; +import Actions from "./Actions"; +import constants from "./constants"; +import Util from "./Util"; -let onkeyup_jump_to_line = e => { +let onkeyup_jump_to_line = (e: any) => { if (e.keyCode === constants.ENTER_BUTTON_NUM) { Actions.set_line_state(e.currentTarget.value); } }; -let click_shutdown_button = function() { - // no need to show confirmation before leaving, because we're about to prompt the user - window.onbeforeunload = () => null; - // prompt user - if ( - window.confirm( - "This will terminate the gdbgui for all browser tabs running gdbgui (and their gdb processes). Continue?" - ) === true - ) { - // user wants to shutdown, redirect them to the shutdown page - window.location = "/shutdown"; - } else { - // re-add confirmation before leaving page (when user actually leaves at a later time) - window.onbeforeunload = () => "some text"; - } -}; - let show_license = function() { Actions.show_modal( "gdbgui license", @@ -121,11 +104,6 @@ const menu = ( Session Information -
  • - - Shutdown gdbgui server - -
  • @@ -159,6 +137,7 @@ const menu = ( ); -class TopBar extends React.Component { +type State = any; + +class TopBar extends React.Component<{}, State> { + spinner_timeout: any; + spinner_timeout_msec: any; constructor() { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // state local to the component this.state = { @@ -186,6 +170,7 @@ class TopBar extends React.Component { show_spinner: false }; // global state attached to this component + // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState( this, [ @@ -202,7 +187,7 @@ class TopBar extends React.Component { this.spinner_timeout = null; this.spinner_timeout_msec = 5000; } - store_update_callback(keys) { + store_update_callback(keys: any) { if (keys.indexOf("waiting_for_response") !== -1) { this._clear_spinner_timeout(); this.setState({ show_spinner: false }); @@ -237,9 +222,10 @@ class TopBar extends React.Component { className="btn-group btn-group" > e.stopPropagation()} + onClick={(e: any) => e.stopPropagation()} content={
    @@ -269,6 +255,7 @@ class TopBar extends React.Component { constants.source_code_states.ASSM_AND_SOURCE_CACHED || this.state.source_code_state === constants.source_code_states.ASSM_CACHED ) { + // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. toggle_assm_button = (