1 /*
2 FXiTe - The Free eXtensIble Text Editor
3 Copyright (c) 2009-2012 Jeffrey Pohlmeyer <yetanothergeek@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify it
6 under the terms of the GNU General Public License version 3 as
7 published by the Free Software Foundation.
8
9 This software is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19
20 #include <fx.h>
21 #include <SciLexer.h>
22
23 #include "prefs.h"
24 #include "scidoc.h"
25 #include "appwin_pub.h"
26
27 #include "intl.h"
28 #include "scidoc_util.h"
29
30 static FXSelector id_scintilla=0;
31 static FXSelector id_macro_record=0;
32
SetScintillaSelector(FXSelector sel)33 void SciDocUtils::SetScintillaSelector(FXSelector sel) { id_scintilla=sel; }
34
SetMacroRecordSelector(FXSelector sel)35 void SciDocUtils::SetMacroRecordSelector(FXSelector sel) { id_macro_record=sel; }
36
37
CharAdded(SciDoc * sci,long line,long pos,int ch,Settings * prefs,SciDoc * recording)38 void SciDocUtils::CharAdded(SciDoc*sci, long line, long pos, int ch, Settings*prefs, SciDoc*recording)
39 {
40 if ( (line<=0) || (prefs->AutoIndent==AUTO_INDENT_NONE)) { return; }
41 bool smart=prefs->AutoIndent==AUTO_INDENT_SMART;
42 int indent_width=prefs->IndentWidth;
43 if (recording) { recording->sendMessage(SCI_STOPRECORD,0,0); }
44 switch (ch) {
45 case '\r': {
46 if (sci->sendMessage(SCI_GETEOLMODE,0,0)!=SC_EOL_CR) { break; } // or fall through for Mac.
47 }
48 case '\n': {
49 int prev_line=line-1;
50 bool tmp_tab=false;
51 if ( (sci->GetLineLength(prev_line)>0) && !sci->UseTabs() ) {
52 long prev_pos=sci->sendMessage(SCI_POSITIONFROMLINE,prev_line,0);
53 if (sci->sendMessage(SCI_GETCHARAT,prev_pos,0)=='\t') {
54 tmp_tab=true; // If previous line has a tab, override the editor preference.
55 }
56 }
57 long prev_indent=sci->sendMessage(SCI_GETLINEINDENTATION, prev_line, 0);
58 long curr_indent=sci->sendMessage(SCI_GETLINEINDENTATION, line, 0);
59 int tab_width=sci->TabWidth();
60 if (smart) {
61 long prev_pos=pos-2;
62 long eolmode=sci->sendMessage(SCI_GETEOLMODE,0,0);
63 if (eolmode==SC_EOL_CRLF) { prev_pos--; }
64 int prev_char=sci->sendMessage(SCI_GETCHARAT,prev_pos,0);
65 if (prev_char=='{') {
66 if (sci->sendMessage(SCI_GETCHARAT,pos,0)=='}') {
67 sci->sendString(SCI_INSERTTEXT,pos,
68 (eolmode==SC_EOL_LF)?"\n":(eolmode==SC_EOL_CRLF)?"\r\n":"\r");
69 sci->SetLineIndentation(line+1,prev_indent);
70 sci->sendMessage(SCI_GOTOPOS,pos,0);
71 }
72 prev_indent += sci->UseTabs()?tab_width:indent_width;
73 }
74 }
75 if ( curr_indent < prev_indent ) {
76 if (tmp_tab) {
77 sci->UseTabs(true);
78 sci->SetLineIndentation(line,prev_indent);
79 sci->UseTabs(false);
80 } else {
81 sci->SetLineIndentation(line,prev_indent);
82 }
83 if (sci->UseTabs()||tmp_tab) {
84 sci->GoToPos(sci->sendMessage(SCI_POSITIONFROMLINE,line,0)+(prev_indent/tab_width));
85 }
86 }
87 break;
88 }
89 case '}': {
90 if (smart) {
91 sci->getApp()->runWhileEvents();
92 int opener=sci->sendMessage(SCI_BRACEMATCH,pos-1,0);
93 if (opener>=0) {
94 long match_line=sci->sendMessage(SCI_LINEFROMPOSITION,opener,0);
95 if (match_line<line) {
96 long match_indent=sci->sendMessage(SCI_GETLINEINDENTATION, match_line, 0);
97 sci->sendMessage(SCI_SETLINEINDENTATION, line, match_indent);
98 }
99 }
100 }
101 }
102 }
103 if (recording) { recording->sendMessage(SCI_STARTRECORD,0,0); }
104 }
105
106
107
AdjustIndent(SciDoc * sci,char ch,Settings * prefs,SciDoc * recording)108 void SciDocUtils::AdjustIndent(SciDoc*sci, char ch, Settings*prefs, SciDoc*recording)
109 {
110 sci->getApp()->runWhileEvents();
111 long pos=sci->sendMessage(SCI_GETCURRENTPOS,0,0);
112 long line=sci->sendMessage(SCI_LINEFROMPOSITION,pos,0);
113 CharAdded(sci, line, pos, ch, prefs, recording);
114 }
115
116
117
118 // Check for an already-selected filename
GetFilenameFromSelection(FXMainWindow * tw,SciDoc * sci,FXString & filename)119 static void GetFilenameFromSelection(FXMainWindow*tw,SciDoc*sci, FXString &filename)
120 {
121 #ifdef WIN32
122 sci->GetSelText(filename);
123 #else // On X11 platforms, try first to get a filename from the X-Selection
124 FXuchar*xsel=NULL;
125 FXuint xlen=0;
126 FXDragType types[] = { tw->textType, tw->utf8Type, tw->stringType, 0 };
127 for ( FXDragType*type=types; *type; type++ ) {
128 if (tw->getDNDData(FROM_SELECTION,*type, xsel, xlen) && xsel && *xsel) {
129 FXuchar*eol=(FXuchar*)memchr(xsel,'\n', xlen);
130 FXuint n = eol ? (eol-xsel) : xlen;
131 filename.assign((FXchar*)xsel,n);
132 filename=filename.simplify();
133 if (!FXStat::exists(filename.contains(':')?filename.section(':',0):filename)) {
134 filename=FXString::null;
135 }
136 break;
137 }
138 if ( filename.empty() ) { sci->GetSelText(filename); }
139 }
140 #endif
141 }
142
143
144
145 // Try to find a filename at the current position in the document.
GetFilenameAtCursor(SciDoc * sci,FXString & filename)146 static bool GetFilenameAtCursor(SciDoc*sci, FXString &filename)
147 {
148 long max=sci->GetTextLength();
149 if (max<=0) { return false; }
150 TextRange range;
151 memset(&range,0,sizeof(range));
152 range.chrg.cpMin=sci->GetCaretPos();
153 if ( (range.chrg.cpMin>0) && (sci->CharAt(range.chrg.cpMin)<='*') && (sci->CharAt(range.chrg.cpMin-1)>'*') ) {
154 // Caret is at the end of a phrase, back up one before looking for start...
155 range.chrg.cpMin--;
156 }
157 // Read backwards till we find the start of our phrase...
158 while ( (range.chrg.cpMin>0) && (sci->CharAt(range.chrg.cpMin)>'*') ) { range.chrg.cpMin--; }
159 if ( (range.chrg.cpMin<max) && (sci->CharAt(range.chrg.cpMin)<='*') ) { range.chrg.cpMin++; }
160 if (range.chrg.cpMin>=max) { return false; }
161 range.chrg.cpMax=range.chrg.cpMin+1;
162 // Now read forward, looking for the end of our phrase...
163 while ( (range.chrg.cpMax<max) && (sci->CharAt(range.chrg.cpMax)>'*') ) { range.chrg.cpMax++; }
164 long len=range.chrg.cpMax-range.chrg.cpMin;
165 if (len<=0) { return false; }
166 range.lpstrText=(char*)calloc(len+1,1);
167 sci->sendMessage(SCI_GETTEXTRANGE,0,reinterpret_cast<sptr_t>(&range));
168 filename=range.lpstrText;
169 free(range.lpstrText);
170 return filename.empty()?false:true;
171 }
172
173
174
175 // Look for file: first in active document's directory; then in current working directory
OpenLocalIncludeFile(SciDoc * sci,const FXString & filename,const FXString & line)176 static bool OpenLocalIncludeFile(SciDoc*sci, const FXString &filename, const FXString &line)
177 {
178 if (!sci->Filename().empty()) {
179 FXString fullpath=FXPath::directory(sci->Filename())+PATHSEPSTRING+filename;
180 if (FXStat::exists(fullpath)) {
181 TopWinPub::OpenFile(fullpath.text(),line.text(),false,true);
182 return true;
183 }
184 }
185 if (FXStat::exists(filename)) {
186 TopWinPub::OpenFile(filename.text(),line.text(),false,true);
187 return true;
188 }
189 return false;
190 }
191
192
193
194 // Look for file in system include directories
OpenSystemIncludeFile(SciDoc * sci,const FXString & filename,const FXString & line)195 static bool OpenSystemIncludeFile(SciDoc*sci, const FXString &filename, const FXString &line)
196 {
197 const FXString paths=Settings::SystemIncludePaths();
198 for (FXint i=0; i<paths.contains('\n'); i++) {
199 FXString fullpath=paths.section('\n',i);
200 if (fullpath.empty()) { continue; }
201 fullpath+=PATHSEPSTRING;
202 fullpath+=filename;
203 if (FXStat::exists(fullpath)) {
204 TopWinPub::OpenFile(fullpath.text(),line.text(),false,true);
205 return true;
206 }
207 }
208 return false;
209 }
210
211
212
213 // Look for line number after filename in the form of FILE.EXT:NNN
ParseLineNumberFromFilename(FXString & filename,FXString & line)214 static void ParseLineNumberFromFilename(FXString &filename, FXString &line)
215 {
216 #ifdef WIN32 // Ignore colon in drive spec on WIN32
217 FXint colons=filename.contains(':');
218 if (FXPath::isAbsolute(filename)) {
219 if (colons>1) {
220 line=filename.section(':',2);
221 filename=filename.section(':',0,2);
222 }
223 } else {
224 if (colons>0) {
225 line=filename.section(':',1) ;
226 filename=filename.section(':',0);
227 }
228 }
229 #else
230 if (filename.contains(':')) {
231 line=filename.section(':',1) ;
232 filename=filename.section(':',0);
233 }
234 #endif
235 for (FXint i=0; i<line.length(); i++) {
236 if (!Ascii::isDigit(line[i])) { // If it's not all digits, forget it.
237 line=FXString::null;
238 break;
239 }
240 }
241 }
242
243
244
OpenSelected(FXMainWindow * tw,SciDoc * sci)245 void SciDocUtils::OpenSelected(FXMainWindow*tw, SciDoc*sci)
246 {
247 FXString filename=FXString::null;
248 FXString line=FXString::null;
249 GetFilenameFromSelection(tw,sci,filename);
250 if (filename.empty()) {
251 // Even if nothing is selected, look around for something that might be a filename...
252 if (!GetFilenameAtCursor(sci,filename)) { return; }
253 }
254 ParseLineNumberFromFilename(filename,line);
255 if (sci->sendMessage(SCI_GETLEXER,0,0)==SCLEX_CPP) {
256 bool syshdr=false;
257 if ( (filename[0]=='<') && (filename[filename.length()-1]=='>') ) {
258 filename.erase(0,1);
259 filename.trunc(filename.length()-1);
260 if (filename.empty()) { return; }
261 syshdr=true;
262 }
263 if (FXPath::isAbsolute(filename)&&FXStat::exists(filename)) {
264 TopWinPub::OpenFile(filename.text(),line.text(),false,true);
265 return;
266 }
267 if (syshdr) {
268 if (OpenSystemIncludeFile(sci,filename,line)) { return; }
269 if (OpenLocalIncludeFile( sci,filename,line)) { return; }
270 } else {
271 if (OpenLocalIncludeFile( sci,filename,line)) { return; }
272 if (OpenSystemIncludeFile(sci,filename,line)) { return; }
273 }
274 } else {
275 if (FXStat::exists(filename)) {
276 TopWinPub::OpenFile(filename.text(),line.text(),false,true);
277 return;
278 } else {
279 if ( (!FXPath::isAbsolute(filename)) && (!sci->Filename().empty()) ) {
280 FXString fullpath=FXPath::directory(sci->Filename())+PATHSEPSTRING+filename;
281 if (FXStat::exists(fullpath)) {
282 TopWinPub::OpenFile(fullpath.text(),line.text(),false,true);
283 return;
284 }
285 }
286 }
287 }
288 // Looks like we failed - pretty up the filename so we can use it in an error message
289 filename=filename.section("\n",0);
290 filename.trunc(128);
291 FXMessageBox::error(tw, MBOX_OK, _("File not found"), "%s:\n%s", _("Cannot find file"), filename.text());
292 }
293
294
295
296
SetSciDocPrefs(SciDoc * sci,Settings * prefs)297 void SciDocUtils::SetSciDocPrefs(SciDoc*sci, Settings*prefs)
298 {
299 sci->sendMessage(SCI_SETMULTIPLESELECTION,false,0);
300 sci->sendMessage(SCI_SETADDITIONALSELECTIONTYPING,true,0);
301 sci->ShowLineNumbers(prefs->ShowLineNumbers);
302 sci->ShowWhiteSpace(prefs->ShowWhiteSpace);
303 sci->SetShowEdge(prefs->ShowRightEdge);
304 sci->SetZoom(prefs->ZoomFactor);
305 sci->setFont(prefs->fontdesc.face, prefs->fontdesc.size / 10);
306 sci->sendMessage(SCI_SETEXTRAASCENT,prefs->FontAscent,0);
307 sci->sendMessage(SCI_SETEXTRADESCENT,prefs->FontDescent,0);
308 sci->CaretLineBG(prefs->ShowCaretLine?prefs->CaretLineBG():NULL);
309 sci->RightMarginBG(prefs->RightMarginBG());
310 sci->CaretWidth(prefs->CaretWidth);
311 sci->SmartHome(prefs->SmartHome);
312 sci->SetWrapAware(prefs->WrapAwareHomeEnd);
313 sci->SmoothScroll(prefs->SmoothScroll);
314 sci->TabWidth(prefs->TabWidth);
315 sci->UseTabs(prefs->UseTabs);
316 sci->WhiteSpaceBG(prefs->WhiteSpaceBG());
317 sci->WhiteSpaceFG(prefs->WhiteSpaceFG());
318 sci->SetEdgeColumn(prefs->RightEdgeColumn);
319 sci->SetShowIndent(prefs->ShowIndentGuides);
320
321 sci->CaretFG(prefs->CaretFG());
322 sci->SelectionBG(prefs->SelectionBG());
323 if (prefs->ShowWhiteSpace) { sci->ShowWhiteSpace(true); }
324 if (prefs->ShowLineNumbers) { sci->ShowLineNumbers(true); }
325
326 sci->sendMessage(SCI_SETVIRTUALSPACEOPTIONS,
327 (SCVS_RECTANGULARSELECTION|(prefs->CaretPastEOL?SCVS_USERACCESSIBLE:0)), 0);
328
329 }
330
331
332
CycleSplitter(SciDoc * sci,Settings * prefs)333 void SciDocUtils::CycleSplitter(SciDoc*sci, Settings*prefs)
334 {
335 switch (prefs->SplitView) {
336 case SPLIT_NONE: {
337 switch (sci->GetSplit()) {
338 case SPLIT_NONE: {
339 sci->SetSplit(SPLIT_BELOW);
340 break;
341 }
342 case SPLIT_BELOW: {
343 sci->SetSplit(SPLIT_BESIDE);
344 break;
345 }
346 case SPLIT_BESIDE: {
347 sci->SetSplit(SPLIT_NONE);
348 break;
349 }
350 }
351 break;
352 }
353 case SPLIT_BELOW:
354 case SPLIT_BESIDE: {
355 switch (sci->GetSplit()) {
356 case SPLIT_NONE: {
357 sci->SetSplit(prefs->SplitView);
358 break;
359 }
360 case SPLIT_BELOW:
361 case SPLIT_BESIDE: {
362 sci->SetSplit(SPLIT_NONE);
363 break;
364 }
365 }
366 break;
367 }
368 }
369 SciDoc* sci2=(SciDoc*)sci->getNext();
370 if (sci2) {
371 SetSciDocPrefs(sci2,prefs);
372 sci2->setFocus();
373 } else {
374 sci->setFocus();
375 }
376 }
377
378
Cut(SciDoc * sci)379 void SciDocUtils::Cut(SciDoc*sci)
380 {
381 sci->setFocus();
382 if (sci->GetSelLength()>0) { sci->sendMessage(SCI_CUT,0,0); }
383 }
384
385
386
Copy(SciDoc * sci)387 void SciDocUtils::Copy(SciDoc*sci)
388 {
389 sci->setFocus();
390 // If any text is already selected, make sure the selection is "alive"
391 long start=sci->sendMessage(SCI_GETSELECTIONSTART,0,0);
392 long end=sci->sendMessage(SCI_GETSELECTIONEND,0,0);
393 if (start!=end) {
394 sci->sendMessage(SCI_SETSELECTIONSTART,start,0);
395 sci->sendMessage(SCI_SETSELECTIONEND,end,0);
396 }
397 if (sci->GetSelLength()>0) { sci->sendMessage(SCI_COPY,0,0); }
398 }
399
400
401
Paste(SciDoc * sci)402 void SciDocUtils::Paste(SciDoc*sci)
403 {
404 sci->setFocus();
405 if (sci->sendMessage(SCI_CANPASTE,0,0)) {
406 // If any text is already selected, make sure the selection is "alive"
407 long start=sci->sendMessage(SCI_GETSELECTIONSTART,0,0);
408 long end=sci->sendMessage(SCI_GETSELECTIONEND,0,0);
409 if (start!=end) {
410 sci->sendMessage(SCI_SETSELECTIONSTART,start,0);
411 sci->sendMessage(SCI_SETSELECTIONEND,end,0);
412 }
413 sci->sendMessage(SCI_PASTE,0,0);
414 sci->sendMessage(SCI_CONVERTEOLS,sci->sendMessage(SCI_GETEOLMODE,0,0),0);
415 sci->ScrollWrappedInsert();
416 }
417 }
418
419
420
Indent(SciDoc * sci,bool forward,bool single_space,int indent_width)421 void SciDocUtils::Indent(SciDoc*sci, bool forward, bool single_space, int indent_width)
422 {
423 long msg=forward?SCI_TAB:SCI_BACKTAB;
424 int tab_width=sci->TabWidth();
425 if (single_space)
426 {
427 FXbool use_tabs=sci->UseTabs();
428 sci->UseTabs(false);
429 sci->sendMessage(SCI_SETTABWIDTH,1,0);
430 sci->sendMessage(msg,0,0);
431 sci->TabWidth(tab_width);
432 sci->UseTabs(use_tabs);
433 } else {
434 sci->TabWidth(sci->UseTabs()?tab_width:indent_width);
435 sci->sendMessage(msg,0,0);
436 sci->TabWidth(tab_width);
437 }
438 }
439
440
441
NewSci(FXComposite * p,FXObject * trg,Settings * prefs)442 SciDoc* SciDocUtils::NewSci(FXComposite*p, FXObject*trg, Settings*prefs)
443 {
444 SciDoc*sci=new SciDoc(p,trg,id_scintilla);
445 SetSciDocPrefs(sci,prefs);
446 sci->SetWordWrap(prefs->WordWrap);
447 sci->DoStaleTest(true);
448 return sci;
449 }
450
451
452
InsertFile(SciDoc * sci,const FXString & filename)453 bool SciDocUtils::InsertFile(SciDoc *sci, const FXString &filename)
454 {
455 if (sci->InsertFile(filename.text())) {
456 sci->ScrollWrappedInsert();
457 return true;
458 } else {
459 FXMessageBox::error(sci->getShell(), MBOX_OK, _("Error opening file"), "%s:\n%s\n%s",
460 _("Could not open file"), filename.text(), sci->GetLastError().text());
461 }
462 return false;
463 }
464
465
466
DoneSci(SciDoc * sci,SciDoc * recording)467 void SciDocUtils::DoneSci(SciDoc*sci, SciDoc*recording)
468 {
469 if (recording==sci) { TopWinPub::instance()->handle(NULL,FXSEL(SEL_COMMAND,id_macro_record),NULL); }
470 if (sci->hasClipboard()) { TopWinPub::SaveClipboard(); }
471 FXWindow*page=sci->getParent();
472 FXWindow*tab=page->getPrev();
473 delete sci;
474 delete (FXMenuCommand*)tab->getUserData();
475 delete tab;
476 delete page;
477 }
478
479
480
Filename(SciDoc * sci)481 FXString SciDocUtils::Filename(SciDoc*sci)
482 {
483 return sci->Filename();
484 }
485
486
487
SetFocus(SciDoc * sci)488 void SciDocUtils::SetFocus(SciDoc*sci)
489 {
490 sci->setFocus();
491 }
492
493
494
KillFocus(SciDoc * sci)495 void SciDocUtils::KillFocus(SciDoc*sci)
496 {
497 sci->killFocus();
498 }
499
500
501
CopyText(SciDoc * sci,const FXString & txt)502 void SciDocUtils::CopyText(SciDoc*sci, const FXString &txt)
503 {
504 sci->sendString(SCI_COPYTEXT, txt.length(), txt.text());
505 }
506
507
508
Reload(SciDoc * sci)509 bool SciDocUtils::Reload(SciDoc*sci)
510 {
511 long pos=sci->sendMessage(SCI_GETCURRENTPOS,0,0);
512 SciDoc*sci2=sci->Slave();
513 long pos2=sci2?sci2->sendMessage(SCI_GETCURRENTPOS,0,0):0;
514 if ( !sci->LoadFromFile(sci->Filename().text()) ) {
515 FXMessageBox::error(sci->getShell(),
516 MBOX_OK,_("Reload failed"), "%s\n%s", sci->Filename().text(), sci->GetLastError().text());
517 return false;
518 }
519 sci->sendMessage(SCI_GOTOPOS,pos,0);
520 if (sci2) { sci2->sendMessage(SCI_GOTOPOS,pos2,0); }
521 FXTabItem*tab=(FXTabItem*)sci->getParent()->getPrev();
522 tab->setText(FXPath::name(sci->Filename()));
523 sci->DoStaleTest(true);
524 return true;
525 }
526
527
528
Dirty(SciDoc * sci)529 bool SciDocUtils::Dirty(SciDoc*sci)
530 {
531 return sci->Dirty();
532 }
533
534
535
SaveToFile(SciDoc * sci,const char * filename,bool as_itself)536 bool SciDocUtils::SaveToFile(SciDoc*sci, const char*filename, bool as_itself)
537 {
538 if (!sci->SaveToFile(filename,as_itself)) { return false; }
539 if (as_itself) {
540 ((FXTabItem*)(sci->getParent()->getPrev()))->setText(FXPath::name(filename));
541 }
542 return true;
543 }
544
545
546
GetLastError(SciDoc * sci)547 const FXString SciDocUtils::GetLastError(SciDoc*sci)
548 {
549 return sci->GetLastError();
550 }
551
552
553
ID(SciDoc * sci)554 FXival SciDocUtils::ID(SciDoc*sci)
555 {
556 return (FXival)sci->id();
557 }
558
559
NeedBackup(SciDoc * sci,bool need)560 void SciDocUtils::NeedBackup(SciDoc*sci, bool need)
561 {
562 sci->NeedBackup(need);
563 }
564
565