- commit
- 9216eaac551c7a549c428b35fcbba56c56bfc15d
- parent
- 02f23f4a18ede2130d2b0df3d8af6ec838df2a56
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2024-09-06 12:05
lint: tabs to spaces
Diffstat
M | python/pad/__init__.py | 250 | ++++++++++++++++++++++++++++++------------------------------ |
M | python/pad/diff.py | 146 | ++++++++++++++++++++++++++++++------------------------------ |
2 files changed, 198 insertions, 198 deletions
diff --git a/python/pad/__init__.py b/python/pad/__init__.py
@@ -15,152 +15,152 @@ pads = {} 15 15 16 16 17 17 def get_selection(window):18 -1 row, col = window.cursor19 -1 pos = sum([len(line) + 1 for line in window.buffer[:(row - 1)]]) + col20 -1 return [pos, pos]-1 18 row, col = window.cursor -1 19 pos = sum([len(line) + 1 for line in window.buffer[:(row - 1)]]) + col -1 20 return [pos, pos] 21 21 22 22 23 23 def set_selection(window, selection):24 -1 row, col = 0, selection[0]25 -1 for line in window.buffer:26 -1 if col > len(line):27 -1 col -= len(line) + 128 -1 row += 129 -1 else:30 -1 window.cursor = row + 1, col31 -1 break-1 24 row, col = 0, selection[0] -1 25 for line in window.buffer: -1 26 if col > len(line): -1 27 col -= len(line) + 1 -1 28 row += 1 -1 29 else: -1 30 window.cursor = row + 1, col -1 31 break 32 32 33 33 34 34 class Pad:35 -1 def __init__(self, buffer):36 -1 self.id = ''.join(random.sample(string.hexdigits, 6))37 -1 self.buffer = buffer38 -1 self.local_changes = []39 -1 self.staged_changes = []40 -1 self.old = self.get_text()41 -1 self.last_event_id = 042 -143 -1 self.name = os.path.basename(buffer.name).removesuffix('.pad')44 -1 self.url = BASE_URL + self.name45 -146 -1 def get_text(self):47 -1 return '\n'.join(self.buffer[:])48 -149 -1 def start_listen(self):50 -1 vim.command(51 -1 f'let g:pad_curl_{self.buffer.number} = job_start('52 -1 f'["curl", "-N", "{self.url}"], '53 -1 f'{{"out_cb": function("pad#on_channel", [{self.buffer.number}])}}'54 -1 ')'55 -1 )56 -157 -1 def stop_listen(self):58 -1 vim.command(f'call job_stop(g:pad_curl_{self.buffer.number})')59 -160 -1 def on_input(self):61 -1 text = self.get_text()62 -1 change = diff.diff(self.old, text, 3)63 -1 diff.push_change(self.local_changes, change)64 -1 self.old = text65 -166 -1 def send_changes(self):67 -1 if self.staged_changes:68 -1 # TODO: retry again later69 -1 print('abort because of staged changes')70 -1 else:71 -1 self.staged_changes = self.local_changes72 -1 self.local_changes = []73 -1 data = [self.id, 'changes', self.staged_changes]74 -1 try:75 -1 r = session.post(self.url, json=data)76 -1 r.raise_for_status()77 -1 except Exception as e:78 -1 print(e)79 -1 self.local_changes = [*self.staged_changes, *self.local_changes]80 -1 self.staged_changes = []81 -182 -1 def apply_changes(self, changes):83 -1 if vim.current.window.buffer == self.buffer:84 -1 selection = get_selection(vim.current.window)85 -1 else:86 -1 selection = None87 -1 text = self.get_text()88 -1 prior = text89 -1 my_changes = [*self.staged_changes, *self.local_changes]90 -191 -1 for change in reversed(my_changes):92 -1 text = diff.unapply(text, change, selection)93 -194 -1 for change in changes:95 -1 text = diff.apply(text, change, selection)96 -197 -1 for change in my_changes:98 -1 text = diff.apply(text, change, selection)99 -1100 -1 if text != prior:101 -1 self.buffer[:] = text.split('\n')102 -1 if vim.current.window.buffer == self.buffer:103 -1 set_selection(vim.current.window, selection)104 -1 self.old = text105 -1106 -1 def reset_modified(self):107 -1 mod = bool(self.local_changes or self.staged_changes)108 -1 self.buffer.options['modified'] = mod109 -1110 -1 def optimize(self):111 -1 text = self.get_text()112 -1 my_changes = [*self.staged_changes, *self.local_changes]113 -1114 -1 for change in reversed(my_changes):115 -1 text = diff.unapply(text, change)116 -1117 -1 change = diff.diff('', text, 3)118 -1 data = [self.id, 'changes', [change]]119 -1 session.put(self.url, json=data, headers={120 -1 'Last-Event-ID': self.last_event_id121 -1 })-1 35 def __init__(self, buffer): -1 36 self.id = ''.join(random.sample(string.hexdigits, 6)) -1 37 self.buffer = buffer -1 38 self.local_changes = [] -1 39 self.staged_changes = [] -1 40 self.old = self.get_text() -1 41 self.last_event_id = 0 -1 42 -1 43 self.name = os.path.basename(buffer.name).removesuffix('.pad') -1 44 self.url = BASE_URL + self.name -1 45 -1 46 def get_text(self): -1 47 return '\n'.join(self.buffer[:]) -1 48 -1 49 def start_listen(self): -1 50 vim.command( -1 51 f'let g:pad_curl_{self.buffer.number} = job_start(' -1 52 f'["curl", "-N", "{self.url}"], ' -1 53 f'{{"out_cb": function("pad#on_channel", [{self.buffer.number}])}}' -1 54 ')' -1 55 ) -1 56 -1 57 def stop_listen(self): -1 58 vim.command(f'call job_stop(g:pad_curl_{self.buffer.number})') -1 59 -1 60 def on_input(self): -1 61 text = self.get_text() -1 62 change = diff.diff(self.old, text, 3) -1 63 diff.push_change(self.local_changes, change) -1 64 self.old = text -1 65 -1 66 def send_changes(self): -1 67 if self.staged_changes: -1 68 # TODO: retry again later -1 69 print('abort because of staged changes') -1 70 else: -1 71 self.staged_changes = self.local_changes -1 72 self.local_changes = [] -1 73 data = [self.id, 'changes', self.staged_changes] -1 74 try: -1 75 r = session.post(self.url, json=data) -1 76 r.raise_for_status() -1 77 except Exception as e: -1 78 print(e) -1 79 self.local_changes = [*self.staged_changes, *self.local_changes] -1 80 self.staged_changes = [] -1 81 -1 82 def apply_changes(self, changes): -1 83 if vim.current.window.buffer == self.buffer: -1 84 selection = get_selection(vim.current.window) -1 85 else: -1 86 selection = None -1 87 text = self.get_text() -1 88 prior = text -1 89 my_changes = [*self.staged_changes, *self.local_changes] -1 90 -1 91 for change in reversed(my_changes): -1 92 text = diff.unapply(text, change, selection) -1 93 -1 94 for change in changes: -1 95 text = diff.apply(text, change, selection) -1 96 -1 97 for change in my_changes: -1 98 text = diff.apply(text, change, selection) -1 99 -1 100 if text != prior: -1 101 self.buffer[:] = text.split('\n') -1 102 if vim.current.window.buffer == self.buffer: -1 103 set_selection(vim.current.window, selection) -1 104 self.old = text -1 105 -1 106 def reset_modified(self): -1 107 mod = bool(self.local_changes or self.staged_changes) -1 108 self.buffer.options['modified'] = mod -1 109 -1 110 def optimize(self): -1 111 text = self.get_text() -1 112 my_changes = [*self.staged_changes, *self.local_changes] -1 113 -1 114 for change in reversed(my_changes): -1 115 text = diff.unapply(text, change) -1 116 -1 117 change = diff.diff('', text, 3) -1 118 data = [self.id, 'changes', [change]] -1 119 session.put(self.url, json=data, headers={ -1 120 'Last-Event-ID': self.last_event_id -1 121 }) 122 122 123 123 124 124 def get_buffer():125 -1 return int(vim.eval('expand("<abuf>")'), 10)-1 125 return int(vim.eval('expand("<abuf>")'), 10) 126 126 127 127 128 128 def on_open():129 -1 i = get_buffer()130 -1 pad = Pad(vim.buffers[i])131 -1 pads[i] = pad132 -1 pad.start_listen()133 -1 print(f'Connected to {pad.url}')-1 129 i = get_buffer() -1 130 pad = Pad(vim.buffers[i]) -1 131 pads[i] = pad -1 132 pad.start_listen() -1 133 print(f'Connected to {pad.url}') 134 134 135 135 136 136 def on_input():137 -1 pad = pads[get_buffer()]138 -1 pad.on_input()-1 137 pad = pads[get_buffer()] -1 138 pad.on_input() 139 139 140 140 141 141 def on_write():142 -1 pad = pads[get_buffer()]143 -1 pad.send_changes()-1 142 pad = pads[get_buffer()] -1 143 pad.send_changes() 144 144 145 145 146 146 def on_close():147 -1 pad = pads.pop(get_buffer())148 -1 pad.stop_listen()-1 147 pad = pads.pop(get_buffer()) -1 148 pad.stop_listen() 149 149 150 150 151 151 def on_channel(i, msg):152 -1 pad = pads[i]153 -1154 -1 if msg.startswith('data: '):155 -1 data = json.loads(msg.split(': ', 1)[1])156 -1 if data[1] == 'changes':157 -1 if data[0] == pad.id:158 -1 pad.staged_changes = []159 -1 else:160 -1 pad.apply_changes(data[2])161 -1 pad.reset_modified()162 -1163 -1 if (random.random() < 0.05):164 -1 pad.optimize()165 -1 elif msg.startswith('id: '):166 -1 pad.last_event_id = msg.split(': ', 1)[1]-1 152 pad = pads[i] -1 153 -1 154 if msg.startswith('data: '): -1 155 data = json.loads(msg.split(': ', 1)[1]) -1 156 if data[1] == 'changes': -1 157 if data[0] == pad.id: -1 158 pad.staged_changes = [] -1 159 else: -1 160 pad.apply_changes(data[2]) -1 161 pad.reset_modified() -1 162 -1 163 if (random.random() < 0.05): -1 164 pad.optimize() -1 165 elif msg.startswith('id: '): -1 166 pad.last_event_id = msg.split(': ', 1)[1]
diff --git a/python/pad/diff.py b/python/pad/diff.py
@@ -1,105 +1,105 @@ 1 1 def _slice(text, start, end):2 -1 if end:3 -1 return text[start:-end]4 -1 else:5 -1 return text[start:]-1 2 if end: -1 3 return text[start:-end] -1 4 else: -1 5 return text[start:] 6 6 7 7 8 8 def _apply(text, change, selection=None):9 -1 pos, before, after = change-1 9 pos, before, after = change 10 1011 -1 if selection:12 -1 d = diff(before, after, 0)13 -1 if pos + d[0] <= selection[0]:14 -1 selection[0] += len(after) - len(before)15 -1 if pos + d[0] <= selection[1]:16 -1 selection[1] += len(after) - len(before)-1 11 if selection: -1 12 d = diff(before, after, 0) -1 13 if pos + d[0] <= selection[0]: -1 14 selection[0] += len(after) - len(before) -1 15 if pos + d[0] <= selection[1]: -1 16 selection[1] += len(after) - len(before) 17 1718 -1 return text[0:pos] + after + text[pos + len(before):]-1 18 return text[0:pos] + after + text[pos + len(before):] 19 19 20 20 21 21 def apply(text, change, selection=None):22 -1 start, end, before, after = change-1 22 start, end, before, after = change 23 2324 -1 # special handling for resets25 -1 if start == 0 and end == 0 and before == '' and text != '':26 -1 start, end, before, after = diff(text, after, 3)-1 24 # special handling for resets -1 25 if start == 0 and end == 0 and before == '' and text != '': -1 26 start, end, before, after = diff(text, after, 3) 27 2728 -1 # try exact match29 -1 if text[start:].startswith(before):30 -1 return _apply(text, [start, before, after], selection)-1 28 # try exact match -1 29 if text[start:].startswith(before): -1 30 return _apply(text, [start, before, after], selection) 31 3132 -1 # try exact match in similar position33 -1 best = -134 -1 best_dist = len(text)35 -1 for i in range(len(text)):36 -1 if not text[i:].startswith(before):37 -1 continue38 -1 dist = abs(i - start)39 -1 if dist < best_dist:40 -1 best = i41 -1 best_dist = dist42 -1 else:43 -1 break-1 32 # try exact match in similar position -1 33 best = -1 -1 34 best_dist = len(text) -1 35 for i in range(len(text)): -1 36 if not text[i:].startswith(before): -1 37 continue -1 38 dist = abs(i - start) -1 39 if dist < best_dist: -1 40 best = i -1 41 best_dist = dist -1 42 else: -1 43 break 44 4445 -1 if best != -1:46 -1 return _apply(text, [best, before, after], selection)-1 45 if best != -1: -1 46 return _apply(text, [best, before, after], selection) 47 4748 -1 # otherwise, ignore the change49 -1 return text-1 48 # otherwise, ignore the change -1 49 return text 50 50 51 51 52 52 def unapply(text, change, selection=None):53 -1 start, end, before, after = change54 -1 return apply(text, [start, end, after, before], selection)-1 53 start, end, before, after = change -1 54 return apply(text, [start, end, after, before], selection) 55 55 56 56 57 57 def diff(text1, text2, ctx):58 -1 start = 059 -1 end = 0-1 58 start = 0 -1 59 end = 0 60 6061 -1 while (62 -1 start < len(text1)63 -1 and start < len(text2)64 -1 and text1[start] == text2[start]65 -1 ):66 -1 start += 167 -1 while (68 -1 start + end < len(text1)69 -1 and start + end < len(text2)70 -1 and text1[-(end + 1)] == text2[-(end + 1)]71 -1 ):72 -1 end += 1-1 61 while ( -1 62 start < len(text1) -1 63 and start < len(text2) -1 64 and text1[start] == text2[start] -1 65 ): -1 66 start += 1 -1 67 while ( -1 68 start + end < len(text1) -1 69 and start + end < len(text2) -1 70 and text1[-(end + 1)] == text2[-(end + 1)] -1 71 ): -1 72 end += 1 73 7374 -1 start = max(0, start - ctx)75 -1 end = max(0, end - ctx)-1 74 start = max(0, start - ctx) -1 75 end = max(0, end - ctx) 76 7677 -1 return [start, end, _slice(text1, start, end), _slice(text2, start, end)]-1 77 return [start, end, _slice(text1, start, end), _slice(text2, start, end)] 78 78 79 79 80 80 def merge(change1, change2):81 -1 start1, end1, before1, after1 = change182 -1 start2, end2, before2, after2 = change2-1 81 start1, end1, before1, after1 = change1 -1 82 start2, end2, before2, after2 = change2 83 8384 -1 if start1 + len(after1) + end1 == start2 + len(before2) + end2:85 -1 # merge subsequent inserts86 -1 if start2 >= start1 and after1[start2 - start1:].startswith(before2):87 -1 after = _apply(after1, [start2 - start1, before2, after2])88 -1 return [[start1, end1, before1, after]]-1 84 if start1 + len(after1) + end1 == start2 + len(before2) + end2: -1 85 # merge subsequent inserts -1 86 if start2 >= start1 and after1[start2 - start1:].startswith(before2): -1 87 after = _apply(after1, [start2 - start1, before2, after2]) -1 88 return [[start1, end1, before1, after]] 89 8990 -1 # merge subsequent deletes (inverse insert)91 -1 if start1 >= start2 and before2[start1 - start2:].startswith(after1):92 -1 before = _apply(before2, [start1 - start2, after1, before1])93 -1 return [[start2, end2, before, after2]]-1 90 # merge subsequent deletes (inverse insert) -1 91 if start1 >= start2 and before2[start1 - start2:].startswith(after1): -1 92 before = _apply(before2, [start1 - start2, after1, before1]) -1 93 return [[start2, end2, before, after2]] 94 9495 -1 return [change1, change2]-1 95 return [change1, change2] 96 96 97 97 98 98 def push_change(changes, change):99 -1 if not changes:100 -1 changes.append(change)101 -1 else:102 -1 last = changes.pop()103 -1 merged = merge(last, change)104 -1 for change in merged:105 -1 changes.append(change)-1 99 if not changes: -1 100 changes.append(change) -1 101 else: -1 102 last = changes.pop() -1 103 merged = merge(last, change) -1 104 for change in merged: -1 105 changes.append(change)