vimlm 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl
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.2.dist-info → vimlm-0.0.4.dist-info}/METADATA +8 -10
 - vimlm-0.0.4.dist-info/RECORD +7 -0
 - vimlm-0.0.4.dist-info/top_level.txt +1 -0
 - vimlm.py +384 -0
 - vimlm-0.0.2.dist-info/RECORD +0 -6
 - vimlm-0.0.2.dist-info/top_level.txt +0 -1
 - {vimlm-0.0.2.dist-info → vimlm-0.0.4.dist-info}/LICENSE +0 -0
 - {vimlm-0.0.2.dist-info → vimlm-0.0.4.dist-info}/WHEEL +0 -0
 - {vimlm-0.0.2.dist-info → vimlm-0.0.4.dist-info}/entry_points.txt +0 -0
 
| 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Metadata-Version: 2.2
         
     | 
| 
       2 
2 
     | 
    
         
             
            Name: vimlm
         
     | 
| 
       3 
     | 
    
         
            -
            Version: 0.0. 
     | 
| 
      
 3 
     | 
    
         
            +
            Version: 0.0.4
         
     | 
| 
       4 
4 
     | 
    
         
             
            Summary: VimLM - LLM-powered Vim assistant
         
     | 
| 
       5 
5 
     | 
    
         
             
            Home-page: https://github.com/JosefAlbers/vimlm
         
     | 
| 
       6 
6 
     | 
    
         
             
            Author: Josef Albers
         
     | 
| 
       7 
7 
     | 
    
         
             
            Author-email: albersj66@gmail.com
         
     | 
| 
       8 
     | 
    
         
            -
            Requires-Python: >=3. 
     | 
| 
      
 8 
     | 
    
         
            +
            Requires-Python: >=3.12.8
         
     | 
| 
       9 
9 
     | 
    
         
             
            Description-Content-Type: text/markdown
         
     | 
| 
       10 
10 
     | 
    
         
             
            License-File: LICENSE
         
     | 
| 
       11 
     | 
    
         
            -
            Requires-Dist: nanollama==0.0. 
     | 
| 
      
 11 
     | 
    
         
            +
            Requires-Dist: nanollama==0.0.3b0
         
     | 
| 
       12 
12 
     | 
    
         
             
            Requires-Dist: watchfiles==1.0.4
         
     | 
| 
       13 
13 
     | 
    
         
             
            Dynamic: author
         
     | 
| 
       14 
14 
     | 
    
         
             
            Dynamic: author-email
         
     | 
| 
         @@ -21,11 +21,9 @@ Dynamic: summary 
     | 
|
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
            # VimLM - Vim Language Model Assistant for privacy-conscious developers
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
            
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
            Unlike proprietary cloud-based AI models, VimLM runs entirely offline, ensuring complete privacy, data security, and control. You’re not just using a tool—you own it. Fine-tune, modify, or extend it as needed, without reliance on big tech platforms.
         
     | 
| 
      
 26 
     | 
    
         
            +
            An LLM-powered coding companion for Vim, inspired by GitHub Copilot/Cursor. Integrates contextual code understanding, summarization, and AI assistance directly into your Vim workflow.
         
     | 
| 
       29 
27 
     | 
    
         | 
| 
       30 
28 
     | 
    
         
             
            ## Features
         
     | 
| 
       31 
29 
     | 
    
         | 
| 
         @@ -70,15 +68,15 @@ vimlm your_file.js 
     | 
|
| 
       70 
68 
     | 
    
         
             
                - Example prompt: "AJAX-ify this app !@#$ ~/scrap/jph00/hypermedia-applications.summ.md"
         
     | 
| 
       71 
69 
     | 
    
         | 
| 
       72 
70 
     | 
    
         
             
            5. **Follow-Up**: After initial response:
         
     | 
| 
       73 
     | 
    
         
            -
                - `Ctrl- 
     | 
| 
      
 71 
     | 
    
         
            +
                - `Ctrl-r`: Continue thread
         
     | 
| 
       74 
72 
     | 
    
         
             
                - Example follow-up: "In Manifest V3"
         
     | 
| 
       75 
73 
     | 
    
         | 
| 
       76 
74 
     | 
    
         
             
            ## Key Bindings
         
     | 
| 
       77 
75 
     | 
    
         | 
| 
       78 
76 
     | 
    
         
             
            | Binding    | Mode          | Action                                 |
         
     | 
| 
       79 
77 
     | 
    
         
             
            |------------|---------------|----------------------------------------|
         
     | 
| 
       80 
     | 
    
         
            -
            | `Ctrl- 
     | 
| 
       81 
     | 
    
         
            -
            | `Ctrl- 
     | 
| 
      
 78 
     | 
    
         
            +
            | `Ctrl-l`   | Normal/Visual | Send current file + selection to LLM   |
         
     | 
| 
      
 79 
     | 
    
         
            +
            | `Ctrl-r`   | Normal        | Continue conversation                  |
         
     | 
| 
       82 
80 
     | 
    
         
             
            | `Esc`      | Prompt        | Cancel input                           |
         
     | 
| 
       83 
81 
     | 
    
         | 
| 
       84 
82 
     | 
    
         | 
| 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            vimlm.py,sha256=kCJphqs6B-ZUGzeikiW3u629Nw5Ih3DlDJV1rsKGXAY,13410
         
     | 
| 
      
 2 
     | 
    
         
            +
            vimlm-0.0.4.dist-info/LICENSE,sha256=f1xgK8fAXg_intwnbc9nLkHf7ODPLtgpHs7DetQHOro,11343
         
     | 
| 
      
 3 
     | 
    
         
            +
            vimlm-0.0.4.dist-info/METADATA,sha256=EdZyb9SyLpy0qyK_jILHa7nrd5G8lPxOm6Xfy7NMfiQ,2832
         
     | 
| 
      
 4 
     | 
    
         
            +
            vimlm-0.0.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
         
     | 
| 
      
 5 
     | 
    
         
            +
            vimlm-0.0.4.dist-info/entry_points.txt,sha256=mU5V4MYsuIzCc6YB-Ro-6USSHWN5vHw8UDnTEoq0isw,36
         
     | 
| 
      
 6 
     | 
    
         
            +
            vimlm-0.0.4.dist-info/top_level.txt,sha256=I8GjqoiP--scYsO3AfLhha-6Ax9ci3IvbWvVbPv8g94,6
         
     | 
| 
      
 7 
     | 
    
         
            +
            vimlm-0.0.4.dist-info/RECORD,,
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            vimlm
         
     | 
    
        vimlm.py
    ADDED
    
    | 
         @@ -0,0 +1,384 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Copyright 2025 Josef Albers
         
     | 
| 
      
 2 
     | 
    
         
            +
            # 
         
     | 
| 
      
 3 
     | 
    
         
            +
            # Licensed under the Apache License, Version 2.0 (the "License");
         
     | 
| 
      
 4 
     | 
    
         
            +
            # you may not use this file except in compliance with the License.
         
     | 
| 
      
 5 
     | 
    
         
            +
            # You may obtain a copy of the License at
         
     | 
| 
      
 6 
     | 
    
         
            +
            # 
         
     | 
| 
      
 7 
     | 
    
         
            +
            #     http://www.apache.org/licenses/LICENSE-2.0
         
     | 
| 
      
 8 
     | 
    
         
            +
            # 
         
     | 
| 
      
 9 
     | 
    
         
            +
            # Unless required by applicable law or agreed to in writing, software
         
     | 
| 
      
 10 
     | 
    
         
            +
            # distributed under the License is distributed on an "AS IS" BASIS,
         
     | 
| 
      
 11 
     | 
    
         
            +
            # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         
     | 
| 
      
 12 
     | 
    
         
            +
            # See the License for the specific language governing permissions and
         
     | 
| 
      
 13 
     | 
    
         
            +
            # limitations under the License.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            import asyncio
         
     | 
| 
      
 16 
     | 
    
         
            +
            import subprocess
         
     | 
| 
      
 17 
     | 
    
         
            +
            import json
         
     | 
| 
      
 18 
     | 
    
         
            +
            import os
         
     | 
| 
      
 19 
     | 
    
         
            +
            from watchfiles import awatch
         
     | 
| 
      
 20 
     | 
    
         
            +
            from nanollama32 import Chat
         
     | 
| 
      
 21 
     | 
    
         
            +
            import shutil
         
     | 
| 
      
 22 
     | 
    
         
            +
            import time
         
     | 
| 
      
 23 
     | 
    
         
            +
            from itertools import accumulate
         
     | 
| 
      
 24 
     | 
    
         
            +
            import argparse
         
     | 
| 
      
 25 
     | 
    
         
            +
            import tempfile
         
     | 
| 
      
 26 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 27 
     | 
    
         
            +
            from string import Template
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            DEBUG = True
         
     | 
| 
      
 30 
     | 
    
         
            +
            NUM_TOKEN = 2000
         
     | 
| 
      
 31 
     | 
    
         
            +
            SEP_CMD = '!@#$'
         
     | 
| 
      
 32 
     | 
    
         
            +
            VIMLM_DIR = os.path.expanduser("~/vimlm")
         
     | 
| 
      
 33 
     | 
    
         
            +
            WATCH_DIR = os.path.expanduser("~/vimlm/watch_dir")
         
     | 
| 
      
 34 
     | 
    
         
            +
            CFG_FILE = "cfg.json"
         
     | 
| 
      
 35 
     | 
    
         
            +
            LOG_FILE = "log.json"
         
     | 
| 
      
 36 
     | 
    
         
            +
            LTM_FILE = "cache.json"
         
     | 
| 
      
 37 
     | 
    
         
            +
            OUT_FILE = "response.md"
         
     | 
| 
      
 38 
     | 
    
         
            +
            IN_FILES = ["context", "yank", "user", "tree"]
         
     | 
| 
      
 39 
     | 
    
         
            +
            CFG_PATH = os.path.join(VIMLM_DIR, CFG_FILE)
         
     | 
| 
      
 40 
     | 
    
         
            +
            LOG_PATH = os.path.join(VIMLM_DIR, LOG_FILE)
         
     | 
| 
      
 41 
     | 
    
         
            +
            LTM_PATH = os.path.join(VIMLM_DIR, LTM_FILE)
         
     | 
| 
      
 42 
     | 
    
         
            +
            OUT_PATH = os.path.join(WATCH_DIR, OUT_FILE) 
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            if os.path.exists(WATCH_DIR):
         
     | 
| 
      
 45 
     | 
    
         
            +
                shutil.rmtree(WATCH_DIR)
         
     | 
| 
      
 46 
     | 
    
         
            +
            os.makedirs(WATCH_DIR)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            try:
         
     | 
| 
      
 49 
     | 
    
         
            +
                with open(CFG_PATH, "r") as f:
         
     | 
| 
      
 50 
     | 
    
         
            +
                    cfg = cfg.load(f)
         
     | 
| 
      
 51 
     | 
    
         
            +
                DEBUG = config.get("DEBUG", DEBUG)
         
     | 
| 
      
 52 
     | 
    
         
            +
                NUM_TOKEN = config.get("NUM_TOKEN", NUM_TOKEN)
         
     | 
| 
      
 53 
     | 
    
         
            +
                SEP_CMD = config.get("SEP_CMD", SEP_CMD)
         
     | 
| 
      
 54 
     | 
    
         
            +
            except:
         
     | 
| 
      
 55 
     | 
    
         
            +
                with open(CFG_PATH, 'w') as f:
         
     | 
| 
      
 56 
     | 
    
         
            +
                    json.dump(dict(DEBUG=DEBUG, NUM_TOKEN=NUM_TOKEN, SEP_CMD=SEP_CMD), f, indent=2)
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            def toout(s, key='tovim'):
         
     | 
| 
      
 59 
     | 
    
         
            +
                with open(OUT_PATH, 'w', encoding='utf-8') as f:
         
     | 
| 
      
 60 
     | 
    
         
            +
                    f.write(s)
         
     | 
| 
      
 61 
     | 
    
         
            +
                tolog(s, key)
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
            def tolog(log, key='debug'):
         
     | 
| 
      
 64 
     | 
    
         
            +
                if not DEBUG and key == 'debug':
         
     | 
| 
      
 65 
     | 
    
         
            +
                    return
         
     | 
| 
      
 66 
     | 
    
         
            +
                if os.path.exists(LOG_PATH):
         
     | 
| 
      
 67 
     | 
    
         
            +
                    with open(LOG_PATH, "r", encoding="utf-8") as log_f:
         
     | 
| 
      
 68 
     | 
    
         
            +
                        logs = json.load(log_f)
         
     | 
| 
      
 69 
     | 
    
         
            +
                else:
         
     | 
| 
      
 70 
     | 
    
         
            +
                    logs = []
         
     | 
| 
      
 71 
     | 
    
         
            +
                logs.append(dict(key=key, log=log, timestamp=time.ctime()))
         
     | 
| 
      
 72 
     | 
    
         
            +
                with open(LOG_PATH, "w", encoding="utf-8") as log_f:
         
     | 
| 
      
 73 
     | 
    
         
            +
                    json.dump(logs, log_f, indent=2)
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
            toout('Loading LLM...')
         
     | 
| 
      
 76 
     | 
    
         
            +
            chat = Chat(variant='uncn_llama_32_3b_it')
         
     | 
| 
      
 77 
     | 
    
         
            +
            toout('LLM is ready')
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
            def is_binary(file_path):
         
     | 
| 
      
 80 
     | 
    
         
            +
                try:
         
     | 
| 
      
 81 
     | 
    
         
            +
                    with open(file_path, 'rb') as f:
         
     | 
| 
      
 82 
     | 
    
         
            +
                        chunk = f.read(1024)
         
     | 
| 
      
 83 
     | 
    
         
            +
                        chunk.decode('utf-8') 
         
     | 
| 
      
 84 
     | 
    
         
            +
                    return False 
         
     | 
| 
      
 85 
     | 
    
         
            +
                except UnicodeDecodeError:
         
     | 
| 
      
 86 
     | 
    
         
            +
                    return True
         
     | 
| 
      
 87 
     | 
    
         
            +
                except Exception as e:
         
     | 
| 
      
 88 
     | 
    
         
            +
                    return f"Error: {e}"
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
            def split_str(doc, max_len=2000, get_len=len):
         
     | 
| 
      
 91 
     | 
    
         
            +
                chunks, current_chunk, current_len = [], [], 0
         
     | 
| 
      
 92 
     | 
    
         
            +
                lines = doc.splitlines(keepends=True)
         
     | 
| 
      
 93 
     | 
    
         
            +
                atomic_chunks, temp = [], []
         
     | 
| 
      
 94 
     | 
    
         
            +
                for line in lines:
         
     | 
| 
      
 95 
     | 
    
         
            +
                    if line.strip():
         
     | 
| 
      
 96 
     | 
    
         
            +
                        temp.append(line)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 98 
     | 
    
         
            +
                        if temp:
         
     | 
| 
      
 99 
     | 
    
         
            +
                            atomic_chunks.append("".join(temp))
         
     | 
| 
      
 100 
     | 
    
         
            +
                            temp = []
         
     | 
| 
      
 101 
     | 
    
         
            +
                        atomic_chunks.append(line) 
         
     | 
| 
      
 102 
     | 
    
         
            +
                if temp:
         
     | 
| 
      
 103 
     | 
    
         
            +
                    atomic_chunks.append("".join(temp))
         
     | 
| 
      
 104 
     | 
    
         
            +
                for chunk in atomic_chunks:
         
     | 
| 
      
 105 
     | 
    
         
            +
                    if current_len + get_len(chunk) > max_len and current_chunk:
         
     | 
| 
      
 106 
     | 
    
         
            +
                        chunks.append("".join(current_chunk))
         
     | 
| 
      
 107 
     | 
    
         
            +
                        current_chunk, current_len = [], 0
         
     | 
| 
      
 108 
     | 
    
         
            +
                    current_chunk.append(chunk)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    current_len += get_len(chunk)
         
     | 
| 
      
 110 
     | 
    
         
            +
                if current_chunk:
         
     | 
| 
      
 111 
     | 
    
         
            +
                    if current_len < max_len / 2 and len(chunks) > 0:
         
     | 
| 
      
 112 
     | 
    
         
            +
                        chunks[-1] += "".join(current_chunk)
         
     | 
| 
      
 113 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 114 
     | 
    
         
            +
                        chunks.append("".join(current_chunk))
         
     | 
| 
      
 115 
     | 
    
         
            +
                return chunks
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
            def retrieve(src_path, max_len=2000, get_len=len):
         
     | 
| 
      
 118 
     | 
    
         
            +
                src_path = os.path.expanduser(src_path)
         
     | 
| 
      
 119 
     | 
    
         
            +
                result = {}
         
     | 
| 
      
 120 
     | 
    
         
            +
                if not os.path.exists(src_path):
         
     | 
| 
      
 121 
     | 
    
         
            +
                    tolog(f"The path {src_path} does not exist.", 'retrieve')
         
     | 
| 
      
 122 
     | 
    
         
            +
                    return result
         
     | 
| 
      
 123 
     | 
    
         
            +
                if os.path.isfile(src_path):
         
     | 
| 
      
 124 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 125 
     | 
    
         
            +
                        with open(src_path, 'r', encoding='utf-8', errors='ignore') as f:
         
     | 
| 
      
 126 
     | 
    
         
            +
                            content = f.read()
         
     | 
| 
      
 127 
     | 
    
         
            +
                        result = {src_path:dict(timestamp=os.path.getmtime(src_path), list_str=split_str(content, max_len=max_len, get_len=get_len))}
         
     | 
| 
      
 128 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 129 
     | 
    
         
            +
                        tolog(f'Skipped {filename} due to {e}', 'retrieve')
         
     | 
| 
      
 130 
     | 
    
         
            +
                    return result
         
     | 
| 
      
 131 
     | 
    
         
            +
                for filename in os.listdir(src_path):
         
     | 
| 
      
 132 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 133 
     | 
    
         
            +
                        file_path = os.path.join(src_path, filename)
         
     | 
| 
      
 134 
     | 
    
         
            +
                        if filename.startswith('.') or is_binary(file_path):
         
     | 
| 
      
 135 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 136 
     | 
    
         
            +
                        if os.path.isfile(file_path):
         
     | 
| 
      
 137 
     | 
    
         
            +
                            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
         
     | 
| 
      
 138 
     | 
    
         
            +
                                content = f.read()
         
     | 
| 
      
 139 
     | 
    
         
            +
                            result[file_path] = dict(timestamp=os.path.getmtime(file_path), list_str=split_str(content, max_len=max_len, get_len=get_len))
         
     | 
| 
      
 140 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 141 
     | 
    
         
            +
                        tolog(f'Skipped {filename} due to {e}', 'retrieve')
         
     | 
| 
      
 142 
     | 
    
         
            +
                        continue
         
     | 
| 
      
 143 
     | 
    
         
            +
                return result
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
            def get_ntok(s):
         
     | 
| 
      
 146 
     | 
    
         
            +
                return len(chat.tokenizer.encode(s)[0])
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
            def ingest(src):
         
     | 
| 
      
 149 
     | 
    
         
            +
                def load_cache(cache_path=LTM_PATH):
         
     | 
| 
      
 150 
     | 
    
         
            +
                    if os.path.exists(cache_path):
         
     | 
| 
      
 151 
     | 
    
         
            +
                        with open(cache_path, 'r', encoding='utf-8') as f:
         
     | 
| 
      
 152 
     | 
    
         
            +
                            return json.load(f)
         
     | 
| 
      
 153 
     | 
    
         
            +
                    return {}
         
     | 
| 
      
 154 
     | 
    
         
            +
                def dump_cache(new_data, cache_path=LTM_PATH):
         
     | 
| 
      
 155 
     | 
    
         
            +
                    current_data = load_cache(cache_path)
         
     | 
| 
      
 156 
     | 
    
         
            +
                    for k, v in new_data.items():
         
     | 
| 
      
 157 
     | 
    
         
            +
                        if k not in current_data or v['timestamp'] > current_data[k]['timestamp']:
         
     | 
| 
      
 158 
     | 
    
         
            +
                            current_data[k] = v
         
     | 
| 
      
 159 
     | 
    
         
            +
                    with open(cache_path, 'w', encoding='utf-8') as f:
         
     | 
| 
      
 160 
     | 
    
         
            +
                        json.dump(current_data, f, indent=2)
         
     | 
| 
      
 161 
     | 
    
         
            +
                toout('Ingesting...')
         
     | 
| 
      
 162 
     | 
    
         
            +
                format_ingest = '{volat}{incoming}\n\n---\n\nPlease provide a succint bullet point summary for above:'
         
     | 
| 
      
 163 
     | 
    
         
            +
                format_volat = 'Here is a summary of part 1 of **{k}**:\n\n---\n\n{newsum}\n\n---\n\nHere is the next part:\n\n---\n\n'
         
     | 
| 
      
 164 
     | 
    
         
            +
                dict_doc = retrieve(src, get_len=get_ntok)
         
     | 
| 
      
 165 
     | 
    
         
            +
                dict_sum = {}
         
     | 
| 
      
 166 
     | 
    
         
            +
                cache = load_cache()
         
     | 
| 
      
 167 
     | 
    
         
            +
                for k, v in dict_doc.items():
         
     | 
| 
      
 168 
     | 
    
         
            +
                    v_stamp = v['timestamp']
         
     | 
| 
      
 169 
     | 
    
         
            +
                    if v_stamp == cache.get(k, {}).get('timestamp'):
         
     | 
| 
      
 170 
     | 
    
         
            +
                        dict_sum[k] = cache[k]
         
     | 
| 
      
 171 
     | 
    
         
            +
                        continue
         
     | 
| 
      
 172 
     | 
    
         
            +
                    list_str = v['list_str']
         
     | 
| 
      
 173 
     | 
    
         
            +
                    if len(list_str) == 0:
         
     | 
| 
      
 174 
     | 
    
         
            +
                        continue
         
     | 
| 
      
 175 
     | 
    
         
            +
                    if len(list_str) > 1:
         
     | 
| 
      
 176 
     | 
    
         
            +
                        max_new_sum = int(NUM_TOKEN / len(list_str) / 2)
         
     | 
| 
      
 177 
     | 
    
         
            +
                        volat = f'**{k}**:\n'
         
     | 
| 
      
 178 
     | 
    
         
            +
                        accum = ''
         
     | 
| 
      
 179 
     | 
    
         
            +
                        for s in list_str:
         
     | 
| 
      
 180 
     | 
    
         
            +
                            chat.reset()
         
     | 
| 
      
 181 
     | 
    
         
            +
                            newsum = chat(format_ingest.format(volat=volat, incoming=s.strip()), max_new=max_new_sum, verbose=False, stream=None)[0][:-10].strip()
         
     | 
| 
      
 182 
     | 
    
         
            +
                            accum += newsum + ' ...\n'
         
     | 
| 
      
 183 
     | 
    
         
            +
                            volat = format_volat.format(k=k, newsum=newsum)
         
     | 
| 
      
 184 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 185 
     | 
    
         
            +
                        accum = list_str[0]
         
     | 
| 
      
 186 
     | 
    
         
            +
                    chat.reset()
         
     | 
| 
      
 187 
     | 
    
         
            +
                    toout('')
         
     | 
| 
      
 188 
     | 
    
         
            +
                    chat_summary = chat(format_ingest.format(volat=f'**{k}**:\n', incoming=accum), max_new=int(NUM_TOKEN/4), verbose=False, stream=OUT_PATH)[0][:-10].strip()
         
     | 
| 
      
 189 
     | 
    
         
            +
                    dict_sum[k] = dict(timestamp=v_stamp, summary=chat_summary)
         
     | 
| 
      
 190 
     | 
    
         
            +
                dump_cache(dict_sum)
         
     | 
| 
      
 191 
     | 
    
         
            +
                result = ''
         
     | 
| 
      
 192 
     | 
    
         
            +
                for (k,v) in dict_sum.items():
         
     | 
| 
      
 193 
     | 
    
         
            +
                    result += f'--- Summary of **{k}** ---\n{v["summary"]}\n\n'
         
     | 
| 
      
 194 
     | 
    
         
            +
                result += '---\n\n'
         
     | 
| 
      
 195 
     | 
    
         
            +
                return result
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
            def process_command(data, str_template):
         
     | 
| 
      
 198 
     | 
    
         
            +
                try:
         
     | 
| 
      
 199 
     | 
    
         
            +
                    if len(data['user_command'].strip()) > 0:
         
     | 
| 
      
 200 
     | 
    
         
            +
                        src = data['user_command']
         
     | 
| 
      
 201 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 202 
     | 
    
         
            +
                        src = data['dir']
         
     | 
| 
      
 203 
     | 
    
         
            +
                    return ingest(src) + str_template
         
     | 
| 
      
 204 
     | 
    
         
            +
                except Exception as e:
         
     | 
| 
      
 205 
     | 
    
         
            +
                    tolog(f'Failed to ingest due to {e}', 'process_command')
         
     | 
| 
      
 206 
     | 
    
         
            +
                    return str_template
         
     | 
| 
      
 207 
     | 
    
         
            +
               
         
     | 
| 
      
 208 
     | 
    
         
            +
            async def monitor_directory():
         
     | 
| 
      
 209 
     | 
    
         
            +
                async for changes in awatch(WATCH_DIR):
         
     | 
| 
      
 210 
     | 
    
         
            +
                    found_files = {os.path.basename(f) for _, f in changes}
         
     | 
| 
      
 211 
     | 
    
         
            +
                    tolog(f'{found_files=}') # DEBUG
         
     | 
| 
      
 212 
     | 
    
         
            +
                    if IN_FILES[-1] in found_files and set(IN_FILES).issubset(set(os.listdir(WATCH_DIR))):
         
     | 
| 
      
 213 
     | 
    
         
            +
                        tolog(f'listdir()={os.listdir(WATCH_DIR)}') # DEBUG
         
     | 
| 
      
 214 
     | 
    
         
            +
                        data = {}
         
     | 
| 
      
 215 
     | 
    
         
            +
                        for file in IN_FILES:
         
     | 
| 
      
 216 
     | 
    
         
            +
                            path = os.path.join(WATCH_DIR, file)
         
     | 
| 
      
 217 
     | 
    
         
            +
                            with open(path, 'r', encoding='utf-8') as f:
         
     | 
| 
      
 218 
     | 
    
         
            +
                                data[file] = f.read().strip()
         
     | 
| 
      
 219 
     | 
    
         
            +
                            os.remove(os.path.join(WATCH_DIR, file))
         
     | 
| 
      
 220 
     | 
    
         
            +
                        if 'followup' in os.listdir(WATCH_DIR):
         
     | 
| 
      
 221 
     | 
    
         
            +
                            os.remove(os.path.join(WATCH_DIR, 'followup'))
         
     | 
| 
      
 222 
     | 
    
         
            +
                            data['followup'] = True
         
     | 
| 
      
 223 
     | 
    
         
            +
                            str_template = '{user}'
         
     | 
| 
      
 224 
     | 
    
         
            +
                        await process_files(data)
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
            async def process_files(data):
         
     | 
| 
      
 227 
     | 
    
         
            +
                tolog(f'{data=}')
         
     | 
| 
      
 228 
     | 
    
         
            +
                if 'followup' in data:
         
     | 
| 
      
 229 
     | 
    
         
            +
                    str_template = "{user}"
         
     | 
| 
      
 230 
     | 
    
         
            +
                else:
         
     | 
| 
      
 231 
     | 
    
         
            +
                    full_path = data['tree']
         
     | 
| 
      
 232 
     | 
    
         
            +
                    data['dir'] = os.path.dirname(full_path)
         
     | 
| 
      
 233 
     | 
    
         
            +
                    data['file'] = os.path.basename(full_path)
         
     | 
| 
      
 234 
     | 
    
         
            +
                    data['ext'] = os.path.splitext(full_path)[1][1:]
         
     | 
| 
      
 235 
     | 
    
         
            +
                    str_template = ''
         
     | 
| 
      
 236 
     | 
    
         
            +
                    if len(data['file']) > 0 and data['file'] != '.tmp':
         
     | 
| 
      
 237 
     | 
    
         
            +
                        str_template += '**{file}**\n'
         
     | 
| 
      
 238 
     | 
    
         
            +
                    if len(data['context']) > 0:
         
     | 
| 
      
 239 
     | 
    
         
            +
                        str_template += '```{ext}\n{context}\n```\n\n'
         
     | 
| 
      
 240 
     | 
    
         
            +
                    if len(data['yank']) > 0:
         
     | 
| 
      
 241 
     | 
    
         
            +
                        if '\n' in data['yank']:
         
     | 
| 
      
 242 
     | 
    
         
            +
                            str_template += "```{ext}\n{yank}\n```\n\n"
         
     | 
| 
      
 243 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 244 
     | 
    
         
            +
                            str_template += "`{yank}` "
         
     | 
| 
      
 245 
     | 
    
         
            +
                    str_template += '{user_prompt}'
         
     | 
| 
      
 246 
     | 
    
         
            +
                    if SEP_CMD in data['user']:
         
     | 
| 
      
 247 
     | 
    
         
            +
                        data['user_prompt'], data['user_command'] = (x.strip() for x in data['user'].split(SEP_CMD))
         
     | 
| 
      
 248 
     | 
    
         
            +
                        str_template = process_command(data, str_template)
         
     | 
| 
      
 249 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 250 
     | 
    
         
            +
                        data['user_prompt'] = data['user']
         
     | 
| 
      
 251 
     | 
    
         
            +
                    chat.reset()
         
     | 
| 
      
 252 
     | 
    
         
            +
                tolog(f'{str_template=}') # DEBUG
         
     | 
| 
      
 253 
     | 
    
         
            +
                prompt = str_template.format(**data)
         
     | 
| 
      
 254 
     | 
    
         
            +
                tolog(prompt, 'tollm')
         
     | 
| 
      
 255 
     | 
    
         
            +
                toout('')
         
     | 
| 
      
 256 
     | 
    
         
            +
                response = chat(prompt, max_new=NUM_TOKEN - get_ntok(prompt), verbose=False, stream=OUT_PATH)
         
     | 
| 
      
 257 
     | 
    
         
            +
                toout(response[0][:-10].strip())
         
     | 
| 
      
 258 
     | 
    
         
            +
                tolog(response[-1], 'tps')
         
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
      
 260 
     | 
    
         
            +
            VIMLMSCRIPT = Template(r"""
         
     | 
| 
      
 261 
     | 
    
         
            +
            let s:watched_dir = expand('$WATCH_DIR')
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
            function! Monitor()
         
     | 
| 
      
 264 
     | 
    
         
            +
                write
         
     | 
| 
      
 265 
     | 
    
         
            +
                let response_path = s:watched_dir . '/response.md'
         
     | 
| 
      
 266 
     | 
    
         
            +
                rightbelow vsplit | execute 'view ' . response_path
         
     | 
| 
      
 267 
     | 
    
         
            +
                setlocal autoread
         
     | 
| 
      
 268 
     | 
    
         
            +
                setlocal readonly
         
     | 
| 
      
 269 
     | 
    
         
            +
                setlocal nobuflisted
         
     | 
| 
      
 270 
     | 
    
         
            +
                filetype detect
         
     | 
| 
      
 271 
     | 
    
         
            +
                syntax on
         
     | 
| 
      
 272 
     | 
    
         
            +
                wincmd h
         
     | 
| 
      
 273 
     | 
    
         
            +
                let s:monitor_timer = timer_start(100, 'CheckForUpdates', {'repeat': -1})
         
     | 
| 
      
 274 
     | 
    
         
            +
            endfunction
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
            function! CheckForUpdates(timer)
         
     | 
| 
      
 277 
     | 
    
         
            +
                let bufnum = bufnr(s:watched_dir . '/response.md')
         
     | 
| 
      
 278 
     | 
    
         
            +
                if bufnum == -1
         
     | 
| 
      
 279 
     | 
    
         
            +
                    call timer_stop(s:monitor_timer)
         
     | 
| 
      
 280 
     | 
    
         
            +
                    return
         
     | 
| 
      
 281 
     | 
    
         
            +
                endif
         
     | 
| 
      
 282 
     | 
    
         
            +
                silent! checktime
         
     | 
| 
      
 283 
     | 
    
         
            +
            endfunction
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
            function! s:CustomInput(prompt) abort
         
     | 
| 
      
 286 
     | 
    
         
            +
                let input = ''
         
     | 
| 
      
 287 
     | 
    
         
            +
                let clean_prompt = a:prompt
         
     | 
| 
      
 288 
     | 
    
         
            +
                let aborted = v:false
         
     | 
| 
      
 289 
     | 
    
         
            +
                echohl Question
         
     | 
| 
      
 290 
     | 
    
         
            +
                echon clean_prompt
         
     | 
| 
      
 291 
     | 
    
         
            +
                echohl None
         
     | 
| 
      
 292 
     | 
    
         
            +
                while 1
         
     | 
| 
      
 293 
     | 
    
         
            +
                    let c = getchar()
         
     | 
| 
      
 294 
     | 
    
         
            +
                    if type(c) == v:t_number
         
     | 
| 
      
 295 
     | 
    
         
            +
                        if c == 13 " Enter
         
     | 
| 
      
 296 
     | 
    
         
            +
                            break
         
     | 
| 
      
 297 
     | 
    
         
            +
                        elseif c == 27 " Esc
         
     | 
| 
      
 298 
     | 
    
         
            +
                            let aborted = v:true
         
     | 
| 
      
 299 
     | 
    
         
            +
                            break
         
     | 
| 
      
 300 
     | 
    
         
            +
                        elseif c == 8 || c == 127 " Backspace
         
     | 
| 
      
 301 
     | 
    
         
            +
                            if len(input) > 0
         
     | 
| 
      
 302 
     | 
    
         
            +
                                let input = input[:-2]
         
     | 
| 
      
 303 
     | 
    
         
            +
                                echon "\r" . clean_prompt . input . ' '
         
     | 
| 
      
 304 
     | 
    
         
            +
                                execute "normal! \<BS>"
         
     | 
| 
      
 305 
     | 
    
         
            +
                            endif
         
     | 
| 
      
 306 
     | 
    
         
            +
                        else
         
     | 
| 
      
 307 
     | 
    
         
            +
                            let char = nr2char(c)
         
     | 
| 
      
 308 
     | 
    
         
            +
                            let input .= char
         
     | 
| 
      
 309 
     | 
    
         
            +
                            echon char
         
     | 
| 
      
 310 
     | 
    
         
            +
                        endif
         
     | 
| 
      
 311 
     | 
    
         
            +
                    endif
         
     | 
| 
      
 312 
     | 
    
         
            +
                endwhile
         
     | 
| 
      
 313 
     | 
    
         
            +
                let input_length = aborted ? 0 : len(input)
         
     | 
| 
      
 314 
     | 
    
         
            +
                let clear_length = len(clean_prompt) + input_length
         
     | 
| 
      
 315 
     | 
    
         
            +
                echon "\r" . repeat(' ', clear_length) . "\r"
         
     | 
| 
      
 316 
     | 
    
         
            +
                return aborted ? v:null : input
         
     | 
| 
      
 317 
     | 
    
         
            +
            endfunction
         
     | 
| 
      
 318 
     | 
    
         
            +
             
     | 
| 
      
 319 
     | 
    
         
            +
            function! SaveUserInput()
         
     | 
| 
      
 320 
     | 
    
         
            +
                let user_input = s:CustomInput('Ask LLM: ')
         
     | 
| 
      
 321 
     | 
    
         
            +
                if user_input is v:null
         
     | 
| 
      
 322 
     | 
    
         
            +
                    echo "Input aborted"
         
     | 
| 
      
 323 
     | 
    
         
            +
                    return
         
     | 
| 
      
 324 
     | 
    
         
            +
                endif
         
     | 
| 
      
 325 
     | 
    
         
            +
                let user_file = s:watched_dir . '/user'
         
     | 
| 
      
 326 
     | 
    
         
            +
                call writefile([user_input], user_file, 'w')
         
     | 
| 
      
 327 
     | 
    
         
            +
                let current_file = expand('%:p')
         
     | 
| 
      
 328 
     | 
    
         
            +
                let tree_file = s:watched_dir . '/tree'
         
     | 
| 
      
 329 
     | 
    
         
            +
                call writefile([current_file], tree_file, 'w')
         
     | 
| 
      
 330 
     | 
    
         
            +
            endfunction
         
     | 
| 
      
 331 
     | 
    
         
            +
             
     | 
| 
      
 332 
     | 
    
         
            +
            function! SaveUserInput()
         
     | 
| 
      
 333 
     | 
    
         
            +
                let user_input = s:CustomInput('Ask LLM: ')
         
     | 
| 
      
 334 
     | 
    
         
            +
                if user_input is v:null
         
     | 
| 
      
 335 
     | 
    
         
            +
                    echo "Input aborted"
         
     | 
| 
      
 336 
     | 
    
         
            +
                    return
         
     | 
| 
      
 337 
     | 
    
         
            +
                endif
         
     | 
| 
      
 338 
     | 
    
         
            +
                let user_file = s:watched_dir . '/user'
         
     | 
| 
      
 339 
     | 
    
         
            +
                call writefile([user_input], user_file, 'w')
         
     | 
| 
      
 340 
     | 
    
         
            +
                let current_file = expand('%:p')
         
     | 
| 
      
 341 
     | 
    
         
            +
                let tree_file = s:watched_dir . '/tree'
         
     | 
| 
      
 342 
     | 
    
         
            +
                call writefile([current_file], tree_file, 'w')
         
     | 
| 
      
 343 
     | 
    
         
            +
            endfunction
         
     | 
| 
      
 344 
     | 
    
         
            +
             
     | 
| 
      
 345 
     | 
    
         
            +
            function! FollowUpInput()
         
     | 
| 
      
 346 
     | 
    
         
            +
                call writefile([], s:watched_dir . '/yank', 'w')
         
     | 
| 
      
 347 
     | 
    
         
            +
                call writefile([], s:watched_dir . '/context', 'w')
         
     | 
| 
      
 348 
     | 
    
         
            +
                call writefile([], s:watched_dir . '/followup', 'w')
         
     | 
| 
      
 349 
     | 
    
         
            +
                call SaveUserInput()
         
     | 
| 
      
 350 
     | 
    
         
            +
            endfunction
         
     | 
| 
      
 351 
     | 
    
         
            +
             
     | 
| 
      
 352 
     | 
    
         
            +
            vnoremap <C-l> :w! $WATCH_DIR/yank<CR>:w! $WATCH_DIR/context<CR>:call SaveUserInput()<CR>
         
     | 
| 
      
 353 
     | 
    
         
            +
            nnoremap <C-l> V:w! $WATCH_DIR/yank<CR>:w! $WATCH_DIR/context<CR>:call SaveUserInput()<CR>
         
     | 
| 
      
 354 
     | 
    
         
            +
            nnoremap <C-r> :call FollowUpInput()<CR>
         
     | 
| 
      
 355 
     | 
    
         
            +
             
     | 
| 
      
 356 
     | 
    
         
            +
            call Monitor()
         
     | 
| 
      
 357 
     | 
    
         
            +
            """).safe_substitute(dict(WATCH_DIR=WATCH_DIR))
         
     | 
| 
      
 358 
     | 
    
         
            +
             
     | 
| 
      
 359 
     | 
    
         
            +
            async def main():
         
     | 
| 
      
 360 
     | 
    
         
            +
                parser = argparse.ArgumentParser(description="VimLM - LLM-powered Vim assistant")
         
     | 
| 
      
 361 
     | 
    
         
            +
                parser.add_argument("vim_args", nargs=argparse.REMAINDER, help="Vim arguments")
         
     | 
| 
      
 362 
     | 
    
         
            +
                args = parser.parse_args()
         
     | 
| 
      
 363 
     | 
    
         
            +
                with tempfile.NamedTemporaryFile(mode='w', suffix='.vim', delete=False) as f:
         
     | 
| 
      
 364 
     | 
    
         
            +
                    f.write(VIMLMSCRIPT)
         
     | 
| 
      
 365 
     | 
    
         
            +
                    vim_script = f.name
         
     | 
| 
      
 366 
     | 
    
         
            +
                vim_command = ["vim", "-c", f"source {vim_script}"]
         
     | 
| 
      
 367 
     | 
    
         
            +
                if args.vim_args:
         
     | 
| 
      
 368 
     | 
    
         
            +
                    vim_command.extend(args.vim_args)
         
     | 
| 
      
 369 
     | 
    
         
            +
                else:
         
     | 
| 
      
 370 
     | 
    
         
            +
                    vim_command.append('.tmp')
         
     | 
| 
      
 371 
     | 
    
         
            +
                monitor_task = asyncio.create_task(monitor_directory())
         
     | 
| 
      
 372 
     | 
    
         
            +
                vim_process = await asyncio.create_subprocess_exec(*vim_command)
         
     | 
| 
      
 373 
     | 
    
         
            +
                await vim_process.wait()
         
     | 
| 
      
 374 
     | 
    
         
            +
                monitor_task.cancel()
         
     | 
| 
      
 375 
     | 
    
         
            +
                try:
         
     | 
| 
      
 376 
     | 
    
         
            +
                    await monitor_task
         
     | 
| 
      
 377 
     | 
    
         
            +
                except asyncio.CancelledError:
         
     | 
| 
      
 378 
     | 
    
         
            +
                    pass
         
     | 
| 
      
 379 
     | 
    
         
            +
             
     | 
| 
      
 380 
     | 
    
         
            +
            def run():
         
     | 
| 
      
 381 
     | 
    
         
            +
                asyncio.run(main())
         
     | 
| 
      
 382 
     | 
    
         
            +
             
     | 
| 
      
 383 
     | 
    
         
            +
            if __name__ == '__main__':
         
     | 
| 
      
 384 
     | 
    
         
            +
                run()
         
     | 
    
        vimlm-0.0.2.dist-info/RECORD
    DELETED
    
    | 
         @@ -1,6 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            vimlm-0.0.2.dist-info/LICENSE,sha256=f1xgK8fAXg_intwnbc9nLkHf7ODPLtgpHs7DetQHOro,11343
         
     | 
| 
       2 
     | 
    
         
            -
            vimlm-0.0.2.dist-info/METADATA,sha256=ntusBBeCTMoPlEFM7HBrHzCU1scKWQ09Dx6QA_Rod0s,3178
         
     | 
| 
       3 
     | 
    
         
            -
            vimlm-0.0.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
         
     | 
| 
       4 
     | 
    
         
            -
            vimlm-0.0.2.dist-info/entry_points.txt,sha256=mU5V4MYsuIzCc6YB-Ro-6USSHWN5vHw8UDnTEoq0isw,36
         
     | 
| 
       5 
     | 
    
         
            -
            vimlm-0.0.2.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
         
     | 
| 
       6 
     | 
    
         
            -
            vimlm-0.0.2.dist-info/RECORD,,
         
     | 
| 
         @@ -1 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     |