to reflect the new license. We understand that people may be surprised that we're moving the header entirely to discuss the new license. We checked this carefully with the Foundation's lawyer and we believe this is the correct approach. Essentially, all code in the project is now made available by the LLVM project under our new license, so you will see that the license headers include that license only. Some of our contributors have contributed code under our old license, and accordingly, we have retained a copy of our old license notice in the top-level files in each project and repository. llvm-svn: 351636
239 lines
8.2 KiB
Python
239 lines
8.2 KiB
Python
##===-- 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()
|