From a6cb233290b92b3ea6cf5a8fa751bc90bc29eaae Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Wed, 22 Jul 2026 18:06:49 +0300 Subject: [PATCH 1/2] chore: update README.md --- README.md | 131 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 88 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index e8371cd..f7a7069 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,118 @@ # Vim Code Runner - run your code the same way as with [vs code runner](https://github.com/formulahendry/vscode-code-runner) + https://github.com/user-attachments/assets/63109233-1e5d-4d54-b890-30eb07dab826 + - [Vim Code Runner - run your code the same way as with vs code runner](#vim-code-runner---run-your-code-the-same-way-as-with-vs-code-runner) - - [Requirements](#requirements) - - [Installation](#installation) - - [Usage](#usage) - - [`CodeRunnerRunByFileExt`](#coderunnerrunbyfileext) - - [`CodeRunnerRunByFileType`](#coderunnerrunbyfiletype) - - [`CodeRunnerRunByGlob`](#coderunnerrunbyglob) - - [`CodeRunnerRun`](#coderunnerrun) - - [`coderunner#Load()`](#coderunnerload) - - [`coderunner#RemoveCoderunnerTempfiles()`](#coderunnerremovecoderunnertempfiles) - - [Configuration](#configuration) - - [`g:coderunner_by_file_ext`](#gcoderunner_by_file_ext) - - [`g:coderunner_by_file_type`](#gcoderunner_by_file_type) - - [`g:coderunner_by_glob`](#gcoderunner_by_glob) - - [`g:coderunner_executor`](#gcoderunner_executor) - - [`g:coderunner_ignore_selection`](#gcoderunner_ignore_selection) - - [`g:coderunner_remove_coderunner_tempfiles_on_exit`](#gcoderunner_remove_coderunner_tempfiles_on_exit) - - [`g:coderunner_respect_shebang`](#gcoderunner_respect_shebang) - - [`g:coderunner_runners_order`](#gcoderunner_runners_order) - - [`g:coderunner_save_all_files_before_run`](#gcoderunner_save_all_files_before_run) - - [`g:coderunner_save_file_before_run`](#gcoderunner_save_file_before_run) - - [`g:coderunner_tempfile_prefix`](#gcoderunner_tempfile_prefix) - - [Interpolated variables](#interpolated-variables) - - [For developers](#for-developers) - - [Setup environment](#setup-environment) - - [Plugin architecture](#plugin-architecture) + - [Requirements](#requirements) + - [Installation](#installation) + - [Usage](#usage) + - [`CodeRunnerRunByFileExt`](#coderunnerrunbyfileext) + - [`CodeRunnerRunByFileType`](#coderunnerrunbyfiletype) + - [`CodeRunnerRunByGlob`](#coderunnerrunbyglob) + - [`CodeRunnerRun`](#coderunnerrun) + - [`coderunner#Load()`](#coderunnerload) + - [`coderunner#RemoveCoderunnerTempfiles()`](#coderunnerremovecoderunnertempfiles) + - [Configuration](#configuration) + - [`g:coderunner_by_file_ext`](#gcoderunner_by_file_ext) + - [`g:coderunner_by_file_type`](#gcoderunner_by_file_type) + - [`g:coderunner_by_glob`](#gcoderunner_by_glob) + - [`g:coderunner_executor`](#gcoderunner_executor) + - [`g:coderunner_ignore_selection`](#gcoderunner_ignore_selection) + - [`g:coderunner_remove_coderunner_tempfiles_on_exit`](#gcoderunner_remove_coderunner_tempfiles_on_exit) + - [`g:coderunner_respect_shebang`](#gcoderunner_respect_shebang) + - [`g:coderunner_runners_order`](#gcoderunner_runners_order) + - [`g:coderunner_save_all_files_before_run`](#gcoderunner_save_all_files_before_run) + - [`g:coderunner_save_file_before_run`](#gcoderunner_save_file_before_run) + - [`g:coderunner_tempfile_prefix`](#gcoderunner_tempfile_prefix) + - [Interpolated variables](#interpolated-variables) + - [For developers](#for-developers) + - [Setup environment](#setup-environment) + - [Plugin architecture](#plugin-architecture) ## Requirements + - Vim version 8.0+ with Python3 support. Check with: - ```vim - :echo has('python3') " should return 1 - ``` -- To use `CodeRunnerRunByGlob` python version must be above 3.13. Check with: - ```vim - :py3 import sys;print(sys.version) " should return >= 3.13 - ``` + ```vim + :echo has('python3') " should return 1 + ``` +- To use `CodeRunnerRunByGlob` python version must be above 3.10. Check with: + ```vim + :py3 import sys;print(sys.version) " should return >= 3.10 + ``` ## Installation + Install the Vim plugin with your favorite plugin manager, e.g. vim-plug: + ```vim Plug 'ZaharChernenko/vim-code-runner' ``` ## Usage + ### `CodeRunnerRunByFileExt` + Tries to execute the code if the extension of the current file matches the extension from [`g:coderunner_by_file_ext`](#gcoderunner_by_file_ext), the extension is a dot + the last part of the file name after the last dot. + ### `CodeRunnerRunByFileType` + Tries to execute the code if the current file type is defined in the [`g:coderunner_by_file_type`](#gcoderunner_by_file_type), to find out what type of open file run `:echo &filetype`. + ### `CodeRunnerRunByGlob` + Tries to execute the code if the full path of the current file corresponds to some glob pattern in [`g:coderunner_by_glob`](#gcoderunner_by_glob), **it is important that this function works only with vim, which has version python3.13+.** + ### `CodeRunnerRun` + Tries to execute the code using the fallback strategy defined in [`g:coderunner_runners_order`](#gcoderunner_runners_order). + ### `coderunner#Load()` -The function that loads the coderunner module also updates the variables: g:coderunner_by_file_ext, g:coderunner_by_file_type g:coderunner_by_glob. + +The function that loads the coderunner module also updates the variables: g:coderunner_by_file_ext, g:coderunner_by_file_type, g:coderunner_by_glob. + ### `coderunner#RemoveCoderunnerTempfiles()` + The function that cleans up temporary files that were created due to run with selection, it clears only those files that were created in the current vim session. ## Configuration + ### `g:coderunner_by_file_ext` + Hash table with mapping of file extensions to commands, default `{}`, example: + ```vim let g:coderunner_by_file_ext = { \ '.cpp': 'bash -c "cd \"$dir\" && g++ -o cpp_output -std=c++2a *.cpp && ./cpp_output"', \ '.py': 'bash -c "cd \"$dir\" && python3 \"$fullFileName\""', \ } ``` + ### `g:coderunner_by_file_type` + Hash table with mapping of file types to commands, default `{}`, example: + ```vim let g:coderunner_by_file_type = { \ 'cpp': 'bash -c "cd \"$dir\" && g++ -o cpp_output -std=c++2a *.cpp && ./cpp_output"', \ 'python': 'bash -c "cd \"$dir\" && python3 \"$fullFileName\""', \ } ``` + To find out what type of open file run `:echo &filetype`. + ### `g:coderunner_by_glob` + Hash table with mapping glob patterns into commands, given that the hash table is unordered, coderunner sorts the patterns in reverse lexicographic order, trying to find the most accurate one first, default `{}`, example: + ```vim let g:coderunner_by_glob = { \ '**/*.cpp': 'bash -c "cd \"$dir\" && g++ -o cpp_output -std=c++2a *.cpp && ./cpp_output"', \ '**/*.py': 'bash -c "cd \"$dir\" && python3 \"$fullFileName\""', \ } ``` + ### `g:coderunner_executor` + Any vim command that can execute a string is suitable, default `ter`, default behaviour: https://github.com/user-attachments/assets/bea987a0-7269-4dc4-ae01-590a71d9dd5f @@ -91,28 +120,45 @@ https://github.com/user-attachments/assets/bea987a0-7269-4dc4-ae01-590a71d9dd5f It is important to note that a regular terminal does not execute commands related through logical operators, for example `&&`, so you need to use `bash -c` with a string. But for example, if you have [floaterm](https://github.com/voldikss/vim-floaterm) plugin installed, you can set the following command: + ```vim let g:coderunner_executor = "FloatermNew --autoclose=0" ``` https://github.com/user-attachments/assets/374e11d4-efd8-42ae-bdce-92b5df0cdb39 + ### `g:coderunner_ignore_selection` + If 1 ranges do not work, the command is executed for the entire file, default `0`. + ### `g:coderunner_remove_coderunner_tempfiles_on_exit` + If 1, then when exiting the vim, it calls the [coderunner#RemoveCoderunnerTempfiles()](#coderunnerremovecoderunnertempfiles) command, default `0`. + ### `g:coderunner_respect_shebang` -if 1, the shebang command is executed, even if there are matches in the hash tables, default `1`. + +If 1, the shebang command is executed, even if there are matches in the hash tables, default `1`. + ### `g:coderunner_runners_order` + Defines the order of searching for matches when executing the [CodeRunnerRun](#coderunnerrun) command, default `['by_glob', 'by_file_ext', 'by_file_type']`. + ### `g:coderunner_save_all_files_before_run` + If 1, saves all open files before execution, note files are saved only if a runner is found, default `0`. + ### `g:coderunner_save_file_before_run` + If 1, saves the current file before execution, default `0`. + ### `g:coderunner_tempfile_prefix` + The prefix with which files will be saved in the directory of the executable file when executing commands with selection. ## Interpolated variables + Some string sequences starting with $ will be replaced, here is a list of them: + - $workspaceRoot - `getcwd()` - $fullFileName - $fileNameWithoutExt @@ -125,14 +171,18 @@ Some string sequences starting with $ will be replaced, here is a list of them: Example of the work can be viewed in the [tests](https://github.com/ZaharChernenko/vim-code-runner/blob/main/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py). ## For developers + ### Setup environment + ```shell cd python_coderunner uv sync --group dev source .venv/bin/activate pre-commit install ``` + ### Plugin architecture + ```mermaid classDiagram TCodeRunner "1" o--> "1" IConfig : aggregates @@ -244,7 +294,7 @@ class IEditor { class ICommandsExecutor { <> - Сlass for executing a string command only. + Class for executing a string command only. + execute(command: str) None } @@ -259,9 +309,9 @@ class IMessagePrinter { class TBasicCommandDispatcherStrategySelector { # config: IConfig # shebang_dispatcher: TShebangCommandBuildersDispatcher - # file_type_dispatcher: TFileExtCommandBuildersDispatcher - # file_ext_dispatcher: TFileTypeCommandBuildersDispatcher + # file_ext_dispatcher: TFileExtCommandBuildersDispatcher # file_glob_dispatcher: ICommandBuildersDispatcher + # file_type_dispatcher: TFileTypeCommandBuildersDispatcher + dispatch_by_file_type(file_path_abs: str) Optional[ICommandBuilder] + dispatch_by_file_ext(file_path_abs: str) Optional[ICommandBuilder] + dispatch_by_glob(file_path_abs: str) Optional[ICommandBuilder] @@ -274,22 +324,18 @@ class TShebangCommandBuildersDispatcher { # file_info_extractor: IFileInfoExtractor } - class TGlobCommandBuildersDispatcher { # file_info_extractor: IFileInfoExtractor } - class TFileExtCommandBuildersDispatcher { # file_info_extractor: IFileInfoExtractor } - class TFileTypeCommandBuildersDispatcher { # file_info_extractor: IFileInfoExtractor } - class ICommandBuildersDispatcher { <> Dispatch is optional for fallback strategy @@ -300,11 +346,10 @@ class ICommandBuildersDispatcher { class TInterpolatorCommandBuilder { # template_string: str # file_info_extractor: IFileInfoExtractor - + TInterpolatorCommandBuilder(template_string: str, file_info_extractor: IFileInfoExtractor) + + TInterpolatorCommandBuilder(template_string: str, project_info_extractor: IProjectInfoExtractor, file_info_extractor: IFileInfoExtractor) # interpolate(file_path_abs: str) str } - class ICommandBuilder { <> Build requires absolute file path, because file can be temporary, like From 8321b5c9016760768dff8cd5db33705dc69b6d40 Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Wed, 22 Jul 2026 18:07:24 +0300 Subject: [PATCH 2/2] fix: interpolation bug --- .../interpolator_command_builder.py | 46 ++++++++++--------- .../test_interpolator_command_builder.py | 4 ++ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/python_coderunner/src/command_builder/interpolator_command_builder.py b/python_coderunner/src/command_builder/interpolator_command_builder.py index 84e08c2..40f6f78 100644 --- a/python_coderunner/src/command_builder/interpolator_command_builder.py +++ b/python_coderunner/src/command_builder/interpolator_command_builder.py @@ -1,4 +1,3 @@ -import re from typing import ClassVar from ..file_info_extractor import IFileInfoExtractor @@ -7,14 +6,14 @@ class TInterpolatorCommandBuilder(ICommandBuilder): - _WORKSPACE_ROOT_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$workspaceRoot") - _FULL_FILE_NAME_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fullFileName") - _FILE_NAME_WITHOUT_EXT_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fileNameWithoutExt") - _FILE_NAME_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fileName") - _FILE_EXT: ClassVar[re.Pattern[str]] = re.compile(r"\$fileExt") - _DRIVE_LETTER_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$driveLetter") - _DIR_WITHOUT_TRAILING_SLASH_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$dirWithoutTrailingSlash") - _DIR_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$dir") + _WORKSPACE_ROOT_VAR: ClassVar[str] = "$workspaceRoot" + _FULL_FILE_NAME_VAR: ClassVar[str] = "$fullFileName" + _FILE_NAME_WITHOUT_EXT_VAR: ClassVar[str] = "$fileNameWithoutExt" + _FILE_NAME_VAR: ClassVar[str] = "$fileName" + _FILE_EXT_VAR: ClassVar[str] = "$fileExt" + _DRIVE_LETTER_VAR: ClassVar[str] = "$driveLetter" + _DIR_WITHOUT_TRAILING_SLASH_VAR: ClassVar[str] = "$dirWithoutTrailingSlash" + _DIR_VAR: ClassVar[str] = "$dir" def __init__( self, @@ -34,23 +33,26 @@ def _interpolate(self, file_path_abs: str) -> str: The reverse alphabetical order is important so that substitutions are performed greedily, i.e. $dirWithoutTrailingSlash must be before $dir. """ - interpolated_str: str = self._WORKSPACE_ROOT_PATTERN.sub( - self._project_info_extractor.get_workspace_root(), self._template_string + interpolated_str: str = self._template_string.replace( + self._WORKSPACE_ROOT_VAR, self._project_info_extractor.get_workspace_root() ) - interpolated_str = self._FULL_FILE_NAME_PATTERN.sub(file_path_abs, interpolated_str) - interpolated_str = self._FILE_NAME_WITHOUT_EXT_PATTERN.sub( - self._file_info_extractor.get_file_name_without_ext(file_path_abs), interpolated_str + interpolated_str = interpolated_str.replace(self._FULL_FILE_NAME_VAR, file_path_abs) + interpolated_str = interpolated_str.replace( + self._FILE_NAME_WITHOUT_EXT_VAR, self._file_info_extractor.get_file_name_without_ext(file_path_abs) ) - interpolated_str = self._FILE_NAME_PATTERN.sub( - self._file_info_extractor.get_file_name(file_path_abs), interpolated_str + interpolated_str = interpolated_str.replace( + self._FILE_NAME_VAR, self._file_info_extractor.get_file_name(file_path_abs) ) - interpolated_str = self._FILE_EXT.sub(self._file_info_extractor.get_file_ext(file_path_abs), interpolated_str) - interpolated_str = self._DRIVE_LETTER_PATTERN.sub( - self._file_info_extractor.get_drive_letter(file_path_abs), interpolated_str + interpolated_str = interpolated_str.replace( + self._FILE_EXT_VAR, self._file_info_extractor.get_file_ext(file_path_abs) ) - interpolated_str = self._DIR_WITHOUT_TRAILING_SLASH_PATTERN.sub( - self._file_info_extractor.get_dir_without_trailing_slash(file_path_abs), interpolated_str + interpolated_str = interpolated_str.replace( + self._DRIVE_LETTER_VAR, self._file_info_extractor.get_drive_letter(file_path_abs) ) - interpolated_str = self._DIR_PATTERN.sub(self._file_info_extractor.get_dir(file_path_abs), interpolated_str) + interpolated_str = interpolated_str.replace( + self._DIR_WITHOUT_TRAILING_SLASH_VAR, + self._file_info_extractor.get_dir_without_trailing_slash(file_path_abs), + ) + interpolated_str = interpolated_str.replace(self._DIR_VAR, self._file_info_extractor.get_dir(file_path_abs)) return interpolated_str diff --git a/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py b/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py index ab68501..c7d7c70 100644 --- a/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py +++ b/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py @@ -11,6 +11,7 @@ def test_build( fixture_project_info_extractor, fixture_file_info_extractor ) workspace_root: str = fixture_project_info_extractor.get_workspace_root() + comparator("$dir", "/home/user/file.txt", "/home/user/") comparator("$dirWithoutTrailingSlash", "/home/user/file.txt", "/home/user") comparator("$dirWithoutTrailingSlash$dir", "/home/user/file.txt", "/home/user/home/user/") @@ -25,6 +26,9 @@ def test_build( comparator("$workspaceRoot/$fileName", "/home/user/file.txt", f"{workspace_root}/file.txt") comparator("$workspaceRoot/$fileNameWithoutExt", "/home/user/file", f"{workspace_root}/file") + # Windows path containing \u (users) and \t (test) to trigger the re.sub bug + comparator("$fullFileName", r"C:\users\test\app.py", r"C:\users\test\app.py") + class TComparator: def __init__(self, project_info_extractor: IProjectInfoExtractor, file_info_extractor: IFileInfoExtractor): self._project_info_extractor: IProjectInfoExtractor = project_info_extractor