Skip to content

Commit 68fc3c8

Browse files
[refactor] fix selection (#10)
* updated python version in pyproject.toml * fix: coderunner#GetSelectedText * added: ci
1 parent cd28fc5 commit 68fc3c8

File tree

9 files changed

+319
-2696
lines changed

9 files changed

+319
-2696
lines changed

.github/workflows/ci.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: CI Pipeline
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
env:
10+
PYTHON_VERSION: "3.13"
11+
UV_VERSION: "0.6.16"
12+
13+
jobs:
14+
pre-commit:
15+
name: Run pre-commit
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Set up Python ${{ env.PYTHON_VERSION }}
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: ${{ env.PYTHON_VERSION }}
24+
25+
- name: Install uv
26+
run: |
27+
pip install uv==${{ env.UV_VERSION }}
28+
29+
- name: Install deps
30+
run: |
31+
uv pip install --system --requirements python_coderunner/pyproject.toml
32+
33+
- name: Run pre-commit
34+
run: |
35+
pre-commit install
36+
pre-commit run --all-files
37+
38+
tests:
39+
name: Run tests
40+
needs: pre-commit
41+
runs-on: ubuntu-latest
42+
steps:
43+
- uses: actions/checkout@v4
44+
45+
- name: Set up Python ${{ env.PYTHON_VERSION }}
46+
uses: actions/setup-python@v5
47+
with:
48+
python-version: ${{ env.PYTHON_VERSION }}
49+
50+
- name: Install uv
51+
run: |
52+
pip install uv==${{ env.UV_VERSION }}
53+
54+
- name: Install deps
55+
run: |
56+
uv pip install --system --requirements python_coderunner/pyproject.toml
57+
58+
- name: Run tests
59+
run: |
60+
cd python_coderunner
61+
pytest

autoload/coderunner.vim

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
let s:save_cpo = &cpo
33
set cpo&vim
44

5+
command! -range CodeRunnerRun call coderunner#Run(visualmode(), <range>, <line1>, <line2>)
6+
command! -range CodeRunnerRunByFileExt call coderunner#RunByFileExt(visualmode(), <range>, <line1>, <line2>)
7+
command! -range CodeRunnerRunByFileType call coderunner#RunByFileType(visualmode(), <range>, <line1>, <line2>)
8+
command! -range CodeRunnerRunByGlob call coderunner#RunByGlob(visualmode(), <range>, <line1>, <line2>)
9+
10+
511
let s:script_folder_path = escape(expand('<sfile>:p:h'), '\')
612

713

@@ -30,11 +36,6 @@ def coderunner_run():
3036
coderunner.run()
3137

3238

33-
@safe_coderunner_access
34-
def coderunner_run_by_glob():
35-
coderunner.run_by_glob()
36-
37-
3839
@safe_coderunner_access
3940
def coderunner_run_by_file_ext():
4041
coderunner.run_by_file_ext()
@@ -45,6 +46,11 @@ def coderunner_run_by_file_type():
4546
coderunner.run_by_file_type()
4647

4748

49+
@safe_coderunner_access
50+
def coderunner_run_by_glob():
51+
coderunner.run_by_glob()
52+
53+
4854
@safe_coderunner_access
4955
def coderunner_remove_coderunner_tempfiles():
5056
coderunner.remove_coderunner_tempfiles()
@@ -74,30 +80,30 @@ EOF
7480
endfunction
7581

7682

77-
function coderunner#Run() abort
83+
function coderunner#Run(visualmode, range, first_line, last_line) range abort
7884
python3 << EOF
7985
coderunner_run()
8086
EOF
8187
endfunction
8288

8389

84-
function coderunner#RunByGlob() abort
90+
function coderunner#RunByFileExt(visualmode, range, first_line, last_line) range abort
8591
python3 << EOF
86-
coderunner_run_by_glob()
92+
coderunner_run_by_file_ext()
8793
EOF
8894
endfunction
8995

9096

91-
function coderunner#RunByFileExt() abort
97+
function coderunner#RunByFileType(visualmode, range, first_line, last_line) range abort
9298
python3 << EOF
93-
coderunner_run_by_file_ext()
99+
coderunner_run_by_file_type()
94100
EOF
95101
endfunction
96102

97103

98-
function coderunner#RunByFileType() abort
104+
function coderunner#RunByGlob(visualmode, range, first_line, last_line) range abort
99105
python3 << EOF
100-
coderunner_run_by_file_type()
106+
coderunner_run_by_glob()
101107
EOF
102108
endfunction
103109

@@ -116,24 +122,35 @@ EOF
116122
endfunction
117123

118124

119-
function coderunner#GetSelectedText()
120-
if mode() !~# '[vV]'
125+
function! coderunner#GetSelectedText(visualmode, range, first_line, last_line) abort
126+
" a slightly modified version from https://github.com/voldikss/vim-floaterm
127+
if a:range == 0
121128
return v:null
122-
end
123-
124-
execute "normal! \<Esc>"
125-
126-
let [line_start, column_start] = getpos("'<")[1:2]
127-
let [line_end, column_end] = getpos("'>")[1:2]
128-
let lines = getline(line_start, line_end)
129-
130-
if len(lines) == 0
131-
return ''
129+
elseif a:range == 1
130+
let lines = [getline(a:first_line)]
131+
else
132+
let [selected_line_1, selected_col_1] = getpos("'<")[1:2]
133+
let [selected_line_2, selected_col_2] = getpos("'>")[1:2]
134+
if selected_line_1 == 0 || selected_col_1 == 0 || selected_line_2 == 0 || selected_col_2 == 0
135+
\ || a:first_line != selected_line_1 || a:last_line != selected_line_2
136+
let lines = getline(a:first_line, a:last_line)
137+
else
138+
let lines = getline(selected_line_1, selected_line_2)
139+
if !empty(lines)
140+
if a:visualmode ==# 'v'
141+
let lines[-1] = lines[-1][: selected_col_2 - (&selection == 'inclusive' ? 1 : 2)]
142+
let lines[0] = lines[0][selected_col_1 - 1:]
143+
elseif a:visualmode ==# 'V'
144+
elseif a:visualmode == "\<c-v>"
145+
let i = 0
146+
for line in lines
147+
let lines[i] = line[selected_col_1 - 1: selected_col_2 - (&selection == 'inclusive' ? 1 : 2)]
148+
let i = i + 1
149+
endfor
150+
endif
151+
endif
152+
endif
132153
endif
133-
134-
let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
135-
let lines[0] = lines[0][column_start - 1:]
136-
137154
return join(lines, "\n")
138155
endfunction
139156

python_coderunner/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "vim-code-runner"
33
version = "0.1.0"
4-
requires-python = ">=3.5"
4+
requires-python = ">=3.13"
55
dependencies = [
66
"isort>=4.3.21",
77
"mypy>=0.910",
@@ -11,6 +11,7 @@ dependencies = [
1111
"pytest-mock>=3.5.1",
1212
"pyyaml>=5.4.1",
1313
"requests>=2.27.1",
14+
"ruff>=0.12.0",
1415
]
1516

1617
[tool.pytest.ini_options]

python_coderunner/src/command_dispatcher_strategy_selector/basic.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ def dispatch(self, file_path_abs: str) -> Optional[ICommandBuilder]:
5454
return command_builder
5555

5656
for dispatcher in self._config_manager.get_dispatchers_order():
57-
if (
58-
dispatcher == EDispatchersTypes.BY_GLOB
59-
and (command_builder := self.dispatch_by_glob(file_path_abs)) is not None
60-
):
61-
return command_builder
6257
if (
6358
dispatcher == EDispatchersTypes.BY_FILE_EXT
6459
and (command_builder := self.dispatch_by_file_ext(file_path_abs)) is not None
@@ -69,5 +64,10 @@ def dispatch(self, file_path_abs: str) -> Optional[ICommandBuilder]:
6964
and (command_builder := self.dispatch_by_file_type(file_path_abs)) is not None
7065
):
7166
return command_builder
67+
if (
68+
dispatcher == EDispatchersTypes.BY_GLOB
69+
and (command_builder := self.dispatch_by_glob(file_path_abs)) is not None
70+
):
71+
return command_builder
7272

7373
return command_builder

python_coderunner/src/config_manager/basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55

66
class EDispatchersTypes(StrEnum):
7-
BY_FILE_TYPE = "by_file_type"
87
BY_FILE_EXT = "by_file_ext"
8+
BY_FILE_TYPE = "by_file_type"
99
BY_GLOB = "by_glob"
1010

1111

python_coderunner/src/editor/vim_editor.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ def get_current_file_name(self) -> str:
1010
return vim.current.buffer.name
1111

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

1615
def save_all_files(self) -> None:
1716
vim.command("wa")

python_coderunner/src/editor_service_for_coderunner/basic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,4 @@ def remove_coderunner_tempfiles(self) -> None:
5858
os.unlink(temp_file_abs_path)
5959
except OSError:
6060
pass
61+
self._temp_files.clear()

python_coderunner/tests/unit/conftest.py

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
import tempfile
55
import unittest
6-
from typing import Callable, Dict, Generator, List, Sequence, Tuple
6+
from typing import Callable, Dict, Generator, Sequence, Tuple
77
from unittest.mock import MagicMock
88

99
import pytest
@@ -70,70 +70,13 @@ def fixture_config_manager(request: pytest.FixtureRequest) -> Generator[TBasicCo
7070
yield from request.param()
7171

7272

73-
def vim_project_info_extractor_factory(file_info_extractor: IFileInfoExtractor) -> Generator[IProjectInfoExtractor]:
74-
with tempfile.TemporaryDirectory() as temp_dir:
75-
extractor = TVimProjectInfoExtractor(file_info_extractor)
76-
with unittest.mock.patch.object(
77-
extractor,
78-
"get_workspace_root",
79-
return_value=temp_dir,
80-
):
81-
yield extractor
82-
83-
84-
def get_all_project_info_extractors_factories(
85-
file_info_extractors_factories: Sequence[Callable[[], IFileInfoExtractor]],
86-
) -> Sequence[Callable[..., Generator[IProjectInfoExtractor]]]:
87-
"""
88-
The problem is that the params in the fixture are defined only once, so if you use the fixture
89-
more than once, all the generators inside are invalidated, so you need to create a generator factory.
90-
"""
91-
project_info_extractors_factories: list[Callable[..., Generator[IProjectInfoExtractor]]] = []
92-
for file_info_extractor_factory in file_info_extractors_factories:
93-
project_info_extractors_factories.append(
94-
lambda file_info_extractor_factory=file_info_extractor_factory: vim_project_info_extractor_factory(
95-
file_info_extractor_factory()
96-
)
97-
)
98-
99-
return project_info_extractors_factories
100-
101-
102-
def get_all_file_info_extractors_factories() -> Sequence[Callable[[], IFileInfoExtractor]]:
103-
return (TBasicFileInfoExtractor,)
104-
105-
10673
@pytest.fixture
10774
def fixture_shebang_command_builders_dispatcher(
10875
fixture_file_info_extractor: IFileInfoExtractor,
10976
) -> TShebangCommandBuildersDispatcher:
11077
return TShebangCommandBuildersDispatcher(fixture_file_info_extractor)
11178

11279

113-
@pytest.fixture
114-
def fixture_glob_command_builders_dispatcher() -> TGlobCommandBuildersDispatcher:
115-
glob_patterns: Tuple[str, ...] = (
116-
"**/*.py",
117-
"**/test.py",
118-
"*.js",
119-
"*.java",
120-
"*.cpp",
121-
"*.8xp.txt",
122-
"*.*.*",
123-
"**/*.log",
124-
"**/*.*.*",
125-
)
126-
glob_to_builder: Tuple[Tuple[re.Pattern, ICommandBuilder], ...] = tuple(
127-
(
128-
re.compile(glob.translate(pattern, recursive=True, include_hidden=True)),
129-
MagicMock(spec=ICommandBuilder, build=MagicMock(return_value=pattern)),
130-
)
131-
for pattern in sorted(glob_patterns, reverse=True)
132-
)
133-
134-
return TGlobCommandBuildersDispatcher(glob_to_builder)
135-
136-
13780
@pytest.fixture
13881
def fixture_file_ext_command_builders_dispatcher(
13982
fixture_file_info_extractor: IFileInfoExtractor,
@@ -173,6 +116,63 @@ def fixture_file_type_command_builders_dispatcher(
173116
)
174117

175118

119+
@pytest.fixture
120+
def fixture_glob_command_builders_dispatcher() -> TGlobCommandBuildersDispatcher:
121+
glob_patterns: Tuple[str, ...] = (
122+
"**/*.py",
123+
"**/test.py",
124+
"*.js",
125+
"*.java",
126+
"*.cpp",
127+
"*.8xp.txt",
128+
"*.*.*",
129+
"**/*.log",
130+
"**/*.*.*",
131+
)
132+
glob_to_builder: Tuple[Tuple[re.Pattern, ICommandBuilder], ...] = tuple(
133+
(
134+
re.compile(glob.translate(pattern, recursive=True, include_hidden=True)),
135+
MagicMock(spec=ICommandBuilder, build=MagicMock(return_value=pattern)),
136+
)
137+
for pattern in sorted(glob_patterns, reverse=True)
138+
)
139+
140+
return TGlobCommandBuildersDispatcher(glob_to_builder)
141+
142+
143+
def vim_project_info_extractor_factory(file_info_extractor: IFileInfoExtractor) -> Generator[IProjectInfoExtractor]:
144+
with tempfile.TemporaryDirectory() as temp_dir:
145+
extractor = TVimProjectInfoExtractor(file_info_extractor)
146+
with unittest.mock.patch.object(
147+
extractor,
148+
"get_workspace_root",
149+
return_value=temp_dir,
150+
):
151+
yield extractor
152+
153+
154+
def get_all_project_info_extractors_factories(
155+
file_info_extractors_factories: Sequence[Callable[[], IFileInfoExtractor]],
156+
) -> Sequence[Callable[..., Generator[IProjectInfoExtractor]]]:
157+
"""
158+
The problem is that the params in the fixture are defined only once, so if you use the fixture
159+
more than once, all the generators inside are invalidated, so you need to create a generator factory.
160+
"""
161+
project_info_extractors_factories: list[Callable[..., Generator[IProjectInfoExtractor]]] = []
162+
for file_info_extractor_factory in file_info_extractors_factories:
163+
project_info_extractors_factories.append(
164+
lambda file_info_extractor_factory=file_info_extractor_factory: vim_project_info_extractor_factory(
165+
file_info_extractor_factory()
166+
)
167+
)
168+
169+
return project_info_extractors_factories
170+
171+
172+
def get_all_file_info_extractors_factories() -> Sequence[Callable[[], IFileInfoExtractor]]:
173+
return (TBasicFileInfoExtractor,)
174+
175+
176176
@pytest.fixture(params=get_all_project_info_extractors_factories(get_all_file_info_extractors_factories()))
177177
def fixture_project_info_extractor(request: pytest.FixtureRequest) -> Generator[IFileInfoExtractor]:
178178
yield from request.param()

0 commit comments

Comments
 (0)