1/**
2 * @file LexFSharp.cxx
3 * Lexer for F# 5.0
4 * Copyright (c) 2021 Robert Di Pardo <dipardo.r@gmail.com>
5 * Parts of LexerFSharp::Lex were adapted from LexCaml.cxx by Robert Roessler ("RR").
6 * Parts of LexerFSharp::Fold were adapted from LexCPP.cxx by Neil Hodgson and Udo Lechner.
7 * The License.txt file describes the conditions under which this software may be distributed.
8 */
9// clang-format off
10#include <cstdlib>
11#include <cassert>
12
13#include <string>
14#include <string_view>
15#include <map>
16#include <functional>
17
18#include "ILexer.h"
19#include "Scintilla.h"
20#include "SciLexer.h"
21
22#include "WordList.h"
23#include "LexAccessor.h"
24#include "StyleContext.h"
25#include "CharacterSet.h"
26#include "LexerModule.h"
27#include "OptionSet.h"
28#include "DefaultLexer.h"
29// clang-format on
30
31using namespace Scintilla;
32using namespace Lexilla;
33
34static const char *const lexerName = "fsharp";
35static constexpr int WORDLIST_SIZE = 5;
36static const char *const fsharpWordLists[] = {
37 "standard language keywords",
38 "core functions, including those in the FSharp.Collections namespace",
39 "built-in types, core namespaces, modules",
40 "optional",
41 "optional",
42 nullptr,
43};
44static constexpr int keywordClasses[] = {
45 SCE_FSHARP_KEYWORD, SCE_FSHARP_KEYWORD2, SCE_FSHARP_KEYWORD3, SCE_FSHARP_KEYWORD4, SCE_FSHARP_KEYWORD5,
46};
47
48namespace {
49
50struct OptionsFSharp {
51 bool fold;
52 bool foldCompact;
53 bool foldComment;
54 bool foldCommentStream;
55 bool foldCommentMultiLine;
56 bool foldPreprocessor;
57 bool foldImports;
58 OptionsFSharp() {
59 fold = true;
60 foldCompact = true;
61 foldComment = true;
62 foldCommentStream = true;
63 foldCommentMultiLine = true;
64 foldPreprocessor = false;
65 foldImports = true;
66 }
67};
68
69struct OptionSetFSharp : public OptionSet<OptionsFSharp> {
70 OptionSetFSharp() {
71 DefineProperty("fold", &OptionsFSharp::fold);
72 DefineProperty("fold.compact", &OptionsFSharp::foldCompact);
73 DefineProperty("fold.comment", &OptionsFSharp::foldComment,
74 "Setting this option to 0 disables comment folding in F# files.");
75
76 DefineProperty("fold.fsharp.comment.stream", &OptionsFSharp::foldCommentStream,
77 "Setting this option to 0 disables folding of ML-style comments in F# files when "
78 "fold.comment=1.");
79
80 DefineProperty("fold.fsharp.comment.multiline", &OptionsFSharp::foldCommentMultiLine,
81 "Setting this option to 0 disables folding of grouped line comments in F# files when "
82 "fold.comment=1.");
83
84 DefineProperty("fold.fsharp.preprocessor", &OptionsFSharp::foldPreprocessor,
85 "Setting this option to 1 enables folding of F# compiler directives.");
86
87 DefineProperty("fold.fsharp.imports", &OptionsFSharp::foldImports,
88 "Setting this option to 0 disables folding of F# import declarations.");
89
90 DefineWordListSets(fsharpWordLists);
91 }
92};
93
94const CharacterSet setOperators = CharacterSet(CharacterSet::setNone, "~^'-+*/%=@|&<>()[]{};,:!?");
95const CharacterSet setClosingTokens = CharacterSet(CharacterSet::setNone, ")}]");
96const CharacterSet setFormatSpecs = CharacterSet(CharacterSet::setNone, ".%aAbcdeEfFgGiMoOstuxX0123456789");
97const CharacterSet setFormatFlags = CharacterSet(CharacterSet::setNone, ".-+0 ");
98const CharacterSet numericMetaChars1 = CharacterSet(CharacterSet::setNone, "_IbeEflmnosuxy");
99const CharacterSet numericMetaChars2 = CharacterSet(CharacterSet::setNone, "lnsy");
100std::map<int, int> numericPrefixes = { { 'b', 2 }, { 'o', 8 }, { 'x', 16 } };
101constexpr Sci_Position ZERO_LENGTH = -1;
102
103struct FSharpString {
104 Sci_Position startPos;
105 int startChar;
106 FSharpString() {
107 startPos = ZERO_LENGTH;
108 startChar = '"';
109 }
110 constexpr bool HasLength() const {
111 return startPos > ZERO_LENGTH;
112 }
113 constexpr bool CanInterpolate() const {
114 return startChar == '$';
115 }
116};
117
118class UnicodeChar {
119 enum class Notation { none, asciiDec, asciiHex, utf16, utf32 };
120 Notation type = Notation::none;
121 // single-byte Unicode char (000 - 255)
122 int asciiDigits[3] = { 0 };
123 int maxDigit = '9';
124 int toEnd = 0;
125 bool invalid = false;
126
127public:
128 UnicodeChar() noexcept = default;
129 explicit UnicodeChar(const int prefix) {
130 if (IsADigit(prefix)) {
131 *asciiDigits = prefix;
132 if (*asciiDigits >= '0' && *asciiDigits <= '2') {
133 type = Notation::asciiDec;
134 // count first digit as "prefix"
135 toEnd = 2;
136 }
137 } else if (prefix == 'x' || prefix == 'u' || prefix == 'U') {
138 switch (prefix) {
139 case 'x':
140 type = Notation::asciiHex;
141 toEnd = 2;
142 break;
143 case 'u':
144 type = Notation::utf16;
145 toEnd = 4;
146 break;
147 case 'U':
148 type = Notation::utf32;
149 toEnd = 8;
150 break;
151 }
152 }
153 }
154 void Parse(const int ch) {
155 invalid = false;
156 switch (type) {
157 case Notation::asciiDec: {
158 maxDigit = (*asciiDigits < '2') ? '9' : (asciiDigits[1] <= '4') ? '9' : '5';
159 if (IsADigit(ch) && asciiDigits[1] <= maxDigit && ch <= maxDigit) {
160 asciiDigits[1] = ch;
161 toEnd--;
162 } else {
163 invalid = true;
164 }
165 break;
166 }
167 case Notation::asciiHex:
168 case Notation::utf16:
169 if (IsADigit(ch, 16)) {
170 toEnd--;
171 } else {
172 invalid = true;
173 }
174 break;
175 case Notation::utf32:
176 if ((toEnd > 6 && ch == '0') || (toEnd <= 6 && IsADigit(ch, 16))) {
177 toEnd--;
178 } else {
179 invalid = true;
180 }
181 break;
182 case Notation::none:
183 break;
184 }
185 }
186 constexpr bool AtEnd() noexcept {
187 return invalid || type == Notation::none || (type != Notation::none && toEnd < 0);
188 }
189};
190
191inline bool MatchStreamCommentStart(StyleContext &cxt) {
192 // match (* ... *), but allow point-free usage of the `*` operator,
193 // e.g. List.fold (*) 1 [ 1; 2; 3 ]
194 return (cxt.Match('(', '*') && cxt.GetRelative(2) != ')');
195}
196
197inline bool MatchStreamCommentEnd(const StyleContext &cxt) {
198 return (cxt.ch == ')' && cxt.chPrev == '*');
199}
200
201inline bool MatchLineComment(const StyleContext &cxt) {
202 // style shebang lines as comments in F# scripts:
203 // https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=30&zoom=auto,-98,537
204 return cxt.Match('/', '/') || cxt.Match('#', '!');
205}
206
207inline bool MatchLineNumberStart(StyleContext &cxt) {
208 return cxt.atLineStart && (cxt.MatchIgnoreCase("#line") ||
209 (cxt.ch == '#' && (IsADigit(cxt.chNext) || IsADigit(cxt.GetRelative(2)))));
210}
211
212inline bool MatchPPDirectiveStart(const StyleContext &cxt) {
213 return (cxt.atLineStart && cxt.ch == '#' && iswordstart(cxt.chNext));
214}
215
216inline bool MatchTypeAttributeStart(const StyleContext &cxt) {
217 return cxt.Match('[', '<');
218}
219
220inline bool MatchTypeAttributeEnd(const StyleContext &cxt) {
221 return (cxt.ch == ']' && cxt.chPrev == '>');
222}
223
224inline bool MatchQuotedExpressionStart(const StyleContext &cxt) {
225 return cxt.Match('<', '@');
226}
227
228inline bool MatchQuotedExpressionEnd(const StyleContext &cxt) {
229 return (cxt.ch == '>' && cxt.chPrev == '@');
230}
231
232inline bool MatchStringStart(const StyleContext &cxt) {
233 return (cxt.ch == '"' || cxt.Match('@', '"') || cxt.Match('$', '"') || cxt.Match('`', '`'));
234}
235
236inline bool FollowsEscapedBackslash(StyleContext &cxt) {
237 int count = 0;
238 for (Sci_Position offset = 1; cxt.GetRelative(-offset) == '\\'; offset++)
239 count++;
240 return count % 2 != 0;
241}
242
243inline bool MatchStringEnd(StyleContext &cxt, const FSharpString &fsStr) {
244 return (fsStr.HasLength() &&
245 // end of quoted identifier?
246 ((cxt.ch == '`' && cxt.chPrev == '`') ||
247 // end of literal or interpolated triple-quoted string?
248 ((fsStr.startChar == '"' || (fsStr.CanInterpolate() && cxt.chPrev != '$')) &&
249 cxt.MatchIgnoreCase("\"\"\"")) ||
250 // end of verbatim string?
251 (fsStr.startChar == '@' &&
252 // embedded quotes must be in pairs
253 cxt.ch == '"' && cxt.chNext != '"' &&
254 (cxt.chPrev != '"' || (cxt.chPrev == '"' &&
255 // empty verbatim string?
256 (cxt.GetRelative(-2) == '@' ||
257 // pair of quotes at end of string?
258 (cxt.GetRelative(-2) == '"' && cxt.GetRelative(-3) != '@'))))))) ||
259 (!fsStr.HasLength() && cxt.ch == '"' &&
260 ((cxt.chPrev != '\\' || (cxt.GetRelative(-2) == '\\' && !FollowsEscapedBackslash(cxt))) ||
261 // treat backslashes as char literals in verbatim strings
262 (fsStr.startChar == '@' && cxt.chPrev == '\\')));
263}
264
265inline bool MatchCharacterStart(StyleContext &cxt) {
266 // don't style generic type parameters: 'a, 'b, 'T, etc.
267 return (cxt.ch == '\'' && !(cxt.chPrev == ':' || cxt.GetRelative(-2) == ':'));
268}
269
270inline bool CanEmbedQuotes(StyleContext &cxt) {
271 // allow unescaped double quotes inside literal or interpolated triple-quoted strings, verbatim strings,
272 // and quoted identifiers:
273 // - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/strings
274 // - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/interpolated-strings#syntax
275 // - https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=25&zoom=auto,-98,600
276 return cxt.MatchIgnoreCase("$\"\"\"") || cxt.MatchIgnoreCase("\"\"\"") || cxt.Match('@', '"') ||
277 cxt.Match('`', '`');
278}
279
280inline bool IsNumber(StyleContext &cxt, const int base = 10) {
281 return IsADigit(cxt.ch, base) || (IsADigit(cxt.chPrev, base) && numericMetaChars1.Contains(cxt.ch)) ||
282 (IsADigit(cxt.GetRelative(-2), base) && numericMetaChars2.Contains(cxt.ch));
283}
284
285inline bool IsFloat(const StyleContext &cxt) {
286 return (cxt.ch == '.' && IsADigit(cxt.chPrev)) ||
287 ((cxt.ch == '+' || cxt.ch == '-' ) && IsADigit(cxt.chNext));
288}
289
290inline bool IsLineEnd(StyleContext &cxt, const Sci_Position offset) {
291 const int ch = cxt.GetRelative(offset, '\n');
292 return (ch == '\r' || ch == '\n');
293}
294
295class LexerFSharp : public DefaultLexer {
296 WordList keywords[WORDLIST_SIZE];
297 OptionsFSharp options;
298 OptionSetFSharp optionSet;
299
300public:
301 explicit LexerFSharp() : DefaultLexer(lexerName, SCLEX_FSHARP) {
302 }
303 static ILexer5 *LexerFactoryFSharp() {
304 return new LexerFSharp();
305 }
306 virtual ~LexerFSharp() {
307 }
308 void SCI_METHOD Release() noexcept override {
309 delete this;
310 }
311 int SCI_METHOD Version() const noexcept override {
312 return lvRelease5;
313 }
314 const char *SCI_METHOD GetName() noexcept override {
315 return lexerName;
316 }
317 int SCI_METHOD GetIdentifier() noexcept override {
318 return SCLEX_FSHARP;
319 }
320 int SCI_METHOD LineEndTypesSupported() noexcept override {
321 return SC_LINE_END_TYPE_DEFAULT;
322 }
323 void *SCI_METHOD PrivateCall(int, void *) noexcept override {
324 return nullptr;
325 }
326 const char *SCI_METHOD DescribeWordListSets() override {
327 return optionSet.DescribeWordListSets();
328 }
329 const char *SCI_METHOD PropertyNames() override {
330 return optionSet.PropertyNames();
331 }
332 int SCI_METHOD PropertyType(const char *name) override {
333 return optionSet.PropertyType(name);
334 }
335 const char *SCI_METHOD DescribeProperty(const char *name) override {
336 return optionSet.DescribeProperty(name);
337 }
338 const char *SCI_METHOD PropertyGet(const char *key) override {
339 return optionSet.PropertyGet(key);
340 }
341 Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override {
342 if (optionSet.PropertySet(&options, key, val)) {
343 return 0;
344 }
345 return ZERO_LENGTH;
346 }
347 Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override;
348 void SCI_METHOD Lex(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) override;
349 void SCI_METHOD Fold(Sci_PositionU start, Sci_Position length, int initStyle,IDocument *pAccess) override;
350};
351
352Sci_Position SCI_METHOD LexerFSharp::WordListSet(int n, const char *wl) {
353 WordList *wordListN = nullptr;
354 Sci_Position firstModification = ZERO_LENGTH;
355
356 if (n < WORDLIST_SIZE) {
357 wordListN = &keywords[n];
358 }
359 if (wordListN) {
360 WordList wlNew;
361 wlNew.Set(wl);
362
363 if (*wordListN != wlNew) {
364 wordListN->Set(wl);
365 firstModification = 0;
366 }
367 }
368 return firstModification;
369}
370
371void SCI_METHOD LexerFSharp::Lex(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) {
372 LexAccessor styler(pAccess);
373 StyleContext sc(start, static_cast<Sci_PositionU>(length), initStyle, styler);
374 Sci_PositionU cursor = 0;
375 UnicodeChar uniCh = UnicodeChar();
376 FSharpString fsStr = FSharpString();
377 constexpr Sci_Position MAX_WORD_LEN = 64;
378 constexpr int SPACE = ' ';
379 int currentBase = 10;
380
381 while (sc.More()) {
382 Sci_PositionU colorSpan = sc.currentPos - 1;
383 int state = -1;
384 bool advance = true;
385
386 switch (sc.state & 0xff) {
387 case SCE_FSHARP_DEFAULT:
388 cursor = sc.currentPos;
389
390 if (MatchLineNumberStart(sc)) {
391 state = SCE_FSHARP_LINENUM;
392 } else if (MatchPPDirectiveStart(sc)) {
393 state = SCE_FSHARP_PREPROCESSOR;
394 } else if (MatchLineComment(sc)) {
395 state = SCE_FSHARP_COMMENTLINE;
396 sc.Forward();
397 sc.ch = SPACE;
398 } else if (MatchStreamCommentStart(sc)) {
399 state = SCE_FSHARP_COMMENT;
400 sc.Forward();
401 sc.ch = SPACE;
402 } else if (MatchTypeAttributeStart(sc)) {
403 state = SCE_FSHARP_ATTRIBUTE;
404 sc.Forward();
405 } else if (MatchQuotedExpressionStart(sc)) {
406 state = SCE_FSHARP_QUOTATION;
407 sc.Forward();
408 } else if (MatchCharacterStart(sc)) {
409 state = SCE_FSHARP_CHARACTER;
410 } else if (MatchStringStart(sc)) {
411 fsStr.startChar = sc.ch;
412 fsStr.startPos = ZERO_LENGTH;
413 if (CanEmbedQuotes(sc)) {
414 // double quotes after this position should be non-terminating
415 fsStr.startPos = static_cast<Sci_Position>(sc.currentPos - cursor);
416 }
417 if (sc.ch == '`') {
418 state = SCE_FSHARP_QUOT_IDENTIFIER;
419 } else if (sc.ch == '@') {
420 state = SCE_FSHARP_VERBATIM;
421 } else {
422 state = SCE_FSHARP_STRING;
423 }
424 } else if (IsADigit(sc.ch, currentBase) ||
425 ((sc.ch == '+' || sc.ch == '-') && IsADigit(sc.chNext))) {
426 state = SCE_FSHARP_NUMBER;
427 if (sc.ch == '0') {
428 const int prefix = sc.chNext;
429 if (numericPrefixes.find(prefix) != numericPrefixes.end()) {
430 currentBase = numericPrefixes[prefix];
431 }
432 }
433 } else if (setOperators.Contains(sc.ch) &&
434 // don't use operator style in async keywords (e.g. `return!`)
435 !(sc.ch == '!' && iswordstart(sc.chPrev)) &&
436 // don't use operator style in member access, array/string indexing
437 !(sc.ch == '.' && (sc.chPrev == '\"' || iswordstart(sc.chPrev)) &&
438 (iswordstart(sc.chNext) || sc.chNext == '['))) {
439 state = SCE_FSHARP_OPERATOR;
440 } else if (iswordstart(sc.ch)) {
441 state = SCE_FSHARP_IDENTIFIER;
442 } else {
443 state = SCE_FSHARP_DEFAULT;
444 }
445 break;
446 case SCE_FSHARP_LINENUM:
447 case SCE_FSHARP_PREPROCESSOR:
448 case SCE_FSHARP_COMMENTLINE:
449 if (sc.MatchLineEnd()) {
450 state = SCE_FSHARP_DEFAULT;
451 advance = false;
452 }
453 break;
454 case SCE_FSHARP_COMMENT:
455 case SCE_FSHARP_ATTRIBUTE:
456 case SCE_FSHARP_QUOTATION:
457 if (MatchStreamCommentEnd(sc) || MatchTypeAttributeEnd(sc) || MatchQuotedExpressionEnd(sc)) {
458 state = SCE_FSHARP_DEFAULT;
459 colorSpan++;
460 }
461 break;
462 case SCE_FSHARP_CHARACTER:
463 if (sc.chPrev == '\\' && sc.GetRelative(-2) != '\\') {
464 uniCh = UnicodeChar(sc.ch);
465 } else if (sc.ch == '\'' &&
466 ((sc.chPrev == ' ' && sc.GetRelative(-2) == '\'') || sc.chPrev != '\\' ||
467 (sc.chPrev == '\\' && sc.GetRelative(-2) == '\\'))) {
468 // byte literal?
469 if (sc.Match('\'', 'B')) {
470 sc.Forward();
471 colorSpan++;
472 }
473 if (!sc.atLineEnd) {
474 colorSpan++;
475 } else {
476 sc.ChangeState(SCE_FSHARP_IDENTIFIER);
477 }
478 state = SCE_FSHARP_DEFAULT;
479 } else {
480 uniCh.Parse(sc.ch);
481 if (uniCh.AtEnd() && (sc.currentPos - cursor) >= 2) {
482 // terminate now, since we left the char behind
483 sc.ChangeState(SCE_FSHARP_IDENTIFIER);
484 advance = false;
485 }
486 }
487 break;
488 case SCE_FSHARP_STRING:
489 case SCE_FSHARP_VERBATIM:
490 case SCE_FSHARP_QUOT_IDENTIFIER:
491 if (MatchStringEnd(sc, fsStr)) {
492 const Sci_Position strLen = static_cast<Sci_Position>(sc.currentPos - cursor);
493 // backtrack to start of string
494 for (Sci_Position i = -strLen; i < 0; i++) {
495 const int startQuote = sc.GetRelative(i);
496 if (startQuote == '\"' || (startQuote == '`' && sc.GetRelative(i - 1) == '`')) {
497 // byte array?
498 if (sc.Match('\"', 'B')) {
499 sc.Forward();
500 colorSpan++;
501 }
502 if (!sc.atLineEnd) {
503 colorSpan++;
504 } else {
505 sc.ChangeState(SCE_FSHARP_IDENTIFIER);
506 }
507 state = SCE_FSHARP_DEFAULT;
508 break;
509 }
510 }
511 } else if (sc.ch == '%' &&
512 !(fsStr.startChar == '`' || sc.MatchIgnoreCase("% ") || sc.MatchIgnoreCase("% \"")) &&
513 (setFormatSpecs.Contains(sc.chNext) || setFormatFlags.Contains(sc.chNext))) {
514 if (fsStr.CanInterpolate() && sc.chNext != '%') {
515 for (Sci_Position i = 2; i < length && !IsLineEnd(sc, i); i++) {
516 if (sc.GetRelative(i) == '{') {
517 state = setFormatSpecs.Contains(sc.GetRelative(i - 1))
518 ? SCE_FSHARP_FORMAT_SPEC
519 : state;
520 break;
521 }
522 }
523 } else {
524 state = SCE_FSHARP_FORMAT_SPEC;
525 }
526 }
527 break;
528 case SCE_FSHARP_IDENTIFIER:
529 if (!(iswordstart(sc.ch) || sc.ch == '\'')) {
530 const Sci_Position wordLen = static_cast<Sci_Position>(sc.currentPos - cursor);
531 if (wordLen < MAX_WORD_LEN) {
532 // wordLength is believable as keyword, [re-]construct token - RR
533 char token[MAX_WORD_LEN] = { 0 };
534 for (Sci_Position i = -wordLen; i < 0; i++) {
535 token[wordLen + i] = static_cast<char>(sc.GetRelative(i));
536 }
537 token[wordLen] = '\0';
538 // a snake_case_identifier can never be a keyword
539 if (!(sc.ch == '_' || sc.GetRelative(-wordLen - 1) == '_')) {
540 for (int i = 0; i < WORDLIST_SIZE; i++) {
541 if (keywords[i].InList(token)) {
542 sc.ChangeState(keywordClasses[i]);
543 break;
544 }
545 }
546 }
547 }
548 state = SCE_FSHARP_DEFAULT;
549 advance = false;
550 }
551 break;
552 case SCE_FSHARP_OPERATOR:
553 // special-case "()" and "[]" tokens as KEYWORDS - RR
554 if (setClosingTokens.Contains(sc.ch) &&
555 ((sc.ch == ')' && sc.chPrev == '(') || (sc.ch == ']' && sc.chPrev == '['))) {
556 sc.ChangeState(SCE_FSHARP_KEYWORD);
557 colorSpan++;
558 } else {
559 advance = false;
560 }
561 state = SCE_FSHARP_DEFAULT;
562 break;
563 case SCE_FSHARP_NUMBER:
564 state = (IsNumber(sc, currentBase) || IsFloat(sc))
565 ? SCE_FSHARP_NUMBER
566 // change style even when operators aren't spaced
567 : setOperators.Contains(sc.ch) ? SCE_FSHARP_OPERATOR : SCE_FSHARP_DEFAULT;
568 currentBase = (state == SCE_FSHARP_NUMBER) ? currentBase : 10;
569 break;
570 case SCE_FSHARP_FORMAT_SPEC:
571 if (!setFormatSpecs.Contains(sc.chNext) ||
572 !(setFormatFlags.Contains(sc.ch) || IsADigit(sc.ch)) ||
573 (setFormatFlags.Contains(sc.ch) && sc.ch == sc.chNext)) {
574 colorSpan++;
575 state = (fsStr.startChar == '@') ? SCE_FSHARP_VERBATIM : SCE_FSHARP_STRING;
576 }
577 break;
578 }
579
580 if (state >= SCE_FSHARP_DEFAULT) {
581 styler.ColourTo(colorSpan, sc.state);
582 sc.ChangeState(state);
583 }
584
585 if (advance) {
586 sc.Forward();
587 }
588 }
589
590 sc.Complete();
591}
592
593bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, const Sci_Position end = 1,
594 const int chAttr = SCE_FSHARP_DEFAULT);
595
596void FoldLexicalGroup(LexAccessor &styler, int &levelNext, const Sci_Position lineCurrent, const char *word,
597 const int chAttr);
598
599void SCI_METHOD LexerFSharp::Fold(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) {
600 if (!options.fold) {
601 return;
602 }
603
604 LexAccessor styler(pAccess);
605 const Sci_Position startPos = static_cast<Sci_Position>(start);
606 const Sci_PositionU endPos = start + length;
607 Sci_Position lineCurrent = styler.GetLine(startPos);
608 Sci_Position lineNext = lineCurrent + 1;
609 Sci_Position lineStartNext = styler.LineStart(lineNext);
610 int style = initStyle;
611 int styleNext = styler.StyleAt(startPos);
612 char chNext = styler[startPos];
613 int levelNext;
614 int levelCurrent = SC_FOLDLEVELBASE;
615 int visibleChars = 0;
616
617 if (lineCurrent > 0) {
618 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 0x10;
619 }
620
621 levelNext = levelCurrent;
622
623 for (Sci_PositionU i = start; i < endPos; i++) {
624 const Sci_Position currentPos = static_cast<Sci_Position>(i);
625 const bool atEOL = currentPos == (lineStartNext - 1);
626 const bool atLineOrDocEnd = (atEOL || (i == (endPos - 1)));
627 const bool atDosOrMacEOL = (atEOL || styler.SafeGetCharAt(currentPos) == '\r');
628 const int stylePrev = style;
629 const char ch = chNext;
630 const bool inLineComment = (stylePrev == SCE_FSHARP_COMMENTLINE);
631 style = styleNext;
632 styleNext = styler.StyleAt(currentPos + 1);
633 chNext = styler.SafeGetCharAt(currentPos + 1);
634
635 if (options.foldComment) {
636 if (options.foldCommentMultiLine && inLineComment && atDosOrMacEOL &&
637 (lineCurrent > 0 || styler.StyleAt(lineStartNext) == SCE_FSHARP_COMMENTLINE)) {
638 FoldLexicalGroup(styler, levelNext, lineCurrent, "//", SCE_FSHARP_COMMENTLINE);
639 }
640
641 if (options.foldCommentStream && style == SCE_FSHARP_COMMENT && !inLineComment) {
642 if (stylePrev != SCE_FSHARP_COMMENT) {
643 levelNext++;
644 } else if (styleNext != SCE_FSHARP_COMMENT && !atEOL) {
645 levelNext--;
646 }
647 }
648 }
649
650 if (options.foldPreprocessor && style == SCE_FSHARP_PREPROCESSOR) {
651 if (styler.Match(currentPos, "#if")) {
652 levelNext++;
653 } else if (styler.Match(currentPos, "#endif")) {
654 levelNext--;
655 }
656 }
657
658 if (options.foldImports && atLineOrDocEnd &&
659 LineContains(styler, "open ", lineCurrent, 1, SCE_FSHARP_KEYWORD)) {
660 FoldLexicalGroup(styler, levelNext, lineCurrent, "open ", SCE_FSHARP_KEYWORD);
661 }
662
663 if (!IsASpace(ch)) {
664 visibleChars++;
665 }
666
667 if (atLineOrDocEnd) {
668 int levelUse = levelCurrent;
669 int lev = levelUse | levelNext << 16;
670
671 if (visibleChars == 0 && options.foldCompact) {
672 lev |= SC_FOLDLEVELWHITEFLAG;
673 }
674 if (levelUse < levelNext) {
675 lev |= SC_FOLDLEVELHEADERFLAG;
676 }
677 if (lev != styler.LevelAt(lineCurrent)) {
678 styler.SetLevel(lineCurrent, lev);
679 }
680
681 visibleChars = 0;
682 lineCurrent++;
683 lineNext = lineCurrent + 1;
684 lineStartNext = styler.LineStart(lineNext);
685 levelCurrent = levelNext;
686
687 if (atEOL && (currentPos == (styler.Length() - 1))) {
688 styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG);
689 }
690 }
691 }
692}
693
694bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, const Sci_Position end,
695 const int chAttr) {
696 bool found = false;
697 bool requireStyle = (chAttr > SCE_FSHARP_DEFAULT);
698 const Sci_Position limit = (end > 1) ? end : start;
699 for (Sci_Position i = start; i < styler.LineEnd(limit); i++) {
700 if (styler.Match(i, word)) {
701 found = requireStyle ? styler.StyleAt(i) == chAttr : true;
702 break;
703 }
704 }
705 return found;
706}
707
708void FoldLexicalGroup(LexAccessor &styler, int &levelNext, const Sci_Position lineCurrent, const char *word,
709 const int chAttr) {
710 const Sci_Position linePrev = lineCurrent - 1;
711 const Sci_Position lineNext = lineCurrent + 1;
712 const Sci_Position lineStartPrev = styler.LineStart(linePrev);
713 const Sci_Position lineStartNext = styler.LineStart(lineNext);
714 const bool atFoldGroupStart =
715 (lineCurrent == 0 || !LineContains(styler, word, lineStartPrev, linePrev, chAttr)) &&
716 LineContains(styler, word, lineStartNext, lineNext, chAttr);
717 const bool atFoldGroupEnd =
718 LineContains(styler, word, lineStartPrev, linePrev, chAttr) &&
719 !LineContains(styler, word, lineStartNext, lineNext, chAttr);
720
721 if (atFoldGroupStart) {
722 levelNext++;
723 } else if (atFoldGroupEnd && levelNext > SC_FOLDLEVELBASE) {
724 levelNext--;
725 }
726}
727} // namespace
728
729LexerModule lmFSharp(SCLEX_FSHARP, LexerFSharp::LexerFactoryFSharp, "fsharp", fsharpWordLists);
730