1#!/usr/local/bin/python3.8 2# 3# PLASMA : Generate an indented asm code (pseudo-C) with colored syntax. 4# Copyright (C) 2015 Joel 5# 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18# 19 20import curses 21from curses import A_UNDERLINE, color_pair, A_REVERSE 22 23from plasma.lib.custom_colors import * 24from plasma.lib.consts import * 25from plasma.lib.ui.widget import Widget 26 27 28ALPHANUM = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 29 30 31def check_match_word(line, idx, word): 32 if idx != 0 and line[idx - 1] in ALPHANUM: 33 return False 34 if idx + len(word) != len(line) and line[idx + len(word)] in ALPHANUM: 35 return False 36 return True 37 38 39class Listbox(Widget): 40 def __init__(self, x, y, w, h, output): 41 Widget.__init__(self, x, y, w, h) 42 43 # Coordinates of the cursor inside the box 44 self.win_y = 0 45 self.cursor_y = 0 46 self.cursor_x = 0 47 48 if output is not None: 49 self.output = output 50 self.token_lines = output.token_lines 51 52 self.search_hi = [] 53 self.search_bin = None 54 self.word_accepted_chars = ["_", "@", ".", "$", ":", "?"] 55 56 # Note: all these functions should return a boolean. The value is true 57 # if the screen must be refreshed (not re-drawn, in this case call 58 # explictly self.draw or self.reload_asm if the output changed). 59 60 self.mapping = { 61 b"\x1b\x5b\x44": self.k_left, 62 b"\x1b\x5b\x43": self.k_right, 63 b"\x1b\x5b\x41": self.k_up, 64 b"\x1b\x5b\x42": self.k_down, 65 b"\x1b\x5b\x35\x7e": self.k_pageup, 66 b"\x1b\x5b\x36\x7e": self.k_pagedown, 67 b"g": self.k_top, 68 b"G": self.k_bottom, 69 b"\x01": self.k_home, # ctrl-a 70 b"\x05": self.k_end, # ctrl-e 71 b"\x1b\x5b\x37\x7e": self.k_home, 72 b"\x1b\x5b\x38\x7e": self.k_end, 73 b" ": self.cmd_highlight_current_word, 74 b"\x0b": self.cmd_highlight_clear, # ctrl-k 75 b"\x1b\x5b\x31\x3b\x35\x44": self.k_ctrl_left, 76 b"\x1b\x5b\x31\x3b\x35\x43": self.k_ctrl_right, 77 b"\n": self.k_enter, 78 b"q": self.k_q, 79 b"\x1b": self.k_q, 80 } 81 82 self.cursor_position_utf8 = { 83 0: "█", 84 1: "▇", 85 2: "▆", 86 3: "▅", 87 4: "▄", 88 5: "▃", 89 6: "▂", 90 7: "▁", 91 } 92 93 94 def is_tok_var(self): 95 num_line = self.win_y + self.cursor_y 96 tokens = self.output.token_lines[num_line] 97 98 x = self.cursor_x 99 if x >= len(self.output.lines[num_line]): 100 return None 101 102 i = 0 103 for (s, col, _) in tokens: 104 i += len(s) 105 if x < i: 106 return col == COLOR_VAR.val 107 108 return False 109 110 111 def get_word_under_cursor(self): 112 num_line = self.win_y + self.cursor_y 113 line = self.output.lines[num_line] 114 115 if len(line) == 0: 116 return None 117 118 x = self.cursor_x 119 if x >= len(line): 120 return None 121 122 if not line[x].isalnum() and not line[x] in self.word_accepted_chars: 123 return None 124 125 curr = [] 126 while x >= 0 and (line[x].isalnum() or line[x] in self.word_accepted_chars): 127 x -= 1 128 x += 1 129 130 while x < len(line) and (line[x].isalnum() or \ 131 line[x] in self.word_accepted_chars): 132 curr.append(line[x]) 133 x += 1 134 135 if curr[-1] == ":": 136 return "".join(curr[:-1]) 137 138 if curr: 139 return "".join(curr) 140 return None 141 142 143 def goto_line(self, new_line): 144 curr_line = self.win_y + self.cursor_y 145 diff = new_line - curr_line 146 if diff > 0: 147 self.scroll_down(diff, False) 148 elif diff < 0: 149 self.scroll_up(-diff, False) 150 151 152 def draw(self): 153 i = 0 154 while i < self.height: 155 if self.win_y + i < len(self.token_lines): 156 self.print_line(i) 157 else: 158 # force to clear the entire line 159 self.screen.move(i, 0) 160 self.screen.clrtoeol() 161 i += 1 162 163 # Print the scroll cursor on the right. It uses utf-8 block characters. 164 165 y = self.get_y_scroll() 166 i = y % 8 167 y = y // 8 168 169 self.screen.insstr(y, self.width - 1, 170 self.cursor_position_utf8[i], 171 color_pair(COLOR_SCROLL_CURSOR)) 172 173 if i != 0 and y + 1 < self.height: 174 self.screen.insstr(y + 1, self.width - 1, 175 self.cursor_position_utf8[i], 176 color_pair(COLOR_SCROLL_CURSOR) | A_REVERSE) 177 178 179 def draw_cursor(self): 180 size_line = len(self.output.lines[self.win_y + self.cursor_y]) 181 if size_line == 0: 182 x = 0 183 elif self.cursor_x >= size_line: 184 x = size_line - 1 185 else: 186 x = self.cursor_x 187 188 self.screen.move(self.cursor_y, x) 189 190 191 def print_line(self, i): 192 num_line = self.win_y + i 193 is_current_line = self.cursor_y == i and self.has_focus 194 force_exit = False 195 x = 0 196 197 for (string, col, is_bold) in self.token_lines[num_line]: 198 if x + len(string) >= self.width - 1: 199 string = string[:self.width - x - 1] 200 force_exit = True 201 202 c = color_pair(col) 203 204 if is_current_line: 205 c |= A_UNDERLINE 206 207 if is_bold: 208 c |= curses.A_BOLD 209 210 self.screen.addstr(i, x, string, c) 211 212 x += len(string) 213 if force_exit: 214 break 215 216 if is_current_line and not force_exit: 217 n = self.width - x - 1 218 self.screen.addstr(i, x, " " * n, color_pair(0) | A_UNDERLINE) 219 x += n 220 221 self.highlight_search(i) 222 self.screen.move(i, x) 223 224 225 def get_y_scroll(self): 226 # Because the scroll can have 8 states 227 h8 = self.height * 8 228 if len(self.token_lines) <= self.height: 229 return 0 230 y = self.win_y * h8 // (len(self.token_lines) - self.height) 231 if y >= h8 - 8: 232 return h8 - 8 233 return y 234 235 236 def callback_mouse_left(self, x, y): 237 self.cursor_x = x 238 self.goto_line(self.win_y + y) 239 self.cmd_highlight_current_word(True) 240 self.check_cursor_x() 241 242 243 def callback_mouse_up(self): 244 self.scroll_up(3, True) 245 246 247 def callback_mouse_down(self): 248 self.scroll_down(3, True) 249 250 251 def callback_mouse_double_left(self): 252 return False 253 254 255 def highlight_search(self, i): 256 if not self.search_hi: 257 return 258 num_line = self.win_y + i 259 start = 0 260 for word in self.search_hi: 261 while 1: 262 idx = self.output.lines[num_line].find(word, start) 263 if idx == -1 or idx >= self.width: 264 break 265 if check_match_word(self.output.lines[num_line], idx, word): 266 self.screen.chgat(i, idx, len(word), curses.color_pair(1)) 267 start = idx + 1 268 269 270 def scroll_up(self, n, do_page_scroll): 271 if do_page_scroll: 272 wy = self.win_y - n 273 y = self.cursor_y + n 274 line = self.win_y + self.cursor_y 275 276 wy = self.dump_update_up(wy) 277 278 if wy >= 0: 279 self.win_y = wy 280 if y <= self.height - 3: 281 if line != len(self.token_lines): 282 self.cursor_y = y 283 else: 284 self.cursor_y = self.height - 4 285 else: 286 self.win_y = 0 287 else: 288 # TODO: find another way 289 for i in range(n): 290 self.dump_update_up(self.win_y) 291 292 if self.win_y == 0: 293 if self.cursor_y == 0: 294 break 295 self.cursor_y -= 1 296 else: 297 if self.cursor_y == 3: 298 self.win_y -= 1 299 else: 300 self.cursor_y -= 1 301 302 303 def scroll_down(self, n, do_page_scroll): 304 if do_page_scroll: 305 wy = self.win_y + n 306 y = self.cursor_y - n 307 308 self.dump_update_bottom(wy) 309 310 if wy > len(self.token_lines) - self.height: 311 if wy < len(self.token_lines) - 3: 312 self.win_y = wy 313 else: 314 self.win_y = len(self.token_lines) - 3 - 1 315 if y >= 3: 316 self.cursor_y = y 317 else: 318 self.cursor_y = 3 319 else: 320 self.win_y = wy 321 if y >= 3: 322 self.cursor_y = y 323 else: 324 self.cursor_y = 3 325 else: 326 # TODO: find another way 327 for i in range(n): 328 self.dump_update_bottom(self.win_y) 329 330 if self.win_y >= len(self.token_lines) - self.height: 331 if self.win_y + self.cursor_y == len(self.token_lines) - 1: 332 break 333 self.cursor_y += 1 334 else: 335 if self.cursor_y == self.height - 4: 336 self.win_y += 1 337 else: 338 self.cursor_y += 1 339 340 341 def dump_update_up(self, wy): 342 return wy 343 344 345 def dump_update_bottom(self, wy): 346 return 347 348 349 def check_cursor_x(self): 350 size_line = len(self.output.lines[self.win_y + self.cursor_y]) 351 if size_line == 0: 352 self.cursor_x = 0 353 elif self.cursor_x >= size_line: 354 self.cursor_x = size_line - 1 355 356 357 # Commands / Mapping keys 358 359 360 def k_left(self): 361 self.check_cursor_x() 362 if self.cursor_x > 0: 363 self.cursor_x -= 1 364 return False 365 366 def k_right(self): 367 self.cursor_x += 1 368 self.check_cursor_x() 369 return False 370 371 def k_down(self): 372 self.scroll_down(1, False) 373 return True 374 375 def k_up(self): 376 self.scroll_up(1, False) 377 return True 378 379 def k_pageup(self): 380 self.scroll_up(self.height - 1, True) 381 return True 382 383 def k_pagedown(self): 384 self.scroll_down(self.height - 1, True) 385 return True 386 387 def k_enter(self): 388 self.should_stop = True 389 self.value_selected = True 390 return False 391 392 393 def k_q(self): 394 self.should_stop = True 395 self.value_selected = False 396 return False 397 398 399 def k_home(self): 400 # TODO: fix self.cursor_x >= w 401 if self.cursor_x == 0: 402 line = self.output.lines[self.win_y + self.cursor_y] 403 while self.cursor_x < len(line): 404 if line[self.cursor_x] != " ": 405 break 406 self.cursor_x += 1 407 else: 408 self.cursor_x = 0 409 return False 410 411 def k_end(self): 412 # TODO: fix self.cursor_x >= w 413 size_line = len(self.output.lines[self.win_y + self.cursor_y]) 414 if size_line >= self.width: 415 self.cursor_x = self.width - 1 416 elif size_line > 0: 417 self.cursor_x = size_line - 1 418 else: 419 self.cursor_x = 0 420 return False 421 422 def k_ctrl_right(self): 423 self.check_cursor_x() 424 # TODO: fix self.cursor_x >= w 425 line = self.output.lines[self.win_y + self.cursor_y] 426 x = self.cursor_x 427 while x < len(line) and line[x] == " " and x < self.width: 428 x += 1 429 while x < len(line) and line[x] != " " and x < self.width: 430 x += 1 431 self.cursor_x = x 432 433 def k_ctrl_left(self): 434 self.check_cursor_x() 435 line = self.output.lines[self.win_y + self.cursor_y] 436 x = self.cursor_x 437 if x == 0: 438 return 439 x -= 1 440 while x > 0 and line[x] == " ": 441 x -= 1 442 while x > 0 and line[x] != " ": 443 x -= 1 444 if x != 0: 445 x += 1 446 self.cursor_x = x 447 448 def k_prev_paragraph(self): 449 l = self.win_y + self.cursor_y - 1 450 while l > 0 and len(self.output.lines[l]) != 0: 451 l -= 1 452 if l >= 0: 453 self.goto_line(l) 454 self.check_cursor_x() 455 return True 456 457 def k_next_paragraph(self): 458 l = self.win_y + self.cursor_y + 1 459 while l < len(self.output.lines)-1 and len(self.output.lines[l]) != 0: 460 l += 1 461 if l < len(self.output.lines): 462 self.goto_line(l) 463 self.check_cursor_x() 464 return True 465 466 467 def k_top(self): 468 self.cursor_y = 0 469 self.win_y = 0 470 self.cursor_x = 0 471 return True 472 473 474 def k_bottom(self): 475 self.cursor_x = 0 476 if self.win_y >= len(self.token_lines) - self.height: 477 self.cursor_y += len(self.token_lines) - \ 478 self.win_y - self.cursor_y - 1 479 else: 480 self.cursor_y = self.height - 1 481 self.win_y = len(self.token_lines) - self.height 482 return True 483 484 485 def cmd_highlight_current_word(self, from_mouse_event=False): 486 # When we click on a word with the mouse, we must be explicitly 487 # on the word. 488 if not from_mouse_event: 489 num_line = self.win_y + self.cursor_y 490 line = self.output.lines[num_line] 491 if self.cursor_x >= len(line): 492 self.cursor_x = len(line) - 1 493 494 w = self.get_word_under_cursor() 495 if w is None: 496 return False 497 self.search_hi = [w] 498 return True 499 500 501 def cmd_highlight_clear(self): 502 self.search_hi.clear() 503 return True 504