1 //////////////////////////////////////////////////////////////////////////
2 //
3 // pgAdmin III - PostgreSQL Tools
4 //
5 // Copyright (C) 2002 - 2016, The pgAdmin Development Team
6 // This software is released under the PostgreSQL Licence
7 //
8 // ctlSQLBox.cpp - SQL syntax highlighting textbox
9 //
10 //////////////////////////////////////////////////////////////////////////
11 
12 #include "pgAdmin3.h"
13 
14 // wxWindows headers
15 #include <wx/wx.h>
16 #include <wx/stc/stc.h>
17 #include <wx/sysopt.h>
18 
19 // App headers
20 #include "db/pgSet.h"
21 #include "ctl/ctlSQLBox.h"
22 #include "dlg/dlgFindReplace.h"
23 #include "frm/menu.h"
24 #include "utils/sysProcess.h"
25 
26 wxString ctlSQLBox::sqlKeywords;
27 
28 // Additional pl/pgsql keywords we should highlight
29 wxString plpgsqlKeywords = wxT(" elsif exception exit loop raise record return text while");
30 //
31 // Additional Text Search keywords we should highlight
32 wxString ftsKeywords = wxT(" gettoken lextypes headline init lexize");
33 
34 // Additional pgScript keywords we should highlight
35 wxString pgscriptKeywords = wxT(" assert break columns continue date datetime file go lines ")
36                             wxT(" log print record reference regexrmline string waitfor while");
37 
BEGIN_EVENT_TABLE(ctlSQLBox,wxStyledTextCtrl)38 BEGIN_EVENT_TABLE(ctlSQLBox, wxStyledTextCtrl)
39 	EVT_KEY_DOWN(ctlSQLBox::OnKeyDown)
40 	EVT_MENU(MNU_FIND, ctlSQLBox::OnSearchReplace)
41 	EVT_MENU(MNU_AUTOCOMPLETE, ctlSQLBox::OnAutoComplete)
42 	EVT_KILL_FOCUS(ctlSQLBox::OnKillFocus)
43 #ifdef __WXMAC__
44 	EVT_STC_PAINTED(-1,  ctlSQLBox::OnPositionStc)
45 #else
46 	EVT_STC_UPDATEUI(-1, ctlSQLBox::OnPositionStc)
47 #endif
48 	EVT_STC_MARGINCLICK(-1, ctlSQLBox::OnMarginClick)
49 	EVT_END_PROCESS(-1,  ctlSQLBox::OnEndProcess)
50 END_EVENT_TABLE()
51 
52 
53 IMPLEMENT_DYNAMIC_CLASS(ctlSQLBox, wxStyledTextCtrl)
54 
55 
56 ctlSQLBox::ctlSQLBox()
57 {
58 	m_dlgFindReplace = 0;
59 	m_autoIndent = false;
60 	m_autocompDisabled = false;
61 	process = 0;
62 	processID = 0;
63 }
64 
65 
ctlSQLBox(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style)66 ctlSQLBox::ctlSQLBox(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
67 {
68 	m_dlgFindReplace = 0;
69 
70 	m_database = NULL;
71 
72 	m_autocompDisabled = false;
73 	process = 0;
74 	processID = 0;
75 
76 	Create(parent, id, pos, size, style);
77 }
78 
79 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style)80 void ctlSQLBox::Create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
81 {
82 	wxStyledTextCtrl::Create(parent, id , pos, size, style);
83 
84 	// Clear all styles
85 	StyleClearAll();
86 
87 	// Font
88 	extern sysSettings *settings;
89 	wxFont fntSQLBox = settings->GetSQLFont();
90 
91 	wxColour bgColor = settings->GetSQLBoxColourBackground();
92 	if (settings->GetSQLBoxUseSystemBackground())
93 	{
94 		bgColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
95 	}
96 
97 	wxColour frColor = settings->GetSQLBoxColourForeground();
98 	if (settings->GetSQLBoxUseSystemForeground())
99 	{
100 		frColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
101 	}
102 	StyleSetBackground(wxSTC_STYLE_DEFAULT, bgColor);
103 	StyleSetForeground(wxSTC_STYLE_DEFAULT, frColor);
104 	StyleSetFont(wxSTC_STYLE_DEFAULT, fntSQLBox);
105 
106 	SetSelBackground(true, wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
107 	SetSelForeground(true, wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
108 
109 	SetCaretForeground(settings->GetSQLColourCaret());
110 
111 	SetMarginWidth(1, 0);
112 	SetTabWidth(settings->GetIndentSpaces());
113 	SetUseTabs(!settings->GetSpacesForTabs());
114 
115 	// Setup the different highlight colurs
116 	for (int i = 0; i < 34; ++ i )
117 	{
118 		if (i > 0 && i < 12)
119 			StyleSetForeground(i, settings->GetSQLBoxColour(i));
120 		else
121 			StyleSetForeground(i, frColor);
122 		StyleSetBackground(i, bgColor);
123 		StyleSetFont(i, fntSQLBox);
124 	}
125 
126 	// Keywords in uppercase?
127 
128 	if (settings->GetSQLKeywordsInUppercase())
129 		StyleSetCase(5, wxSTC_CASE_UPPER);
130 
131 	// Margin style
132 	StyleSetBackground(wxSTC_STYLE_LINENUMBER, settings->GetSQLMarginBackgroundColour());
133 
134 	// Brace maching styles
135 	StyleSetBackground(34, wxColour(0x99, 0xF9, 0xFF));
136 	StyleSetBackground(35, wxColour(0xFF, 0xCF, 0x27));
137 	StyleSetFont(34, fntSQLBox);
138 	StyleSetFont(35, fntSQLBox);
139 
140 	// SQL Lexer and keywords.
141 	if (sqlKeywords.IsEmpty())
142 		FillKeywords(sqlKeywords);
143 	SetLexer(wxSTC_LEX_SQL);
144 	SetKeyWords(0, sqlKeywords + plpgsqlKeywords + ftsKeywords + pgscriptKeywords);
145 
146 	// Enable folding
147 	SetMarginSensitive(2, true);
148 
149 	SetMarginType(2, wxSTC_MARGIN_SYMBOL); // margin 2 for symbols
150 	SetMarginMask(2, wxSTC_MASK_FOLDERS);  // set up mask for folding symbols
151 	SetMarginSensitive(2, true);           // this one needs to be mouse-aware
152 	SetMarginWidth(2, 16);                 // set margin 2 16 px wide
153 
154 	MarkerDefine(wxSTC_MARKNUM_FOLDEREND,     wxSTC_MARK_BOXPLUSCONNECTED,  *wxWHITE, *wxBLACK);
155 	MarkerDefine(wxSTC_MARKNUM_FOLDEROPENMID, wxSTC_MARK_BOXMINUSCONNECTED, *wxWHITE, *wxBLACK);
156 	MarkerDefine(wxSTC_MARKNUM_FOLDERMIDTAIL, wxSTC_MARK_TCORNER,  *wxWHITE, *wxBLACK);
157 	MarkerDefine(wxSTC_MARKNUM_FOLDERTAIL,    wxSTC_MARK_LCORNER,  *wxWHITE, *wxBLACK);
158 	MarkerDefine(wxSTC_MARKNUM_FOLDERSUB,     wxSTC_MARK_VLINE,    *wxWHITE, *wxBLACK);
159 	MarkerDefine(wxSTC_MARKNUM_FOLDER,        wxSTC_MARK_BOXPLUS,  *wxWHITE, *wxBLACK);
160 	MarkerDefine(wxSTC_MARKNUM_FOLDEROPEN,    wxSTC_MARK_BOXMINUS, *wxWHITE, *wxBLACK);
161 
162 	SetProperty(wxT("fold"), wxT("1"));
163 	SetFoldFlags(16);
164 
165 	// Setup accelerators
166 	wxAcceleratorEntry entries[2];
167 	entries[0].Set(wxACCEL_CTRL, (int)'F', MNU_FIND);
168 	entries[1].Set(wxACCEL_CTRL, WXK_SPACE, MNU_AUTOCOMPLETE);
169 	wxAcceleratorTable accel(2, entries);
170 	SetAcceleratorTable(accel);
171 
172 	// Autocompletion configuration
173 	AutoCompSetSeparator('\t');
174 	AutoCompSetChooseSingle(true);
175 	AutoCompSetIgnoreCase(true);
176 	AutoCompSetFillUps(wxT(" \t"));
177 	AutoCompSetDropRestOfWord(true);
178 
179 	SetEOLMode(settings->GetLineEndingType());
180 }
181 
SetDatabase(pgConn * db)182 void ctlSQLBox::SetDatabase(pgConn *db)
183 {
184 	m_database = db;
185 }
186 
OnSearchReplace(wxCommandEvent & ev)187 void ctlSQLBox::OnSearchReplace(wxCommandEvent &ev)
188 {
189 	if (!m_dlgFindReplace)
190 	{
191 		m_dlgFindReplace = new dlgFindReplace(this);
192 		m_dlgFindReplace->Show(true);
193 	}
194 	else
195 	{
196 		m_dlgFindReplace->Show(true);
197 		m_dlgFindReplace->SetFocus();
198 	}
199 
200 	wxString selText = GetSelectedText();
201 	if (!selText.IsEmpty())
202 	{
203 		m_dlgFindReplace->SetFindString(selText);
204 	}
205 
206 	m_dlgFindReplace->FocusSearch();
207 }
208 
Find(const wxString & find,bool wholeWord,bool matchCase,bool useRegexps,bool startAtTop,bool reverse)209 bool ctlSQLBox::Find(const wxString &find, bool wholeWord, bool matchCase, bool useRegexps, bool startAtTop, bool reverse)
210 {
211 	if (!DoFind(find, wxString(wxEmptyString), false, wholeWord, matchCase, useRegexps, startAtTop, reverse))
212 	{
213 		wxWindow *w = wxWindow::FindFocus();
214 		wxMessageBox(_("Reached the end of the document"), _("Find text"), wxICON_EXCLAMATION | wxOK, w);
215 		return false;
216 	}
217 	return true;
218 }
219 
Replace(const wxString & find,const wxString & replace,bool wholeWord,bool matchCase,bool useRegexps,bool startAtTop,bool reverse)220 bool ctlSQLBox::Replace(const wxString &find, const wxString &replace, bool wholeWord, bool matchCase, bool useRegexps, bool startAtTop, bool reverse)
221 {
222 	if (!DoFind(find, replace, true, wholeWord, matchCase, useRegexps, startAtTop, reverse))
223 	{
224 		wxWindow *w = wxWindow::FindFocus();
225 		wxMessageBox(_("Reached the end of the document"), _("Replace text"), wxICON_EXCLAMATION | wxOK, w);
226 		return false;
227 	}
228 	return true;
229 }
230 
ReplaceAll(const wxString & find,const wxString & replace,bool wholeWord,bool matchCase,bool useRegexps)231 bool ctlSQLBox::ReplaceAll(const wxString &find, const wxString &replace, bool wholeWord, bool matchCase, bool useRegexps)
232 {
233 	// Use DoFind to repeatedly replace text
234 	int count = 0;
235 	int initialPos = GetCurrentPos();
236 	GotoPos(0);
237 
238 	while(DoFind(find, replace, true, wholeWord, matchCase, useRegexps, false, false))
239 		count++;
240 
241 	GotoPos(initialPos);
242 
243 	wxString msg;
244 	msg.Printf(wxPLURAL("%d replacement made.", "%d replacements made.", count), count);
245 	wxMessageBox(msg, _("Replace all"), wxOK | wxICON_INFORMATION);
246 
247 	if (count)
248 		return true;
249 	else
250 		return false;
251 }
252 
DoFind(const wxString & find,const wxString & replace,bool doReplace,bool wholeWord,bool matchCase,bool useRegexps,bool startAtTop,bool reverse)253 bool ctlSQLBox::DoFind(const wxString &find, const wxString &replace, bool doReplace, bool wholeWord, bool matchCase, bool useRegexps, bool startAtTop, bool reverse)
254 {
255 	int flags = 0;
256 	int startPos = GetSelectionStart();
257 	int endPos = GetTextLength();
258 
259 	// Setup flags
260 	if (wholeWord)
261 		flags |= wxSTC_FIND_WHOLEWORD;
262 
263 	if (matchCase)
264 		flags |= wxSTC_FIND_MATCHCASE;
265 
266 	// Replace the current selection, if there is one and it matches the find param.
267 	wxString current = GetSelectedText();
268 	if (doReplace)
269 	{
270 		if (useRegexps)
271 		{
272 			CharacterRange cr = RegexFindText(GetSelectionStart(), GetSelectionEnd(), find);
273 			if (GetSelectionStart() == cr.cpMin && GetSelectionEnd() == cr.cpMax)
274 			{
275 				if (cr.cpMin == cr.cpMax) // Must be finding a special char, such as $ (line end)
276 				{
277 					InsertText(cr.cpMax, replace);
278 					SetSelection(cr.cpMax, cr.cpMax + replace.Length());
279 					SetCurrentPos(cr.cpMax + replace.Length());
280 
281 					// Stop if we've got to the end. This is important for the $
282 					// case where it'll just keep finding the end of the line!!
283 					if ((int)(cr.cpMin + replace.Length()) == GetLength())
284 						return false;
285 				}
286 				else
287 				{
288 					ReplaceSelection(replace);
289 					SetSelection(startPos, startPos + replace.Length());
290 					SetCurrentPos(startPos + replace.Length());
291 				}
292 			}
293 		}
294 		else if ((matchCase && current == find) || (!matchCase && current.Upper() == find.Upper()))
295 		{
296 			ReplaceSelection(replace);
297 			if (!reverse)
298 			{
299 				SetSelection(startPos, startPos + replace.Length());
300 				SetCurrentPos(startPos + replace.Length());
301 			}
302 			else
303 			{
304 				SetSelection(startPos + replace.Length(), startPos);
305 				SetCurrentPos(startPos);
306 			}
307 		}
308 	}
309 
310 	////////////////////////////////////////////////////////////////////////
311 	// Figure out the starting position for the next search
312 	////////////////////////////////////////////////////////////////////////
313 
314 	if (startAtTop)
315 	{
316 		startPos = 0;
317 		endPos = GetTextLength();
318 	}
319 	else
320 	{
321 		if (reverse)
322 		{
323 			endPos = 0;
324 			startPos = GetCurrentPos();
325 		}
326 		else
327 		{
328 			endPos = GetTextLength();
329 			startPos = GetCurrentPos();
330 		}
331 	}
332 
333 	size_t selStart = 0, selEnd = 0;
334 
335 	if (useRegexps)
336 	{
337 		CharacterRange cr = RegexFindText(startPos, endPos, find);
338 		selStart = cr.cpMin;
339 		selEnd = cr.cpMax;
340 	}
341 	else
342 	{
343 		selStart = FindText(startPos, endPos, find, flags);
344 		selEnd = selStart + find.Length();
345 	}
346 
347 	if (selStart != (size_t)(-1))
348 	{
349 		if (reverse)
350 		{
351 			SetCurrentPos(selStart);
352 			SetSelection(selEnd, selStart);
353 		}
354 		else
355 		{
356 			SetCurrentPos(selEnd);
357 			SetSelection(selStart, selEnd);
358 		}
359 		EnsureCaretVisible();
360 		return true;
361 	}
362 	else
363 		return false;
364 }
365 
OnKeyDown(wxKeyEvent & event)366 void ctlSQLBox::OnKeyDown(wxKeyEvent &event)
367 {
368 #ifdef __WXGTK__
369 	event.m_metaDown = false;
370 #endif
371 
372 	// Get the line ending type
373 	wxString lineEnd;
374 	switch (GetEOLMode())
375 	{
376 		case wxSTC_EOL_LF:
377 			lineEnd = wxT("\n");
378 			break;
379 		case wxSTC_EOL_CRLF:
380 			lineEnd = wxT("\r\n");
381 			break;
382 		case wxSTC_EOL_CR:
383 			lineEnd = wxT("\r");
384 			break;
385 	}
386 
387 	// Block comment/uncomment
388 	if (event.GetKeyCode() == 'K')
389 	{
390 		// Comment (Ctrl+k)
391 		if (event.GetModifiers() == wxMOD_CONTROL)
392 		{
393 			if (BlockComment(false))
394 				return;
395 		}
396 		// Uncomment (Ctrl+Shift+K)
397 		else if (event.GetModifiers() == (wxMOD_CONTROL | wxMOD_SHIFT))
398 		{
399 			if (BlockComment(true))
400 				return;
401 		}
402 	}
403 
404 	// Autoindent
405 	if (m_autoIndent && event.GetKeyCode() == WXK_RETURN)
406 	{
407 		wxString indent, line;
408 		line = GetLine(GetCurrentLine());
409 
410 		// Get the offset for the current line - basically, whether
411 		// or not it ends with a \r\n, \n or \r, and if so, the length
412 		int offset =  0;
413 		if (line.EndsWith(wxT("\r\n")))
414 			offset = 2;
415 		else if (line.EndsWith(wxT("\n")))
416 			offset = 1;
417 		else if (line.EndsWith(wxT("\r")))
418 			offset = 1;
419 
420 		// Get the indent. This is every leading space or tab on the
421 		// line, up until the current cursor position.
422 		int x = 0;
423 		int max = line.Length() - (GetLineEndPosition(GetCurrentLine()) - GetCurrentPos()) - offset;
424 		if(line != wxEmptyString)
425 		{
426 			while ((line[x] == '\t' || line[x] == ' ') && x < max)
427 				indent += line[x++];
428 		}
429 
430 		// Select any indent in front of the cursor to be removed. If
431 		// the cursor is positioned after any non-indent characters,
432 		// we don't remove anything. If there is already some selected,
433 		// don't select anything new at all.
434 		if (indent.Length() != 0 &&
435 		        (unsigned int)GetCurrentPos() <= ((GetLineEndPosition(GetCurrentLine()) - line.Length()) + indent.Length() + offset) &&
436 		        GetSelectedText() == wxEmptyString)
437 			SetSelection(GetLineEndPosition(GetCurrentLine()) - line.Length() + offset, GetLineEndPosition(GetCurrentLine()) - line.Length() + indent.Length() + offset);
438 
439 		// Lose any selected text.
440 		ReplaceSelection(wxEmptyString);
441 
442 		// Insert a replacement \n (or whatever), and the indent at the insertion point.
443 		InsertText(GetCurrentPos(), lineEnd + indent);
444 
445 		// Now, reset the position, and clear the selection
446 		SetCurrentPos(GetCurrentPos() + indent.Length() + lineEnd.Length());
447 		SetSelection(GetCurrentPos(), GetCurrentPos());
448 	}
449 	else if (m_dlgFindReplace && event.GetKeyCode() == WXK_F3)
450 	{
451 		m_dlgFindReplace->FindNext();
452 	}
453 	else
454 		event.Skip();
455 }
456 
BlockComment(bool uncomment)457 bool ctlSQLBox::BlockComment(bool uncomment)
458 {
459 	wxString lineEnd;
460 	switch (GetEOLMode())
461 	{
462 		case wxSTC_EOL_LF:
463 			lineEnd = wxT("\n");
464 			break;
465 		case wxSTC_EOL_CRLF:
466 			lineEnd = wxT("\r\n");
467 			break;
468 		case wxSTC_EOL_CR:
469 			lineEnd = wxT("\r");
470 			break;
471 	}
472 
473 	// Save the start position
474 	const wxString comment = wxT("-- ");
475 	int start = GetSelectionStart();
476 
477 	if (!GetSelectedText().IsEmpty())
478 	{
479 		wxString selection = GetSelectedText();
480 		if (!uncomment)
481 		{
482 			selection.Replace(lineEnd, lineEnd + comment);
483 			selection.Prepend(comment);
484 			if (selection.EndsWith(comment))
485 				selection = selection.Left(selection.Length() - comment.Length());
486 		}
487 		else
488 		{
489 			selection.Replace(lineEnd + comment, lineEnd);
490 			if (selection.StartsWith(comment))
491 				selection = selection.Right(selection.Length() - comment.Length());
492 		}
493 		ReplaceSelection(selection);
494 		SetSelection(start, start + selection.Length());
495 	}
496 	else
497 	{
498 		// No text selection - (un)comment the current line
499 		int column = GetColumn(start);
500 		int curLineNum = GetCurrentLine();
501 		int pos = PositionFromLine(curLineNum);
502 
503 		if (!uncomment)
504 		{
505 			InsertText(pos, comment);
506 		}
507 		else
508 		{
509 			wxString t = GetTextRange(pos, pos + comment.Length());
510 			if (t == comment)
511 			{
512 				// The line starts with a comment, so we remove it
513 				SetTargetStart(pos);
514 				SetTargetEnd(pos + comment.Length());
515 				ReplaceTarget(wxT(""));
516 			}
517 			else
518 			{
519 				// The line doesn't start with a comment, do nothing
520 				return false;
521 			}
522 		}
523 
524 		if (GetLineCount() > curLineNum)
525 		{
526 			wxString nextLine = GetLine(curLineNum + 1);
527 			if (nextLine.EndsWith(lineEnd))
528 				nextLine = nextLine.Left(nextLine.Length() - lineEnd.Length());
529 
530 			int nextColumn = (nextLine.Length() < (unsigned int)column ? nextLine.Length() : column);
531 			GotoPos(PositionFromLine(curLineNum + 1) + nextColumn);
532 		}
533 	}
534 
535 	return true;
536 }
537 
OnKillFocus(wxFocusEvent & event)538 void ctlSQLBox::OnKillFocus(wxFocusEvent &event)
539 {
540 	AutoCompCancel();
541 	event.Skip();
542 }
543 
UpdateLineNumber()544 void ctlSQLBox::UpdateLineNumber()
545 {
546 	bool showlinenumber;
547 
548 	settings->Read(wxT("frmQuery/ShowLineNumber"), &showlinenumber, false);
549 	if (showlinenumber)
550 	{
551 		long int width = TextWidth(wxSTC_STYLE_LINENUMBER,
552 		                           wxT(" ") + NumToStr((long int)GetLineCount()) + wxT(" "));
553 		if (width != GetMarginWidth(0))
554 		{
555 			SetMarginWidth(0, width);
556 			Update();
557 		}
558 	}
559 	else
560 	{
561 		SetMarginWidth(0, 0);
562 	}
563 }
564 
OnEndProcess(wxProcessEvent & ev)565 void ctlSQLBox::OnEndProcess(wxProcessEvent &ev)
566 {
567 	if (process)
568 	{
569 		processErrorOutput = process->ReadErrorStream();
570 		processOutput += process->ReadInputStream();
571 		processExitCode = ev.GetExitCode();
572 		delete process;
573 		process = 0;
574 		processID = 0;
575 	}
576 }
577 
ExternalFormat()578 wxString ctlSQLBox::ExternalFormat()
579 {
580 	wxString msg;
581 	processOutput = wxEmptyString;
582 
583 	bool isSelected = true;
584 	wxString processInput = GetSelectedText();
585 	if (processInput.IsEmpty())
586 	{
587 		processInput = GetText();
588 		isSelected = false;
589 	}
590 	if (processInput.IsEmpty())
591 		return _("Nothing to format.");
592 
593 	wxString formatCmd = settings->GetExtFormatCmd();
594 	if (formatCmd.IsEmpty())
595 	{
596 		return _("You need to setup a formatting command");
597 	}
598 
599 	if (process)
600 	{
601 		delete process;
602 		process = NULL;
603 		processID = 0;
604 	}
605 	processOutput = wxEmptyString;
606 	processErrorOutput = wxEmptyString;
607 	processExitCode = 0;
608 
609 	process = new sysProcess(this, wxConvUTF8);
610 	processID = wxExecute(formatCmd, wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, process);
611 	if (!processID)
612 	{
613 		delete process;
614 		process = NULL;
615 		processID = 0;
616 		msg = _("Couldn't run formatting command: ") + formatCmd;
617 		return msg;
618 	}
619 	process->WriteOutputStream(processInput);
620 	process->CloseOutput();
621 
622 	int timeoutMs = settings->GetExtFormatTimeout();
623 	int timeoutStepMs = 100;
624 	int i = 0;
625 	while (process && i * timeoutStepMs < timeoutMs)
626 	{
627 		wxSafeYield();
628 		if (process)
629 			processOutput += process->ReadInputStream();
630 		wxSafeYield();
631 		wxMilliSleep(timeoutStepMs);
632 		i++;
633 	}
634 
635 	if (process)
636 	{
637 		AbortProcess();
638 		return wxString::Format(_("Formatting command did not respond in %d ms"), timeoutMs);
639 	}
640 
641 	if (processExitCode != 0)
642 	{
643 		processErrorOutput.Replace(wxT("\n"), wxT(" "));
644 		msg = wxString::Format(_("Error %d: "), processExitCode) + processErrorOutput;
645 		return msg;
646 	}
647 	else if (processOutput.Trim().IsEmpty())
648 	{
649 		return _("Formatting command error: Output is empty.");
650 	}
651 
652 	if (isSelected)
653 		ReplaceSelection(processOutput);
654 	else
655 		SetText(processOutput);
656 
657 	return _("Formatting complete.");
658 }
659 
AbortProcess()660 void ctlSQLBox::AbortProcess()
661 {
662 	if (process && processID)
663 	{
664 #ifdef __WXMSW__
665 		// SIGTERM is useless for Windows console apps
666 		wxKill(processID, wxSIGKILL, NULL, wxKILL_CHILDREN);
667 #else
668 		wxKill(processID, wxSIGTERM, NULL, wxKILL_CHILDREN);
669 #endif
670 		processID = 0;
671 	}
672 }
673 
OnPositionStc(wxStyledTextEvent & event)674 void ctlSQLBox::OnPositionStc(wxStyledTextEvent &event)
675 {
676 	int pos = GetCurrentPos();
677 	wxChar ch = GetCharAt(pos - 1);
678 	wxChar nextch = GetCharAt(pos);
679 	int st = GetStyleAt(pos - 1);
680 	int match;
681 
682 
683 	// Line numbers
684 	// Ensure we don't recurse through any paint handlers on Mac
685 #ifdef __WXMAC__
686 	Freeze();
687 #endif
688 	UpdateLineNumber();
689 #ifdef __WXMAC__
690 	Thaw();
691 #endif
692 
693 	// Clear all highlighting
694 	BraceBadLight(wxSTC_INVALID_POSITION);
695 
696 	// Check for braces that aren't in comment styles,
697 	// double quoted styles or single quoted styles
698 	if ((ch == '{' || ch == '}' ||
699 	        ch == '[' || ch == ']' ||
700 	        ch == '(' || ch == ')') &&
701 	        st != 2 && st != 6 && st != 7)
702 	{
703 		match = BraceMatch(pos - 1);
704 		if (match != wxSTC_INVALID_POSITION)
705 			BraceHighlight(pos - 1, match);
706 	}
707 	else if ((nextch == '{' || nextch == '}' ||
708 	          nextch == '[' || nextch == ']' ||
709 	          nextch == '(' || nextch == ')') &&
710 	         st != 2 && st != 6 && st != 7)
711 	{
712 		match = BraceMatch(pos);
713 		if (match != wxSTC_INVALID_POSITION)
714 			BraceHighlight(pos, match);
715 	}
716 
717 	// Roll back through the doc and highlight any unmatched braces
718 	while ((pos--) >= 0)
719 	{
720 		ch = GetCharAt(pos);
721 		st = GetStyleAt(pos);
722 
723 		if ((ch == '{' || ch == '}' ||
724 		        ch == '[' || ch == ']' ||
725 		        ch == '(' || ch == ')') &&
726 		        st != 2 && st != 6 && st != 7)
727 		{
728 			match = BraceMatch(pos);
729 			if (match == wxSTC_INVALID_POSITION)
730 			{
731 				BraceBadLight(pos);
732 				break;
733 			}
734 		}
735 	}
736 
737 	event.Skip();
738 }
739 
740 
OnMarginClick(wxStyledTextEvent & event)741 void ctlSQLBox::OnMarginClick(wxStyledTextEvent &event)
742 {
743 	if (event.GetMargin() == 2)
744 		ToggleFold(LineFromPosition(event.GetPosition()));
745 
746 	event.Skip();
747 }
748 
749 
750 extern "C" char *tab_complete(const char *allstr, const int startptr, const int endptr, void *dbptr);
OnAutoComplete(wxCommandEvent & rev)751 void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
752 {
753 	if (GetReadOnly())
754 		return;
755 	if (m_database == NULL)
756 		return;
757 	if (m_autocompDisabled)
758 		return;
759 
760 	wxString what = GetCurLine().Left(GetCurrentPos() - PositionFromLine(GetCurrentLine()));;
761 	int spaceidx = what.Find(' ', true);
762 
763 	char *tab_ret;
764 	if (spaceidx == -1)
765 		tab_ret = tab_complete(what.mb_str(wxConvUTF8), 0, what.Len() + 1, m_database);
766 	else
767 		tab_ret = tab_complete(what.mb_str(wxConvUTF8), spaceidx + 1, what.Len() + 1, m_database);
768 
769 	if (tab_ret == NULL || tab_ret[0] == '\0')
770 		return; /* No autocomplete available for this string */
771 
772 	wxString wxRet = wxString(tab_ret, wxConvUTF8);
773 	free(tab_ret);
774 
775 	// Switch to the generic list control. Native doesn't play well with
776 	// autocomplete on Mac.
777 #ifdef __WXMAC__
778 	wxSystemOptions::SetOption(wxT("mac.listctrl.always_use_generic"), true);
779 #endif
780 
781 	if (spaceidx == -1)
782 		AutoCompShow(what.Len(), wxRet);
783 	else
784 		AutoCompShow(what.Len() - spaceidx - 1, wxRet);
785 
786 	// Now switch back
787 #ifdef __WXMAC__
788 	wxSystemOptions::SetOption(wxT("mac.listctrl.always_use_generic"), false);
789 #endif
790 }
791 
792 
~ctlSQLBox()793 ctlSQLBox::~ctlSQLBox()
794 {
795 	if (m_dlgFindReplace)
796 	{
797 		m_dlgFindReplace->Destroy();
798 		m_dlgFindReplace = 0;
799 	}
800 	AbortProcess();
801 }
802 
803 
804 /*
805  * Callback function from tab-complete.c, bridging the gap between C++ and C.
806  * Execute a query using the C++ APIs, returning it as a tab separated
807  * "char*-string"
808  * The query is expected to return only one column, and will have an ORDER BY
809  * clause for this column added automatically.
810  */
811 extern "C"
pg_query_to_single_ordered_string(char * query,void * dbptr)812 char *pg_query_to_single_ordered_string(char *query, void *dbptr)
813 {
814 	pgConn *db = (pgConn *)dbptr;
815 	pgSet *res = db->ExecuteSet(wxString(query, wxConvUTF8) + wxT(" ORDER BY 1"));
816 	if (!res)
817 		return NULL;
818 
819 	wxString ret = wxString();
820 	wxString tmp;
821 
822 	while (!res->Eof())
823 	{
824 		tmp =  res->GetVal(0);
825 		if (tmp.Mid(tmp.Length() - 1) == wxT("."))
826 			ret += tmp + wxT("\t");
827 		else
828 			ret += tmp + wxT(" \t");
829 
830 		res->MoveNext();
831 	}
832 
833 	if(res)
834 	{
835 		delete res;
836 		res = NULL;
837 	}
838 	ret.Trim();
839 	// Trims both space and tab, but we want to keep the space!
840 	if (ret.Length() > 0)
841 		ret += wxT(" ");
842 
843 	return strdup(ret.mb_str(wxConvUTF8));
844 }
845 
846 
847 // Find some text in the document.
RegexFindText(int minPos,int maxPos,const wxString & text)848 CharacterRange ctlSQLBox::RegexFindText(int minPos, int maxPos, const wxString &text)
849 {
850 	TextToFind  ft;
851 	ft.chrg.cpMin = minPos;
852 	ft.chrg.cpMax = maxPos;
853 	wxWX2MBbuf buf = text.mb_str(wxConvUTF8);
854 	ft.lpstrText = (char *)(const char *)buf;
855 
856 	if (SendMsg(2150, wxSTC_FIND_REGEXP, (long)&ft) == -1)
857 	{
858 		ft.chrgText.cpMin = -1;
859 		ft.chrgText.cpMax = -1;
860 	}
861 
862 	return ft.chrgText;
863 }
864