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