/** * @file LexFSharp.cxx * Lexer for F# 5.0 * Copyright (c) 2021 Robert Di Pardo * Parts of LexerFSharp::Lex were adapted from LexCaml.cxx by Robert Roessler ("RR"). * Parts of LexerFSharp::Fold were adapted from LexCPP.cxx by Neil Hodgson and Udo Lechner. * The License.txt file describes the conditions under which this software may be distributed. */ // clang-format off #include #include #include #include #include #include #include "ILexer.h" #include "Scintilla.h" #include "SciLexer.h" #include "WordList.h" #include "LexAccessor.h" #include "StyleContext.h" #include "CharacterSet.h" #include "LexerModule.h" #include "OptionSet.h" #include "DefaultLexer.h" // clang-format on using namespace Scintilla; using namespace Lexilla; static const char *const lexerName = "fsharp"; static constexpr int WORDLIST_SIZE = 5; static const char *const fsharpWordLists[] = { "standard language keywords", "core functions, including those in the FSharp.Collections namespace", "built-in types, core namespaces, modules", "optional", "optional", nullptr, }; static constexpr int keywordClasses[] = { SCE_FSHARP_KEYWORD, SCE_FSHARP_KEYWORD2, SCE_FSHARP_KEYWORD3, SCE_FSHARP_KEYWORD4, SCE_FSHARP_KEYWORD5, }; namespace { struct OptionsFSharp { bool fold; bool foldCompact; bool foldComment; bool foldCommentStream; bool foldCommentMultiLine; bool foldPreprocessor; bool foldImports; OptionsFSharp() { fold = true; foldCompact = true; foldComment = true; foldCommentStream = true; foldCommentMultiLine = true; foldPreprocessor = false; foldImports = true; } }; struct OptionSetFSharp : public OptionSet { OptionSetFSharp() { DefineProperty("fold", &OptionsFSharp::fold); DefineProperty("fold.compact", &OptionsFSharp::foldCompact); DefineProperty("fold.comment", &OptionsFSharp::foldComment, "Setting this option to 0 disables comment folding in F# files."); DefineProperty("fold.fsharp.comment.stream", &OptionsFSharp::foldCommentStream, "Setting this option to 0 disables folding of ML-style comments in F# files when " "fold.comment=1."); DefineProperty("fold.fsharp.comment.multiline", &OptionsFSharp::foldCommentMultiLine, "Setting this option to 0 disables folding of grouped line comments in F# files when " "fold.comment=1."); DefineProperty("fold.fsharp.preprocessor", &OptionsFSharp::foldPreprocessor, "Setting this option to 1 enables folding of F# compiler directives."); DefineProperty("fold.fsharp.imports", &OptionsFSharp::foldImports, "Setting this option to 0 disables folding of F# import declarations."); DefineWordListSets(fsharpWordLists); } }; const CharacterSet setOperators = CharacterSet(CharacterSet::setNone, "~^'-+*/%=@|&<>()[]{};,:!?"); const CharacterSet setClosingTokens = CharacterSet(CharacterSet::setNone, ")}]"); const CharacterSet setFormatSpecs = CharacterSet(CharacterSet::setNone, ".%aAbBcdeEfFgGiMoOstuxX0123456789"); const CharacterSet setDotNetFormatSpecs = CharacterSet(CharacterSet::setNone, "cCdDeEfFgGnNpPxX"); const CharacterSet setFormatFlags = CharacterSet(CharacterSet::setNone, ".-+0 "); const CharacterSet numericMetaChars1 = CharacterSet(CharacterSet::setNone, "_IbeEflmnosuxy"); const CharacterSet numericMetaChars2 = CharacterSet(CharacterSet::setNone, "lnsy"); std::map numericPrefixes = { { 'b', 2 }, { 'o', 8 }, { 'x', 16 } }; constexpr Sci_Position ZERO_LENGTH = -1; struct FSharpString { Sci_Position startPos; int startChar; FSharpString() { startPos = ZERO_LENGTH; startChar = '"'; } constexpr bool HasLength() const { return startPos > ZERO_LENGTH; } constexpr bool CanInterpolate() const { return startChar == '$'; } }; class UnicodeChar { enum class Notation { none, asciiDec, asciiHex, utf16, utf32 }; Notation type = Notation::none; // single-byte Unicode char (000 - 255) int asciiDigits[3] = { 0 }; int maxDigit = '9'; int toEnd = 0; bool invalid = false; public: UnicodeChar() noexcept = default; explicit UnicodeChar(const int prefix) { if (IsADigit(prefix)) { *asciiDigits = prefix; if (*asciiDigits >= '0' && *asciiDigits <= '2') { type = Notation::asciiDec; // count first digit as "prefix" toEnd = 2; } } else if (prefix == 'x' || prefix == 'u' || prefix == 'U') { switch (prefix) { case 'x': type = Notation::asciiHex; toEnd = 2; break; case 'u': type = Notation::utf16; toEnd = 4; break; case 'U': type = Notation::utf32; toEnd = 8; break; } } } void Parse(const int ch) { invalid = false; switch (type) { case Notation::asciiDec: { maxDigit = (*asciiDigits < '2') ? '9' : (asciiDigits[1] <= '4') ? '9' : '5'; if (IsADigit(ch) && asciiDigits[1] <= maxDigit && ch <= maxDigit) { asciiDigits[1] = ch; toEnd--; } else { invalid = true; } break; } case Notation::asciiHex: case Notation::utf16: if (IsADigit(ch, 16)) { toEnd--; } else { invalid = true; } break; case Notation::utf32: if ((toEnd > 6 && ch == '0') || (toEnd <= 6 && IsADigit(ch, 16))) { toEnd--; } else { invalid = true; } break; case Notation::none: break; } } constexpr bool AtEnd() noexcept { return invalid || type == Notation::none || (type != Notation::none && toEnd < 0); } }; inline bool MatchStreamCommentStart(StyleContext &cxt) { // match (* ... *), but allow point-free usage of the `*` operator, // e.g. List.fold (*) 1 [ 1; 2; 3 ] return (cxt.Match('(', '*') && cxt.GetRelative(2) != ')'); } inline bool MatchStreamCommentEnd(const StyleContext &cxt) { return (cxt.ch == ')' && cxt.chPrev == '*'); } inline bool MatchLineComment(const StyleContext &cxt) { // style shebang lines as comments in F# scripts: // https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=30&zoom=auto,-98,537 return cxt.Match('/', '/') || cxt.Match('#', '!'); } inline bool MatchLineNumberStart(StyleContext &cxt) { return cxt.atLineStart && (cxt.MatchIgnoreCase("#line") || (cxt.ch == '#' && (IsADigit(cxt.chNext) || IsADigit(cxt.GetRelative(2))))); } inline bool MatchPPDirectiveStart(const StyleContext &cxt) { return (cxt.atLineStart && cxt.ch == '#' && iswordstart(cxt.chNext)); } inline bool MatchTypeAttributeStart(const StyleContext &cxt) { return cxt.Match('[', '<'); } inline bool MatchTypeAttributeEnd(const StyleContext &cxt) { return (cxt.ch == ']' && cxt.chPrev == '>'); } inline bool MatchQuotedExpressionStart(const StyleContext &cxt) { return cxt.Match('<', '@'); } inline bool MatchQuotedExpressionEnd(const StyleContext &cxt) { return (cxt.ch == '>' && cxt.chPrev == '@'); } inline bool MatchStringStart(const StyleContext &cxt) { return (cxt.ch == '"' || cxt.Match('@', '"') || cxt.Match('$', '"') || cxt.Match('`', '`')); } inline bool FollowsEscapedBackslash(StyleContext &cxt) { int count = 0; for (Sci_Position offset = 1; cxt.GetRelative(-offset) == '\\'; offset++) count++; return count % 2 != 0; } inline bool MatchStringEnd(StyleContext &cxt, const FSharpString &fsStr) { return (fsStr.HasLength() && // end of quoted identifier? ((cxt.ch == '`' && cxt.chPrev == '`') || // end of literal or interpolated triple-quoted string? ((fsStr.startChar == '"' || (fsStr.CanInterpolate() && cxt.chPrev != '$')) && cxt.MatchIgnoreCase("\"\"\"")) || // end of verbatim string? (fsStr.startChar == '@' && // embedded quotes must be in pairs cxt.ch == '"' && cxt.chNext != '"' && (cxt.chPrev != '"' || (cxt.chPrev == '"' && // empty verbatim string? (cxt.GetRelative(-2) == '@' || // pair of quotes at end of string? (cxt.GetRelative(-2) == '"' && cxt.GetRelative(-3) != '@'))))))) || (!fsStr.HasLength() && cxt.ch == '"' && ((cxt.chPrev != '\\' || (cxt.GetRelative(-2) == '\\' && !FollowsEscapedBackslash(cxt))) || // treat backslashes as char literals in verbatim strings (fsStr.startChar == '@' && cxt.chPrev == '\\'))); } inline bool MatchCharacterStart(StyleContext &cxt) { // don't style generic type parameters: 'a, 'b, 'T, etc. return (cxt.ch == '\'' && !(cxt.chPrev == ':' || cxt.GetRelative(-2) == ':')); } inline bool CanEmbedQuotes(StyleContext &cxt) { // allow unescaped double quotes inside literal or interpolated triple-quoted strings, verbatim strings, // and quoted identifiers: // - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/strings // - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/interpolated-strings#syntax // - https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=25&zoom=auto,-98,600 return cxt.MatchIgnoreCase("$\"\"\"") || cxt.MatchIgnoreCase("\"\"\"") || cxt.Match('@', '"') || cxt.Match('`', '`'); } inline bool IsNumber(StyleContext &cxt, const int base = 10) { return IsADigit(cxt.ch, base) || (IsADigit(cxt.chPrev, base) && numericMetaChars1.Contains(cxt.ch)) || (IsADigit(cxt.GetRelative(-2), base) && numericMetaChars2.Contains(cxt.ch)); } inline bool IsFloat(const StyleContext &cxt) { return (cxt.ch == '.' && IsADigit(cxt.chPrev)) || ((cxt.ch == '+' || cxt.ch == '-' ) && IsADigit(cxt.chNext)); } inline bool IsLineEnd(StyleContext &cxt, const Sci_Position offset) { const int ch = cxt.GetRelative(offset, '\n'); return (ch == '\r' || ch == '\n'); } class LexerFSharp : public DefaultLexer { WordList keywords[WORDLIST_SIZE]; OptionsFSharp options; OptionSetFSharp optionSet; public: explicit LexerFSharp() : DefaultLexer(lexerName, SCLEX_FSHARP) { } static ILexer5 *LexerFactoryFSharp() { return new LexerFSharp(); } virtual ~LexerFSharp() { } void SCI_METHOD Release() noexcept override { delete this; } int SCI_METHOD Version() const noexcept override { return lvRelease5; } const char *SCI_METHOD GetName() noexcept override { return lexerName; } int SCI_METHOD GetIdentifier() noexcept override { return SCLEX_FSHARP; } int SCI_METHOD LineEndTypesSupported() noexcept override { return SC_LINE_END_TYPE_DEFAULT; } void *SCI_METHOD PrivateCall(int, void *) noexcept override { return nullptr; } const char *SCI_METHOD DescribeWordListSets() override { return optionSet.DescribeWordListSets(); } const char *SCI_METHOD PropertyNames() override { return optionSet.PropertyNames(); } int SCI_METHOD PropertyType(const char *name) override { return optionSet.PropertyType(name); } const char *SCI_METHOD DescribeProperty(const char *name) override { return optionSet.DescribeProperty(name); } const char *SCI_METHOD PropertyGet(const char *key) override { return optionSet.PropertyGet(key); } Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override { if (optionSet.PropertySet(&options, key, val)) { return 0; } return ZERO_LENGTH; } Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override; void SCI_METHOD Lex(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) override; void SCI_METHOD Fold(Sci_PositionU start, Sci_Position length, int initStyle,IDocument *pAccess) override; }; Sci_Position SCI_METHOD LexerFSharp::WordListSet(int n, const char *wl) { WordList *wordListN = nullptr; Sci_Position firstModification = ZERO_LENGTH; if (n < WORDLIST_SIZE) { wordListN = &keywords[n]; } if (wordListN) { WordList wlNew; wlNew.Set(wl); if (*wordListN != wlNew) { wordListN->Set(wl); firstModification = 0; } } return firstModification; } void SCI_METHOD LexerFSharp::Lex(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) { LexAccessor styler(pAccess); StyleContext sc(start, static_cast(length), initStyle, styler); Sci_PositionU cursor = 0; UnicodeChar uniCh = UnicodeChar(); FSharpString fsStr = FSharpString(); constexpr Sci_Position MAX_WORD_LEN = 64; constexpr int SPACE = ' '; int currentBase = 10; bool isInterpolated = false; while (sc.More()) { Sci_PositionU colorSpan = sc.currentPos - 1; int state = -1; bool advance = true; switch (sc.state & 0xff) { case SCE_FSHARP_DEFAULT: cursor = sc.currentPos; if (MatchLineNumberStart(sc)) { state = SCE_FSHARP_LINENUM; } else if (MatchPPDirectiveStart(sc)) { state = SCE_FSHARP_PREPROCESSOR; } else if (MatchLineComment(sc)) { state = SCE_FSHARP_COMMENTLINE; sc.Forward(); sc.ch = SPACE; } else if (MatchStreamCommentStart(sc)) { state = SCE_FSHARP_COMMENT; sc.Forward(); sc.ch = SPACE; } else if (MatchTypeAttributeStart(sc)) { state = SCE_FSHARP_ATTRIBUTE; sc.Forward(); } else if (MatchQuotedExpressionStart(sc)) { state = SCE_FSHARP_QUOTATION; sc.Forward(); } else if (MatchCharacterStart(sc)) { state = SCE_FSHARP_CHARACTER; } else if (MatchStringStart(sc)) { fsStr.startChar = sc.ch; fsStr.startPos = ZERO_LENGTH; if (CanEmbedQuotes(sc)) { // double quotes after this position should be non-terminating fsStr.startPos = static_cast(sc.currentPos - cursor); } if (sc.ch == '`') { state = SCE_FSHARP_QUOT_IDENTIFIER; } else if (sc.ch == '@') { state = SCE_FSHARP_VERBATIM; } else { state = SCE_FSHARP_STRING; } } else if (IsADigit(sc.ch, currentBase) || ((sc.ch == '+' || sc.ch == '-') && IsADigit(sc.chNext))) { state = SCE_FSHARP_NUMBER; if (sc.ch == '0') { const int prefix = sc.chNext; if (numericPrefixes.find(prefix) != numericPrefixes.end()) { currentBase = numericPrefixes[prefix]; } } } else if (setOperators.Contains(sc.ch) && // don't use operator style in async keywords (e.g. `return!`) !(sc.ch == '!' && iswordstart(sc.chPrev)) && // don't use operator style in member access, array/string indexing !(sc.ch == '.' && (sc.chPrev == '\"' || iswordstart(sc.chPrev)) && (iswordstart(sc.chNext) || sc.chNext == '['))) { state = SCE_FSHARP_OPERATOR; } else if (iswordstart(sc.ch)) { state = SCE_FSHARP_IDENTIFIER; } else { state = SCE_FSHARP_DEFAULT; } break; case SCE_FSHARP_LINENUM: case SCE_FSHARP_PREPROCESSOR: case SCE_FSHARP_COMMENTLINE: if (sc.MatchLineEnd()) { state = SCE_FSHARP_DEFAULT; advance = false; } break; case SCE_FSHARP_COMMENT: case SCE_FSHARP_ATTRIBUTE: case SCE_FSHARP_QUOTATION: if (MatchStreamCommentEnd(sc) || MatchTypeAttributeEnd(sc) || MatchQuotedExpressionEnd(sc)) { state = SCE_FSHARP_DEFAULT; colorSpan++; } break; case SCE_FSHARP_CHARACTER: if (sc.chPrev == '\\' && sc.GetRelative(-2) != '\\') { uniCh = UnicodeChar(sc.ch); } else if (sc.ch == '\'' && ((sc.chPrev == ' ' && sc.GetRelative(-2) == '\'') || sc.chPrev != '\\' || (sc.chPrev == '\\' && sc.GetRelative(-2) == '\\'))) { // byte literal? if (sc.Match('\'', 'B')) { sc.Forward(); colorSpan++; } if (!sc.atLineEnd) { colorSpan++; } else { sc.ChangeState(SCE_FSHARP_IDENTIFIER); } state = SCE_FSHARP_DEFAULT; } else { uniCh.Parse(sc.ch); if (uniCh.AtEnd() && (sc.currentPos - cursor) >= 2) { // terminate now, since we left the char behind sc.ChangeState(SCE_FSHARP_IDENTIFIER); advance = false; } } break; case SCE_FSHARP_STRING: case SCE_FSHARP_VERBATIM: case SCE_FSHARP_QUOT_IDENTIFIER: if (MatchStringEnd(sc, fsStr)) { const Sci_Position strLen = static_cast(sc.currentPos - cursor); // backtrack to start of string for (Sci_Position i = -strLen; i < 0; i++) { const int startQuote = sc.GetRelative(i); if (startQuote == '\"' || (startQuote == '`' && sc.GetRelative(i - 1) == '`')) { // byte array? if (sc.Match('\"', 'B')) { sc.Forward(); colorSpan++; } if (!sc.atLineEnd) { colorSpan++; } else { sc.ChangeState(SCE_FSHARP_IDENTIFIER); } state = SCE_FSHARP_DEFAULT; break; } } } else if (sc.ch == '%' && !(fsStr.startChar == '`' || sc.MatchIgnoreCase("% ") || sc.MatchIgnoreCase("% \"")) && (setFormatSpecs.Contains(sc.chNext) || setFormatFlags.Contains(sc.chNext))) { if (fsStr.CanInterpolate() && sc.chNext != '%') { for (Sci_Position i = 2; i < length && !IsLineEnd(sc, i); i++) { if (sc.GetRelative(i) == '{') { state = setFormatSpecs.Contains(sc.GetRelative(i - 1)) ? SCE_FSHARP_FORMAT_SPEC : state; break; } } } else { state = SCE_FSHARP_FORMAT_SPEC; } } else if (isInterpolated) { if (sc.ch == ',') { // .NET alignment specifier? state = (sc.chNext == '+' || sc.chNext == '-' || IsADigit(sc.chNext)) ? SCE_FSHARP_FORMAT_SPEC : state; } else if (sc.ch == ':') { // .NET format specifier? state = setDotNetFormatSpecs.Contains(sc.chNext) ? SCE_FSHARP_FORMAT_SPEC : state; } else if (sc.chNext == '}') { isInterpolated = false; sc.Forward(); state = SCE_FSHARP_STRING; } } else if (fsStr.CanInterpolate() && sc.ch == '{') { isInterpolated = true; } break; case SCE_FSHARP_IDENTIFIER: if (!(iswordstart(sc.ch) || sc.ch == '\'')) { const Sci_Position wordLen = static_cast(sc.currentPos - cursor); if (wordLen < MAX_WORD_LEN) { // wordLength is believable as keyword, [re-]construct token - RR char token[MAX_WORD_LEN] = { 0 }; for (Sci_Position i = -wordLen; i < 0; i++) { token[wordLen + i] = static_cast(sc.GetRelative(i)); } token[wordLen] = '\0'; // a snake_case_identifier can never be a keyword if (!(sc.ch == '_' || sc.GetRelative(-wordLen - 1) == '_')) { for (int i = 0; i < WORDLIST_SIZE; i++) { if (keywords[i].InList(token)) { sc.ChangeState(keywordClasses[i]); break; } } } } state = SCE_FSHARP_DEFAULT; advance = false; } break; case SCE_FSHARP_OPERATOR: // special-case "()" and "[]" tokens as KEYWORDS - RR if (setClosingTokens.Contains(sc.ch) && ((sc.ch == ')' && sc.chPrev == '(') || (sc.ch == ']' && sc.chPrev == '['))) { sc.ChangeState(SCE_FSHARP_KEYWORD); colorSpan++; } else { advance = false; } state = SCE_FSHARP_DEFAULT; break; case SCE_FSHARP_NUMBER: state = (IsNumber(sc, currentBase) || IsFloat(sc)) ? SCE_FSHARP_NUMBER // change style even when operators aren't spaced : setOperators.Contains(sc.ch) ? SCE_FSHARP_OPERATOR : SCE_FSHARP_DEFAULT; currentBase = (state == SCE_FSHARP_NUMBER) ? currentBase : 10; break; case SCE_FSHARP_FORMAT_SPEC: if (!(isInterpolated && IsADigit(sc.chNext)) && (!setFormatSpecs.Contains(sc.chNext) || !(setFormatFlags.Contains(sc.ch) || IsADigit(sc.ch)) || (setFormatFlags.Contains(sc.ch) && sc.ch == sc.chNext))) { colorSpan++; state = (fsStr.startChar == '@') ? SCE_FSHARP_VERBATIM : SCE_FSHARP_STRING; } break; } if (state >= SCE_FSHARP_DEFAULT) { styler.ColourTo(colorSpan, sc.state); sc.ChangeState(state); } if (advance) { sc.Forward(); } } sc.Complete(); } bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, const int chAttr = SCE_FSHARP_DEFAULT); void FoldLexicalGroup(LexAccessor &styler, int &levelNext, const Sci_Position lineCurrent, const char *word, const int chAttr); void SCI_METHOD LexerFSharp::Fold(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) { if (!options.fold) { return; } LexAccessor styler(pAccess); const Sci_Position startPos = static_cast(start); const Sci_PositionU endPos = start + length; Sci_Position lineCurrent = styler.GetLine(startPos); Sci_Position lineNext = lineCurrent + 1; Sci_Position lineStartNext = styler.LineStart(lineNext); int style = initStyle; int styleNext = styler.StyleAt(startPos); char chNext = styler[startPos]; int levelNext; int levelCurrent = SC_FOLDLEVELBASE; int visibleChars = 0; if (lineCurrent > 0) { levelCurrent = styler.LevelAt(lineCurrent - 1) >> 0x10; } levelNext = levelCurrent; for (Sci_PositionU i = start; i < endPos; i++) { const Sci_Position currentPos = static_cast(i); const bool atEOL = (currentPos == (lineStartNext - 1) || styler.SafeGetCharAt(currentPos) == '\r'); const bool atLineOrDocEnd = (atEOL || (i == (endPos - 1))); const int stylePrev = style; const char ch = chNext; const bool inLineComment = (stylePrev == SCE_FSHARP_COMMENTLINE); const bool inOpenStatement = LineContains(styler, "open ", lineCurrent, SCE_FSHARP_KEYWORD); style = styleNext; styleNext = styler.StyleAt(currentPos + 1); chNext = styler.SafeGetCharAt(currentPos + 1); if (options.foldComment) { if (options.foldCommentMultiLine && inLineComment && atEOL && (lineCurrent > 0 || LineContains(styler, "//", lineNext, SCE_FSHARP_COMMENTLINE))) { FoldLexicalGroup(styler, levelNext, lineCurrent, "//", SCE_FSHARP_COMMENTLINE); } if (options.foldCommentStream && style == SCE_FSHARP_COMMENT && !inLineComment) { if (stylePrev != SCE_FSHARP_COMMENT) { levelNext++; } else if (styleNext != SCE_FSHARP_COMMENT && !atEOL) { levelNext--; } } } if (options.foldPreprocessor && style == SCE_FSHARP_PREPROCESSOR) { if (styler.Match(currentPos, "#if")) { levelNext++; } else if (styler.Match(currentPos, "#endif")) { levelNext--; } } if (options.foldImports && inOpenStatement && atEOL) { FoldLexicalGroup(styler, levelNext, lineCurrent, "open ", SCE_FSHARP_KEYWORD); } if (!IsASpace(ch)) { visibleChars++; } if (atLineOrDocEnd) { int levelUse = levelCurrent; int lev = levelUse | levelNext << 16; if (visibleChars == 0 && options.foldCompact) { lev |= SC_FOLDLEVELWHITEFLAG; } if (levelUse < levelNext) { lev |= SC_FOLDLEVELHEADERFLAG; } if (lev != styler.LevelAt(lineCurrent)) { styler.SetLevel(lineCurrent, lev); } visibleChars = 0; lineCurrent++; lineNext = lineCurrent + 1; lineStartNext = styler.LineStart(lineNext); levelCurrent = levelNext; if (atEOL && (currentPos == (styler.Length() - 1))) { styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG); } } } } bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, const int chAttr) { bool found = false; bool requireStyle = (chAttr > SCE_FSHARP_DEFAULT); for (Sci_Position i = styler.LineStart(start); i < styler.LineStart(start + 1) - 1; i++) { if (styler.Match(i, word)) { found = requireStyle ? styler.StyleAt(i) == chAttr : true; break; } } return found; } void FoldLexicalGroup(LexAccessor &styler, int &levelNext, const Sci_Position lineCurrent, const char *word, const int chAttr) { const Sci_Position linePrev = lineCurrent - 1; const Sci_Position lineNext = lineCurrent + 1; const bool follows = LineContains(styler, word, linePrev, chAttr); const bool isFollowed = LineContains(styler, word, lineNext, chAttr); if (isFollowed && !follows) { levelNext++; } else if (!isFollowed && follows && levelNext > SC_FOLDLEVELBASE) { levelNext--; } } } // namespace LexerModule lmFSharp(SCLEX_FSHARP, LexerFSharp::LexerFactoryFSharp, "fsharp", fsharpWordLists);