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