// Copyright 2008-2010 Sergiu Dotenco. The License.txt file describes the // conditions under which this software may be distributed. /** * @file LexBibTeX.cxx * @brief General BibTeX coloring scheme. * @author Sergiu Dotenco * @date April 18, 2009 */ #include #include #include #include #include #include #include #include "ILexer.h" #include "Scintilla.h" #include "SciLexer.h" #include "PropSetSimple.h" #include "WordList.h" #include "LexAccessor.h" #include "Accessor.h" #include "StyleContext.h" #include "CharacterSet.h" #include "LexerModule.h" #ifdef SCI_NAMESPACE using namespace Scintilla; #endif namespace { bool IsAlphabetic(unsigned int ch) { return IsASCII(ch) && std::isalpha(ch) != 0; } bool IsAlphaNumeric(char ch) { return IsASCII(ch) && std::isalnum(ch); } bool EqualCaseInsensitive(const char* a, const char* b) { return CompareCaseInsensitive(a, b) == 0; } bool EntryWithoutKey(const char* name) { return EqualCaseInsensitive(name,"string"); } char GetClosingBrace(char openbrace) { char result = openbrace; switch (openbrace) { case '(': result = ')'; break; case '{': result = '}'; break; } return result; } bool IsEntryStart(char prev, char ch) { return prev != '\\' && ch == '@'; } bool IsEntryStart(const StyleContext& sc) { return IsEntryStart(sc.chPrev, sc.ch); } void ColorizeBibTeX(Sci_PositionU start_pos, Sci_Position length, int /*init_style*/, WordList* keywordlists[], Accessor& styler) { WordList &EntryNames = *keywordlists[0]; bool fold_compact = styler.GetPropertyInt("fold.compact", 1) != 0; std::string buffer; buffer.reserve(25); // We always colorize a section from the beginning, so let's // search for the @ character which isn't escaped, i.e. \@ while (start_pos > 0 && !IsEntryStart(styler.SafeGetCharAt(start_pos - 1), styler.SafeGetCharAt(start_pos))) { --start_pos; ++length; } styler.StartAt(start_pos); styler.StartSegment(start_pos); Sci_Position current_line = styler.GetLine(start_pos); int prev_level = styler.LevelAt(current_line) & SC_FOLDLEVELNUMBERMASK; int current_level = prev_level; int visible_chars = 0; bool in_comment = false ; StyleContext sc(start_pos, length, SCE_BIBTEX_DEFAULT, styler); bool going = sc.More(); // needed because of a fuzzy end of file state char closing_brace = 0; bool collect_entry_name = false; for (; going; sc.Forward()) { if (!sc.More()) going = false; // we need to go one behind the end of text if (in_comment) { if (sc.atLineEnd) { sc.SetState(SCE_BIBTEX_DEFAULT); in_comment = false; } } else { // Found @entry if (IsEntryStart(sc)) { sc.SetState(SCE_BIBTEX_UNKNOWN_ENTRY); sc.Forward(); ++current_level; buffer.clear(); collect_entry_name = true; } else if ((sc.state == SCE_BIBTEX_ENTRY || sc.state == SCE_BIBTEX_UNKNOWN_ENTRY) && (sc.ch == '{' || sc.ch == '(')) { // Entry name colorization done // Found either a { or a ( after entry's name, e.g. @entry(...) @entry{...} // Closing counterpart needs to be stored. closing_brace = GetClosingBrace(sc.ch); sc.SetState(SCE_BIBTEX_DEFAULT); // Don't colorize { ( // @string doesn't have any key if (EntryWithoutKey(buffer.c_str())) sc.ForwardSetState(SCE_BIBTEX_PARAMETER); else sc.ForwardSetState(SCE_BIBTEX_KEY); // Key/label colorization } // Need to handle the case where entry's key is empty // e.g. @book{,...} if (sc.state == SCE_BIBTEX_KEY && sc.ch == ',') { // Key/label colorization done sc.SetState(SCE_BIBTEX_DEFAULT); // Don't colorize the , sc.ForwardSetState(SCE_BIBTEX_PARAMETER); // Parameter colorization } else if (sc.state == SCE_BIBTEX_PARAMETER && sc.ch == '=') { sc.SetState(SCE_BIBTEX_DEFAULT); // Don't colorize the = sc.ForwardSetState(SCE_BIBTEX_VALUE); // Parameter value colorization Sci_Position start = sc.currentPos; // We need to handle multiple situations: // 1. name"one two {three}" // 2. name={one {one two {two}} three} // 3. year=2005 // Skip ", { until we encounter the first alphanumerical character while (sc.More() && !(IsAlphaNumeric(sc.ch) || sc.ch == '"' || sc.ch == '{')) sc.Forward(); if (sc.More()) { // Store " or { char ch = sc.ch; // Not interested in alphanumerical characters if (IsAlphaNumeric(ch)) ch = 0; int skipped = 0; if (ch) { // Skip preceding " or { such as in name={{test}}. // Remember how many characters have been skipped // Make sure that empty values, i.e. "" are also handled correctly while (sc.More() && (sc.ch == ch && (ch != '"' || skipped < 1))) { sc.Forward(); ++skipped; } } // Closing counterpart for " is the same character if (ch == '{') ch = '}'; // We have reached the parameter value // In case the open character was a alnum char, skip until , is found // otherwise until skipped == 0 while (sc.More() && (skipped > 0 || (!ch && !(sc.ch == ',' || sc.ch == closing_brace)))) { // Make sure the character isn't escaped if (sc.chPrev != '\\') { // Parameter value contains a { which is the 2nd case described above if (sc.ch == '{') ++skipped; // Remember it else if (sc.ch == '}') --skipped; else if (skipped == 1 && sc.ch == ch && ch == '"') // Don't ignore cases like {"o} skipped = 0; } sc.Forward(); } } // Don't colorize the , sc.SetState(SCE_BIBTEX_DEFAULT); // Skip until the , or entry's closing closing_brace is found // since this parameter might be the last one while (sc.More() && !(sc.ch == ',' || sc.ch == closing_brace)) sc.Forward(); int state = SCE_BIBTEX_PARAMETER; // The might be more parameters // We've reached the closing closing_brace for the bib entry // in case no " or {} has been used to enclose the value, // as in 3rd case described above if (sc.ch == closing_brace) { --current_level; // Make sure the text between entries is not colored // using parameter's style state = SCE_BIBTEX_DEFAULT; } Sci_Position end = sc.currentPos; current_line = styler.GetLine(end); // We have possibly skipped some lines, so the folding levels // have to be adjusted separately for (Sci_Position i = styler.GetLine(start); i <= styler.GetLine(end); ++i) styler.SetLevel(i, prev_level); sc.ForwardSetState(state); } if (sc.state == SCE_BIBTEX_PARAMETER && sc.ch == closing_brace) { sc.SetState(SCE_BIBTEX_DEFAULT); --current_level; } // Non escaped % found which represents a comment until the end of the line if (sc.chPrev != '\\' && sc.ch == '%') { in_comment = true; sc.SetState(SCE_BIBTEX_COMMENT); } } if (sc.state == SCE_BIBTEX_UNKNOWN_ENTRY || sc.state == SCE_BIBTEX_ENTRY) { if (!IsAlphabetic(sc.ch) && collect_entry_name) collect_entry_name = false; if (collect_entry_name) { buffer += static_cast(tolower(sc.ch)); if (EntryNames.InList(buffer.c_str())) sc.ChangeState(SCE_BIBTEX_ENTRY); else sc.ChangeState(SCE_BIBTEX_UNKNOWN_ENTRY); } } if (sc.atLineEnd) { int level = prev_level; if (visible_chars == 0 && fold_compact) level |= SC_FOLDLEVELWHITEFLAG; if ((current_level > prev_level)) level |= SC_FOLDLEVELHEADERFLAG; // else if (current_level < prev_level) // level |= SC_FOLDLEVELBOXFOOTERFLAG; // Deprecated if (level != styler.LevelAt(current_line)) { styler.SetLevel(current_line, level); } ++current_line; prev_level = current_level; visible_chars = 0; } if (!isspacechar(sc.ch)) ++visible_chars; } sc.Complete(); // Fill in the real level of the next line, keeping the current flags as they will be filled in later int flagsNext = styler.LevelAt(current_line) & ~SC_FOLDLEVELNUMBERMASK; styler.SetLevel(current_line, prev_level | flagsNext); } } static const char * const BibTeXWordLists[] = { "Entry Names", 0, }; LexerModule lmBibTeX(SCLEX_BIBTEX, ColorizeBibTeX, "bib", 0, BibTeXWordLists); // Entry Names // article, book, booklet, conference, inbook, // incollection, inproceedings, manual, mastersthesis, // misc, phdthesis, proceedings, techreport, unpublished, // string, url