source: git/src/cavernlog.cc @ 7e7433c

RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since 7e7433c was 7ffb1c5, checked in by Olly Betts <olly@…>, 5 years ago

Ignore error location column beyond end of line

Previously we'd crash if cavern incorrectly reported such a column.

  • Property mode set to 100644
File size: 21.8 KB
RevLine 
[6bec10c]1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
[2a00719]4 * Copyright (C) 2005,2006,2010,2011,2012,2014,2015,2016 Olly Betts
[6bec10c]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"
[fb5887c]28#include "mainfrm.h"
[6bec10c]29#include "message.h"
30
[baeae66]31#include <errno.h>
[6bec10c]32#include <stdio.h>
[a90632c]33#include <stdlib.h>
[6bec10c]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
[15ba0b5]43#include <wx/process.h>
44
[cc2d7ad]45#define GVIM_COMMAND "gvim +'call cursor($l,$c)' $f"
46#define VIM_COMMAND "x-terminal-emulator -e vim +'call cursor($l,$c)' $f"
[81d94a4]47#define NVIM_COMMAND "x-terminal-emulator -e nvim +'call cursor($l,$c)' $f"
[cc2d7ad]48#define GEDIT_COMMAND "gedit $f +$l:$c"
49// Pluma currently ignores the column, but include it assuming some future
50// version will add support.
51#define PLUMA_COMMAND "pluma +$l:$c $f"
52#define EMACS_COMMAND "x-terminal-emulator -e emacs +$l:$c $f"
53#define NANO_COMMAND "x-terminal-emulator -e nano +$l,$c $f"
54#define JED_COMMAND "x-terminal-emulator -e jed $f -g $l"
55#define KATE_COMMAND "kate -l $l -c $c $f"
56
57#ifdef __WXMSW__
58# define DEFAULT_EDITOR_COMMAND "notepad $f"
59#elif defined __WXMAC__
60# define DEFAULT_EDITOR_COMMAND "open -t $f"
61#else
62# define DEFAULT_EDITOR_COMMAND VIM_COMMAND
63#endif
64
[0e81a88]65enum { LOG_REPROCESS = 1234, LOG_SAVE = 1235 };
[fb5887c]66
[e768f29]67static const wxString badutf8_html(
68    wxT("<span style=\"color:white;background-color:red;\">&#xfffd;</span>"));
69static const wxString badutf8(wxUniChar(0xfffd));
70
[fc626ae]71// New event type for passing a chunk of cavern output from the worker thread
[15ba0b5]72// to the main thread (or from the idle event handler if we're not using
73// threads).
[fc626ae]74class CavernOutputEvent;
75
76wxDEFINE_EVENT(wxEVT_CAVERN_OUTPUT, CavernOutputEvent);
77
78class CavernOutputEvent : public wxEvent {
79  public:
80    char buf[1000];
81    int len;
82    CavernOutputEvent() : wxEvent(0, wxEVT_CAVERN_OUTPUT), len(0) { }
83
84    wxEvent * Clone() const {
85        CavernOutputEvent * e = new CavernOutputEvent();
86        e->len = len;
87        if (len > 0) memcpy(e->buf, buf, len);
88        return e;
89    }
90};
91
[15ba0b5]92#ifdef CAVERNLOG_USE_THREADS
[fc626ae]93class CavernThread : public wxThread {
94  protected:
95    virtual ExitCode Entry();
96
97    CavernLogWindow *handler;
98
[15ba0b5]99    wxInputStream * in;
[fc626ae]100
101  public:
[15ba0b5]102    CavernThread(CavernLogWindow *handler_, wxInputStream * in_)
103        : wxThread(wxTHREAD_DETACHED), handler(handler_), in(in_) { }
[fc626ae]104
105    ~CavernThread() {
106        wxCriticalSectionLocker enter(handler->thread_lock);
107        handler->thread = NULL;
108    }
109};
110
111wxThread::ExitCode
112CavernThread::Entry()
113{
[15ba0b5]114    while (true) {
115        CavernOutputEvent * e = new CavernOutputEvent();
116        in->Read(e->buf, sizeof(e->buf));
117        size_t n = in->LastRead();
118        if (n == 0 || TestDestroy()) {
119            delete e;
120            return (wxThread::ExitCode)0;
121        }
122        if (n == 1 && e->buf[0] == '\n') {
123            // Don't send an event with just a blank line in.
124            in->Read(e->buf + 1, sizeof(e->buf) - 1);
125            n += in->LastRead();
126            if (TestDestroy()) {
127                delete e;
128                return (wxThread::ExitCode)0;
129            }
130        }
131        e->len = n;
132        handler->QueueEvent(e);
133    }
134}
135
136#else
137
138void
139CavernLogWindow::OnIdle(wxIdleEvent& event)
140{
141    if (cavern_out == NULL) return;
142
143    wxInputStream * in = cavern_out->GetInputStream();
144
145    if (!in->CanRead()) {
146        // Avoid a tight busy-loop on idle events.
147        wxMilliSleep(10);
148    }
149    if (in->CanRead()) {
[fc626ae]150        CavernOutputEvent * e = new CavernOutputEvent();
[15ba0b5]151        in->Read(e->buf, sizeof(e->buf));
152        size_t n = in->LastRead();
153        if (n == 0) {
[fc626ae]154            delete e;
[15ba0b5]155            return;
[fc626ae]156        }
[15ba0b5]157        e->len = n;
158        QueueEvent(e);
159    }
160
161    event.RequestMore();
[fc626ae]162}
163#endif
164
[fb5887c]165BEGIN_EVENT_TABLE(CavernLogWindow, wxHtmlWindow)
[81e1aa4]166    EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
[0e81a88]167    EVT_BUTTON(LOG_SAVE, CavernLogWindow::OnSave)
[fb5887c]168    EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
[15ba0b5]169    EVT_COMMAND(wxID_ANY, wxEVT_CAVERN_OUTPUT, CavernLogWindow::OnCavernOutput)
[0415bac]170#ifdef CAVERNLOG_USE_THREADS
[fc626ae]171    EVT_CLOSE(CavernLogWindow::OnClose)
172#else
[8991d7f]173    EVT_IDLE(CavernLogWindow::OnIdle)
[fc626ae]174#endif
[15ba0b5]175    EVT_END_PROCESS(wxID_ANY, CavernLogWindow::OnEndProcess)
[fb5887c]176END_EVENT_TABLE()
177
[549eb37]178wxString escape_for_shell(wxString s, bool protect_dash)
[6bec10c]179{
180#ifdef __WXMSW__
[faf83bee]181    // Correct quoting rules are insane:
182    //
[764fe32]183    // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
[faf83bee]184    //
185    // Thankfully wxExecute passes the command string to CreateProcess(), so
186    // at least we don't need to quote for cmd.exe too.
187    if (s.empty() || s.find_first_of(wxT(" \"\t\n\v")) != s.npos) {
188        // Need to quote.
189        s.insert(0, wxT('"'));
190        for (size_t p = 1; p < s.size(); ++p) {
191            size_t backslashes = 0;
192            while (s[p] == wxT('\\')) {
193                ++backslashes;
194                if (++p == s.size()) {
195                    // Escape all the backslashes, since they're before
196                    // the closing quote we add below.
197                    s.append(backslashes, wxT('\\'));
198                    goto done;
199                }
200            }
201
202            if (s[p] == wxT('"')) {
203                // Escape any preceding backslashes and this quote.
204                s.insert(p, backslashes + 1, wxT('\\'));
205                p += backslashes + 1;
[eff69a7]206            }
[6bec10c]207        }
[faf83bee]208done:
209        s.append(wxT('"'));
[6bec10c]210    }
211#else
[faf83bee]212    size_t p = 0;
[6bec10c]213    if (protect_dash && !s.empty() && s[0u] == '-') {
214        // If the filename starts with a '-', protect it from being
215        // treated as an option by prepending "./".
[5627cbb]216        s.insert(0, wxT("./"));
[6bec10c]217        p = 2;
218    }
219    while (p < s.size()) {
220        // Exclude a few safe characters which are common in filenames
221        if (!isalnum(s[p]) && strchr("/._-", s[p]) == NULL) {
[6baad4a]222            s.insert(p, 1, wxT('\\'));
[6bec10c]223            ++p;
224        }
225        ++p;
226    }
227#endif
228    return s;
229}
230
[549eb37]231wxString get_command_path(const wxChar * command_name)
232{
233#ifdef __WXMSW__
234    wxString cmd;
235    {
236        DWORD len = 256;
237        wchar_t *buf = NULL;
238        while (1) {
239            DWORD got;
240            buf = (wchar_t*)osrealloc(buf, len * 2);
241            got = GetModuleFileNameW(NULL, buf, len);
242            if (got < len) break;
243            len += len;
244        }
245        /* Strange Win32 nastiness - strip prefix "\\?\" if present */
246        wchar_t *start = buf;
247        if (wcsncmp(start, L"\\\\?\\", 4) == 0) start += 4;
248        wchar_t * slash = wcsrchr(start, L'\\');
249        if (slash) {
250            cmd.assign(start, slash - start + 1);
251        }
252        osfree(buf);
253    }
254#else
255    wxString cmd = wxString(msg_exepth(), wxConvUTF8);
256#endif
257    cmd += command_name;
258    return cmd;
259}
260
[d7b53e3]261CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, const wxString & survey_, wxWindow * parent)
[b004e36]262    : wxHtmlWindow(parent),
[f207751]263      mainfrm(mainfrm_), cavern_out(NULL), highlight(NULL),
[8991d7f]264      link_count(0), end(buf), init_done(false), survey(survey_)
[0415bac]265#ifdef CAVERNLOG_USE_THREADS
[fc626ae]266      , thread(NULL)
267#endif
[fb5887c]268{
[93ff5cc]269    int fsize = parent->GetFont().GetPointSize();
270    int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
[5627cbb]271    SetFonts(wxString(), wxString(), sizes);
[93ff5cc]272}
273
[8991d7f]274CavernLogWindow::~CavernLogWindow()
275{
[15ba0b5]276#ifdef CAVERNLOG_USE_THREADS
277    if (thread) stop_thread();
278#endif
[c1144fe]279    if (cavern_out) {
280        wxEndBusyCursor();
[15ba0b5]281        cavern_out->Detach();
[c1144fe]282    }
[8991d7f]283}
284
[0415bac]285#ifdef CAVERNLOG_USE_THREADS
[fc626ae]286void
287CavernLogWindow::stop_thread()
288{
[15ba0b5]289    // Killing the subprocess by its pid is theoretically racy, but in practice
290    // it's not going to cause issues, and it's all the wxProcess API seems to
291    // allow us to do.  If we don't kill the subprocess, we need to wait for it
292    // to write out some output - there seems to be no way to do the equivalent
[b4ae834]293    // of select() with a timeout on a wxInputStream.
[15ba0b5]294    //
295    // The only alternative to this seems to be to do:
296    //
297    //     while (!s.CanRead()) {
298    //         if (TestDestroy()) return (wxThread::ExitCode)0;
299    //         wxMilliSleep(N);
300    //     }
301    //
302    // But that makes the log window update sluggishly, and we're using a
303    // worker thread precisely to try to avoid having to do dumb stuff like
304    // this.
305    wxProcess::Kill(cavern_out->GetPid());
306
[fc626ae]307    {
308        wxCriticalSectionLocker enter(thread_lock);
309        if (thread) {
[2a00719]310            wxThreadError res;
311            res = thread->Delete(NULL, wxTHREAD_WAIT_BLOCK);
312            if (res != wxTHREAD_NO_ERROR) {
[fc626ae]313                // FIXME
314            }
315        }
316    }
317
318    // Wait for thread to complete.
319    while (true) {
320        {
321            wxCriticalSectionLocker enter(thread_lock);
322            if (!thread) break;
323        }
324        wxMilliSleep(1);
325    }
326}
327
328void
329CavernLogWindow::OnClose(wxCloseEvent &)
330{
331    if (thread) stop_thread();
332    Destroy();
333}
334#endif
335
[6bec10c]336void
337CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
338{
339    wxString href = link.GetHref();
340    wxString title = link.GetTarget();
[544a46c]341    size_t colon2 = href.rfind(wxT(':'));
342    if (colon2 == wxString::npos)
343        return;
344    size_t colon = href.rfind(wxT(':'), colon2 - 1);
[3d3fb6c]345    if (colon == wxString::npos)
346        return;
[cc2d7ad]347    wxString cmd;
[b8ba399]348    wxChar * p = wxGetenv(wxT("SURVEXEDITOR"));
[3d3fb6c]349    if (p) {
[b8ba399]350        cmd = p;
[3d3fb6c]351        if (!cmd.find(wxT("$f"))) {
352            cmd += wxT(" $f");
[6bec10c]353        }
[cc2d7ad]354    } else {
355        p = wxGetenv(wxT("VISUAL"));
356        if (!p) p = wxGetenv(wxT("EDITOR"));
357        if (!p) {
358            cmd = wxT(DEFAULT_EDITOR_COMMAND);
359        } else {
360            cmd = p;
361            if (cmd == "gvim") {
362                cmd = wxT(GVIM_COMMAND);
363            } else if (cmd == "vim") {
364                cmd = wxT(VIM_COMMAND);
[81d94a4]365            } else if (cmd == "nvim") {
366                cmd = wxT(NVIM_COMMAND);
[cc2d7ad]367            } else if (cmd == "gedit") {
368                cmd = wxT(GEDIT_COMMAND);
369            } else if (cmd == "pluma") {
370                cmd = wxT(PLUMA_COMMAND);
371            } else if (cmd == "emacs") {
372                cmd = wxT(EMACS_COMMAND);
373            } else if (cmd == "nano") {
374                cmd = wxT(NANO_COMMAND);
375            } else if (cmd == "jed") {
376                cmd = wxT(JED_COMMAND);
377            } else if (cmd == "kate") {
378                cmd = wxT(KATE_COMMAND);
379            } else {
380                // Escape any $.
381                cmd.Replace(wxT("$"), wxT("$$"));
382                cmd += wxT(" $f");
383            }
384        }
[3d3fb6c]385    }
386    size_t i = 0;
387    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
388        if (++i >= cmd.size()) break;
389        switch ((int)cmd[i]) {
390            case wxT('$'):
391                cmd.erase(i, 1);
392                break;
393            case wxT('f'): {
394                wxString f = escape_for_shell(href.substr(0, colon), true);
395                cmd.replace(i - 1, 2, f);
396                i += f.size() - 1;
397                break;
398            }
399            case wxT('t'): {
400                wxString t = escape_for_shell(title);
401                cmd.replace(i - 1, 2, t);
402                i += t.size() - 1;
403                break;
404            }
405            case wxT('l'): {
406                wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
407                cmd.replace(i - 1, 2, l);
408                i += l.size() - 1;
409                break;
[1d71195]410            }
[3d3fb6c]411            case wxT('c'): {
412                wxString l;
[b2b852d]413                if (colon2 >= href.size() - 1)
[3d3fb6c]414                    l = wxT("0");
415                else
416                    l = escape_for_shell(href.substr(colon2 + 1));
417                cmd.replace(i - 1, 2, l);
418                i += l.size() - 1;
419                break;
420            }
421            default:
422                ++i;
[6bec10c]423        }
[3d3fb6c]424    }
[faf83bee]425
426    if (wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER) >= 0)
[3d3fb6c]427        return;
[faf83bee]428
[3d3fb6c]429    wxString m;
[736f7df]430    // TRANSLATORS: %s is replaced by the command we attempted to run.
[3d3fb6c]431    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
432    m += wxT(" (");
433    m += wxString(strerror(errno), wxConvUTF8);
434    m += wxT(')');
435    wxGetApp().ReportError(m);
[6bec10c]436}
437
[8991d7f]438void
[6bec10c]439CavernLogWindow::process(const wxString &file)
440{
[00f4e69]441    SetPage(wxString());
[0415bac]442#ifdef CAVERNLOG_USE_THREADS
[fc626ae]443    if (thread) stop_thread();
444#endif
[c1144fe]445    if (cavern_out) {
[15ba0b5]446        cavern_out->Detach();
[15033fd]447        cavern_out = NULL;
[c1144fe]448    } else {
449        wxBeginBusyCursor();
450    }
451
[fb5887c]452    SetFocus();
453    filename = file;
454
[8991d7f]455    link_count = 0;
456    cur.resize(0);
[0e81a88]457    log_txt.resize(0);
458
[6bec10c]459#ifdef __WXMSW__
[15322f2]460    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
[6bec10c]461#else
[06b1227]462    setenv("SURVEX_UTF8", "1", 1);
[6bec10c]463#endif
[93ff5cc]464
[6bec10c]465    wxString escaped_file = escape_for_shell(file, true);
[549eb37]466    wxString cmd = get_command_path(L"cavern");
[9e50f755]467    cmd = escape_for_shell(cmd, false);
[5627cbb]468    cmd += wxT(" -o ");
[6bec10c]469    cmd += escaped_file;
[5627cbb]470    cmd += wxT(' ');
[6bec10c]471    cmd += escaped_file;
472
[15ba0b5]473    cavern_out = wxProcess::Open(cmd);
[6bec10c]474    if (!cavern_out) {
[5627cbb]475        wxString m;
[3d3fb6c]476        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
[5627cbb]477        m += wxT(" (");
[2e4b8cd]478        m += wxString(strerror(errno), wxConvUTF8);
[5627cbb]479        m += wxT(')');
[6bec10c]480        wxGetApp().ReportError(m);
[8991d7f]481        return;
[6bec10c]482    }
483
[15ba0b5]484    // We want to receive the wxProcessEvent when cavern exits.
485    cavern_out->SetNextHandler(this);
[40b02e8]486
[0415bac]487#ifdef CAVERNLOG_USE_THREADS
[15ba0b5]488    thread = new CavernThread(this, cavern_out->GetInputStream());
[fc626ae]489    if (thread->Run() != wxTHREAD_NO_ERROR) {
490        wxGetApp().ReportError(wxT("Thread failed to start"));
491        delete thread;
492        thread = NULL;
493    }
[15ba0b5]494#endif
[fc626ae]495}
496
497void
498CavernLogWindow::OnCavernOutput(wxCommandEvent & e_)
499{
500    CavernOutputEvent & e = (CavernOutputEvent&)e_;
501
502    if (e.len > 0) {
503        ssize_t n = e.len;
[27222f8]504        if (size_t(n) > sizeof(buf) - (end - buf)) abort();
[fc626ae]505        memcpy(end, e.buf, n);
[330cb03]506        log_txt.append((const char *)end, n);
507        end += n;
[b3ee5f5]508
509        const unsigned char * p = buf;
510
511        while (p != end) {
512            int ch = *p++;
513            if (ch >= 0x80) {
514                // Decode multi-byte UTF-8 sequence.
515                if (ch < 0xc0) {
[184d6d4]516                    // Invalid UTF-8 sequence.
517                    goto bad_utf8;
[b3ee5f5]518                } else if (ch < 0xe0) {
519                    /* 2 byte sequence */
520                    if (p == end) {
521                        // Incomplete UTF-8 sequence - try to read more.
522                        break;
523                    }
524                    int ch1 = *p++;
525                    if ((ch1 & 0xc0) != 0x80) {
[184d6d4]526                        // Invalid UTF-8 sequence.
527                        goto bad_utf8;
[b3ee5f5]528                    }
529                    ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
530                } else if (ch < 0xf0) {
531                    /* 3 byte sequence */
532                    if (end - p <= 1) {
533                        // Incomplete UTF-8 sequence - try to read more.
534                        break;
535                    }
536                    int ch1 = *p++;
537                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6);
538                    if ((ch1 & 0xc0) != 0x80) {
[184d6d4]539                        // Invalid UTF-8 sequence.
540                        goto bad_utf8;
[b3ee5f5]541                    }
542                    int ch2 = *p++;
543                    if ((ch2 & 0xc0) != 0x80) {
[184d6d4]544                        // Invalid UTF-8 sequence.
545                        goto bad_utf8;
[b3ee5f5]546                    }
547                    ch |= (ch2 & 0x3f);
[6bec10c]548                } else {
[184d6d4]549                    // Overlong UTF-8 sequence.
550                    goto bad_utf8;
[6bec10c]551                }
552            }
553
[e768f29]554            if (false) {
555bad_utf8:
556                // Resync to next byte which starts a UTF-8 sequence.
557                while (p != end) {
558                    if (*p < 0x80 || (*p >= 0xc0 && *p < 0xf0)) break;
559                    ++p;
560                }
561                cur += badutf8_html;
562                continue;
563            }
564
[b3ee5f5]565            switch (ch) {
566                case '\r':
567                    // Ignore.
568                    break;
569                case '\n': {
570                    if (cur.empty()) continue;
[99865c9]571                    if (cur[0] == ' ') {
572                        if (source_line.empty()) {
573                            // Source line shown for context.  Store it so we
574                            // can use the caret line to highlight it.
575                            swap(source_line, cur);
576                        } else {
577                            size_t caret = cur.rfind('^');
578                            if (caret != wxString::npos) {
579                                size_t tilde = cur.rfind('~');
580                                if (tilde == wxString::npos || tilde < caret) {
581                                    tilde = caret;
582                                }
583                                cur = "&nbsp;";
584                                // FIXME: Need to count each & entity as one character...
585                                cur.append(source_line, 1, caret - 1);
[7ffb1c5]586                                if (caret < source_line.size()) {
587                                    cur.append("<b>");
588                                    cur.append(highlight ? highlight : wxT("<span \"color:green\">"));
589                                    cur.append(source_line, caret, tilde + 1 - caret);
590                                    cur.append("</span></b>");
591                                }
[816eae6]592                                if (tilde + 1 < source_line.size()) {
593                                    cur.append(source_line, tilde + 1, wxString::npos);
594                                }
[99865c9]595                            } else {
[f207751]596                                // No caret in second line - just output both.
597                                source_line.replace(0, 1, "&nbsp;");
598                                source_line += "<br>\n&nbsp;";
599                                source_line.append(cur, 1, wxString::npos);
[99865c9]600                                swap(cur, source_line);
601                            }
602                            cur += "<br>\n";
603                            AppendToPage(cur);
604                            cur.clear();
605                            source_line.clear();
606                        }
607                        continue;
608                    }
[f207751]609
610                    if (!source_line.empty()) {
611                        // Previous line was a source line without column info
612                        // so just show it.
613                        source_line.replace(0, 1, "&nbsp;");
614                        source_line += "<br>\n";
615                        AppendToPage(source_line);
616                        source_line.clear();
617                    }
[a07ee70]618#ifndef __WXMSW__
[b3ee5f5]619                    size_t colon = cur.find(':');
[a07ee70]620#else
[b3ee5f5]621                    // If the path is "C:\path\to\file.svx" then don't split at the
622                    // : after the drive letter!  FIXME: better to look for ": "?
623                    size_t colon = cur.find(':', 2);
[6bec10c]624#endif
[b3ee5f5]625                    if (colon != wxString::npos && colon < cur.size() - 2) {
626                        ++colon;
627                        size_t i = colon;
628                        while (i < cur.size() - 2 &&
629                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
630                            ++i;
631                        }
632                        if (i > colon && cur[i] == wxT(':') ) {
[da96015]633                            colon = i;
[b3ee5f5]634                            // Check for column number.
635                            while (++i < cur.size() - 2 &&
636                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
[f9f0f0c]637                            bool have_column = (i > colon + 1 && cur[i] == wxT(':'));
638                            if (have_column) {
[b3ee5f5]639                                colon = i;
[544a46c]640                            } else {
641                                // If there's no colon, include a trailing ':'
642                                // so that we can unambiguously split the href
643                                // value up into filename, line and column.
644                                ++colon;
[b3ee5f5]645                            }
646                            wxString tag = wxT("<a href=\"");
647                            tag.append(cur, 0, colon);
648                            while (cur[++i] == wxT(' ')) { }
649                            tag += wxT("\" target=\"");
[e768f29]650                            wxString target(cur, i, wxString::npos);
651                            target.Replace(badutf8_html, badutf8);
652                            tag += target;
[b3ee5f5]653                            tag += wxT("\">");
654                            cur.insert(0, tag);
[40b02e8]655                            size_t offset = colon + tag.size();
656                            cur.insert(offset, wxT("</a>"));
657                            offset += 4 + 2;
[8991d7f]658
[f9f0f0c]659                            if (!have_column) --offset;
660
[8991d7f]661                            static const wxString & error_marker = wmsg(/*error*/93) + ":";
662                            static const wxString & warning_marker = wmsg(/*warning*/4) + ":";
663
[40b02e8]664                            if (cur.substr(offset, error_marker.size()) == error_marker) {
665                                // Show "error" marker in red.
[99865c9]666                                highlight = wxT("<span style=\"color:red\">");
667                                cur.insert(offset, highlight);
[58055e11]668                                offset += 24 + error_marker.size() - 1;
[40b02e8]669                                cur.insert(offset, wxT("</span>"));
670                            } else if (cur.substr(offset, warning_marker.size()) == warning_marker) {
671                                // Show "warning" marker in orange.
[99865c9]672                                highlight = wxT("<span style=\"color:orange\">");
673                                cur.insert(offset, highlight);
[58055e11]674                                offset += 27 + warning_marker.size() - 1;
[40b02e8]675                                cur.insert(offset, wxT("</span>"));
[99865c9]676                            } else {
677                                highlight = NULL;
[40b02e8]678                            }
679
[b3ee5f5]680                            ++link_count;
[da96015]681                        }
[6bec10c]682                    }
[93ff5cc]683
[b3ee5f5]684                    // Save the scrollbar positions.
685                    int scroll_x = 0, scroll_y = 0;
686                    GetViewStart(&scroll_x, &scroll_y);
[93ff5cc]687
[b3ee5f5]688                    cur += wxT("<br>\n");
689                    AppendToPage(cur);
[93ff5cc]690
[b3ee5f5]691                    if (!link_count) {
[8991d7f]692                        // Auto-scroll the window until we've reported a
693                        // warning or error.
[b3ee5f5]694                        int x, y;
695                        GetVirtualSize(&x, &y);
696                        int xs, ys;
697                        GetClientSize(&xs, &ys);
698                        y -= ys;
699                        int xu, yu;
700                        GetScrollPixelsPerUnit(&xu, &yu);
701                        Scroll(scroll_x, y / yu);
702                    } else {
703                        // Restore the scrollbar positions.
704                        Scroll(scroll_x, scroll_y);
705                    }
[a75f5a1]706
[b3ee5f5]707                    cur.clear();
708                    break;
[6bec10c]709                }
[b3ee5f5]710                case '<':
711                    cur += wxT("&lt;");
712                    break;
713                case '>':
714                    cur += wxT("&gt;");
715                    break;
716                case '&':
717                    cur += wxT("&amp;");
718                    break;
719                case '"':
[bc9c09a]720                    cur += wxT("&#34;");
[b3ee5f5]721                    continue;
722                default:
[aef136e]723#ifdef wxUSE_UNICODE
724                    cur += wxChar(ch);
725#else
726                    // This approach means that highlighting of "error" or
727                    // "warning" won't work in translations where they contain
728                    // non-ASCII characters, but wxWidgets >= 3.0 in always
729                    // Unicode, so this corner case is already very uncommon,
730                    // and will become irrelevant with time.
[b3ee5f5]731                    if (ch >= 128) {
732                        cur += wxString::Format(wxT("&#%u;"), ch);
733                    } else {
734                        cur += (char)ch;
735                    }
[aef136e]736#endif
[b3ee5f5]737            }
[6bec10c]738        }
[b3ee5f5]739
740        size_t left = end - p;
741        end = buf + left;
742        if (left) memmove(buf, p, left);
[8991d7f]743        Update();
744        return;
[6bec10c]745    }
[8991d7f]746
[f207751]747    if (!source_line.empty()) {
748        // Previous line was a source line without column info
749        // so just show it.
750        source_line.replace(0, 1, "&nbsp;");
751        source_line += "<br>\n";
752        AppendToPage(source_line);
753        source_line.clear();
754    }
755
[15ba0b5]756    if (e.len <= 0 && buf != end) {
[184d6d4]757        // Truncated UTF-8 sequence.
[e768f29]758        cur += badutf8_html;
[fc626ae]759    }
[f207751]760    if (!cur.empty()) {
761        cur += "<br>\n";
762        AppendToPage("<hr>" + cur);
763    }
[fb5887c]764
[0e81a88]765    /* TRANSLATORS: Label for button in aven’s cavern log window which
766     * allows the user to save the log to a file. */
767    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
768                                  (int)LOG_SAVE,
[9cd0657]769                                  wmsg(/*&Save Log*/446).c_str()));
[c1144fe]770    wxEndBusyCursor();
[15ba0b5]771    delete cavern_out;
[8991d7f]772    cavern_out = NULL;
[15ba0b5]773    if (e.len < 0) {
774        /* Negative length indicates non-zero exit status from cavern. */
[736f7df]775        /* TRANSLATORS: Label for button in aven’s cavern log window which
776         * causes the survey data to be reprocessed. */
[bbed692]777        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d name=\"%s\">"),
778                                      (int)LOG_REPROCESS,
[9cd0657]779                                      wmsg(/*&Reprocess*/184).c_str()));
[8991d7f]780        return;
[6bec10c]781    }
[bbed692]782    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
783                                  (int)LOG_REPROCESS,
[9cd0657]784                                  wmsg(/*&Reprocess*/184).c_str()));
[d7b53e3]785    AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
786    Update();
787    init_done = false;
[fb5887c]788
[15ba0b5]789    {
790        wxString file3d(filename, 0, filename.length() - 3);
791        file3d.append(wxT("3d"));
792        if (!mainfrm->LoadData(file3d, survey)) {
793            return;
794        }
[d7b53e3]795    }
[15ba0b5]796
[8991d7f]797    if (link_count == 0) {
798        wxCommandEvent dummy;
799        OnOK(dummy);
[fb5887c]800    }
[15ba0b5]801}
802
803void
804CavernLogWindow::OnEndProcess(wxProcessEvent & evt)
805{
806    CavernOutputEvent * e = new CavernOutputEvent();
807    // Zero length indicates successful exit, negative length unsuccessful exit.
808    e->len = (evt.GetExitCode() == 0 ? 0 : -1);
809    QueueEvent(e);
[fb5887c]810}
811
[0e81a88]812void
[330cb03]813CavernLogWindow::OnReprocess(wxCommandEvent &)
[8991d7f]814{
815    process(filename);
816}
817
818void
[0e81a88]819CavernLogWindow::OnSave(wxCommandEvent &)
820{
821    wxString filelog(filename, 0, filename.length() - 3);
822    filelog += wxT("log");
823#ifdef __WXMOTIF__
824    wxString ext(wxT("*.log"));
825#else
[bb71423]826    /* TRANSLATORS: Log files from running cavern (extension .log) */
[0e81a88]827    wxString ext = wmsg(/*Log files*/447);
828    ext += wxT("|*.log");
829#endif
830    wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
831                     wxString(), filelog, ext,
832                     wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
833    if (dlg.ShowModal() != wxID_OK) return;
834    filelog = dlg.GetPath();
[3206c12]835    FILE * fh_log = wxFopen(filelog, wxT("w"));
[0e81a88]836    if (!fh_log) {
837        wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), filelog.c_str()));
838        return;
839    }
840    fwrite(log_txt.data(), log_txt.size(), 1, fh_log);
841    fclose(fh_log);
842}
843
[fb5887c]844void
845CavernLogWindow::OnOK(wxCommandEvent &)
846{
[d7b53e3]847    if (init_done) {
848        mainfrm->HideLog(this);
849    } else {
[5e0b9f9d]850        mainfrm->InitialiseAfterLoad(filename, survey);
[d7b53e3]851        init_done = true;
852    }
[fb5887c]853}
Note: See TracBrowser for help on using the repository browser.