/ editor.py
editor.py
  1  import curses
  2  import sys
  3  
  4  RIGHT_KEYS = set("qwertasdfgzxcvb12345`~!@#$%^&*()-_=+\\|")
  5  LEFT_KEYS = set("yuiophjklnm67890[]{};:'\",.<>/?")
  6  
  7  OPCODES = {
  8      "00000001": "set [1] to value of address [2]",
  9      "00000010": "read from user 1 bit into [1]",
 10      "00000011": "write [1]",
 11      "00000100": "set [1] to value of [2]",
 12      "00010100": "add [2] + [3] and place it in [1]",
 13      "00011100": "multiply [2] * [3] and place it in [1]",
 14      "00011000": "subtract [2] - [3] and place it in [1]",
 15      "00011010": "divide [2] / [3] and place it in [1]",
 16      "00100001": "if [0] == [1] goto [2]",
 17      "00100010": "if [0] >= [1] goto [2]",
 18      "00100011": "if [0] <= [1] goto [2]",
 19      "00100110": "if [0] > [1] goto [2]",
 20      "00100111": "if [0] < [1] goto [2]",
 21      "00100101": "if [0] != [1] goto [2]",
 22      "00001111": "run from memory [1] with args memory[2:]",
 23      "00001010": "load from persistent byte persistent[1] to memory[2]",
 24      "00010101": "write to persistent memory memory[1] -> persistent[memory[2]]",
 25      "01000000": "goto line [1] and run from there",
 26      "01011111": "thread: start line [0], end line [1], time [2]"
 27  }
 28  
 29  
 30  def prompt_filename(stdscr, prompt):
 31      curses.echo()
 32      stdscr.addstr(curses.LINES - 2, 0, prompt)
 33      stdscr.clrtoeol()
 34      filename = stdscr.getstr(curses.LINES - 2, len(prompt)).decode('utf-8')
 35      curses.noecho()
 36      return filename.strip()
 37  
 38  
 39  def find_opcode_start(line, cursor_x):
 40      bit_positions = [i for i, c in enumerate(line) if c in '01']
 41      if not bit_positions:
 42          return None
 43  
 44      for i, pos in enumerate(bit_positions):
 45          if pos >= cursor_x:
 46              bit_index = i
 47              break
 48      else:
 49          bit_index = len(bit_positions) - 1
 50  
 51      start_bit = (bit_index // 8) * 8
 52      if start_bit + 1 > len(bit_positions):
 53          return None
 54      return bit_positions[start_bit]
 55  
 56  
 57  def get_bits_from(line, start, count=8):
 58      bits = []
 59      i = start
 60      while i < len(line) and len(bits) < count:
 61          if line[i] in '01':
 62              bits.append(line[i])
 63          i += 1
 64      return ''.join(bits)
 65  
 66  
 67  def is_backspace(key):
 68      return key in ('\b', '\x7f', '\x08') or key == curses.KEY_BACKSPACE
 69  
 70  
 71  def main(stdscr, filename=None):
 72      curses.curs_set(1)
 73      stdscr.clear()
 74      text = ['']
 75      y, x = 0, 0
 76  
 77      def save_file(fname):
 78          with open(fname, 'w') as f:
 79              f.write('\n'.join(text))
 80  
 81      def load_file(fname):
 82          nonlocal text
 83          try:
 84              with open(fname, 'r') as f:
 85                  lines = f.read().splitlines()
 86                  text = lines if lines else ['']
 87          except:
 88              text = ['']
 89  
 90      if filename:
 91          load_file(filename)
 92  
 93      while True:
 94          stdscr.clear()
 95          height, width = stdscr.getmaxyx()
 96          right_width = 40
 97          text_width = width - right_width - 1
 98  
 99          status = f"File: {filename or 'Untitled'} - [s] Save  [l] Load  [q] Quit"
100          stdscr.addstr(height - 1, 0, status[:width - 1], curses.A_REVERSE)
101  
102          # Draw text with opcode highlighting
103          for idx, line in enumerate(text[:height - 2]):
104              start_idx = end_idx = None
105              if idx == y:
106                  start_idx = find_opcode_start(line, x)
107                  if start_idx is not None:
108                      end_idx = start_idx + 8 + line[start_idx:start_idx+8].count(' ')
109  
110              for i, c in enumerate(line[:text_width]):
111                  attr = curses.A_NORMAL
112                  if start_idx is not None and start_idx <= i < start_idx + 16:
113                      if c in '01':
114                          attr = curses.A_REVERSE
115                  stdscr.addch(idx, i, c, attr)
116  
117          # Opcode panel
118          stdscr.addstr(0, text_width + 1, "Current Opcode:", curses.A_UNDERLINE)
119          current_line = text[y] if y < len(text) else ""
120          opcode_start = find_opcode_start(current_line, x)
121          if opcode_start is not None:
122              bits = get_bits_from(current_line, opcode_start, 8)
123              if bits:
124                  if len(bits) == 8 and bits in OPCODES:
125                      stdscr.addstr(1, text_width + 1, f"{bits}: {OPCODES[bits]}", curses.A_BOLD)
126                  else:
127                      stdscr.addstr(1, text_width + 1, f"{bits}: (partial)", curses.A_DIM)
128  
129                  stdscr.addstr(3, text_width + 1, "Next possible opcodes:", curses.A_UNDERLINE)
130                  matches = [f"{k}: {v}" for k, v in OPCODES.items() if k.startswith(bits) and k != bits]
131                  if matches:
132                      for i, match in enumerate(matches[:height - 6]):
133                          stdscr.addstr(4 + i, text_width + 1, match[:right_width])
134                  else:
135                      stdscr.addstr(4, text_width + 1, "No matching opcodes.", curses.A_DIM)
136              else:
137                  stdscr.addstr(1, text_width + 1, "No bits at cursor", curses.A_DIM)
138          else:
139              stdscr.addstr(1, text_width + 1, "Move to opcode bits.", curses.A_DIM)
140  
141          stdscr.move(y, x)
142          stdscr.refresh()
143  
144          key = stdscr.get_wch()
145  
146          # ==== Key Handling ====
147          if isinstance(key, str):
148              if key.lower() == 'q':
149                  break
150              elif key.lower() == 's':
151                  if not filename:
152                      filename = prompt_filename(stdscr, "Save as: ")
153                  if filename:
154                      save_file(filename)
155              elif key.lower() == 'l':
156                  filename = prompt_filename(stdscr, "Load file: ")
157                  if filename:
158                      load_file(filename)
159                      y, x = 0, 0
160              elif is_backspace(key):
161                  if x > 0:
162                      text[y] = text[y][:x - 1] + text[y][x:]
163                      x -= 1
164                  elif y > 0:
165                      prev_len = len(text[y - 1])
166                      text[y - 1] += text[y]
167                      del text[y]
168                      y -= 1
169                      x = prev_len
170              elif key == '\n':
171                  line = text[y]
172                  text[y] = line[:x]
173                  text.insert(y + 1, line[x:])
174                  y += 1
175                  x = 0
176              elif key == ' ':
177                  text[y] = text[y][:x] + ' ' + text[y][x:]
178                  x += 1
179              elif key.lower() in LEFT_KEYS:
180                  text[y] = text[y][:x] + '0' + text[y][x:]
181                  x += 1
182              elif key.lower() in RIGHT_KEYS:
183                  text[y] = text[y][:x] + '1' + text[y][x:]
184                  x += 1
185              elif key in ['0', '1']:
186                  text[y] = text[y][:x] + key + text[y][x:]
187                  x += 1
188  
189          elif key == curses.KEY_BACKSPACE:
190              if x > 0:
191                  text[y] = text[y][:x - 1] + text[y][x:]
192                  x -= 1
193              elif y > 0:
194                  prev_len = len(text[y - 1])
195                  text[y - 1] += text[y]
196                  del text[y]
197                  y -= 1
198                  x = prev_len
199          elif key == curses.KEY_LEFT:
200              if x > 0:
201                  x -= 1
202              elif y > 0:
203                  y -= 1
204                  x = len(text[y])
205          elif key == curses.KEY_RIGHT:
206              if x < len(text[y]):
207                  x += 1
208              elif y + 1 < len(text):
209                  y += 1
210                  x = 0
211          elif key == curses.KEY_UP:
212              if y > 0:
213                  y -= 1
214                  x = min(x, len(text[y]))
215          elif key == curses.KEY_DOWN:
216              if y + 1 < len(text):
217                  y += 1
218                  x = min(x, len(text[y]))
219  
220  
221  if __name__ == '__main__':
222      filename = sys.argv[1] if len(sys.argv) > 1 else None
223      curses.wrapper(main, filename)