vimlm 0.0.9__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vimlm-0.0.9 → vimlm-0.1.0}/PKG-INFO +70 -10
- {vimlm-0.0.9 → vimlm-0.1.0}/README.md +67 -7
- {vimlm-0.0.9 → vimlm-0.1.0}/setup.py +1 -1
- {vimlm-0.0.9 → vimlm-0.1.0}/vimlm.egg-info/PKG-INFO +70 -10
- vimlm-0.1.0/vimlm.egg-info/requires.txt +3 -0
- {vimlm-0.0.9 → vimlm-0.1.0}/vimlm.py +184 -66
- vimlm-0.0.9/vimlm.egg-info/requires.txt +0 -3
- {vimlm-0.0.9 → vimlm-0.1.0}/LICENSE +0 -0
- {vimlm-0.0.9 → vimlm-0.1.0}/setup.cfg +0 -0
- {vimlm-0.0.9 → vimlm-0.1.0}/vimlm.egg-info/SOURCES.txt +0 -0
- {vimlm-0.0.9 → vimlm-0.1.0}/vimlm.egg-info/dependency_links.txt +0 -0
- {vimlm-0.0.9 → vimlm-0.1.0}/vimlm.egg-info/entry_points.txt +0 -0
- {vimlm-0.0.9 → vimlm-0.1.0}/vimlm.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: vimlm
|
3
|
-
Version: 0.0
|
3
|
+
Version: 0.1.0
|
4
4
|
Summary: VimLM - LLM-powered Vim assistant
|
5
5
|
Home-page: https://github.com/JosefAlbers/vimlm
|
6
6
|
Author: Josef Albers
|
@@ -8,8 +8,8 @@ Author-email: albersj66@gmail.com
|
|
8
8
|
Requires-Python: >=3.12.8
|
9
9
|
Description-Content-Type: text/markdown
|
10
10
|
License-File: LICENSE
|
11
|
-
Requires-Dist: nanollama>=0.0.
|
12
|
-
Requires-Dist: mlx_lm_utils>=0.0.
|
11
|
+
Requires-Dist: nanollama>=0.0.6
|
12
|
+
Requires-Dist: mlx_lm_utils>=0.0.4
|
13
13
|
Requires-Dist: watchfiles==1.0.4
|
14
14
|
Dynamic: author
|
15
15
|
Dynamic: author-email
|
@@ -20,7 +20,6 @@ Dynamic: requires-dist
|
|
20
20
|
Dynamic: requires-python
|
21
21
|
Dynamic: summary
|
22
22
|
|
23
|
-
|
24
23
|
# VimLM - AI-Powered Coding Assistant for Vim
|
25
24
|
|
26
25
|

|
@@ -48,7 +47,45 @@ pip install vimlm
|
|
48
47
|
vimlm
|
49
48
|
```
|
50
49
|
|
51
|
-
##
|
50
|
+
## Smart Autocomplete
|
51
|
+
|
52
|
+
### **Basic Usage**
|
53
|
+
|
54
|
+
| Key Binding | Mode | Action |
|
55
|
+
|-------------|---------|-----------------------------------------|
|
56
|
+
| `Ctrl-l` | Insert | Generate code suggestion |
|
57
|
+
| `Ctrl-p` | Insert | Insert generated code |
|
58
|
+
|
59
|
+
*Example Workflow*:
|
60
|
+
1. Place cursor where you need code
|
61
|
+
```python
|
62
|
+
def quicksort(arr):
|
63
|
+
if len(arr) <= 1:
|
64
|
+
return arr
|
65
|
+
pivot = arr[len(arr) // 2]
|
66
|
+
# <Cursor here>
|
67
|
+
middle = [x for x in arr if x == pivot]
|
68
|
+
right = [x for x in arr if x > pivot]
|
69
|
+
return quicksort(left) + middle + quicksort(right)
|
70
|
+
```
|
71
|
+
|
72
|
+
2. `Ctrl-l` to trigger → `Ctrl-p` to insert
|
73
|
+
|
74
|
+
### **Repository-Level Code Completion**
|
75
|
+
|
76
|
+
| Option | Description |
|
77
|
+
|------------|------------------------------------------|
|
78
|
+
| `--repo` | Paths to include as repository context |
|
79
|
+
|
80
|
+
The `--repo` option enhances autocomplete by providing repository-level context to the LLM.
|
81
|
+
|
82
|
+
*Example Workflow*:
|
83
|
+
1. Launch VimLM with repo context: `vimlm main.py --repo utils/*`
|
84
|
+
2. In Insert mode, place cursor where completion is needed
|
85
|
+
3. `Ctrl-l` to generate suggestions informed by repository context
|
86
|
+
4. `Ctrl-p` to accept and insert the code
|
87
|
+
|
88
|
+
## Conversational Assistance
|
52
89
|
|
53
90
|
| Key Binding | Mode | Action |
|
54
91
|
|-------------|---------------|----------------------------------------|
|
@@ -86,12 +123,12 @@ vimlm
|
|
86
123
|
|
87
124
|
`!` prefix to embed inline directives in prompts:
|
88
125
|
|
89
|
-
| Directive | Description
|
90
|
-
|
126
|
+
| Directive | Description |
|
127
|
+
|------------------|--------------------------------------------|
|
91
128
|
| `!include PATH` | Add file/directory/shell output to context |
|
92
|
-
| `!deploy DEST` | Save code blocks to directory
|
93
|
-
| `!continue N` | Continue stopped response
|
94
|
-
| `!followup` | Continue conversation
|
129
|
+
| `!deploy DEST` | Save code blocks to directory |
|
130
|
+
| `!continue N` | Continue stopped response |
|
131
|
+
| `!followup` | Continue conversation |
|
95
132
|
|
96
133
|
### 1. **Context Layering**
|
97
134
|
```text
|
@@ -141,6 +178,29 @@ Simplify complex tasks by chaining multiple commands together into a single, reu
|
|
141
178
|
:VimLM Add Google-style docstrings !include % !continue 4000
|
142
179
|
```
|
143
180
|
|
181
|
+
## Smart Autocomplete
|
182
|
+
**Fill code gaps intelligently**
|
183
|
+
|
184
|
+
| Key Binding | Mode | Action |
|
185
|
+
|-------------|---------|-----------------------------------------|
|
186
|
+
| `Ctrl-l` | Insert | Generate code suggestion |
|
187
|
+
| `Ctrl-p` | Insert | Insert generated code |
|
188
|
+
|
189
|
+
*Example Workflow*:
|
190
|
+
1. Place cursor where you need code
|
191
|
+
```python
|
192
|
+
def quicksort(arr):
|
193
|
+
if len(arr) <= 1:
|
194
|
+
return arr
|
195
|
+
pivot = arr[len(arr) // 2]
|
196
|
+
# <Cursor here>
|
197
|
+
middle = [x for x in arr if x == pivot]
|
198
|
+
right = [x for x in arr if x > pivot]
|
199
|
+
return quicksort(left) + middle + quicksort(right)
|
200
|
+
```
|
201
|
+
|
202
|
+
2. `Ctrl-l` to trigger → `Ctrl-p` to insert
|
203
|
+
|
144
204
|
## Configuration
|
145
205
|
|
146
206
|
### 1. **Model Settings**
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# VimLM - AI-Powered Coding Assistant for Vim
|
3
2
|
|
4
3
|

|
@@ -26,7 +25,45 @@ pip install vimlm
|
|
26
25
|
vimlm
|
27
26
|
```
|
28
27
|
|
29
|
-
##
|
28
|
+
## Smart Autocomplete
|
29
|
+
|
30
|
+
### **Basic Usage**
|
31
|
+
|
32
|
+
| Key Binding | Mode | Action |
|
33
|
+
|-------------|---------|-----------------------------------------|
|
34
|
+
| `Ctrl-l` | Insert | Generate code suggestion |
|
35
|
+
| `Ctrl-p` | Insert | Insert generated code |
|
36
|
+
|
37
|
+
*Example Workflow*:
|
38
|
+
1. Place cursor where you need code
|
39
|
+
```python
|
40
|
+
def quicksort(arr):
|
41
|
+
if len(arr) <= 1:
|
42
|
+
return arr
|
43
|
+
pivot = arr[len(arr) // 2]
|
44
|
+
# <Cursor here>
|
45
|
+
middle = [x for x in arr if x == pivot]
|
46
|
+
right = [x for x in arr if x > pivot]
|
47
|
+
return quicksort(left) + middle + quicksort(right)
|
48
|
+
```
|
49
|
+
|
50
|
+
2. `Ctrl-l` to trigger → `Ctrl-p` to insert
|
51
|
+
|
52
|
+
### **Repository-Level Code Completion**
|
53
|
+
|
54
|
+
| Option | Description |
|
55
|
+
|------------|------------------------------------------|
|
56
|
+
| `--repo` | Paths to include as repository context |
|
57
|
+
|
58
|
+
The `--repo` option enhances autocomplete by providing repository-level context to the LLM.
|
59
|
+
|
60
|
+
*Example Workflow*:
|
61
|
+
1. Launch VimLM with repo context: `vimlm main.py --repo utils/*`
|
62
|
+
2. In Insert mode, place cursor where completion is needed
|
63
|
+
3. `Ctrl-l` to generate suggestions informed by repository context
|
64
|
+
4. `Ctrl-p` to accept and insert the code
|
65
|
+
|
66
|
+
## Conversational Assistance
|
30
67
|
|
31
68
|
| Key Binding | Mode | Action |
|
32
69
|
|-------------|---------------|----------------------------------------|
|
@@ -64,12 +101,12 @@ vimlm
|
|
64
101
|
|
65
102
|
`!` prefix to embed inline directives in prompts:
|
66
103
|
|
67
|
-
| Directive | Description
|
68
|
-
|
104
|
+
| Directive | Description |
|
105
|
+
|------------------|--------------------------------------------|
|
69
106
|
| `!include PATH` | Add file/directory/shell output to context |
|
70
|
-
| `!deploy DEST` | Save code blocks to directory
|
71
|
-
| `!continue N` | Continue stopped response
|
72
|
-
| `!followup` | Continue conversation
|
107
|
+
| `!deploy DEST` | Save code blocks to directory |
|
108
|
+
| `!continue N` | Continue stopped response |
|
109
|
+
| `!followup` | Continue conversation |
|
73
110
|
|
74
111
|
### 1. **Context Layering**
|
75
112
|
```text
|
@@ -119,6 +156,29 @@ Simplify complex tasks by chaining multiple commands together into a single, reu
|
|
119
156
|
:VimLM Add Google-style docstrings !include % !continue 4000
|
120
157
|
```
|
121
158
|
|
159
|
+
## Smart Autocomplete
|
160
|
+
**Fill code gaps intelligently**
|
161
|
+
|
162
|
+
| Key Binding | Mode | Action |
|
163
|
+
|-------------|---------|-----------------------------------------|
|
164
|
+
| `Ctrl-l` | Insert | Generate code suggestion |
|
165
|
+
| `Ctrl-p` | Insert | Insert generated code |
|
166
|
+
|
167
|
+
*Example Workflow*:
|
168
|
+
1. Place cursor where you need code
|
169
|
+
```python
|
170
|
+
def quicksort(arr):
|
171
|
+
if len(arr) <= 1:
|
172
|
+
return arr
|
173
|
+
pivot = arr[len(arr) // 2]
|
174
|
+
# <Cursor here>
|
175
|
+
middle = [x for x in arr if x == pivot]
|
176
|
+
right = [x for x in arr if x > pivot]
|
177
|
+
return quicksort(left) + middle + quicksort(right)
|
178
|
+
```
|
179
|
+
|
180
|
+
2. `Ctrl-l` to trigger → `Ctrl-p` to insert
|
181
|
+
|
122
182
|
## Configuration
|
123
183
|
|
124
184
|
### 1. **Model Settings**
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: vimlm
|
3
|
-
Version: 0.0
|
3
|
+
Version: 0.1.0
|
4
4
|
Summary: VimLM - LLM-powered Vim assistant
|
5
5
|
Home-page: https://github.com/JosefAlbers/vimlm
|
6
6
|
Author: Josef Albers
|
@@ -8,8 +8,8 @@ Author-email: albersj66@gmail.com
|
|
8
8
|
Requires-Python: >=3.12.8
|
9
9
|
Description-Content-Type: text/markdown
|
10
10
|
License-File: LICENSE
|
11
|
-
Requires-Dist: nanollama>=0.0.
|
12
|
-
Requires-Dist: mlx_lm_utils>=0.0.
|
11
|
+
Requires-Dist: nanollama>=0.0.6
|
12
|
+
Requires-Dist: mlx_lm_utils>=0.0.4
|
13
13
|
Requires-Dist: watchfiles==1.0.4
|
14
14
|
Dynamic: author
|
15
15
|
Dynamic: author-email
|
@@ -20,7 +20,6 @@ Dynamic: requires-dist
|
|
20
20
|
Dynamic: requires-python
|
21
21
|
Dynamic: summary
|
22
22
|
|
23
|
-
|
24
23
|
# VimLM - AI-Powered Coding Assistant for Vim
|
25
24
|
|
26
25
|

|
@@ -48,7 +47,45 @@ pip install vimlm
|
|
48
47
|
vimlm
|
49
48
|
```
|
50
49
|
|
51
|
-
##
|
50
|
+
## Smart Autocomplete
|
51
|
+
|
52
|
+
### **Basic Usage**
|
53
|
+
|
54
|
+
| Key Binding | Mode | Action |
|
55
|
+
|-------------|---------|-----------------------------------------|
|
56
|
+
| `Ctrl-l` | Insert | Generate code suggestion |
|
57
|
+
| `Ctrl-p` | Insert | Insert generated code |
|
58
|
+
|
59
|
+
*Example Workflow*:
|
60
|
+
1. Place cursor where you need code
|
61
|
+
```python
|
62
|
+
def quicksort(arr):
|
63
|
+
if len(arr) <= 1:
|
64
|
+
return arr
|
65
|
+
pivot = arr[len(arr) // 2]
|
66
|
+
# <Cursor here>
|
67
|
+
middle = [x for x in arr if x == pivot]
|
68
|
+
right = [x for x in arr if x > pivot]
|
69
|
+
return quicksort(left) + middle + quicksort(right)
|
70
|
+
```
|
71
|
+
|
72
|
+
2. `Ctrl-l` to trigger → `Ctrl-p` to insert
|
73
|
+
|
74
|
+
### **Repository-Level Code Completion**
|
75
|
+
|
76
|
+
| Option | Description |
|
77
|
+
|------------|------------------------------------------|
|
78
|
+
| `--repo` | Paths to include as repository context |
|
79
|
+
|
80
|
+
The `--repo` option enhances autocomplete by providing repository-level context to the LLM.
|
81
|
+
|
82
|
+
*Example Workflow*:
|
83
|
+
1. Launch VimLM with repo context: `vimlm main.py --repo utils/*`
|
84
|
+
2. In Insert mode, place cursor where completion is needed
|
85
|
+
3. `Ctrl-l` to generate suggestions informed by repository context
|
86
|
+
4. `Ctrl-p` to accept and insert the code
|
87
|
+
|
88
|
+
## Conversational Assistance
|
52
89
|
|
53
90
|
| Key Binding | Mode | Action |
|
54
91
|
|-------------|---------------|----------------------------------------|
|
@@ -86,12 +123,12 @@ vimlm
|
|
86
123
|
|
87
124
|
`!` prefix to embed inline directives in prompts:
|
88
125
|
|
89
|
-
| Directive | Description
|
90
|
-
|
126
|
+
| Directive | Description |
|
127
|
+
|------------------|--------------------------------------------|
|
91
128
|
| `!include PATH` | Add file/directory/shell output to context |
|
92
|
-
| `!deploy DEST` | Save code blocks to directory
|
93
|
-
| `!continue N` | Continue stopped response
|
94
|
-
| `!followup` | Continue conversation
|
129
|
+
| `!deploy DEST` | Save code blocks to directory |
|
130
|
+
| `!continue N` | Continue stopped response |
|
131
|
+
| `!followup` | Continue conversation |
|
95
132
|
|
96
133
|
### 1. **Context Layering**
|
97
134
|
```text
|
@@ -141,6 +178,29 @@ Simplify complex tasks by chaining multiple commands together into a single, reu
|
|
141
178
|
:VimLM Add Google-style docstrings !include % !continue 4000
|
142
179
|
```
|
143
180
|
|
181
|
+
## Smart Autocomplete
|
182
|
+
**Fill code gaps intelligently**
|
183
|
+
|
184
|
+
| Key Binding | Mode | Action |
|
185
|
+
|-------------|---------|-----------------------------------------|
|
186
|
+
| `Ctrl-l` | Insert | Generate code suggestion |
|
187
|
+
| `Ctrl-p` | Insert | Insert generated code |
|
188
|
+
|
189
|
+
*Example Workflow*:
|
190
|
+
1. Place cursor where you need code
|
191
|
+
```python
|
192
|
+
def quicksort(arr):
|
193
|
+
if len(arr) <= 1:
|
194
|
+
return arr
|
195
|
+
pivot = arr[len(arr) // 2]
|
196
|
+
# <Cursor here>
|
197
|
+
middle = [x for x in arr if x == pivot]
|
198
|
+
right = [x for x in arr if x > pivot]
|
199
|
+
return quicksort(left) + middle + quicksort(right)
|
200
|
+
```
|
201
|
+
|
202
|
+
2. `Ctrl-l` to trigger → `Ctrl-p` to insert
|
203
|
+
|
144
204
|
## Configuration
|
145
205
|
|
146
206
|
### 1. **Model Settings**
|
@@ -12,10 +12,13 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
import nanollama
|
16
|
+
import mlx_lm_utils
|
15
17
|
import asyncio
|
16
18
|
import subprocess
|
17
19
|
import json
|
18
20
|
import os
|
21
|
+
import glob
|
19
22
|
from watchfiles import awatch
|
20
23
|
import shutil
|
21
24
|
from datetime import datetime
|
@@ -27,20 +30,22 @@ from string import Template
|
|
27
30
|
import re
|
28
31
|
|
29
32
|
DEFAULTS = dict(
|
30
|
-
LLM_MODEL =
|
33
|
+
LLM_MODEL = "mlx-community/Qwen2.5-Coder-3B-Instruct-4bit", # None | "mlx-community/DeepSeek-R1-Distill-Qwen-7B-4bit" | "mlx-community/deepseek-r1-distill-qwen-1.5b" | "mlx-community/phi-4-4bit" (8.25gb) | "mlx-community/Qwen2.5-Coder-14B-Instruct-4bit" (8.31gb) | "mlx-community/Qwen2.5-Coder-3B-Instruct-4bit" (1.74gb)
|
34
|
+
FIM_MODEL = "mlx-community/Qwen2.5-Coder-0.5B-4bit", # None | "mlx-community/Qwen2.5-Coder-32B-4bit" | "mlx-community/Qwen2.5-Coder-0.5B-4bit" (278mb)
|
31
35
|
NUM_TOKEN = 2000,
|
32
36
|
USE_LEADER = False,
|
33
37
|
KEY_MAP = {},
|
34
38
|
DO_RESET = True,
|
35
39
|
SHOW_USER = False,
|
36
40
|
SEP_CMD = '!',
|
37
|
-
|
38
|
-
|
41
|
+
THINK = ('<think>', '</think>'),
|
42
|
+
VERSION = '0.1.0',
|
43
|
+
DEBUG = True,
|
39
44
|
)
|
40
45
|
|
41
46
|
DATE_FORM = "%Y_%m_%d_%H_%M_%S"
|
42
|
-
VIMLM_DIR = os.path.expanduser("
|
43
|
-
WATCH_DIR = os.path.expanduser("
|
47
|
+
VIMLM_DIR = os.path.expanduser("~/.vimlm")
|
48
|
+
WATCH_DIR = os.path.expanduser("~/.vimlm/watch_dir")
|
44
49
|
CFG_FILE = 'cfg.json'
|
45
50
|
LOG_FILE = "log.json"
|
46
51
|
LTM_FILE = "cache.json"
|
@@ -51,35 +56,35 @@ LOG_PATH = os.path.join(VIMLM_DIR, LOG_FILE)
|
|
51
56
|
LTM_PATH = os.path.join(VIMLM_DIR, LTM_FILE)
|
52
57
|
OUT_PATH = os.path.join(WATCH_DIR, OUT_FILE)
|
53
58
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
if
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
59
|
+
def reset_dir(dir_path):
|
60
|
+
if os.path.exists(dir_path):
|
61
|
+
shutil.rmtree(dir_path)
|
62
|
+
os.makedirs(dir_path)
|
63
|
+
|
64
|
+
def initialize():
|
65
|
+
def is_incompatible(config):
|
66
|
+
v_str = config.get('VERSION', '0.0.0')
|
67
|
+
for min_v, usr_v in zip(DEFAULTS['VERSION'].split('.'), v_str.split('.')):
|
68
|
+
if int(min_v) < int(usr_v):
|
69
|
+
return False
|
70
|
+
elif int(min_v) > int(usr_v):
|
71
|
+
return True
|
72
|
+
return False
|
73
|
+
try:
|
74
|
+
with open(CFG_PATH, "r") as f:
|
75
|
+
config = json.load(f)
|
76
|
+
if is_incompatible(config):
|
77
|
+
raise ValueError('Incompatible version')
|
78
|
+
except Exception as e:
|
79
|
+
print('Initializing config')
|
80
|
+
reset_dir(VIMLM_DIR)
|
81
|
+
config = DEFAULTS
|
82
|
+
with open(CFG_PATH, 'w') as f:
|
83
|
+
json.dump(DEFAULTS, f, indent=2)
|
84
|
+
for k, v in DEFAULTS.items():
|
85
|
+
globals()[k] = config.get(k, v)
|
86
|
+
|
87
|
+
initialize()
|
83
88
|
|
84
89
|
def toout(s, key=None, mode=None):
|
85
90
|
key = '' if key is None else ':'+key
|
@@ -112,18 +117,8 @@ def print_log():
|
|
112
117
|
print(log["log"])
|
113
118
|
print('\033[0m')
|
114
119
|
|
115
|
-
toout('Loading LLM...')
|
116
|
-
if LLM_MODEL is None:
|
117
|
-
from nanollama import Chat
|
118
|
-
chat = Chat(model_path='uncn_llama_32_3b_it')
|
119
|
-
toout(f'LLM is ready')
|
120
|
-
else:
|
121
|
-
from mlx_lm_utils import Chat
|
122
|
-
chat = Chat(model_path=LLM_MODEL)
|
123
|
-
toout(f'{model_path.split('/')[-1]} is ready')
|
124
|
-
|
125
120
|
def deploy(dest=None, src=None, reformat=True):
|
126
|
-
prompt_deploy = 'Reformat the
|
121
|
+
prompt_deploy = 'Reformat the response to ensure each code block is preceded by a filename in **filename.ext** format, with only alphanumeric characters, dots, underscores, or hyphens in the filename. Remove any extraneous characters from filenames.'
|
127
122
|
tolog(f'deploy {dest=} {src=} {reformat=}')
|
128
123
|
if src:
|
129
124
|
chat.reset()
|
@@ -281,13 +276,10 @@ def ingest(src, max_len=NUM_TOKEN):
|
|
281
276
|
for i, s in enumerate(list_str):
|
282
277
|
chat.reset()
|
283
278
|
toout(f'\n\nIngesting {k_base} {i+1}/{len(list_str)}...\n\n', mode='a')
|
284
|
-
tolog(f'{s=}') # DEBUG
|
285
279
|
newsum = chat(format_ingest.format(volat=volat, incoming=s.rstrip()), max_new=max_new_sum, verbose=False, stream=OUT_PATH)['text'].rstrip()
|
286
|
-
tolog(f'{k} {i+1}/{len(list_str)}: {newsum=}') # DEBUG
|
287
280
|
accum += newsum + ' ...\n'
|
288
281
|
volat = format_volat.format(k=k, newsum=newsum)
|
289
282
|
toout(f'\n\nIngesting {k_base}...\n\n', mode='a')
|
290
|
-
tolog(f'{accum=}') # DEBUG
|
291
283
|
if chat.get_ntok(accum) <= max_new_accum:
|
292
284
|
chat_summary = accum.strip()
|
293
285
|
else:
|
@@ -302,6 +294,15 @@ def ingest(src, max_len=NUM_TOKEN):
|
|
302
294
|
return result
|
303
295
|
|
304
296
|
def process_command(data):
|
297
|
+
if 'fim' in data:
|
298
|
+
toout('Autocompleting...')
|
299
|
+
response = fim.fim(prefix=data['context'], suffix=data['yank'], current_path=data['tree'])
|
300
|
+
toout(response['autocomplete'], 'fim')
|
301
|
+
tolog(response)
|
302
|
+
data['user_prompt'] = ''
|
303
|
+
return data
|
304
|
+
for i in IN_FILES:
|
305
|
+
data[i] = data[i].strip()
|
305
306
|
if len(data['user']) == 0:
|
306
307
|
response = chat.resume(max_new=NUM_TOKEN, verbose=False, stream=OUT_PATH)
|
307
308
|
toout(response['text'], mode='a')
|
@@ -321,7 +322,7 @@ def process_command(data):
|
|
321
322
|
arg = cmd.removeprefix('continue').strip('(').strip(')').strip().strip('"').strip("'").strip()
|
322
323
|
data['max_new'] = NUM_TOKEN if len(arg) == 0 else int(arg)
|
323
324
|
response = chat.resume(max_new=data['max_new'], verbose=False, stream=OUT_PATH)
|
324
|
-
toout(response['text']
|
325
|
+
toout(response['text'])
|
325
326
|
tolog(response)
|
326
327
|
do_reset = False
|
327
328
|
break
|
@@ -398,18 +399,19 @@ def process_command(data):
|
|
398
399
|
async def monitor_directory():
|
399
400
|
async for changes in awatch(WATCH_DIR):
|
400
401
|
found_files = {os.path.basename(f) for _, f in changes}
|
401
|
-
tolog(f'{found_files=}') # DEBUG
|
402
402
|
if IN_FILES[-1] in found_files and set(IN_FILES).issubset(set(os.listdir(WATCH_DIR))):
|
403
|
-
tolog(f'listdir()={os.listdir(WATCH_DIR)}') # DEBUG
|
404
403
|
data = {}
|
405
404
|
for file in IN_FILES:
|
406
405
|
path = os.path.join(WATCH_DIR, file)
|
407
406
|
with open(path, 'r', encoding='utf-8') as f:
|
408
|
-
data[file] = f.read()
|
407
|
+
data[file] = f.read()
|
409
408
|
os.remove(os.path.join(WATCH_DIR, file))
|
410
409
|
if 'followup' in os.listdir(WATCH_DIR):
|
411
410
|
os.remove(os.path.join(WATCH_DIR, 'followup'))
|
412
411
|
data['followup'] = True
|
412
|
+
if 'fim' in os.listdir(WATCH_DIR):
|
413
|
+
os.remove(os.path.join(WATCH_DIR, 'fim'))
|
414
|
+
data['fim'] = True
|
413
415
|
if 'quit' in os.listdir(WATCH_DIR):
|
414
416
|
os.remove(os.path.join(WATCH_DIR, 'quit'))
|
415
417
|
data['quit'] = True
|
@@ -420,6 +422,8 @@ async def process_files(data):
|
|
420
422
|
str_template = '{include}'
|
421
423
|
data = process_command(data)
|
422
424
|
if len(data['user_prompt']) == 0:
|
425
|
+
if 'wip' in os.listdir(WATCH_DIR):
|
426
|
+
os.remove(os.path.join(WATCH_DIR, 'wip'))
|
423
427
|
return
|
424
428
|
if len(data['file']) > 0:
|
425
429
|
str_template += '**{file}**\n'
|
@@ -434,8 +438,6 @@ async def process_files(data):
|
|
434
438
|
else:
|
435
439
|
str_template += "`{yank}` "
|
436
440
|
str_template += '{user_prompt}'
|
437
|
-
tolog(f'process_files o {data=}') # DEBUG
|
438
|
-
tolog(f'process_files {str_template=}') # DEBUG
|
439
441
|
prompt = str_template.format(**data)
|
440
442
|
tolog(prompt, 'tollm')
|
441
443
|
toout('')
|
@@ -451,6 +453,8 @@ async def process_files(data):
|
|
451
453
|
f.write(response['text'])
|
452
454
|
if 'deploy_dest' in data:
|
453
455
|
deploy(dest=data['deploy_dest'], reformat=False)
|
456
|
+
if 'wip' in os.listdir(WATCH_DIR):
|
457
|
+
os.remove(os.path.join(WATCH_DIR, 'wip'))
|
454
458
|
|
455
459
|
KEYL = KEY_MAP.get('l', 'l')
|
456
460
|
KEYJ = KEY_MAP.get('j', 'j')
|
@@ -639,10 +643,11 @@ function! PasteIntoLastVisualSelection(...)
|
|
639
643
|
endfunction
|
640
644
|
|
641
645
|
function! VimLM(...) range
|
642
|
-
let
|
643
|
-
while filereadable(
|
646
|
+
let wip_file = s:watched_dir . '/wip'
|
647
|
+
while filereadable(wip_file)
|
644
648
|
sleep 100m
|
645
649
|
endwhile
|
650
|
+
call writefile([], wip_file, 'w')
|
646
651
|
let user_input = join(a:000, ' ')
|
647
652
|
if empty(user_input)
|
648
653
|
echo "Usage: :VimLM <prompt> [!command1] [!command2] ..."
|
@@ -656,12 +661,61 @@ function! VimLM(...) range
|
|
656
661
|
let user_file = s:watched_dir . '/user'
|
657
662
|
call writefile([user_input], user_file, 'w')
|
658
663
|
let current_file = expand('%:p')
|
664
|
+
let tree_file = s:watched_dir . '/tree'
|
659
665
|
call writefile([current_file], tree_file, 'w')
|
660
666
|
call ScrollToTop()
|
661
667
|
endfunction
|
662
668
|
|
669
|
+
function! SplitAtCursorInInsert()
|
670
|
+
let pos = getcurpos()
|
671
|
+
let line_num = pos[1]
|
672
|
+
let col = pos[2]
|
673
|
+
let lines = getline(1, '$')
|
674
|
+
let current_line = lines[line_num - 1]
|
675
|
+
let prefix_lines = lines[0:line_num - 2]
|
676
|
+
let prefix_part = strpart(current_line, 0, col - 1)
|
677
|
+
if !empty(prefix_part) || col > 1
|
678
|
+
call add(prefix_lines, prefix_part)
|
679
|
+
endif
|
680
|
+
let suffix_lines = []
|
681
|
+
let suffix_part = strpart(current_line, col - 1)
|
682
|
+
if !empty(suffix_part)
|
683
|
+
call add(suffix_lines, suffix_part)
|
684
|
+
endif
|
685
|
+
if line_num < len(lines)
|
686
|
+
call extend(suffix_lines, lines[line_num:])
|
687
|
+
endif
|
688
|
+
call writefile(prefix_lines, s:watched_dir . '/context', 'b')
|
689
|
+
call writefile(suffix_lines, s:watched_dir . '/yank', 'b')
|
690
|
+
call writefile([], s:watched_dir . '/fim', 'w')
|
691
|
+
call writefile([], s:watched_dir . '/user', 'w')
|
692
|
+
let current_file = expand('%:p')
|
693
|
+
let tree_file = s:watched_dir . '/tree'
|
694
|
+
call writefile([current_file], tree_file, 'w')
|
695
|
+
endfunction
|
696
|
+
|
697
|
+
function! InsertResponse()
|
698
|
+
let response_path = s:watched_dir . '/response.md'
|
699
|
+
if !filereadable(response_path)
|
700
|
+
echoerr "Response file not found: " . response_path
|
701
|
+
return
|
702
|
+
endif
|
703
|
+
let content = readfile(response_path)
|
704
|
+
let text = join(content, "\n")
|
705
|
+
call setreg('z', text)
|
706
|
+
let col = col('.')
|
707
|
+
let line = getline('.')
|
708
|
+
if col == len(line) + 1
|
709
|
+
normal! "zgpa
|
710
|
+
else
|
711
|
+
normal! "zgPa
|
712
|
+
endif
|
713
|
+
endfunction
|
714
|
+
|
663
715
|
command! ToggleVimLM call ToggleVimLM()
|
664
716
|
command! -range -nargs=+ VimLM call VimLM(<f-args>)
|
717
|
+
inoremap <silent> <C-l> <C-\><C-o>:call SplitAtCursorInInsert()<CR>
|
718
|
+
inoremap <silent> <C-p> <C-\><C-o>:call InsertResponse()<CR>
|
665
719
|
nnoremap $mapp :call PasteIntoLastVisualSelection()<CR>
|
666
720
|
vnoremap $mapp <Cmd>:call PasteIntoLastVisualSelection()<CR>
|
667
721
|
vnoremap $mapl <Cmd>:call VisualPrompt()<CR>
|
@@ -670,19 +724,13 @@ nnoremap $mapj :call FollowUpPrompt()<CR>
|
|
670
724
|
call Monitor()
|
671
725
|
""").safe_substitute(dict(WATCH_DIR=WATCH_DIR, mapl=mapl, mapj=mapj, mapp=mapp))
|
672
726
|
|
673
|
-
async def main():
|
674
|
-
parser = argparse.ArgumentParser(description="VimLM - LLM-powered Vim assistant")
|
675
|
-
parser.add_argument('--test', action='store_true')
|
676
|
-
parser.add_argument("vim_args", nargs=argparse.REMAINDER, help="Vim arguments")
|
677
|
-
args = parser.parse_args()
|
678
|
-
if args.test:
|
679
|
-
return
|
727
|
+
async def main(args):
|
680
728
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.vim', delete=False) as f:
|
681
729
|
f.write(VIMLMSCRIPT)
|
682
730
|
vim_script = f.name
|
683
731
|
vim_command = ["vim", "-c", f"source {vim_script}"]
|
684
|
-
if args.
|
685
|
-
vim_command.extend(args.
|
732
|
+
if args.args_vim:
|
733
|
+
vim_command.extend(args.args_vim)
|
686
734
|
else:
|
687
735
|
vim_command.append('.tmp')
|
688
736
|
try:
|
@@ -697,8 +745,78 @@ async def main():
|
|
697
745
|
pass
|
698
746
|
os.remove(vim_script)
|
699
747
|
|
748
|
+
def get_common_dir_and_children(file_paths):
|
749
|
+
dirs = [os.path.dirname(path) for path in file_paths]
|
750
|
+
dir_parts = [path.split(os.sep) for path in dirs]
|
751
|
+
common_parts = []
|
752
|
+
for parts in zip(*dir_parts):
|
753
|
+
if all(part == parts[0] for part in parts):
|
754
|
+
common_parts.append(parts[0])
|
755
|
+
else:
|
756
|
+
break
|
757
|
+
parent_path = os.sep.join(common_parts)
|
758
|
+
child_paths = [os.path.relpath(path, parent_path) for path in file_paths]
|
759
|
+
repo_name = os.path.basename(os.path.dirname(parent_path))
|
760
|
+
return repo_name, parent_path, child_paths
|
761
|
+
|
762
|
+
def get_repo(args_repo, args_vim):
|
763
|
+
if not args_repo:
|
764
|
+
return None
|
765
|
+
vim_files = []
|
766
|
+
for arg in args_vim:
|
767
|
+
if not arg.startswith('-'):
|
768
|
+
if os.path.exists(arg):
|
769
|
+
vim_files.append(os.path.abspath(arg))
|
770
|
+
repo_files = []
|
771
|
+
rest_files = []
|
772
|
+
for pattern in args_repo:
|
773
|
+
expanded_paths = glob.glob(pattern)
|
774
|
+
if expanded_paths:
|
775
|
+
for path in expanded_paths:
|
776
|
+
if not os.path.isfile(path) or path in vim_files+repo_files or os.path.basename(path).startswith('.') or is_binary(path):
|
777
|
+
continue
|
778
|
+
if path in vim_files:
|
779
|
+
rest_files.append(os.path.abspath(path))
|
780
|
+
else:
|
781
|
+
repo_files.append(os.path.abspath(path))
|
782
|
+
repo_name, repo_path, child_paths = get_common_dir_and_children(repo_files+rest_files)
|
783
|
+
repo_names, rest_names = child_paths[:len(repo_files)], child_paths[len(repo_files):]
|
784
|
+
list_content = [f'<|repo_name|>{repo_name}\n']
|
785
|
+
list_mtime = []
|
786
|
+
for p, n in zip(repo_files, repo_names):
|
787
|
+
try:
|
788
|
+
with open(p, 'r') as f:
|
789
|
+
list_content.append(f'<|file_sep|>{n}\n{f.read()}\n')
|
790
|
+
list_mtime.append(int(os.path.getmtime(p)))
|
791
|
+
except Exception as e:
|
792
|
+
tolog(f'Skipped {p} d/t {e}', 'get_repo()')
|
793
|
+
return dict(repo_files=repo_files, rest_files=rest_files, rest_names=rest_names, vim_files=vim_files, list_mtime=list_mtime, list_content=list_content, repo_path=repo_path)
|
794
|
+
|
700
795
|
def run():
|
701
|
-
|
796
|
+
parser = argparse.ArgumentParser(description="VimLM - LLM-powered Vim assistant")
|
797
|
+
parser.add_argument('--test', action='store_true', help="Run in test mode")
|
798
|
+
parser.add_argument('args_vim', nargs='*', help="Vim arguments")
|
799
|
+
parser.add_argument('--repo', nargs='*', help="Paths to directories or files (e.g., assets/*, path/to/file)")
|
800
|
+
args = parser.parse_args()
|
801
|
+
dict_repo = get_repo(args.repo, args.args_vim)
|
802
|
+
tolog(dict_repo, 'get_repo()')
|
803
|
+
if args.test:
|
804
|
+
return
|
805
|
+
reset_dir(WATCH_DIR)
|
806
|
+
toout('Loading LLM...')
|
807
|
+
if LLM_MODEL is None:
|
808
|
+
globals()['chat'] = nanollama.Chat(model_path='uncn_llama_32_3b_it')
|
809
|
+
toout(f'LLM is ready')
|
810
|
+
else:
|
811
|
+
globals()['chat'] = mlx_lm_utils.Chat(model_path=LLM_MODEL, think=THINK)
|
812
|
+
toout(f'{LLM_MODEL.split('/')[-1]} is ready')
|
813
|
+
if FIM_MODEL and FIM_MODEL != LLM_MODEL:
|
814
|
+
globals()['fim'] = mlx_lm_utils.Chat(model_path=FIM_MODEL, cache_dir=VIMLM_DIR, dict_repo=dict_repo)
|
815
|
+
toout(f'\n\n{FIM_MODEL.split("/")[-1]} is ready', mode='a')
|
816
|
+
else:
|
817
|
+
globals()['fim'] = chat
|
818
|
+
chat.set_cache_repo(dict_repo, cache_dir=VIMLM_DIR)
|
819
|
+
asyncio.run(main(args))
|
702
820
|
|
703
821
|
if __name__ == '__main__':
|
704
822
|
run()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|