source: git/src/cavernlog.cc @ 40b02e8

line_contentsstereotravis-osx
Last change on this file since 40b02e8 was 40b02e8, checked in by Olly Betts <olly@…>, 5 years ago

src/cavernlog.cc: Highlight "error" marker in red and "warning"
marker in orange.

  • Property mode set to 100644
File size: 12.2 KB
Line 
1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
4 * Copyright (C) 2005,2006,2010,2011,2012,2014,2015 Olly Betts
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 2 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, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 */
20
21#ifdef HAVE_CONFIG_H
22# include <config.h>
23#endif
24
25#include "aven.h"
26#include "cavernlog.h"
27#include "filename.h"
28#include "mainfrm.h"
29#include "message.h"
30
31#include <errno.h>
32#include <stdio.h>
33#include <stdlib.h>
34
35// For select():
36#ifdef HAVE_SYS_SELECT_H
37#include <sys/select.h>
38#endif
39#include <sys/time.h>
40#include <sys/types.h>
41#include <unistd.h>
42
43enum { LOG_REPROCESS = 1234 };
44
45BEGIN_EVENT_TABLE(CavernLogWindow, wxHtmlWindow)
46    EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
47    EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
48END_EVENT_TABLE()
49
50static wxString escape_for_shell(wxString s, bool protect_dash = false)
51{
52    size_t p = 0;
53#ifdef __WXMSW__
54    bool needs_quotes = false;
55    while (p < s.size()) {
56        wxChar ch = s[p];
57        if (ch < 127) {
58            if (ch == wxT('"')) {
59                s.insert(p, 1, wxT('\\'));
60                ++p;
61                needs_quotes = true;
62            } else if (strchr(" <>&|^", ch)) {
63                needs_quotes = true;
64            }
65        }
66        ++p;
67    }
68    if (needs_quotes) {
69        s.insert(0u, 1, wxT('"'));
70        s += wxT('"');
71    }
72#else
73    if (protect_dash && !s.empty() && s[0u] == '-') {
74        // If the filename starts with a '-', protect it from being
75        // treated as an option by prepending "./".
76        s.insert(0, wxT("./"));
77        p = 2;
78    }
79    while (p < s.size()) {
80        // Exclude a few safe characters which are common in filenames
81        if (!isalnum(s[p]) && strchr("/._-", s[p]) == NULL) {
82            s.insert(p, 1, wxT('\\'));
83            ++p;
84        }
85        ++p;
86    }
87#endif
88    return s;
89}
90
91CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, wxWindow * parent)
92    : wxHtmlWindow(parent), mainfrm(mainfrm_)
93{
94    int fsize = parent->GetFont().GetPointSize();
95    int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
96    SetFonts(wxString(), wxString(), sizes);
97}
98
99void
100CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
101{
102    wxString href = link.GetHref();
103    wxString title = link.GetTarget();
104    size_t colon = href.rfind(wxT(':'));
105    if (colon == wxString::npos)
106        return;
107    size_t colon2 = href.rfind(wxT(':'), colon - 1);
108    if (colon2 != wxString::npos) swap(colon, colon2);
109#ifdef __WXMSW__
110    wxString cmd = wxT("notepad $f");
111#elif defined __WXMAC__
112    wxString cmd = wxT("open -t $f");
113#else
114    wxString cmd = wxT("x-terminal-emulator -title $t -e vim +'call cursor($l,$c)' $f");
115    // wxString cmd = wxT("gedit -b $f +$l:$c $f");
116    // wxString cmd = wxT("x-terminal-emulator -title $t -e emacs +$l $f");
117    // wxString cmd = wxT("x-terminal-emulator -title $t -e nano +$l $f");
118    // wxString cmd = wxT("x-terminal-emulator -title $t -e jed -g $l $f");
119#endif
120    const char * p = getenv("SURVEXEDITOR");
121    if (p) {
122        cmd = wxString(p, wxConvUTF8);
123        if (!cmd.find(wxT("$f"))) {
124            cmd += wxT(" $f");
125        }
126    }
127    size_t i = 0;
128    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
129        if (++i >= cmd.size()) break;
130        switch ((int)cmd[i]) {
131            case wxT('$'):
132                cmd.erase(i, 1);
133                break;
134            case wxT('f'): {
135                wxString f = escape_for_shell(href.substr(0, colon), true);
136                cmd.replace(i - 1, 2, f);
137                i += f.size() - 1;
138                break;
139            }
140            case wxT('t'): {
141                wxString t = escape_for_shell(title);
142                cmd.replace(i - 1, 2, t);
143                i += t.size() - 1;
144                break;
145            }
146            case wxT('l'): {
147                wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
148                cmd.replace(i - 1, 2, l);
149                i += l.size() - 1;
150                break;
151            }
152            case wxT('c'): {
153                wxString l;
154                if (colon2 == wxString::npos)
155                    l = wxT("0");
156                else
157                    l = escape_for_shell(href.substr(colon2 + 1));
158                cmd.replace(i - 1, 2, l);
159                i += l.size() - 1;
160                break;
161            }
162            default:
163                ++i;
164        }
165    }
166#ifdef __WXMSW__
167    if (_wsystem(cmd.c_str()) >= 0)
168        return;
169#else
170    if (system(cmd.mb_str()) >= 0)
171        return;
172#endif
173    wxString m;
174    // TRANSLATORS: %s is replaced by the command we attempted to run.
175    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
176    m += wxT(" (");
177    m += wxString(strerror(errno), wxConvUTF8);
178    m += wxT(')');
179    wxGetApp().ReportError(m);
180}
181
182int
183CavernLogWindow::process(const wxString &file)
184{
185    SetFocus();
186    filename = file;
187
188#ifdef __WXMSW__
189    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
190#else
191    setenv("SURVEX_UTF8", "1", 1);
192#endif
193
194    wxString escaped_file = escape_for_shell(file, true);
195#ifdef __WXMSW__
196    wxString cmd;
197    {
198        DWORD len = 256;
199        wchar_t *buf = NULL;
200        while (1) {
201            DWORD got;
202            buf = (wchar_t*)osrealloc(buf, len * 2);
203            got = GetModuleFileNameW(NULL, buf, len);
204            if (got < len) break;
205            len += len;
206        }
207        /* Strange Win32 nastiness - strip prefix "\\?\" if present */
208        if (wcsncmp(buf, L"\\\\?\\", 4) == 0) buf += 4;
209        wchar_t * slash = wcsrchr(buf, L'\\');
210        if (slash) {
211            cmd.assign(buf, slash - buf + 1);
212        }
213    }
214    cmd += L"cavern";
215    cmd = escape_for_shell(cmd, false);
216#else
217    char *cavern = use_path(msg_exepth(), "cavern");
218    wxString cmd = escape_for_shell(wxString(cavern, wxConvUTF8), false);
219    osfree(cavern);
220#endif
221    cmd += wxT(" -o ");
222    cmd += escaped_file;
223    cmd += wxT(' ');
224    cmd += escaped_file;
225
226#ifdef __WXMSW__
227    FILE * cavern_out = _wpopen(cmd.c_str(), L"r");
228#else
229    FILE * cavern_out = popen(cmd.mb_str(), "r");
230#endif
231    if (!cavern_out) {
232        wxString m;
233        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
234        m += wxT(" (");
235        m += wxString(strerror(errno), wxConvUTF8);
236        m += wxT(')');
237        wxGetApp().ReportError(m);
238        return -2;
239    }
240
241    const wxString & error_marker = wmsg(/*error*/93);
242    const wxString & warning_marker = wmsg(/*warning*/4);
243
244    int cavern_fd;
245#ifdef __WXMSW__
246    cavern_fd = _fileno(cavern_out);
247#else
248    cavern_fd = fileno(cavern_out);
249#endif
250    assert(cavern_fd < FD_SETSIZE); // FIXME we shouldn't just assert, but what else to do?
251    wxString cur;
252    int link_count = 0;
253    unsigned char buf[1024];
254    unsigned char * end = buf;
255    while (true) {
256        fd_set rfds, efds;
257        FD_ZERO(&rfds);
258        FD_SET(cavern_fd, &rfds);
259        FD_ZERO(&efds);
260        FD_SET(cavern_fd, &efds);
261        // Timeout instantly the first time, and call Update() so the window
262        // contents get updated.  If we're still waiting for output after that,
263        // wait for output for 0.1 seconds, then call wxYield() so that the UI
264        // doesn't block.
265        struct timeval timeout;
266        timeout.tv_sec = 0;
267        timeout.tv_usec = 0;
268        bool first = true;
269        while (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
270            if (first) {
271                first = false;
272                Update();
273            } else {
274                wxYield();
275            }
276            FD_SET(cavern_fd, &rfds);
277            FD_SET(cavern_fd, &efds);
278            // Set timeout to 0.1 seconds.
279            timeout.tv_sec = 0;
280            timeout.tv_usec = 100000;
281        }
282        if (!FD_ISSET(cavern_fd, &rfds)) {
283            // Error, which pclose() should report.
284            break;
285        }
286
287        ssize_t r = read(cavern_fd, end, sizeof(buf) - (end - buf));
288        if (r <= 0) {
289            if (r == 0 && buf != end) {
290                // FIXME: Truncated UTF-8 sequence.
291            }
292            break;
293        }
294        end += r;
295
296        const unsigned char * p = buf;
297
298        while (p != end) {
299            int ch = *p++;
300            if (ch >= 0x80) {
301                // Decode multi-byte UTF-8 sequence.
302                if (ch < 0xc0) {
303                    // FIXME: Invalid UTF-8 sequence.
304                    goto abort;
305                } else if (ch < 0xe0) {
306                    /* 2 byte sequence */
307                    if (p == end) {
308                        // Incomplete UTF-8 sequence - try to read more.
309                        break;
310                    }
311                    int ch1 = *p++;
312                    if ((ch1 & 0xc0) != 0x80) {
313                        // FIXME: Invalid UTF-8 sequence.
314                        goto abort;
315                    }
316                    ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
317                } else if (ch < 0xf0) {
318                    /* 3 byte sequence */
319                    if (end - p <= 1) {
320                        // Incomplete UTF-8 sequence - try to read more.
321                        break;
322                    }
323                    int ch1 = *p++;
324                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6);
325                    if ((ch1 & 0xc0) != 0x80) {
326                        // FIXME: Invalid UTF-8 sequence.
327                        goto abort;
328                    }
329                    int ch2 = *p++;
330                    if ((ch2 & 0xc0) != 0x80) {
331                        // FIXME: Invalid UTF-8 sequence.
332                        goto abort;
333                    }
334                    ch |= (ch2 & 0x3f);
335                } else {
336                    // FIXME: Overlong UTF-8 sequence.
337                    goto abort;
338                }
339            }
340
341            switch (ch) {
342                case '\r':
343                    // Ignore.
344                    break;
345                case '\n': {
346                    if (cur.empty()) continue;
347#ifndef __WXMSW__
348                    size_t colon = cur.find(':');
349#else
350                    // If the path is "C:\path\to\file.svx" then don't split at the
351                    // : after the drive letter!  FIXME: better to look for ": "?
352                    size_t colon = cur.find(':', 2);
353#endif
354                    if (colon != wxString::npos && colon < cur.size() - 2) {
355                        ++colon;
356                        size_t i = colon;
357                        while (i < cur.size() - 2 &&
358                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
359                            ++i;
360                        }
361                        if (i > colon && cur[i] == wxT(':') ) {
362                            colon = i;
363                            // Check for column number.
364                            while (++i < cur.size() - 2 &&
365                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
366                            if (i > colon + 1 && cur[i] == wxT(':') ) {
367                                colon = i;
368                            }
369                            wxString tag = wxT("<a href=\"");
370                            tag.append(cur, 0, colon);
371                            while (cur[++i] == wxT(' ')) { }
372                            tag += wxT("\" target=\"");
373                            tag.append(cur, i, wxString::npos);
374                            tag += wxT("\">");
375                            cur.insert(0, tag);
376                            size_t offset = colon + tag.size();
377                            cur.insert(offset, wxT("</a>"));
378                            offset += 4 + 2;
379                            if (cur.substr(offset, error_marker.size()) == error_marker) {
380                                // Show "error" marker in red.
381                                cur.insert(offset, wxT("<span style=\"color:red\">"));
382                                offset += 24 + error_marker.size();
383                                cur.insert(offset, wxT("</span>"));
384                            } else if (cur.substr(offset, warning_marker.size()) == warning_marker) {
385                                // Show "warning" marker in orange.
386                                cur.insert(offset, wxT("<span style=\"color:orange\">"));
387                                offset += 27 + warning_marker.size();
388                                cur.insert(offset, wxT("</span>"));
389                            }
390
391                            ++link_count;
392                        }
393                    }
394
395                    // Save the scrollbar positions.
396                    int scroll_x = 0, scroll_y = 0;
397                    GetViewStart(&scroll_x, &scroll_y);
398
399                    cur += wxT("<br>\n");
400                    AppendToPage(cur);
401
402                    if (!link_count) {
403                        // Auto-scroll the window until we've reported a warning or
404                        // error.
405                        int x, y;
406                        GetVirtualSize(&x, &y);
407                        int xs, ys;
408                        GetClientSize(&xs, &ys);
409                        y -= ys;
410                        int xu, yu;
411                        GetScrollPixelsPerUnit(&xu, &yu);
412                        Scroll(scroll_x, y / yu);
413                    } else {
414                        // Restore the scrollbar positions.
415                        Scroll(scroll_x, scroll_y);
416                    }
417
418                    cur.clear();
419                    break;
420                }
421                case '<':
422                    cur += wxT("&lt;");
423                    break;
424                case '>':
425                    cur += wxT("&gt;");
426                    break;
427                case '&':
428                    cur += wxT("&amp;");
429                    break;
430                case '"':
431                    cur += wxT("&#22;");
432                    continue;
433                default:
434                    if (ch >= 128) {
435                        cur += wxString::Format(wxT("&#%u;"), ch);
436                    } else {
437                        cur += (char)ch;
438                    }
439            }
440        }
441
442        size_t left = end - p;
443        end = buf + left;
444        if (left) memmove(buf, p, left);
445    }
446abort:
447
448    int retval = pclose(cavern_out);
449    if (retval) {
450        /* TRANSLATORS: Label for button in aven’s cavern log window which
451         * causes the survey data to be reprocessed. */
452        AppendToPage(wxString::Format(wxT("<avenbutton default id=1234 name=\"%s\">"),
453                                      wmsg(/*Reprocess*/184).c_str()));
454        if (retval == -1) {
455            wxString m = wxT("Problem running cavern: ");
456            m += wxString(strerror(errno), wxConvUTF8);
457            wxGetApp().ReportError(m);
458            return -2;
459        }
460        return -1;
461    }
462    if (link_count) {
463        AppendToPage(wxString::Format(wxT("<avenbutton id=1234 name=\"%s\">"),
464                                      wmsg(/*Reprocess*/184).c_str()));
465        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
466        Update();
467    }
468    return link_count;
469}
470
471void
472CavernLogWindow::OnReprocess(wxCommandEvent & e)
473{
474    SetPage(wxString());
475    if (process(filename) == 0) {
476        OnOK(e);
477    }
478}
479
480void
481CavernLogWindow::OnOK(wxCommandEvent &)
482{
483    mainfrm->InitialiseAfterLoad(filename);
484}
Note: See TracBrowser for help on using the repository browser.