##===-- sourcewin.py -----------------------------------------*- Python -*-===## ## # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## ##===----------------------------------------------------------------------===## import cui import curses import lldb import lldbutil import re import os class SourceWin(cui.TitledWin): def __init__(self, driver, x, y, w, h): super(SourceWin, self).__init__(x, y, w, h, "Source") self.sourceman = driver.getSourceManager() self.sources = {} self.filename = None self.pc_line = None self.viewline = 0 self.breakpoints = {} self.win.scrollok(1) self.markerPC = ":) " self.markerBP = "B> " self.markerNone = " " try: from pygments.formatters import TerminalFormatter self.formatter = TerminalFormatter() except ImportError: #self.win.addstr("\nWarning: no 'pygments' library found. Syntax highlighting is disabled.") self.lexer = None self.formatter = None pass # FIXME: syntax highlight broken self.formatter = None self.lexer = None def handleEvent(self, event): if isinstance(event, int): self.handleKey(event) return if isinstance(event, lldb.SBEvent): if lldb.SBBreakpoint.EventIsBreakpointEvent(event): self.handleBPEvent(event) if lldb.SBProcess.EventIsProcessEvent(event) and \ not lldb.SBProcess.GetRestartedFromEvent(event): process = lldb.SBProcess.GetProcessFromEvent(event) if not process.IsValid(): return if process.GetState() == lldb.eStateStopped: self.refreshSource(process) elif process.GetState() == lldb.eStateExited: self.notifyExited(process) def notifyExited(self, process): self.win.erase() target = lldbutil.get_description(process.GetTarget()) pid = process.GetProcessID() ec = process.GetExitStatus() self.win.addstr( "\nProcess %s [%d] has exited with exit-code %d" % (target, pid, ec)) def pageUp(self): if self.viewline > 0: self.viewline = self.viewline - 1 self.refreshSource() def pageDown(self): if self.viewline < len(self.content) - self.height + 1: self.viewline = self.viewline + 1 self.refreshSource() pass def handleKey(self, key): if key == curses.KEY_DOWN: self.pageDown() elif key == curses.KEY_UP: self.pageUp() def updateViewline(self): half = self.height / 2 if self.pc_line < half: self.viewline = 0 else: self.viewline = self.pc_line - half + 1 if self.viewline < 0: raise Exception( "negative viewline: pc=%d viewline=%d" % (self.pc_line, self.viewline)) def refreshSource(self, process=None): (self.height, self.width) = self.win.getmaxyx() if process is not None: loc = process.GetSelectedThread().GetSelectedFrame().GetLineEntry() f = loc.GetFileSpec() self.pc_line = loc.GetLine() if not f.IsValid(): self.win.addstr(0, 0, "Invalid source file") return self.filename = f.GetFilename() path = os.path.join(f.GetDirectory(), self.filename) self.setTitle(path) self.content = self.getContent(path) self.updateViewline() if self.filename is None: return if self.formatter is not None: from pygments.lexers import get_lexer_for_filename self.lexer = get_lexer_for_filename(self.filename) bps = [] if not self.filename in self.breakpoints else self.breakpoints[self.filename] self.win.erase() if self.content: self.formatContent(self.content, self.pc_line, bps) def getContent(self, path): content = [] if path in self.sources: content = self.sources[path] else: if os.path.exists(path): with open(path) as x: content = x.readlines() self.sources[path] = content return content def formatContent(self, content, pc_line, breakpoints): source = "" count = 1 self.win.erase() end = min(len(content), self.viewline + self.height) for i in range(self.viewline, end): line_num = i + 1 marker = self.markerNone attr = curses.A_NORMAL if line_num == pc_line: attr = curses.A_REVERSE if line_num in breakpoints: marker = self.markerBP line = "%s%3d %s" % (marker, line_num, self.highlight(content[i])) if len(line) >= self.width: line = line[0:self.width - 1] + "\n" self.win.addstr(line, attr) source += line count = count + 1 return source def highlight(self, source): if self.lexer and self.formatter: from pygments import highlight return highlight(source, self.lexer, self.formatter) else: return source def addBPLocations(self, locations): for path in locations: lines = locations[path] if path in self.breakpoints: self.breakpoints[path].update(lines) else: self.breakpoints[path] = lines def removeBPLocations(self, locations): for path in locations: lines = locations[path] if path in self.breakpoints: self.breakpoints[path].difference_update(lines) else: raise "Removing locations that were never added...no good" def handleBPEvent(self, event): def getLocations(event): locs = {} bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event) if bp.IsInternal(): # don't show anything for internal breakpoints return for location in bp: # hack! getting the LineEntry via SBBreakpointLocation.GetAddress.GetLineEntry does not work good for # inlined frames, so we get the description (which does take # into account inlined functions) and parse it. desc = lldbutil.get_description( location, lldb.eDescriptionLevelFull) match = re.search('at\ ([^:]+):([\d]+)', desc) try: path = match.group(1) line = int(match.group(2).strip()) except ValueError as e: # bp loc unparsable continue if path in locs: locs[path].add(line) else: locs[path] = set([line]) return locs event_type = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) if event_type == lldb.eBreakpointEventTypeEnabled \ or event_type == lldb.eBreakpointEventTypeAdded \ or event_type == lldb.eBreakpointEventTypeLocationsResolved \ or event_type == lldb.eBreakpointEventTypeLocationsAdded: self.addBPLocations(getLocations(event)) elif event_type == lldb.eBreakpointEventTypeRemoved \ or event_type == lldb.eBreakpointEventTypeLocationsRemoved \ or event_type == lldb.eBreakpointEventTypeDisabled: self.removeBPLocations(getLocations(event)) elif event_type == lldb.eBreakpointEventTypeCommandChanged \ or event_type == lldb.eBreakpointEventTypeConditionChanged \ or event_type == lldb.eBreakpointEventTypeIgnoreChanged \ or event_type == lldb.eBreakpointEventTypeThreadChanged \ or event_type == lldb.eBreakpointEventTypeInvalidType: # no-op pass self.refreshSource()