- 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.cursor
19 -1 pos = sum([len(line) + 1 for line in window.buffer[:(row - 1)]]) + col
20 -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) + 1
28 -1 row += 1
29 -1 else:
30 -1 window.cursor = row + 1, col
31 -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 = buffer
38 -1 self.local_changes = []
39 -1 self.staged_changes = []
40 -1 self.old = self.get_text()
41 -1 self.last_event_id = 0
42 -1
43 -1 self.name = os.path.basename(buffer.name).removesuffix('.pad')
44 -1 self.url = BASE_URL + self.name
45 -1
46 -1 def get_text(self):
47 -1 return '\n'.join(self.buffer[:])
48 -1
49 -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 -1
57 -1 def stop_listen(self):
58 -1 vim.command(f'call job_stop(g:pad_curl_{self.buffer.number})')
59 -1
60 -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 = text
65 -1
66 -1 def send_changes(self):
67 -1 if self.staged_changes:
68 -1 # TODO: retry again later
69 -1 print('abort because of staged changes')
70 -1 else:
71 -1 self.staged_changes = self.local_changes
72 -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 -1
82 -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 = None
87 -1 text = self.get_text()
88 -1 prior = text
89 -1 my_changes = [*self.staged_changes, *self.local_changes]
90 -1
91 -1 for change in reversed(my_changes):
92 -1 text = diff.unapply(text, change, selection)
93 -1
94 -1 for change in changes:
95 -1 text = diff.apply(text, change, selection)
96 -1
97 -1 for change in my_changes:
98 -1 text = diff.apply(text, change, selection)
99 -1
100 -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 = text
105 -1
106 -1 def reset_modified(self):
107 -1 mod = bool(self.local_changes or self.staged_changes)
108 -1 self.buffer.options['modified'] = mod
109 -1
110 -1 def optimize(self):
111 -1 text = self.get_text()
112 -1 my_changes = [*self.staged_changes, *self.local_changes]
113 -1
114 -1 for change in reversed(my_changes):
115 -1 text = diff.unapply(text, change)
116 -1
117 -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_id
121 -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] = pad
132 -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 -1
154 -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 -1
163 -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)