Skip to content

Commit

Permalink
Improve Batch string and parentheses highlighting, issue #332.
Browse files Browse the repository at this point in the history
  • Loading branch information
zufuliu committed Jun 22, 2021
1 parent e44c285 commit ea4af7e
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 77 deletions.
5 changes: 3 additions & 2 deletions scintilla/include/SciLexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -469,12 +469,13 @@
#define SCE_BAT_LABEL 7
#define SCE_BAT_OPERATOR 8
#define SCE_BAT_ESCAPECHAR 9
#define SCE_BAT_LABEL_LINE 10
#define SCE_BAT_STRINGNQ 10
#define SCE_BAT_STRINGDQ 11
#define SCE_BAT_STRINGSQ 12
#define SCE_BAT_STRINGBT 13
#define SCE_BAT_NOT_BATCH 14
#define SCE_BAT_LINE_CONTINUATION 15
#define SCE_BAT_LABEL_LINE 15
#define SCE_BAT_LINE_CONTINUATION 16
#define SCE_MAKE_DEFAULT 0
#define SCE_MAKE_COMMENT 1
#define SCE_MAKE_PREPROCESSOR 2
Expand Down
5 changes: 3 additions & 2 deletions scintilla/include/SciLexer.iface
Original file line number Diff line number Diff line change
Expand Up @@ -684,12 +684,13 @@ val SCE_BAT_VARIABLE=6
val SCE_BAT_LABEL=7
val SCE_BAT_OPERATOR=8
val SCE_BAT_ESCAPECHAR=9
val SCE_BAT_LABEL_LINE=10
val SCE_BAT_STRINGNQ=10
val SCE_BAT_STRINGDQ=11
val SCE_BAT_STRINGSQ=12
val SCE_BAT_STRINGBT=13
val SCE_BAT_NOT_BATCH=14
val SCE_BAT_LINE_CONTINUATION=15
val SCE_BAT_LABEL_LINE=15
val SCE_BAT_LINE_CONTINUATION=16
# Lexical states for SCLEX_TCMD
#lex TCMD=SCLEX_TCMD SCE_TCMD_
#val SCE_TCMD_DEFAULT=0
Expand Down
167 changes: 103 additions & 64 deletions scintilla/lexers/LexBatch.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,25 @@ enum {
enum class Command {
None,
Echo,
Argument,
Call,
Goto,
Set,
SetValue,
Escape,
Argument,
};

constexpr bool IsDrive(int chPrev, int chNext) noexcept {
return (chNext == '\\' || chNext == '/') && IsAlpha(chPrev);
}

constexpr bool IsBatchOperator(int ch, Command command) noexcept {
return AnyOf(ch, '&', '|', '<', '>', '(', ')')
return AnyOf(ch, '&', '|', '<', '>')
|| (command != Command::Echo && AnyOf(ch, '=', '@', ',', ';', '?', '*'));
}

constexpr bool IsFileNameChar(int ch) noexcept {
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
return IsGraphic(ch) && !AnyOf(ch, '<', '>', ':', '"', '|', '?', '*', // reserved characters
'&', '(', ')', ',', ';');
}
Expand All @@ -74,24 +76,36 @@ constexpr bool IsVariableEscapeChar(int ch) noexcept {
}

constexpr bool IsStringStyle(int style) noexcept {
return style == SCE_BAT_STRINGDQ || style == SCE_BAT_STRINGSQ || style == SCE_BAT_STRINGBT;
return style == SCE_BAT_STRINGDQ || style == SCE_BAT_STRINGNQ || style == SCE_BAT_STRINGSQ || style == SCE_BAT_STRINGBT;
}

constexpr char GetStringQuote(int style) noexcept {
switch (style) {
case SCE_BAT_STRINGDQ: return '\"';
case SCE_BAT_STRINGSQ: return '\'';
case SCE_BAT_STRINGBT: return '`';
default: return '\0';
}
}

bool DetectBatchEscapeChar(StyleContext &sc, int &outerStyle) noexcept {
bool IsStringArgumentEnd(const StyleContext &sc, int outerStyle, Command command, int parenCount) noexcept {
const int ch = (command == Command::SetValue) ? sc.GetLineNextChar() : sc.ch;
return AnyOf(ch, '\0', '\n', '\r', ' ', '\t', '&', '|', '<', '>')
|| (parenCount != 0 && ch == ')')
|| ch == GetStringQuote(outerStyle);
}

bool DetectBatchEscapeChar(StyleContext &sc, int &outerStyle, Command command) {
// Escape Characters https://www.robvanderwoude.com/escapechars.php
int length = 0;
const int state = (sc.state == SCE_BAT_ESCAPECHAR) ? outerStyle : sc.state;
int state = (sc.state == SCE_BAT_ESCAPECHAR) ? outerStyle : sc.state;

switch (sc.ch) {
case '^':
if (IsEOLChar(sc.chNext)) {
if (IsStringStyle(state)) {
outerStyle = state;
sc.SetState(SCE_BAT_ESCAPECHAR);
} else {
outerStyle = SCE_BAT_DEFAULT;
sc.SetState(SCE_BAT_LINE_CONTINUATION);
}
outerStyle = state = IsStringStyle(state) ? state : SCE_BAT_DEFAULT;
state = (state == SCE_BAT_DEFAULT || state == SCE_BAT_STRINGNQ) ? SCE_BAT_LINE_CONTINUATION : SCE_BAT_ESCAPECHAR;
sc.SetState(state);
return true;
}
if (sc.chNext == '^') {
Expand All @@ -102,20 +116,24 @@ bool DetectBatchEscapeChar(StyleContext &sc, int &outerStyle) noexcept {
break;

case '%':
// TODO: detect for loop
if (sc.chNext == '%' && !IsVariableChar(sc.GetRelative(2))) {
length = 1;
}
break;

case '\"':
// Inside the search pattern of FIND
if (sc.chNext == '"' && IsStringStyle(state)) {
if (sc.chNext == '"' && state == SCE_BAT_STRINGDQ) {
length = 1;
}
break;

case '\\':
// Inside the regex pattern of FINDSTR
if (command == Command::Escape && IsPunctuation(sc.chNext)) {
length = 1;
}
break;
}

Expand Down Expand Up @@ -202,20 +220,22 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
int levelCurrent = SC_FOLDLEVELBASE;
if (sc.currentLine > 0) {
levelCurrent = styler.LevelAt(sc.currentLine - 1) >> 16;
prevLineState = styler.GetLineState(sc.currentLine - 1);
int lineState = styler.GetLineState(sc.currentLine - 1);
prevLineState = lineState;
/*
1: empty line
1: line continuation
2: unused
4: command
8: parenCount
: nestedState
3*4: nestedState
*/
parenCount = (prevLineState >> 8) & 0xff;
if (prevLineState & BatchLineStateLineContinuation) {
parenCount = (lineState >> 8) & 0xff;
if (lineState & BatchLineStateLineContinuation) {
++logicalVisibleChars;
command = static_cast<Command>((prevLineState >> 4) & 15);
command = static_cast<Command>((lineState >> 4) & 15);
}
const int lineState = prevLineState >> 16;
lineState >>= 16;
if (lineState) {
UnpackLineState(lineState, nestedState);
}
Expand All @@ -234,10 +254,15 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
break;

case SCE_BAT_IDENTIFIER:
if (((sc.ch == '^' || sc.ch == '%') && DetectBatchEscapeChar(sc, outerStyle))
if (sc.ch == '.' && sc.LengthCurrent() == 4 && sc.styler.MatchIgnoreCase(sc.styler.GetStartSegment(), "echo")) {
parenBefore = parenCount;
command = Command::Echo;
sc.ChangeState(SCE_BAT_WORD);
sc.ForwardSetState(SCE_BAT_DEFAULT);
} else if (((sc.ch == '^' || sc.ch == '%') && DetectBatchEscapeChar(sc, outerStyle, command))
|| ((sc.ch == '%' || sc.ch == '!') && DetectBatchVariable(sc, outerStyle, varQuoteChar))) {
command = Command::Argument;
} else if (logicalVisibleChars == 1 && sc.ch == ':' && IsDrive(sc.chPrev, sc.chNext)) {
// nop
} else if (sc.ch == ':' && sc.LengthCurrent() == 1 && IsDrive(sc.chPrev, sc.chNext)) {
++logicalVisibleChars;
sc.Forward();
} else if (!IsFileNameChar(sc.ch)) {
Expand All @@ -250,14 +275,15 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
command = Command::None;
parenBefore = parenCount;
sc.ChangeState(SCE_BAT_WORD);
if (StrEqualsAny(s, "echo", "echo.", "title")) {
if (StrEqualsAny(s, "echo", "title")) {
command = Command::Echo;
} else if (StrEqualsAny(s, "do", "else")) {
logicalVisibleChars = 0;
} else if (StrEqual(s, "goto")) {
command = Command::Goto;
} else if (StrEqual(s, "call")) {
command = Command::Call;
logicalVisibleChars = 0;
} else if (StrEqual(s, "set")) {
command = Command::Set;
}
Expand All @@ -266,7 +292,7 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
parenBefore = parenCount;
sc.ChangeState(SCE_BAT_WORD);
} else if (logicalVisibleChars == sc.LengthCurrent()) {
command = Command::Argument;
command = StrEqualsAny(s, "findstr", "reg") ? Command::Escape : Command::Argument;
parenBefore = parenCount;
sc.ChangeState(SCE_BAT_COMMAND);
if (sc.ch == ':') {
Expand Down Expand Up @@ -307,7 +333,7 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
if (sc.ch == varQuoteChar) {
varQuoteChar = '\0';
sc.Forward();
} else if (sc.ch == '\"' || sc.ch == '\'' || sc.ch == '`' || IsEOLChar(sc.ch)) {
} else if (IsEOLChar(sc.ch) || sc.ch == GetStringQuote(outerStyle)) {
// something went wrong
varQuoteChar = '\0';
}
Expand All @@ -319,35 +345,57 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
break;

case SCE_BAT_STRINGDQ:
case SCE_BAT_STRINGNQ:
case SCE_BAT_STRINGSQ:
case SCE_BAT_STRINGBT:
if (DetectBatchEscapeChar(sc, outerStyle)) {
if (DetectBatchEscapeChar(sc, outerStyle, command)) {
// nop
} else if (sc.ch == '%' || sc.ch == '!') {
DetectBatchVariable(sc, outerStyle, varQuoteChar);
} else if ((sc.state == SCE_BAT_STRINGDQ && sc.ch == '\"')
|| (sc.state == SCE_BAT_STRINGSQ && sc.ch == '\'')
|| (sc.state == SCE_BAT_STRINGBT && sc.ch == '`')) {
if (nestedState.empty()) {
sc.ForwardSetState(SCE_BAT_DEFAULT);
} else if (sc.ch == '\"') {
int state = sc.state;
if (state == SCE_BAT_STRINGDQ) {
state = TryTakeAndPop(nestedState);
sc.Forward();
if ((command == Command::SetValue || command == Command::Argument || command == Command::Escape)
&& !IsStringArgumentEnd(sc, state, command, parenCount)) {
state = SCE_BAT_STRINGNQ;
}
} else {
sc.ForwardSetState(nestedState.back());
nestedState.pop_back();
if (state != SCE_BAT_STRINGNQ) {
nestedState.push_back(state);
}
state = SCE_BAT_STRINGDQ;
}
sc.SetState(state);
if (state != SCE_BAT_STRINGDQ) {
continue;
}
} else if (sc.ch == '\"') {
nestedState.push_back(sc.state);
sc.SetState(SCE_BAT_STRINGDQ);
} else if (sc.state == SCE_BAT_STRINGDQ && sc.ch == '=' && command == Command::Set) {
command = Command::Argument;
sc.SetState(SCE_BAT_OPERATOR);
sc.ForwardSetState(SCE_BAT_STRINGDQ);
continue;
} else if (sc.state == SCE_BAT_STRINGDQ) {
if (sc.ch == '=' && command == Command::Set) {
command = Command::SetValue;
sc.SetState(SCE_BAT_OPERATOR);
sc.ForwardSetState(SCE_BAT_STRINGDQ);
continue;
}
} else if (sc.state == SCE_BAT_STRINGNQ) {
const int state = nestedState.empty() ? SCE_BAT_DEFAULT : nestedState.back();
if (IsStringArgumentEnd(sc, state, command, parenCount)) {
sc.SetState(state);
continue;
}
} else if ((sc.state == SCE_BAT_STRINGSQ && sc.ch == '\'') || (sc.state == SCE_BAT_STRINGBT && sc.ch == '`')) {
sc.Forward();
const int chNext = sc.GetDocNextChar();
if (chNext == ')') {
const int state = TryTakeAndPop(nestedState);
sc.SetState(state);
}
}
break;

case SCE_BAT_ESCAPECHAR:
if (!DetectBatchEscapeChar(sc, outerStyle)) {
if (!DetectBatchEscapeChar(sc, outerStyle, command)) {
sc.SetState(outerStyle);
continue;
}
Expand Down Expand Up @@ -381,20 +429,22 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
sc.SetState(SCE_BAT_NOT_BATCH);
levelNext++;
}
} else if (DetectBatchEscapeChar(sc, outerStyle) ||
} else if (DetectBatchEscapeChar(sc, outerStyle, command) ||
((sc.ch == '%' || sc.ch == '!') && DetectBatchVariable(sc, outerStyle, varQuoteChar))) {
// nop
} else if (sc.ch == '\"') {
sc.SetState(SCE_BAT_STRINGDQ);
} else if ((sc.ch == '\'' || sc.ch == '`') && (chPrevNonWhite == '(' && stylePrevNonWhite == SCE_BAT_OPERATOR)) {
parenBefore = parenCount;
sc.SetState((sc.ch == '\'') ? SCE_BAT_STRINGSQ : SCE_BAT_STRINGBT);
} else if (sc.ch == '(' || sc.ch == ')') {
sc.SetState(SCE_BAT_OPERATOR);
if (command != Command::Echo || parenCount > 0) {
const bool handled = (sc.ch == '(') ? (command != Command::Echo) : (parenCount > 0);
if (handled) {
sc.SetState(SCE_BAT_OPERATOR);
if (sc.ch == '(') {
parenCount++;
levelNext++;
} else if (parenCount > 0) {
} else {
parenCount--;
levelNext--;
if (parenCount < parenBefore) {
Expand Down Expand Up @@ -426,7 +476,7 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl

case '=':
if (command == Command::Set) {
command = Command::Argument;
command = Command::SetValue;
}
break;

Expand All @@ -444,14 +494,7 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
default:
break;
}
} else if (command == Command::Call && sc.ch == ':') {
command = Command::Argument;
if (IsDrive(sc.chPrev, sc.chNext)) {
sc.Forward();
} else {
sc.SetState(SCE_BAT_LABEL);
}
} else if (command == Command::Goto && IsGraphic(sc.ch)) {
} else if ((command == Command::Call && sc.ch == ':') || (command == Command::Goto && IsGraphic(sc.ch))) {
command = Command::Argument;
sc.SetState(SCE_BAT_LABEL);
} else if ((logicalVisibleChars == 0 || command == Command::None) && IsFileNameChar(sc.ch)) {
Expand All @@ -477,19 +520,15 @@ void ColouriseBatchDoc(Sci_PositionU startPos, Sci_Position length, int initStyl
lineState |= static_cast<int>(command) << 4;
sc.SetState(SCE_BAT_DEFAULT);
} else {
lineState |= lineVisibleChars ? 0 : BatchLineStateMaskEmptyLine;
lineState |= (lineVisibleChars == 0 || sc.state == SCE_BAT_COMMENT) ? BatchLineStateMaskEmptyLine : 0;
command = Command::None;
logicalVisibleChars = 0;
}
lineVisibleChars = 0;
if (IsStringStyle(sc.state)) {
if (sc.state == SCE_BAT_STRINGDQ) {
if (nestedState.empty()) {
sc.SetState(SCE_BAT_DEFAULT);
} else {
sc.SetState(nestedState.back());
nestedState.pop_back();
}
if (sc.state == SCE_BAT_STRINGDQ || sc.state == SCE_BAT_STRINGNQ) {
const int state = TryTakeAndPop(nestedState);
sc.SetState(state);
}
if (!nestedState.empty()) {
lineState |= PackLineState(nestedState) << 16;
Expand Down
Loading

0 comments on commit ea4af7e

Please # to comment.