Save plain text in the source cache
[external/binutils.git] / gdb / source-cache.c
1 /* Cache of styled source file text
2    Copyright (C) 2018-2019 Free Software Foundation, Inc.
3
4    This file is part of GDB.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 #include "defs.h"
20 #include "source-cache.h"
21 #include "gdbsupport/scoped_fd.h"
22 #include "source.h"
23 #include "cli/cli-style.h"
24 #include "symtab.h"
25 #include "gdbsupport/selftest.h"
26
27 #ifdef HAVE_SOURCE_HIGHLIGHT
28 /* If Gnulib redirects 'open' and 'close' to its replacements
29    'rpl_open' and 'rpl_close' via cpp macros, including <fstream>
30    below with those macros in effect will cause unresolved externals
31    when GDB is linked.  Happens, e.g., in the MinGW build.  */
32 #undef open
33 #undef close
34 #include <sstream>
35 #include <srchilite/sourcehighlight.h>
36 #include <srchilite/langmap.h>
37 #endif
38
39 /* The number of source files we'll cache.  */
40
41 #define MAX_ENTRIES 5
42
43 /* See source-cache.h.  */
44
45 source_cache g_source_cache;
46
47 /* See source-cache.h.  */
48
49 bool
50 source_cache::get_plain_source_lines (struct symtab *s, std::string *lines)
51 {
52   scoped_fd desc (open_source_file_with_line_charpos (s));
53   if (desc.get () < 0)
54     return false;
55
56   struct stat st;
57
58   if (fstat (desc.get (), &st) < 0)
59     perror_with_name (symtab_to_filename_for_display (s));
60
61   /* We could cache this in line_charpos... */
62   lines->resize (st.st_size);
63   if (myread (desc.get (), &(*lines)[0], lines->size ()) < 0)
64     perror_with_name (symtab_to_filename_for_display (s));
65
66   return true;
67 }
68
69 /* A helper function for get_plain_source_lines that extracts the
70    desired source lines from TEXT, putting them into LINES_OUT.  The
71    arguments are as for get_source_lines.  The return value is the
72    desired lines.  */
73 static std::string
74 extract_lines (const std::string &text, int first_line, int last_line)
75 {
76   int lineno = 1;
77   std::string::size_type pos = 0;
78   std::string::size_type first_pos = std::string::npos;
79
80   while (pos != std::string::npos && lineno <= last_line)
81     {
82       std::string::size_type new_pos = text.find ('\n', pos);
83
84       if (lineno == first_line)
85         first_pos = pos;
86
87       pos = new_pos;
88       if (lineno == last_line || pos == std::string::npos)
89         {
90           if (first_pos == std::string::npos)
91             return {};
92           if (pos == std::string::npos)
93             pos = text.size ();
94           else
95             ++pos;
96           return text.substr (first_pos, pos - first_pos);
97         }
98       ++lineno;
99       ++pos;
100     }
101
102   return {};
103 }
104
105 #ifdef HAVE_SOURCE_HIGHLIGHT
106
107 /* Return the Source Highlight language name, given a gdb language
108    LANG.  Returns NULL if the language is not known.  */
109
110 static const char *
111 get_language_name (enum language lang)
112 {
113   switch (lang)
114     {
115     case language_c:
116     case language_objc:
117       return "c.lang";
118
119     case language_cplus:
120       return "cpp.lang";
121
122     case language_d:
123       return "d.lang";
124
125     case language_go:
126       return "go.lang";
127
128     case language_fortran:
129       return "fortran.lang";
130
131     case language_m2:
132       /* Not handled by Source Highlight.  */
133       break;
134
135     case language_asm:
136       return "asm.lang";
137
138     case language_pascal:
139       return "pascal.lang";
140
141     case language_opencl:
142       /* Not handled by Source Highlight.  */
143       break;
144
145     case language_rust:
146       /* Not handled by Source Highlight.  */
147       break;
148
149     case language_ada:
150       return "ada.lang";
151
152     default:
153       break;
154     }
155
156   return nullptr;
157 }
158
159 #endif /* HAVE_SOURCE_HIGHLIGHT */
160
161 /* See source-cache.h.  */
162
163 bool
164 source_cache::get_source_lines (struct symtab *s, int first_line,
165                                 int last_line, std::string *lines)
166 {
167   if (first_line < 1 || last_line < 1 || first_line > last_line)
168     return false;
169
170   std::string fullname = symtab_to_fullname (s);
171
172   for (const auto &item : m_source_map)
173     {
174       if (item.fullname == fullname)
175         {
176           *lines = extract_lines (item.contents, first_line, last_line);
177           return true;
178         }
179     }
180
181   std::string contents;
182   if (!get_plain_source_lines (s, &contents))
183     return false;
184
185 #ifdef HAVE_SOURCE_HIGHLIGHT
186   if (source_styling && gdb_stdout->can_emit_style_escape ())
187     {
188       const char *lang_name = get_language_name (SYMTAB_LANGUAGE (s));
189       if (lang_name != nullptr)
190         {
191           /* The global source highlight object, or null if one was
192              never constructed.  This is stored here rather than in
193              the class so that we don't need to include anything or do
194              conditional compilation in source-cache.h.  */
195           static srchilite::SourceHighlight *highlighter;
196
197           if (highlighter == nullptr)
198             {
199               highlighter = new srchilite::SourceHighlight ("esc.outlang");
200               highlighter->setStyleFile ("esc.style");
201             }
202
203           std::istringstream input (contents);
204           std::ostringstream output;
205           highlighter->highlight (input, output, lang_name, fullname);
206
207           contents = output.str ();
208         }
209     }
210 #endif /* HAVE_SOURCE_HIGHLIGHT */
211
212   source_text result = { std::move (fullname), std::move (contents) };
213   m_source_map.push_back (std::move (result));
214
215   if (m_source_map.size () > MAX_ENTRIES)
216     m_source_map.erase (m_source_map.begin ());
217
218   *lines = extract_lines (m_source_map.back ().contents,
219                           first_line, last_line);
220   return true;
221 }
222
223 #if GDB_SELF_TEST
224 namespace selftests
225 {
226 static void extract_lines_test ()
227 {
228   std::string input_text = "abc\ndef\nghi\njkl\n";
229
230   SELF_CHECK (extract_lines (input_text, 1, 1) == "abc\n");
231   SELF_CHECK (extract_lines (input_text, 2, 1) == "");
232   SELF_CHECK (extract_lines (input_text, 1, 2) == "abc\ndef\n");
233   SELF_CHECK (extract_lines ("abc", 1, 1) == "abc");
234 }
235 }
236 #endif
237
238 void
239 _initialize_source_cache ()
240 {
241 #if GDB_SELF_TEST
242   selftests::register_test ("source-cache", selftests::extract_lines_test);
243 #endif
244 }