Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: CI Pipeline

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

env:
PYTHON_VERSION: "3.13"
UV_VERSION: "0.6.16"

jobs:
pre-commit:
name: Run pre-commit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install uv
run: |
pip install uv==${{ env.UV_VERSION }}

- name: Install deps
run: |
uv pip install --system --requirements python_coderunner/pyproject.toml

- name: Run pre-commit
run: |
pre-commit install
pre-commit run --all-files

tests:
name: Run tests
needs: pre-commit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install uv
run: |
pip install uv==${{ env.UV_VERSION }}

- name: Install deps
run: |
uv pip install --system --requirements python_coderunner/pyproject.toml

- name: Run tests
run: |
cd python_coderunner
pytest
73 changes: 45 additions & 28 deletions autoload/coderunner.vim
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
let s:save_cpo = &cpo
set cpo&vim

command! -range CodeRunnerRun call coderunner#Run(visualmode(), <range>, <line1>, <line2>)
command! -range CodeRunnerRunByFileExt call coderunner#RunByFileExt(visualmode(), <range>, <line1>, <line2>)
command! -range CodeRunnerRunByFileType call coderunner#RunByFileType(visualmode(), <range>, <line1>, <line2>)
command! -range CodeRunnerRunByGlob call coderunner#RunByGlob(visualmode(), <range>, <line1>, <line2>)


let s:script_folder_path = escape(expand('<sfile>:p:h'), '\')


Expand Down Expand Up @@ -30,11 +36,6 @@ def coderunner_run():
coderunner.run()


@safe_coderunner_access
def coderunner_run_by_glob():
coderunner.run_by_glob()


@safe_coderunner_access
def coderunner_run_by_file_ext():
coderunner.run_by_file_ext()
Expand All @@ -45,6 +46,11 @@ def coderunner_run_by_file_type():
coderunner.run_by_file_type()


@safe_coderunner_access
def coderunner_run_by_glob():
coderunner.run_by_glob()


@safe_coderunner_access
def coderunner_remove_coderunner_tempfiles():
coderunner.remove_coderunner_tempfiles()
Expand Down Expand Up @@ -74,30 +80,30 @@ EOF
endfunction


function coderunner#Run() abort
function coderunner#Run(visualmode, range, first_line, last_line) range abort
python3 << EOF
coderunner_run()
EOF
endfunction


function coderunner#RunByGlob() abort
function coderunner#RunByFileExt(visualmode, range, first_line, last_line) range abort
python3 << EOF
coderunner_run_by_glob()
coderunner_run_by_file_ext()
EOF
endfunction


function coderunner#RunByFileExt() abort
function coderunner#RunByFileType(visualmode, range, first_line, last_line) range abort
python3 << EOF
coderunner_run_by_file_ext()
coderunner_run_by_file_type()
EOF
endfunction


function coderunner#RunByFileType() abort
function coderunner#RunByGlob(visualmode, range, first_line, last_line) range abort
python3 << EOF
coderunner_run_by_file_type()
coderunner_run_by_glob()
EOF
endfunction

Expand All @@ -116,24 +122,35 @@ EOF
endfunction


function coderunner#GetSelectedText()
if mode() !~# '[vV]'
function! coderunner#GetSelectedText(visualmode, range, first_line, last_line) abort
" a slightly modified version from https://github.com/voldikss/vim-floaterm
if a:range == 0
return v:null
end

execute "normal! \<Esc>"

let [line_start, column_start] = getpos("'<")[1:2]
let [line_end, column_end] = getpos("'>")[1:2]
let lines = getline(line_start, line_end)

if len(lines) == 0
return ''
elseif a:range == 1
let lines = [getline(a:first_line)]
else
let [selected_line_1, selected_col_1] = getpos("'<")[1:2]
let [selected_line_2, selected_col_2] = getpos("'>")[1:2]
if selected_line_1 == 0 || selected_col_1 == 0 || selected_line_2 == 0 || selected_col_2 == 0
\ || a:first_line != selected_line_1 || a:last_line != selected_line_2
let lines = getline(a:first_line, a:last_line)
else
let lines = getline(selected_line_1, selected_line_2)
if !empty(lines)
if a:visualmode ==# 'v'
let lines[-1] = lines[-1][: selected_col_2 - (&selection == 'inclusive' ? 1 : 2)]
let lines[0] = lines[0][selected_col_1 - 1:]
elseif a:visualmode ==# 'V'
elseif a:visualmode == "\<c-v>"
let i = 0
for line in lines
let lines[i] = line[selected_col_1 - 1: selected_col_2 - (&selection == 'inclusive' ? 1 : 2)]
let i = i + 1
endfor
endif
endif
endif
endif

let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
let lines[0] = lines[0][column_start - 1:]

return join(lines, "\n")
endfunction

Expand Down
3 changes: 2 additions & 1 deletion python_coderunner/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "vim-code-runner"
version = "0.1.0"
requires-python = ">=3.5"
requires-python = ">=3.13"
dependencies = [
"isort>=4.3.21",
"mypy>=0.910",
Expand All @@ -11,6 +11,7 @@ dependencies = [
"pytest-mock>=3.5.1",
"pyyaml>=5.4.1",
"requests>=2.27.1",
"ruff>=0.12.0",
]

[tool.pytest.ini_options]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ def dispatch(self, file_path_abs: str) -> Optional[ICommandBuilder]:
return command_builder

for dispatcher in self._config_manager.get_dispatchers_order():
if (
dispatcher == EDispatchersTypes.BY_GLOB
and (command_builder := self.dispatch_by_glob(file_path_abs)) is not None
):
return command_builder
if (
dispatcher == EDispatchersTypes.BY_FILE_EXT
and (command_builder := self.dispatch_by_file_ext(file_path_abs)) is not None
Expand All @@ -69,5 +64,10 @@ def dispatch(self, file_path_abs: str) -> Optional[ICommandBuilder]:
and (command_builder := self.dispatch_by_file_type(file_path_abs)) is not None
):
return command_builder
if (
dispatcher == EDispatchersTypes.BY_GLOB
and (command_builder := self.dispatch_by_glob(file_path_abs)) is not None
):
return command_builder

return command_builder
2 changes: 1 addition & 1 deletion python_coderunner/src/config_manager/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class EDispatchersTypes(StrEnum):
BY_FILE_TYPE = "by_file_type"
BY_FILE_EXT = "by_file_ext"
BY_FILE_TYPE = "by_file_type"
BY_GLOB = "by_glob"


Expand Down
3 changes: 1 addition & 2 deletions python_coderunner/src/editor/vim_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ def get_current_file_name(self) -> str:
return vim.current.buffer.name

def get_selected_text(self) -> Optional[str]:
# use echo py3eval to debug
return vim.eval("coderunner#GetSelectedText()")
return vim.eval("coderunner#GetSelectedText(a:visualmode, a:range, a:first_line, a:last_line)")

def save_all_files(self) -> None:
vim.command("wa")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ def remove_coderunner_tempfiles(self) -> None:
os.unlink(temp_file_abs_path)
except OSError:
pass
self._temp_files.clear()
116 changes: 58 additions & 58 deletions python_coderunner/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
import tempfile
import unittest
from typing import Callable, Dict, Generator, List, Sequence, Tuple
from typing import Callable, Dict, Generator, Sequence, Tuple
from unittest.mock import MagicMock

import pytest
Expand Down Expand Up @@ -70,70 +70,13 @@ def fixture_config_manager(request: pytest.FixtureRequest) -> Generator[TBasicCo
yield from request.param()


def vim_project_info_extractor_factory(file_info_extractor: IFileInfoExtractor) -> Generator[IProjectInfoExtractor]:
with tempfile.TemporaryDirectory() as temp_dir:
extractor = TVimProjectInfoExtractor(file_info_extractor)
with unittest.mock.patch.object(
extractor,
"get_workspace_root",
return_value=temp_dir,
):
yield extractor


def get_all_project_info_extractors_factories(
file_info_extractors_factories: Sequence[Callable[[], IFileInfoExtractor]],
) -> Sequence[Callable[..., Generator[IProjectInfoExtractor]]]:
"""
The problem is that the params in the fixture are defined only once, so if you use the fixture
more than once, all the generators inside are invalidated, so you need to create a generator factory.
"""
project_info_extractors_factories: list[Callable[..., Generator[IProjectInfoExtractor]]] = []
for file_info_extractor_factory in file_info_extractors_factories:
project_info_extractors_factories.append(
lambda file_info_extractor_factory=file_info_extractor_factory: vim_project_info_extractor_factory(
file_info_extractor_factory()
)
)

return project_info_extractors_factories


def get_all_file_info_extractors_factories() -> Sequence[Callable[[], IFileInfoExtractor]]:
return (TBasicFileInfoExtractor,)


@pytest.fixture
def fixture_shebang_command_builders_dispatcher(
fixture_file_info_extractor: IFileInfoExtractor,
) -> TShebangCommandBuildersDispatcher:
return TShebangCommandBuildersDispatcher(fixture_file_info_extractor)


@pytest.fixture
def fixture_glob_command_builders_dispatcher() -> TGlobCommandBuildersDispatcher:
glob_patterns: Tuple[str, ...] = (
"**/*.py",
"**/test.py",
"*.js",
"*.java",
"*.cpp",
"*.8xp.txt",
"*.*.*",
"**/*.log",
"**/*.*.*",
)
glob_to_builder: Tuple[Tuple[re.Pattern, ICommandBuilder], ...] = tuple(
(
re.compile(glob.translate(pattern, recursive=True, include_hidden=True)),
MagicMock(spec=ICommandBuilder, build=MagicMock(return_value=pattern)),
)
for pattern in sorted(glob_patterns, reverse=True)
)

return TGlobCommandBuildersDispatcher(glob_to_builder)


@pytest.fixture
def fixture_file_ext_command_builders_dispatcher(
fixture_file_info_extractor: IFileInfoExtractor,
Expand Down Expand Up @@ -173,6 +116,63 @@ def fixture_file_type_command_builders_dispatcher(
)


@pytest.fixture
def fixture_glob_command_builders_dispatcher() -> TGlobCommandBuildersDispatcher:
glob_patterns: Tuple[str, ...] = (
"**/*.py",
"**/test.py",
"*.js",
"*.java",
"*.cpp",
"*.8xp.txt",
"*.*.*",
"**/*.log",
"**/*.*.*",
)
glob_to_builder: Tuple[Tuple[re.Pattern, ICommandBuilder], ...] = tuple(
(
re.compile(glob.translate(pattern, recursive=True, include_hidden=True)),
MagicMock(spec=ICommandBuilder, build=MagicMock(return_value=pattern)),
)
for pattern in sorted(glob_patterns, reverse=True)
)

return TGlobCommandBuildersDispatcher(glob_to_builder)


def vim_project_info_extractor_factory(file_info_extractor: IFileInfoExtractor) -> Generator[IProjectInfoExtractor]:
with tempfile.TemporaryDirectory() as temp_dir:
extractor = TVimProjectInfoExtractor(file_info_extractor)
with unittest.mock.patch.object(
extractor,
"get_workspace_root",
return_value=temp_dir,
):
yield extractor


def get_all_project_info_extractors_factories(
file_info_extractors_factories: Sequence[Callable[[], IFileInfoExtractor]],
) -> Sequence[Callable[..., Generator[IProjectInfoExtractor]]]:
"""
The problem is that the params in the fixture are defined only once, so if you use the fixture
more than once, all the generators inside are invalidated, so you need to create a generator factory.
"""
project_info_extractors_factories: list[Callable[..., Generator[IProjectInfoExtractor]]] = []
for file_info_extractor_factory in file_info_extractors_factories:
project_info_extractors_factories.append(
lambda file_info_extractor_factory=file_info_extractor_factory: vim_project_info_extractor_factory(
file_info_extractor_factory()
)
)

return project_info_extractors_factories


def get_all_file_info_extractors_factories() -> Sequence[Callable[[], IFileInfoExtractor]]:
return (TBasicFileInfoExtractor,)


@pytest.fixture(params=get_all_project_info_extractors_factories(get_all_file_info_extractors_factories()))
def fixture_project_info_extractor(request: pytest.FixtureRequest) -> Generator[IFileInfoExtractor]:
yield from request.param()
Expand Down
Loading