/** * @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 LexBash.cxx by Neil Hodgson and Kein-Hong Man. * 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 = true; bool foldCompact = true; bool foldComment = true; bool foldCommentStream = true; bool foldCommentMultiLine = true; bool foldPreprocessor = false; bool 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); } }; struct FSharpString { Sci_Position startPos = INVALID_POSITION; int startChar = '"', nextChar = '\0'; constexpr bool HasLength() const { return startPos > INVALID_POSITION; } constexpr bool CanInterpolate() const { return startChar == '$' || (startChar == '@' && nextChar == '$'); } constexpr bool IsVerbatim() const { return startChar == '@' || (startChar == '$' && nextChar == '@'); } }; 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(StyleContext &cxt) { return (cxt.ch == '"' || cxt.Match("@\"") || cxt.Match("$\"") || 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() && !(fsStr.IsVerbatim() || cxt.chPrev == '$'))) && cxt.MatchIgnoreCase("\"\"\"")) || // end of verbatim string? (fsStr.IsVerbatim() && // embedded quotes must be in pairs cxt.ch == '"' && cxt.chNext != '"' && (cxt.chPrev != '"' || // empty verbatim string? ((cxt.GetRelative(-2) == '@' || cxt.GetRelative(-2) == '$') || // pair of quotes at end of string? (cxt.GetRelative(-2) == '"' && !(cxt.GetRelative(-3) == '@' || cxt.GetRelative(-3) == '$'))))))) || (!fsStr.HasLength() && cxt.ch == '"' && ((cxt.chPrev != '\\' || (cxt.GetRelative(-2) == '\\' && !FollowsEscapedBackslash(cxt))) || // treat backslashes as char literals in verbatim strings (fsStr.IsVerbatim() && 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.Match("$\"\"\"") || cxt.Match("\"\"\"") || cxt.Match("@$\"\"\"") || cxt.Match("$@\"\"\"") || cxt.Match('@', '"') || cxt.Match('`', '`'); } 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; CharacterSet setOperators; CharacterSet setFormatSpecs; CharacterSet setDotNetFormatSpecs; CharacterSet setFormatFlags; CharacterSet numericMetaChars1; CharacterSet numericMetaChars2; std::map numericPrefixes = { { 'b', 2 }, { 'o', 8 }, { 'x', 16 } }; public: explicit LexerFSharp() : DefaultLexer(lexerName, SCLEX_FSHARP), setOperators(CharacterSet::setNone, "~^'-+*/%=@|&<>()[]{};,:!?"), setFormatSpecs(CharacterSet::setNone, ".%aAbBcdeEfFgGiMoOstuxX0123456789"), setDotNetFormatSpecs(CharacterSet::setNone, "cCdDeEfFgGnNpPxX"), setFormatFlags(CharacterSet::setNone, ".-+0 "), numericMetaChars1(CharacterSet::setNone, "_uU"), numericMetaChars2(CharacterSet::setNone, "fFIlLmMnsy") { } LexerFSharp(const LexerFSharp &) = delete; LexerFSharp(LexerFSharp &&) = delete; LexerFSharp &operator=(const LexerFSharp &) = delete; LexerFSharp &operator=(LexerFSharp &&) = delete; 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 INVALID_POSITION; } 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; private: 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(StyleContext &cxt) { if (cxt.MatchIgnoreCase("e+") || cxt.MatchIgnoreCase("e-")) { cxt.Forward(); return true; } return ((cxt.chPrev == '.' && IsADigit(cxt.ch)) || (IsADigit(cxt.chPrev) && (cxt.ch == '.' || numericMetaChars2.Contains(cxt.ch)))); } }; Sci_Position SCI_METHOD LexerFSharp::WordListSet(int n, const char *wl) { WordList *wordListN = nullptr; Sci_Position firstModification = INVALID_POSITION; if (n < WORDLIST_SIZE) { wordListN = &keywords[n]; } if (wordListN && 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_Position lineCurrent = styler.GetLine(static_cast(start)); Sci_PositionU cursor = 0; UnicodeChar uniCh = UnicodeChar(); FSharpString fsStr = FSharpString(); constexpr Sci_Position MAX_WORD_LEN = 64; constexpr int SPACE = ' '; int currentBase = 10; int levelNesting = (lineCurrent >= 1) ? styler.GetLineState(lineCurrent - 1) : 0; 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.nextChar = sc.chNext; fsStr.startPos = INVALID_POSITION; 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 (fsStr.IsVerbatim()) { 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; } 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: if (sc.MatchLineEnd()) { state = SCE_FSHARP_DEFAULT; } break; case SCE_FSHARP_COMMENTLINE: if (sc.atLineStart) { state = SCE_FSHARP_DEFAULT; advance = false; } break; case SCE_FSHARP_COMMENT: if (MatchStreamCommentStart(sc)) { sc.Forward(); sc.ch = SPACE; levelNesting++; } else if (MatchStreamCommentEnd(sc)) { if (levelNesting > 0) levelNesting--; else { state = SCE_FSHARP_DEFAULT; colorSpan++; } } break; case SCE_FSHARP_ATTRIBUTE: case SCE_FSHARP_QUOTATION: if (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 = fsStr.IsVerbatim() ? SCE_FSHARP_VERBATIM : 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 ((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: if ((setOperators.Contains(sc.chPrev) || IsASpaceOrTab(sc.chPrev)) && sc.ch == '0') { if (numericPrefixes.find(sc.chNext) != numericPrefixes.end()) { currentBase = numericPrefixes[sc.chNext]; sc.Forward(2); } } else if ((setOperators.Contains(sc.GetRelative(-2)) || IsASpaceOrTab(sc.GetRelative(-2))) && sc.chPrev == '0') { if (numericPrefixes.find(sc.ch) != numericPrefixes.end()) { currentBase = numericPrefixes[sc.ch]; sc.Forward(); } } 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.IsVerbatim() ? SCE_FSHARP_VERBATIM : SCE_FSHARP_STRING; } break; } if (sc.MatchLineEnd()) { styler.SetLineState(lineCurrent++, (sc.state == SCE_FSHARP_COMMENT) ? levelNesting : 0); advance = true; } 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); Sci_Position startPos = static_cast(start); const Sci_PositionU endPos = start + length; Sci_Position lineCurrent = styler.GetLine(startPos); if (lineCurrent > 0) { lineCurrent--; startPos = styler.LineStart(lineCurrent); initStyle = (startPos > 0) ? styler.StyleAt(startPos - 1) : SCE_FSHARP_DEFAULT; } Sci_Position lineNext = lineCurrent + 1; Sci_Position lineStartNext = styler.LineStart(lineNext); int style = initStyle; int styleNext = styler.StyleAt(startPos); char chNext = styler[startPos]; int levelCurrent = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK; int levelNext = levelCurrent; int visibleChars = 0; for (Sci_PositionU i = static_cast(startPos); i < endPos; i++) { const Sci_Position currentPos = static_cast(i); const bool atEOL = (currentPos == (lineStartNext - 1)); const bool atLineOrDocEnd = (atEOL || (i == (endPos - 1))); const int stylePrev = style; const char ch = chNext; const bool inLineComment = (stylePrev == SCE_FSHARP_COMMENTLINE); style = styleNext; styleNext = styler.StyleAt(currentPos + 1); chNext = styler.SafeGetCharAt(currentPos + 1); if (options.foldComment) { if (options.foldCommentMultiLine && inLineComment && atEOL) { FoldLexicalGroup(styler, levelNext, lineCurrent, "//", SCE_FSHARP_COMMENTLINE); } if (options.foldCommentStream && style == SCE_FSHARP_COMMENT && !inLineComment) { if (stylePrev != SCE_FSHARP_COMMENT || (styler.Match(currentPos, "(*") && !LineContains(styler, "*)", currentPos + 2, SCE_FSHARP_COMMENT))) { levelNext++; } else if ((styleNext != SCE_FSHARP_COMMENT || ((styler.Match(currentPos, "*)") && !LineContains(styler, "(*", styler.LineStart(lineCurrent), SCE_FSHARP_COMMENT)) && styler.GetLineState(lineCurrent - 1) > 0)) && !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 && styler.Match(currentPos, "open ") && styleNext == SCE_FSHARP_KEYWORD) { FoldLexicalGroup(styler, levelNext, lineCurrent, "open ", SCE_FSHARP_KEYWORD); } if (atLineOrDocEnd) { int levelUse = levelCurrent; int lev = levelUse | levelNext << 16; if (visibleChars == 0 && options.foldCompact) { lev |= SC_FOLDLEVELWHITEFLAG; } if ((levelUse < levelNext) && (visibleChars > 0)) { 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 (!IsASpace(ch)) { visibleChars++; } } const int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK; styler.SetLevel(lineCurrent, levelCurrent | flagsNext); } 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 = start; i < styler.LineStart(styler.GetLine(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 = styler.LineStart(lineCurrent - 1); const Sci_Position lineNext = styler.LineStart(lineCurrent + 1); const bool follows = (lineCurrent > 0) && 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 extern const LexerModule lmFSharp(SCLEX_FSHARP, LexerFSharp::LexerFactoryFSharp, "fsharp", fsharpWordLists);