#include "ET++.ph" #ifdef __GNUG__ #pragma implementation #endif #include "CodeTextView.h" #include "Class.h" #include "VObjectText.h" #include "RunArray.h" #include "Env.h" #include "Math.h" #include "WindowSystem.h" #include "Timer.h" #include "System.h" #include "FileData.h" #include "Picture.h" #include "TextMarks.h" #include "RegularExp.h" #include "TextReader.h" #include "String.h" static const char *match = "lass", *obrackets = "({[\"", *cbrackets = ")}]\""; static Ink *commentInk, *functionInk, *classInk; static struct regExps_t { char *rex; RegularExp *r; int tag; // register to put into VisualMark int file; // register used as a filename int pre; // register to put in front of mark int post; // register to put at the end of mark } regExps[] = { { "^\\(#[ \t]*include[ \t]*\"\\)\\(.+\\.xpm3?\\|.+\\.image\\|.+\\.icon\\)\\(\"\\)", 0, 2, 2, 1, 3 }, { "\\(//@Pict:\"\\)\\([^\"]+\\)\\(\"\\)", 0, 0, 2, -1, -1 }, { 0, 0, -1, -1, -1, -1 } }; ONEXIT(CodeTextView) { for (int i= 0; regExps[i].rex; i++) SafeDelete(regExps[i].r); } //---- CodeReformatter --------------------------------------------------------- class CodeReformatter : public Timer { CodeTextView *textView; public: CodeReformatter(CodeTextView *ct, int ms) : Timer(ms) { textView= ct; } bool Notify(); }; bool CodeReformatter::Notify() { if (textView->NeedsReformat()) textView->Reformat(); Reset(); return TRUE; } //---- CodeTextView ------------------------------------------------------------ NewMetaImpl(CodeTextView,VObjectTextView, (TB(autoIndent), T(cursorPos))); CodeTextView::CodeTextView(EvtHandler *eh, const Rectangle &r, VObjectText *t, bool w, TextViewFlags f, const Point &b, TViewAlign ta, int id) : VObjectTextView(eh, r, t, w, f, b, ta, id) { SetStopChars("\n\r"); autoIndent= TRUE; SetupStyles(t->GetFont()); ExitCursorMode(); int interval= Env::GetValue("CodeText.AutoReformatInterval", 10000); if (interval > 0) reformatter= new CodeReformatter(this, interval); else reformatter= 0; } CodeTextView::~CodeTextView() { if (reformatter) { reformatter->Remove(); reformatter= 0; } } Command *CodeTextView::DoKeyCommand(int ch, Token token) { ExitCursorMode(); if (autoIndent && (ch == '\r' || ch == '\n')) { // auto indent int from, to, i; Text *t= GetText(), *scratchText= text->MakeScratchText(0, 20); GetSelection(&from, &to); scratchText->Append('\n'); for (i= StartLine(CharToLine(from)); i < t->End(); i++) { ch= (*t)[i]; if (ch == ' ' || ch == '\t') scratchText->Append(ch); else break; } Command *cmd= InsertText(scratchText); delete scratchText; RevealSelection(); return cmd; } return TextView::DoKeyCommand(ch, token); } void CodeTextView::SetFont(Font *fp) { SetupStyles(fp); TextView::SetFont(fp); } void CodeTextView::SetupStyles(Font *fp) { int declsize= fp->Size() + Env::GetValue("CodeText.DeclarationSizeIncrement", 2); if (commentInk == 0) { if (gColor) { commentInk= new_Ink("CodeText.CommentColor", new_RGBColor(0, 0, 255)); functionInk= new_Ink("CodeText.FunctionColor", new_RGBColor(0, 0, 0)); classInk= new_Ink("CodeText.ClassColor", new_RGBColor(0, 0, 0)); } else commentInk= functionInk= classInk= gInkBlack; } commentStyle= new_CharStyle(new_Font(fp->Fid(), fp->Size(), eFaceItalic), commentInk); functionStyle= new_CharStyle(new_Font(fp->Fid(), declsize, eFaceBold), functionInk); classDeclStyle= new_CharStyle(new_Font(fp->Fid(), declsize, eFaceBold), classInk); plainStyle= new_CharStyle(new_Font(fp->Fid(), fp->Size(), eFacePlain)); } Command *CodeTextView::DoLeftButtonDownCommand(Point p, Token t, int cl) { Point sp(p); ExitCursorMode(); if (!Enabled()) return gNoChanges; if (cl >= 2) { int line, cpos; char *br; Point pp; Text *txt= GetText(); p-= GetInnerOrigin(); PointToPoint(p, &pp, &line, &cpos); if (cpos > 0 && (br= strchr(obrackets,(*txt)[cpos-1]))) { MatchBracketForward(cpos, (int)*br, cbrackets[br-obrackets]); return gNoChanges; } if (cpos < txt->End() && (br= strchr(cbrackets,(*txt)[cpos]))) { MatchBracketBackward(cpos-1, obrackets[br-cbrackets], (int)*br); return gNoChanges; } } return TextView::DoLeftButtonDownCommand(sp, t, cl); } Command *CodeTextView::DoMenuCommand(int m) { ExitCursorMode(); return TextView::DoMenuCommand(m); } Command *CodeTextView::DoOtherEventCommand(Point p, Token t) { ExitCursorMode(); return TextView::DoOtherEventCommand(p, t); } void CodeTextView::MatchBracketForward(int from, int obracket, int cbracket) { Text *txt= GetText(); int ch, stack= 0; for (int i= from; i < txt->End(); i++) { ch= (*txt)[i]; if (ch == cbracket) { if (stack-- == 0) break; } else if (ch == obracket) stack++; } SetSelection(from, i, TRUE); } void CodeTextView::MatchBracketBackward(int from, int obracket, int cbracket) { Text *txt= GetText(); int ch, stack= 0; for (int i= from; i >= 0; i--) { ch= (*txt)[i]; if (ch == obracket) { if (stack-- == 0) break; } else if (ch == cbracket) stack++; } SetSelection(i+1, from+1, TRUE); } Command *CodeTextView::DoCursorKeyCommand(EvtCursorDir cd, Token t) { if (cd == eCrsLeft || cd == eCrsRight) ExitCursorMode(); return TextView::DoCursorKeyCommand(cd, t); } int CodeTextView::CursorPos(int at, int line, EvtCursorDir d, Point p) { int charNo; Point basePoint; if (cursorPos == gPoint_1) CharToPoint(at, &line, &cursorPos); if (d == eCrsDown) line= Math::Min(nLines-1, line+1); else line= Math::Max(0, line-1); basePoint= LineToPoint(line, TRUE) + Point(cursorPos.x, 0); PointToPoint(basePoint, &p, &line, &charNo); return charNo; } PrettyPrinter *CodeTextView::MakePrettyPrinter(Text *t, CharStyle *cs, CharStyle *fs, CharStyle *cds, CharStyle *ps) { return new PrettyPrinter(t, cs, fs, cds, ps); } void CodeTextView::FormatCode() { if (!text->IsKindOf(StyledText)) return; PrettyPrinter *pp= MakePrettyPrinter(text, commentStyle, functionStyle, classDeclStyle, plainStyle); pp->Doit(); SafeDelete(pp); needsReformat= FALSE; } void CodeTextView::SetDefaultStyle() { if (!text->IsKindOf(StyledText)) return; StyledText *stext= (StyledText*)text; RunArray *st= new RunArray(); st->Insert(plainStyle, 0, 0, stext->Size()); RunArray *tmpp= stext->SetCharStyles(st); delete tmpp; } void CodeTextView::DoObserve(int id, int part, void *what, Object *op) { TextView::DoObserve(id, part, what, op); if (op == GetText() && (part == eTextDeleted || eTextReplaced)) InvalidateFormatting(); } void CodeTextView::InvalidateFormatting() { needsReformat= TRUE; if (reformatter) reformatter->Reset(); } void CodeTextView::Reformat() { FormatCode(); if (NumberOfLines() > 500) // cheat ForceRedraw(); else RepairAll(); } Command *CodeTextView::PasteData(Data *data) { if (data->IsKindOf(FileData) && Env::GetValue("CodeText.AllowGraphics", TRUE)) { if (data->CanConvert(Meta(VObject)) || data->CanConvert(Meta(Picture)) || data->CanConvert(Meta(Bitmap))) return new PasteVObjectCommand(this, new ReferenceMark(data->FullName(), form("//@Pict:\"%s\"", data->FullName()))); } return TextView::PasteData(data); } //---- ???? -------------------------------------------------------------------- bool ReadFromAsPureTextWithMarks(IStream &s, long sizehint, StyledText *tx) { if (!Env::GetValue("CodeText.AllowGraphics", TRUE)) { tx->ReadFromAsPureText(s, sizehint); return TRUE; } int size= Env::GetValue("filebrowser.FontSize", 12); Font *fd= new_Font(gFixedFont->Fid(), size, eFacePlain); TextReader tr(tx, new_CharStyle(fd), new_ParaStyle()); char buf[1000]; int len, pos; RegExRegs regs; regExps_t *t; while (s.getline(buf, 1000)) { for (t= regExps; t->rex; t++) { if (t->r == 0) t->r= new RegularExp(t->rex, cCaseSensitive); pos= t->r->SearchForward(buf, &len, 0, -1, cMaxInt, ®s); if (pos >= 0) break; } if (pos < 0) { tr.Append(buf); } else { tr.Append(buf, pos); tr.Append(regs.StringAt(buf, t->pre)); VisualMark *vm= new ReferenceMark(regs.StringAt(buf, t->file)); vm->SetTag(regs.StringAt(buf, t->tag)); tr.InsertVisualMark(vm); tr.Append(regs.StringAt(buf, t->post)); tr.Append(&buf[pos+len]); } } return TRUE; } bool PrintOnAsPureTextWithMarks(OStream &s, StyledText *tx) { // ??? terminate last line with a '\n' ??? //if ((*tx)[tx->Size()-1] != '\n') { // fprintf(stderr, "appending newline\n"); // tx->Append('\n'); //} if (!Env::GetValue("CodeText.AllowGraphics", TRUE)) tx->PrintOnAsPureText(s); else { byte escape= tx->GetMarkChar(); AutoTextIter next(tx); int c; while ((c= next()) != cEOT) { if (c == escape && tx->IsVisualMark(next->GetLastPos())) s.form("%s", tx->GetVisualMarkAt(next->GetLastPos())->GetTag()); else if (c) s.put((char)c); } } return TRUE; } //---- CodeAnalyzer ------------------------------------------------------------ CodeAnalyzer::CodeAnalyzer(Text *t) { text= t; } void CodeAnalyzer::Doit() { inDefine= isescape= inClass= FALSE; lastComment= prevCh= braceStack= inString= line= 0; c= '\n'; int canBeClass= canBeFunction= -1; AutoTextIter next(text, 0, text->Size()); Start(); while ((c= next()) != cEOT) { if (isescape) isescape= FALSE; else switch (c) { case '#': if (prevCh == '\n') inDefine= TRUE; break; case '\\': isescape= TRUE; break; case '\n': inDefine= FALSE; line++; break; case '*': if (prevCh == '/' && inString == 0) { if (canBeFunction == -1 && canBeClass == -1) lastComment= next->GetPos(); FoundComment(&next); prevCh= 0; continue; } break; case '/': if (prevCh == '/' && inString == 0) { if (canBeFunction == -1 && canBeClass == -1) lastComment= next->GetPos(); FoundEndOfLineComment(&next); prevCh= 0; continue; } break; case ';': canBeFunction= canBeClass= -1; break; case '{': if (canBeFunction != -1) { FoundFunctionOrMethod(canBeFunction, lastComment); canBeFunction= -1; } if (canBeClass != -1) { FoundClassDecl(canBeClass); canBeClass= -1; } if (inString == 0) braceStack++; break; case '}': if (inString == 0) braceStack--; break; case '(': if (!inDefine && inString == 0) { if (braceStack == 0 && canBeFunction == -1) canBeFunction= next->GetPos()-1; braceStack++; } break; case ')': if (!inDefine && inString == 0) braceStack--; break; case '\'': case '\"': if (inString == 0) { if (c == '\"') inString= '\"'; else inString= '\''; } else { if ((inString == '\"' && c == '\"') || (inString == '\'' && c == '\'')) inString= 0; } break; default: if (c == 'c' && !inDefine && inString == 0 && !inClass && canBeClass == -1) { if (braceStack == 0) if (inClass= IsClassDecl(next->GetPos())) next->SetPos(next->GetPos()+4); } else if (inClass && Isinword(c)) { inClass= FALSE; canBeClass= next->GetPos(); } break; } prevCh= c; } End(); } void CodeAnalyzer::FoundComment(AutoTextIter *next) { int c, prevCh= 0, l= line, start= (*next)->GetPos()-2; while ((c= (*next)()) != cEOT) { if (c == '\n') line++; if (c == '/' && prevCh == '*') { Comment(l, start, (*next)->GetPos()); break; } prevCh= c; } } void CodeAnalyzer::FoundEndOfLineComment(AutoTextIter *next) { int c, start= (*next)->GetPos()-2; while ((c= (*next)()) != cEOT) if (c == '\n') { Comment(line, start, (*next)->GetPos()-1); line++; break; } } void CodeAnalyzer::FoundFunctionOrMethod(int at, int lastComment) { byte c; int pos= at, len= 0; while (--pos >= lastComment) { c= (*text)[pos]; if (!Isspace(c) && !strchr("[]*+-=%&><|:^%()~/",c)) // overloaded operators break; } while (pos >= lastComment) { c= (*text)[pos]; if (!(Isinword(c) || c == ':' || c == '~' )) break; if (pos-1 < 0) break; pos--; len++; } if (len) { char buf[500]; pos++; text->CopyInStr((byte*)buf, sizeof buf, pos, pos+len); char *p= strchr(buf, ':'); if (!p) Function(line, pos, pos+len, buf, 0); else { *p= '\0'; char *pp= p+2; Function(line, pos, pos+len, pp, buf); } } } bool CodeAnalyzer::IsClassDecl(int at) { const char *p= match; int c; AutoTextIter next(text, at, text->Size()); while ((c= next()) != cEOT && *p) if (c != *p++) return FALSE; return TRUE; } void CodeAnalyzer::FoundClassDecl(int at) { int end, c, start= at-1; AutoTextIter next(text, start, text->Size()); while ((c= next()) != cEOT) if (!Isinword(c)) break; end= next->GetPos()-1; if (start != end) { static byte name[1000]; text->CopyInStr(name, sizeof name, start, end); ClassDecl(line, start, end, (char*)name); } } void CodeAnalyzer::Start() { } void CodeAnalyzer::End() { } void CodeAnalyzer::Comment(int, int, int) { } void CodeAnalyzer::ClassDecl(int, int, int, const char*) { } void CodeAnalyzer::Function(int, int, int, const char*, const char*) { } //---- PrettyPrinter ----------------------------------------------------------- PrettyPrinter::PrettyPrinter(Text *t, CharStyle *cs, CharStyle *fs, CharStyle *cds, CharStyle *ps) : CodeAnalyzer(t) { stext= Guard(t, StyledText); plainStyle= ps; commentStyle= cs; classDeclStyle= cds; functionStyle= fs; } void PrettyPrinter::Start() { st= new RunArray(); st->Insert(plainStyle, 0, 0, stext->Size()); } void PrettyPrinter::End() { RunArray *tmpp= stext->SetCharStyles(st); delete tmpp; } void PrettyPrinter::Comment(int, int start, int end) { st->Insert(commentStyle, start, end, end-start); } void PrettyPrinter::ClassDecl(int, int start, int end, const char*) { st->Insert(classDeclStyle, start, end, end-start); } void PrettyPrinter::Function(int, int start, int end, const char*, const char*) { st->Insert(functionStyle, start, end, end-start); }