xref: /openbsd-src/gnu/llvm/lldb/utils/lui/sourcewin.py (revision 061da546b983eb767bad15e67af1174fb0bcf31c)
1*061da546Spatrick##===-- sourcewin.py -----------------------------------------*- Python -*-===##
2*061da546Spatrick##
3*061da546Spatrick# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*061da546Spatrick# See https://llvm.org/LICENSE.txt for license information.
5*061da546Spatrick# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*061da546Spatrick##
7*061da546Spatrick##===----------------------------------------------------------------------===##
8*061da546Spatrick
9*061da546Spatrickimport cui
10*061da546Spatrickimport curses
11*061da546Spatrickimport lldb
12*061da546Spatrickimport lldbutil
13*061da546Spatrickimport re
14*061da546Spatrickimport os
15*061da546Spatrick
16*061da546Spatrick
17*061da546Spatrickclass SourceWin(cui.TitledWin):
18*061da546Spatrick
19*061da546Spatrick    def __init__(self, driver, x, y, w, h):
20*061da546Spatrick        super(SourceWin, self).__init__(x, y, w, h, "Source")
21*061da546Spatrick        self.sourceman = driver.getSourceManager()
22*061da546Spatrick        self.sources = {}
23*061da546Spatrick
24*061da546Spatrick        self.filename = None
25*061da546Spatrick        self.pc_line = None
26*061da546Spatrick        self.viewline = 0
27*061da546Spatrick
28*061da546Spatrick        self.breakpoints = {}
29*061da546Spatrick
30*061da546Spatrick        self.win.scrollok(1)
31*061da546Spatrick
32*061da546Spatrick        self.markerPC = ":) "
33*061da546Spatrick        self.markerBP = "B> "
34*061da546Spatrick        self.markerNone = "   "
35*061da546Spatrick
36*061da546Spatrick        try:
37*061da546Spatrick            from pygments.formatters import TerminalFormatter
38*061da546Spatrick            self.formatter = TerminalFormatter()
39*061da546Spatrick        except ImportError:
40*061da546Spatrick            #self.win.addstr("\nWarning: no 'pygments' library found. Syntax highlighting is disabled.")
41*061da546Spatrick            self.lexer = None
42*061da546Spatrick            self.formatter = None
43*061da546Spatrick            pass
44*061da546Spatrick
45*061da546Spatrick        # FIXME: syntax highlight broken
46*061da546Spatrick        self.formatter = None
47*061da546Spatrick        self.lexer = None
48*061da546Spatrick
49*061da546Spatrick    def handleEvent(self, event):
50*061da546Spatrick        if isinstance(event, int):
51*061da546Spatrick            self.handleKey(event)
52*061da546Spatrick            return
53*061da546Spatrick
54*061da546Spatrick        if isinstance(event, lldb.SBEvent):
55*061da546Spatrick            if lldb.SBBreakpoint.EventIsBreakpointEvent(event):
56*061da546Spatrick                self.handleBPEvent(event)
57*061da546Spatrick
58*061da546Spatrick            if lldb.SBProcess.EventIsProcessEvent(event) and \
59*061da546Spatrick                    not lldb.SBProcess.GetRestartedFromEvent(event):
60*061da546Spatrick                process = lldb.SBProcess.GetProcessFromEvent(event)
61*061da546Spatrick                if not process.IsValid():
62*061da546Spatrick                    return
63*061da546Spatrick                if process.GetState() == lldb.eStateStopped:
64*061da546Spatrick                    self.refreshSource(process)
65*061da546Spatrick                elif process.GetState() == lldb.eStateExited:
66*061da546Spatrick                    self.notifyExited(process)
67*061da546Spatrick
68*061da546Spatrick    def notifyExited(self, process):
69*061da546Spatrick        self.win.erase()
70*061da546Spatrick        target = lldbutil.get_description(process.GetTarget())
71*061da546Spatrick        pid = process.GetProcessID()
72*061da546Spatrick        ec = process.GetExitStatus()
73*061da546Spatrick        self.win.addstr(
74*061da546Spatrick            "\nProcess %s [%d] has exited with exit-code %d" %
75*061da546Spatrick            (target, pid, ec))
76*061da546Spatrick
77*061da546Spatrick    def pageUp(self):
78*061da546Spatrick        if self.viewline > 0:
79*061da546Spatrick            self.viewline = self.viewline - 1
80*061da546Spatrick            self.refreshSource()
81*061da546Spatrick
82*061da546Spatrick    def pageDown(self):
83*061da546Spatrick        if self.viewline < len(self.content) - self.height + 1:
84*061da546Spatrick            self.viewline = self.viewline + 1
85*061da546Spatrick            self.refreshSource()
86*061da546Spatrick        pass
87*061da546Spatrick
88*061da546Spatrick    def handleKey(self, key):
89*061da546Spatrick        if key == curses.KEY_DOWN:
90*061da546Spatrick            self.pageDown()
91*061da546Spatrick        elif key == curses.KEY_UP:
92*061da546Spatrick            self.pageUp()
93*061da546Spatrick
94*061da546Spatrick    def updateViewline(self):
95*061da546Spatrick        half = self.height / 2
96*061da546Spatrick        if self.pc_line < half:
97*061da546Spatrick            self.viewline = 0
98*061da546Spatrick        else:
99*061da546Spatrick            self.viewline = self.pc_line - half + 1
100*061da546Spatrick
101*061da546Spatrick        if self.viewline < 0:
102*061da546Spatrick            raise Exception(
103*061da546Spatrick                "negative viewline: pc=%d viewline=%d" %
104*061da546Spatrick                (self.pc_line, self.viewline))
105*061da546Spatrick
106*061da546Spatrick    def refreshSource(self, process=None):
107*061da546Spatrick        (self.height, self.width) = self.win.getmaxyx()
108*061da546Spatrick
109*061da546Spatrick        if process is not None:
110*061da546Spatrick            loc = process.GetSelectedThread().GetSelectedFrame().GetLineEntry()
111*061da546Spatrick            f = loc.GetFileSpec()
112*061da546Spatrick            self.pc_line = loc.GetLine()
113*061da546Spatrick
114*061da546Spatrick            if not f.IsValid():
115*061da546Spatrick                self.win.addstr(0, 0, "Invalid source file")
116*061da546Spatrick                return
117*061da546Spatrick
118*061da546Spatrick            self.filename = f.GetFilename()
119*061da546Spatrick            path = os.path.join(f.GetDirectory(), self.filename)
120*061da546Spatrick            self.setTitle(path)
121*061da546Spatrick            self.content = self.getContent(path)
122*061da546Spatrick            self.updateViewline()
123*061da546Spatrick
124*061da546Spatrick        if self.filename is None:
125*061da546Spatrick            return
126*061da546Spatrick
127*061da546Spatrick        if self.formatter is not None:
128*061da546Spatrick            from pygments.lexers import get_lexer_for_filename
129*061da546Spatrick            self.lexer = get_lexer_for_filename(self.filename)
130*061da546Spatrick
131*061da546Spatrick        bps = [] if not self.filename in self.breakpoints else self.breakpoints[self.filename]
132*061da546Spatrick        self.win.erase()
133*061da546Spatrick        if self.content:
134*061da546Spatrick            self.formatContent(self.content, self.pc_line, bps)
135*061da546Spatrick
136*061da546Spatrick    def getContent(self, path):
137*061da546Spatrick        content = []
138*061da546Spatrick        if path in self.sources:
139*061da546Spatrick            content = self.sources[path]
140*061da546Spatrick        else:
141*061da546Spatrick            if os.path.exists(path):
142*061da546Spatrick                with open(path) as x:
143*061da546Spatrick                    content = x.readlines()
144*061da546Spatrick                self.sources[path] = content
145*061da546Spatrick        return content
146*061da546Spatrick
147*061da546Spatrick    def formatContent(self, content, pc_line, breakpoints):
148*061da546Spatrick        source = ""
149*061da546Spatrick        count = 1
150*061da546Spatrick        self.win.erase()
151*061da546Spatrick        end = min(len(content), self.viewline + self.height)
152*061da546Spatrick        for i in range(self.viewline, end):
153*061da546Spatrick            line_num = i + 1
154*061da546Spatrick            marker = self.markerNone
155*061da546Spatrick            attr = curses.A_NORMAL
156*061da546Spatrick            if line_num == pc_line:
157*061da546Spatrick                attr = curses.A_REVERSE
158*061da546Spatrick            if line_num in breakpoints:
159*061da546Spatrick                marker = self.markerBP
160*061da546Spatrick            line = "%s%3d %s" % (marker, line_num, self.highlight(content[i]))
161*061da546Spatrick            if len(line) >= self.width:
162*061da546Spatrick                line = line[0:self.width - 1] + "\n"
163*061da546Spatrick            self.win.addstr(line, attr)
164*061da546Spatrick            source += line
165*061da546Spatrick            count = count + 1
166*061da546Spatrick        return source
167*061da546Spatrick
168*061da546Spatrick    def highlight(self, source):
169*061da546Spatrick        if self.lexer and self.formatter:
170*061da546Spatrick            from pygments import highlight
171*061da546Spatrick            return highlight(source, self.lexer, self.formatter)
172*061da546Spatrick        else:
173*061da546Spatrick            return source
174*061da546Spatrick
175*061da546Spatrick    def addBPLocations(self, locations):
176*061da546Spatrick        for path in locations:
177*061da546Spatrick            lines = locations[path]
178*061da546Spatrick            if path in self.breakpoints:
179*061da546Spatrick                self.breakpoints[path].update(lines)
180*061da546Spatrick            else:
181*061da546Spatrick                self.breakpoints[path] = lines
182*061da546Spatrick
183*061da546Spatrick    def removeBPLocations(self, locations):
184*061da546Spatrick        for path in locations:
185*061da546Spatrick            lines = locations[path]
186*061da546Spatrick            if path in self.breakpoints:
187*061da546Spatrick                self.breakpoints[path].difference_update(lines)
188*061da546Spatrick            else:
189*061da546Spatrick                raise "Removing locations that were never added...no good"
190*061da546Spatrick
191*061da546Spatrick    def handleBPEvent(self, event):
192*061da546Spatrick        def getLocations(event):
193*061da546Spatrick            locs = {}
194*061da546Spatrick
195*061da546Spatrick            bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
196*061da546Spatrick
197*061da546Spatrick            if bp.IsInternal():
198*061da546Spatrick                # don't show anything for internal breakpoints
199*061da546Spatrick                return
200*061da546Spatrick
201*061da546Spatrick            for location in bp:
202*061da546Spatrick                # hack! getting the LineEntry via SBBreakpointLocation.GetAddress.GetLineEntry does not work good for
203*061da546Spatrick                # inlined frames, so we get the description (which does take
204*061da546Spatrick                # into account inlined functions) and parse it.
205*061da546Spatrick                desc = lldbutil.get_description(
206*061da546Spatrick                    location, lldb.eDescriptionLevelFull)
207*061da546Spatrick                match = re.search('at\ ([^:]+):([\d]+)', desc)
208*061da546Spatrick                try:
209*061da546Spatrick                    path = match.group(1)
210*061da546Spatrick                    line = int(match.group(2).strip())
211*061da546Spatrick                except ValueError as e:
212*061da546Spatrick                    # bp loc unparsable
213*061da546Spatrick                    continue
214*061da546Spatrick
215*061da546Spatrick                if path in locs:
216*061da546Spatrick                    locs[path].add(line)
217*061da546Spatrick                else:
218*061da546Spatrick                    locs[path] = set([line])
219*061da546Spatrick            return locs
220*061da546Spatrick
221*061da546Spatrick        event_type = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event)
222*061da546Spatrick        if event_type == lldb.eBreakpointEventTypeEnabled \
223*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeAdded \
224*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeLocationsResolved \
225*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeLocationsAdded:
226*061da546Spatrick            self.addBPLocations(getLocations(event))
227*061da546Spatrick        elif event_type == lldb.eBreakpointEventTypeRemoved \
228*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeLocationsRemoved \
229*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeDisabled:
230*061da546Spatrick            self.removeBPLocations(getLocations(event))
231*061da546Spatrick        elif event_type == lldb.eBreakpointEventTypeCommandChanged \
232*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeConditionChanged \
233*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeIgnoreChanged \
234*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeThreadChanged \
235*061da546Spatrick                or event_type == lldb.eBreakpointEventTypeInvalidType:
236*061da546Spatrick            # no-op
237*061da546Spatrick            pass
238*061da546Spatrick        self.refreshSource()
239