def _save_done(self, modified_buffer: GtkSource.Buffer): """Gtk callback after the saving has been done.""" if self._saving_dialog is not None: self._saving_dialog.hide() self._saving_dialog = None modified_buffer.set_modified(False) self._waiting_for_reload = True # Resync the breakpoints at the Breakpoint Manager. # Collect all line marks and check which is the first temporary opcode text mark in it, this is # the opcode to break on. breakpoints_to_resync = {} for line in range(0, modified_buffer.get_line_count()): marks = EditorTextMarkUtil.get_line_marks_for( modified_buffer, line, 'breakpoint') if len(marks) > 0: for ssb_filename, opcode_offset in EditorTextMarkUtil.get_tmp_opcodes_in_line( modified_buffer, line): if ssb_filename not in breakpoints_to_resync: breakpoints_to_resync[ssb_filename] = [] breakpoints_to_resync[ssb_filename].append(opcode_offset) for ssb_filename, b_points in breakpoints_to_resync.items(): self.file_context.breakpoint_manager.resync(ssb_filename, b_points)
def add_line_mark_for_op(cls, b: GtkSource.Buffer, ssb_filename: str, opcode_addr: int, name: str, category: str, is_for_macro_call: bool): m = cls._get_opcode_mark(b, ssb_filename, opcode_addr, is_for_macro_call) if m is not None: b.create_source_mark(name, category, b.get_iter_at_mark(m))
def scroll_to_op(cls, b: GtkSource.Buffer, view: GtkSource.View, ssb_filename: str, opcode_addr: int, is_for_macro_call: bool): m = cls._get_opcode_mark(b, ssb_filename, opcode_addr, is_for_macro_call) if m is not None: view.scroll_to_mark(m, 0.1, False, 0.1, 0.1) b.place_cursor(b.get_iter_at_mark(m))
def _get_opcode_mark(cls, b: GtkSource.Buffer, ssb_filename: str, opcode_addr: int, is_for_macro_call: bool) -> Optional[Gtk.TextMark]: if is_for_macro_call: return b.get_mark( f'opcode_<<<{ssb_filename}>>>_{opcode_addr}_call') else: return b.get_mark(f'opcode_<<<{ssb_filename}>>>_{opcode_addr}')
def create_opcode_mark(cls, b: GtkSource.Buffer, ssb_filename: str, offset: int, line: int, col: int, is_tmp: bool, is_for_macro_call: bool): textiter = b.get_iter_at_line_offset(line, col) tmp_prefix = 'TMP_' if is_tmp else '' macro_call_suffix = '_call' if is_for_macro_call else '' b.create_mark( f'{tmp_prefix}opcode_<<<{ssb_filename}>>>_{offset}{macro_call_suffix}', textiter)
def remove_breakpoint_line_mark(cls, b: GtkSource.Buffer, ssb_filename: str, opcode_offset: int, category: str): # XXX: This is a bit ugly, but due to the fact, that there can be one call to a macro # in the same file, there can be exactly 0-2 line markers: for i in [0, 1]: m: Gtk.TextMark = b.get_mark( f'for:opcode_<<<{ssb_filename}>>>_{opcode_offset}_{i}') if m is None: return b.remove_source_marks(b.get_iter_at_mark(m), b.get_iter_at_mark(m), category)
def on_buffer_notify_cursor_position(self, buffer: GtkSource.Buffer, *args): textiter = buffer.get_iter_at_offset(buffer.props.cursor_position) if 'string' in buffer.get_context_classes_at_iter(textiter): # iter_backward_to_context_class_toggle and iter_forward_to_context_class_toggle # seem to be broken (because of course they are), so we do it manually. start = self._get_string_start(textiter) end = self._get_string_end(textiter) if start is None or end is None: return True string = buffer.get_text(start, end, False) self.context.on_selected_string_changed(string) return True
def _build_calltip_data(self, textiter: Gtk.TextIter, buffer: GtkSource.Buffer): cursor = textiter.copy() count_commas = 0 count_commas_since_last_lang_string_begin_mark = 0 while cursor.backward_char(): if cursor.get_char() == ')': # We are not in a function, for sure! return None if cursor.get_char() == '{' or cursor.get_char() == '<': # Handle middle of language string or a pos marker count_commas -= count_commas_since_last_lang_string_begin_mark count_commas_since_last_lang_string_begin_mark = 0 if cursor.get_char() == '}' or cursor.get_char() == '>': # Handle end of language string or a pos marker count_commas_since_last_lang_string_begin_mark = 0 if cursor.get_char() == '(': # Handle the opcode/function name start_of_word = cursor.copy() backward_until_space(start_of_word) opcode_name = buffer.get_text(start_of_word, cursor, False) for op in self.opcodes: if op.name == opcode_name: return op, count_commas return None if cursor.get_char() == ',': # Collect commas for the arg index count_commas += 1 count_commas_since_last_lang_string_begin_mark += 1 return None
def __init__(self, preferences, action_name=None): super(SourceView, self).__init__() if action_name: self.action_name = action_name self.set_hexpand(True) self.set_vexpand(True) self.text_buffer = Buffer.new_with_language( LANGS['.%s' % preferences.parser]) self.text_buffer.connect("changed", self.inc_changes) self.source_view = View.new_with_buffer(self.text_buffer) self.spellchecker = Checker() self.spellchecker.connect("language-changed", self.language_changed) self.source_view.override_font( FontDescription.from_string('Monospace')) # self.source_view.set_monospace(True) since 3.16 self.add(self.source_view) editor_pref = preferences.editor self.set_period_save(editor_pref.period_save) self.set_check_spelling(editor_pref.check_spelling, editor_pref.spell_lang) self.set_spaces_instead_of_tabs(editor_pref.spaces_instead_of_tabs) self.source_view.set_tab_width(editor_pref.tab_width) self.source_view.set_auto_indent(editor_pref.auto_indent) self.source_view.set_show_line_numbers(editor_pref.line_numbers) self.source_view.set_show_right_margin(editor_pref.right_margin) self.source_view.set_highlight_current_line(editor_pref.current_line) self.set_text_wrapping(editor_pref.text_wrapping) self.set_white_chars(editor_pref.white_chars)
def add_breakpoint_line_mark(cls, b: GtkSource.Buffer, ssb_filename: str, opcode_offset: int, category: str): ms = [] m: Gtk.TextMark = cls._get_opcode_mark(b, ssb_filename, opcode_offset, True) if m is not None: ms.append(m) m = cls._get_opcode_mark(b, ssb_filename, opcode_offset, False) if m is not None: ms.append(m) for i, m in enumerate(ms): line_iter = b.get_iter_at_line(b.get_iter_at_mark(m).get_line()) lm: Gtk.TextMark = b.get_mark( f'for:opcode_<<<{ssb_filename}>>>_{opcode_offset}_{i}') if lm is not None: return b.create_source_mark( f'for:opcode_<<<{ssb_filename}>>>_{opcode_offset}_{i}', category, line_iter)
def on_sourcebuffer_delete_range(self, buffer: GtkSource.Buffer, start: Gtk.TextIter, end: Gtk.TextIter): if start.get_line() != end.get_line() or start.get_chars_in_line( ) == 0: i = start.copy() ms = [] while i.get_offset() <= end.get_offset(): ms += buffer.get_source_marks_at_iter(i, 'breakpoint') if not i.forward_char(): break for m in ms: self.remove_breakpoint(m) return True
def __init__(self, win, preferences, action_name=None): super(SourceView, self).__init__() if action_name: self.action_name = action_name self.set_hexpand(True) self.set_vexpand(True) self.text_buffer = Buffer.new_with_language(LANGS['.%s' % preferences.parser]) self.text_buffer.connect("changed", self.inc_changes) # TODO: will work when FileSaver and FileLoader will be used # self.text_buffer.set_implicit_trailing_newline(False) self.source_view = View.new_with_buffer(self.text_buffer) adj = self.get_vadjustment() adj.connect("value-changed", self.on_scroll_changed) self.spellchecker = Checker() self.spellchecker.connect("language-changed", self.on_language_changed) self.source_view.override_font( FontDescription.from_string('Monospace')) # self.source_view.set_monospace(True) since 3.16 self.add(self.source_view) editor_pref = preferences.editor self.set_period_save(editor_pref.period_save) self.set_check_spelling(editor_pref.check_spelling, editor_pref.spell_lang) self.set_spaces_instead_of_tabs(editor_pref.spaces_instead_of_tabs) self.source_view.set_tab_width(editor_pref.tab_width) self.source_view.set_auto_indent(editor_pref.auto_indent) self.source_view.set_show_line_numbers(editor_pref.line_numbers) self.source_view.set_show_right_margin(editor_pref.right_margin) self.source_view.set_highlight_current_line(editor_pref.current_line) self.set_text_wrapping(editor_pref.text_wrapping) self.set_white_chars(editor_pref.white_chars) self.search_settings = SearchSettings(wrap_around=True) self.search_context = SearchContext.new(self.text_buffer, self.search_settings) self.search_mark = None self.__win = win timeout_add(200, self.check_in_thread)
def get_line_marks_for(cls, b: GtkSource.Buffer, line: int, category: str) -> List[GtkSource.Mark]: return b.get_source_marks_at_line(line, category)
def remove_all_line_marks(cls, b: GtkSource.Buffer, category: str): b.remove_source_marks(b.get_start_iter(), b.get_end_iter(), category)
def switch_to_new_op_marks(cls, b: GtkSource.Buffer, ssb_filename: str): textiter: Gtk.TextIter = b.get_start_iter().copy() # TODO: This is probably pretty slow while textiter.forward_char(): old_marks_at_pos = [ m for m in textiter.get_marks() if m.get_name() and m.get_name().startswith(f'opcode_<<<{ssb_filename}>>>_') ] new_marks_at_pos = [ m for m in textiter.get_marks() if m.get_name() and m.get_name().startswith(f'TMP_opcode_<<<{ssb_filename}>>>_') ] for m in old_marks_at_pos: b.delete_mark(m) for m in new_marks_at_pos: name = m.get_name() # Maybe by chance an old mark with this name still exists elsewhere, remove it. om = b.get_mark(name[4:]) if om is not None: b.delete_mark(om) # Move by deleting and re-creating. match = MARK_PATTERN_TMP.match(m.get_name()) if match.group(3): b.create_mark( f'opcode_<<<{str(match.group(1))}>>>_{int(match.group(2))}_{match.group(3)}', textiter) else: b.create_mark( f'opcode_<<<{str(match.group(1))}>>>_{int(match.group(2))}', textiter) b.delete_mark(m)
def on_buffer_notify_cursor_position(self, buffer: GtkSource.Buffer, *args): textiter = buffer.get_iter_at_offset(buffer.props.cursor_position) tip = self._build_calltip_data(textiter, buffer) if not tip: if self.position_mark_calltip is not None: self.position_mark_calltip.reset(self._active_widget) if self._active_widget: self._active_widget.destroy() self._active_widget = None self._active_op = None self._active_arg = None return True op: Pmd2ScriptOpCode op, arg_index = tip if not self._active_widget: self._active_widget = GtkSource.CompletionInfo.new() self._active_widget.set_attached_to(self.view) self._active_widget.move_to_iter(self.view, textiter) op_was_same = self._active_op == op if not op_was_same: self._active_op = op for c in self._active_widget.get_children(): self._active_widget.remove(c) outer_box: Gtk.Box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) btn_box: Gtk.Box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 4) outer_box.pack_start(btn_box, True, False, 0) self._active_widget.add(outer_box) if not op_was_same or self._active_arg != arg_index: self._active_arg = arg_index btn_box = self._active_widget.get_children()[0].get_children()[0] for c in btn_box.get_children(): btn_box.remove(c) for i, arg in enumerate(op.arguments): lbl: Gtk.Label = Gtk.Label.new('') if arg_index == i: markup = f'<b>{arg.name}: <i>{arg.type}</i></b>, ' else: markup = f'<span weight="light">{arg.name}: <i>{arg.type}</i></span>, ' if i == len( op.arguments) - 1 and not op.repeating_argument_group: markup = markup.rstrip(', ') lbl.set_markup(markup) btn_box.pack_start(lbl, True, False, 0) if op.repeating_argument_group: lbl = Gtk.Label.new('[') btn_box.pack_start(lbl, True, False, 0) for i, arg in enumerate(op.repeating_argument_group.arguments): lbl = Gtk.Label.new('') # TODO: Support highlighting individual repeating args. (not really used though) if arg_index >= len(op.arguments): markup = f'<b>{arg.name}: <i>{arg.type}</i></b>, ' else: markup = f'<span weight="light">{arg.name}: <i>{arg.type}</i></span>, ' if i == len(op.repeating_argument_group.arguments) - 1: markup = markup.rstrip(', ') lbl.set_markup(markup) btn_box.pack_start(lbl, True, False, 0) lbl = Gtk.Label.new('... ]') btn_box.pack_start(lbl, True, False, 0) if self.position_mark_calltip is not None: self.position_mark_calltip.add_button_if_pos_mark( self._active_widget.get_children()[0], buffer) self._active_widget.show_all() return True