# Copyright (C) 2012, 2013, 2015, 2023 Tom Tromey # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Source view. import gdb import gui import gui.updatewindow from gui.invoker import Invoker from gui.framecache import FrameCommandInvoker import gui.startup from gui.startup import in_gdb_thread, in_gtk_thread import gui.toplevel import gui.events import os.path import gui.gdbutil import gui.params import gui.bpcache from gi.repository import Gtk, GtkSource, GObject, Gdk, GdkPixbuf, Pango class BufferManager: def __init__(self): self.buffers = {} self.lang_manager = None gui.params.source_theme.set_buffer_manager(self) gui.events.location_changed.connect(self._location_changed) # FIXME - emit a warning if this isn't available. if hasattr(gdb.events, "clear_objfiles"): gdb.events.clear_objfiles.connect(self._clear_objfiles) self.empty_buffer = None def release_buffer(self, buff): # FIXME: we should be smart about buffer caching. # Note that BUFF can be the empty buffer. pass @in_gtk_thread def get_empty_buffer(self): if self.empty_buffer is None: self.empty_buffer = GtkSource.Buffer() self.empty_buffer.set_style_scheme(gui.params.source_theme.get_scheme()) return self.empty_buffer @in_gtk_thread def _set_marks(self, buffer, line_set): iter = buffer.get_iter_at_line(0) while True: line = iter.get_line() + 1 if line in line_set: buffer.create_source_mark(None, "executable", iter) if not iter.forward_line(): break @in_gdb_thread def _get_lines_update(self, buffer, symtab): if hasattr(symtab, "linetable"): line_set = set(symtab.linetable().source_lines()) gui.startup.send_to_gtk(lambda: self._set_marks(buffer, line_set)) @in_gtk_thread def get_buffer(self, symtab, filename): if filename in self.buffers: return self.buffers[filename] if not self.lang_manager: self.lang_manager = GtkSource.LanguageManager.get_default() buff = GtkSource.Buffer() if filename: buff.set_language(self.lang_manager.guess_language(filename)) buff.set_style_scheme(gui.params.source_theme.get_scheme()) buff.begin_not_undoable_action() try: contents = open(filename).read() except: return None buff.set_text(contents) buff.end_not_undoable_action() buff.set_modified(False) buff.filename = filename if symtab is not None: gdb.post_event(lambda: self._get_lines_update(buff, symtab)) self.buffers[filename] = buff return buff @in_gtk_thread def _do_change_theme(self): new_scheme = gui.params.source_theme.get_scheme() for filename in self.buffers: self.buffers[filename].set_style_scheme(new_scheme) if self.empty_buffer is not None: self.empty_buffer.set_style_scheme(new_scheme) @in_gdb_thread def change_theme(self): gui.startup.send_to_gtk(self._do_change_theme) @in_gtk_thread def update_breakpoint_location(self, sal, is_set): if is_set: category = "breakpoint" else: category = "executable" [fullname, line] = sal if fullname in self.buffers: buffer = self.buffers[fullname] iter = buffer.get_iter_at_line(line - 1) buffer.remove_source_marks(iter, iter) buffer.create_source_mark(None, category, iter) @in_gdb_thread def _location_changed(self, loc, is_set): gui.startup.send_to_gtk(lambda: self.update_breakpoint_location(loc, is_set)) @in_gtk_thread def _gtk_clear_objfiles(self): empty_buffer = self.get_empty_buffer() for window in gui.toplevel.state.windows(): window.clear_source(empty_buffer) self.buffers = {} @in_gdb_thread def _clear_objfiles(self, ignore): gui.startup.send_to_gtk(self._gtk_clear_objfiles) @in_gtk_thread def clear_last_pointer(self): for key in self.buffers: buff = self.buffers[key] # This could probably be more efficient. buff.remove_source_marks( buff.get_start_iter(), buff.get_end_iter(), "pointer" ) buffer_manager = BufferManager() # Return (FRAME, SYMTAB, FILE, LINE) for the selected frame, or, if # there is no frame, for "main". @in_gdb_thread def get_current_location(): try: frame = gdb.selected_frame() sal = frame.find_sal() symtab = sal.symtab if symtab is not None: filename = symtab.fullname() else: filename = None lineno = sal.line except gdb.error: # FIXME: should use the static location as set by list etc. # No frame - try 'main'. try: frame = None sym = gdb.lookup_global_symbol("main") lineno = sym.line symtab = sym.symtab filename = symtab.fullname() except gdb.error: # Perhaps no symbol file. return (None, None, None, None) except AttributeError: return (None, None, None, None) return (frame, symtab, filename, lineno) class LRUHandler: def __init__(self): self.windows = [] self.work_location = None # What a lame name. @in_gdb_thread def show_source_gdb(self, frame, symtab, srcfile, srcline): if len(self.windows) == 0: self.work_location = (frame, symtab, srcfile, srcline) SourceWindow() gui.startup.send_to_gtk( lambda: self.show_source(frame, symtab, srcfile, srcline) ) @in_gdb_thread def new_source_window(self): self.work_location = get_current_location() SourceWindow() @in_gdb_thread def on_event(self, *args): (frame, symtab, filename, lineno) = get_current_location() if filename is not None: gui.startup.send_to_gtk( lambda: self.show_source(frame, symtab, filename, lineno) ) @in_gdb_thread def _connect_events(self): gdb.events.stop.connect(self.on_event) gui.events.frame_changed.connect(self.on_event) if hasattr(gdb.events, "new_objfile"): gdb.events.new_objfile.connect(self._new_objfile) @in_gdb_thread def _disconnect_events(self): gdb.events.stop.disconnect(self.on_event) gui.event.frame_changed.disconnect(self.on_event) @in_gtk_thread def pick_window(self, frame): # If a window is showing FRAME, use it. # Otherwise, if a window has no frame, use that. # Otherwise, use the first window. no_frame = None for w in self.windows: # Perhaps this is technically not ok. # We should document thread-safety a bit better. # Or just fix it up. if frame == w.frame: return w if w.frame is None and no_frame is None: no_frame = w if no_frame is not None: return no_frame return self.windows[0] @in_gtk_thread def show_source(self, frame, symtab, srcfile, srcline): w = self.pick_window(frame) # LRU policy. self.windows.remove(w) self.windows.append(w) w.frame = frame w.show_source(symtab, srcfile, srcline) @in_gtk_thread def remove(self, window): self.windows.remove(window) if len(self.windows) == 0: gdb.post_event(self._disconnect_events) @in_gtk_thread def add(self, window): self.windows.insert(0, window) if len(self.windows) == 1: gdb.post_event(self._connect_events) # Show something. if self.work_location is not None: (frame, symtab, filename, lineno) = self.work_location self.work_location = None gui.startup.send_to_gtk( lambda: self.show_source(frame, symtab, filename, lineno) ) @in_gdb_thread def _new_objfile(self, event): if len(gdb.objfiles()) == 1: self.on_event() lru_handler = LRUHandler() BUTTON_NAMES = ["step", "next", "continue", "finish", "stop", "up", "down"] class SourceWindow(gui.updatewindow.UpdateWindow): def _get_pixmap(self, filename): path = os.path.join(gui.self_dir, filename) return GdkPixbuf.Pixbuf.new_from_file(path) def __init__(self): super(SourceWindow, self).__init__("source") gdb.events.cont.connect(self._on_cont_event) # Update the buttons. self.on_event() @in_gtk_thread def gtk_initialize(self): self.frame = None self.do_step = Invoker("step") self.do_next = Invoker("next") self.do_continue = Invoker("continue") self.do_finish = Invoker("finish") self.do_stop = Invoker("interrupt") self.do_up = FrameCommandInvoker("up") self.do_down = FrameCommandInvoker("down") builder = gui.startup.create_builder("sourcewindow.xml") builder.connect_signals(self) self.window = builder.get_object("sourcewindow") self.view = builder.get_object("view") # Maybe there is a cleaner way? self.buttons = {} for name in BUTTON_NAMES: self.buttons[name] = builder.get_object(name) self.view.modify_font(gui.params.font_manager.get_font()) self.view.set_show_line_numbers(gui.params.line_numbers.value) self.view.set_tab_width(gui.params.tab_width.value) attrs = GtkSource.MarkAttributes() attrs.set_pixbuf(self._get_pixmap("icons/ok.png")) self.view.set_mark_attributes("executable", attrs, 0) attrs = GtkSource.MarkAttributes() attrs.set_pixbuf(self._get_pixmap("icons/breakpoint-marker.png")) self.view.set_mark_attributes("breakpoint", attrs, 1) attrs = GtkSource.MarkAttributes() attrs.set_pixbuf(self._get_pixmap("icons/line-pointer.png")) self.view.set_mark_attributes("pointer", attrs, 2) self.view.set_buffer(buffer_manager.get_empty_buffer()) lru_handler.add(self) @in_gtk_thread def _update_buttons(self, running): for button in BUTTON_NAMES: if button == "stop": self.buttons[button].set_sensitive(running) else: self.buttons[button].set_sensitive(not running) @in_gdb_thread def on_event(self): running = gui.gdbutil.is_running() gui.startup.send_to_gtk(lambda: self._update_buttons(running)) @in_gdb_thread def _on_cont_event(self, event): self.on_event() @in_gdb_thread def _disconnect_cont_event(self): gdb.events.cont.disconnect(self._on_cont_event) def deleted(self, *args): lru_handler.remove(self) gdb.post_event(self._disconnect_cont_event) super(SourceWindow, self).deleted() def line_mark_activated(self, view, textiter, event): if event.type != Gdk.EventType.BUTTON_PRESS: return if event.button.get_button()[1] != 1: return filename = self.view.get_buffer().filename line = textiter.get_line() + 1 if gui.bpcache.any_breakpoint_at(filename, line): fun = Invoker("clear %s:%d" % (filename, line)) else: fun = Invoker("break %s:%d" % (filename, line)) fun() def _do_scroll(self, buff, srcline): iter = buff.get_iter_at_line(srcline) buff.create_source_mark(None, "pointer", iter) buff.place_cursor(iter) self.view.scroll_mark_onscreen(buff.get_insert()) return False def show_source(self, symtab, srcfile, srcline): buff = buffer_manager.get_buffer(symtab, srcfile) if buff is not None: old_buffer = self.view.get_buffer() self.view.set_buffer(buff) self.fullname = srcfile self.basename = os.path.basename(srcfile) self.update_title() buffer_manager.release_buffer(old_buffer) buffer_manager.clear_last_pointer() GObject.idle_add(self._do_scroll, buff, srcline - 1) # self.view.scroll_to_iter(buff.get_iter_at_line(srcline), 0.0) @in_gtk_thread def set_font(self, pango_font): self.view.modify_font(pango_font) @in_gtk_thread def set_line_numbers(self, want_lines): self.view.set_show_line_numbers(want_lines) @in_gtk_thread def set_tab_width(self, width): self.view.set_tab_width(width) @in_gtk_thread def clear_source(self, buffer): old_buffer = self.view.get_buffer() self.view.set_buffer(buffer) buffer_manager.release_buffer(old_buffer)