User 13204940 Ответов: 2

Необычная проблема подсветки синтаксиса


Привет,

Я потратил довольно много времени на написание синтаксического маркера с использованием регулярных выражений.

Это разрешенные матчи:

1) String|int / boolean, а затем пробел (для объявления переменной, например. Строки mystring)
2) FilterableList & lt;String|int / boolean>
3) открытая скобка, либо ничего, либо (что-то, то пробел) "цитата" (либо ничего, либо пробел, то что-то) закрытая скобка, например (myString + " hello")
4) пробел, то +|-|*|/|==|&& или | / а затем пробел, например. 1 + 2
5) (или )
6) любое число 0-9

Это почти идеально работает, но есть несколько проблем. Видеть Regex Syntax Highlighter-альбом на Imgur[^]

1) вся цитата, включая"", должна быть желтой.
2) FilterableList&ЛТ;&ГТ; должно быть голубым, что внутри должен быть зеленым, если инт|последовательность|логические
3) одна из цитат имеет свой цвет полностью смещенным
4)) не красный.

Кажется, дело в том, что одни матчи мешают другим, есть идеи, как это преодолеть?
Private Sub updateCodeSyntaxHighlighting()
        allowCodeInput = False

        Dim prefix As String = "(?<return>"
        Dim suffix As String = ")( .*)?(?!.)"

        Dim listMatches As New Regex(prefix & "FilterableList<.*>" & suffix)
        Dim variableMatches As New Regex(prefix & "(int|String|boolean)|<(?<return>int|String|boolean)>" & suffix)
        Dim quoteMatches As New Regex("\((.* )?" & prefix & """.*"")( .*)?\)")
        Dim symbolMatches As New Regex(prefix & "( ([+\-/*]|==|&&|\|\|) )|\(|\))")
        Dim numberMatches As New Regex(prefix & "[0-9])")

        Dim selPos As Integer = codeEditorBox.SelectionStart
        Dim selPos2 As Integer = codeEditorBox.GetFirstCharIndexOfCurrentLine

        If Not codeEditorBox.Text = "" Then
            codeEditorBox.Select(0, codeEditorBox.Lines(codeEditorBox.GetLineFromCharIndex(selPos)).Length)
            codeEditorBox.SelectionStart = selPos2
            codeEditorBox.SelectionColor = codeEditorBox.ForeColor
            codeEditorBox.SelectionStart = selPos
        End If

        For Each match As Match In listMatches.Matches(codeEditorBox.Text)
            highlight(match, selPos, Color.FromArgb(64, 255, 255))
        Next

        For Each match As Match In variableMatches.Matches(codeEditorBox.Text)
            highlight(match, selPos, Color.FromArgb(64, 255, 64))
        Next

        For Each match As Match In quoteMatches.Matches(codeEditorBox.Text)
            highlight(match, selPos, Color.FromArgb(255, 255, 64))
        Next

        For Each match As Match In symbolMatches.Matches(codeEditorBox.Text)
            highlight(match, selPos, Color.FromArgb(255, 64, 64))
        Next

        For Each match As Match In numberMatches.Matches(codeEditorBox.Text)
            highlight(match, selPos, Color.FromArgb(50, 155, 155))
        Next

        codeEditorBox.SelectionLength = 0

        allowCodeInput = True
    End Sub

    Private Sub highlight(match As Match, selPos As Integer, c As Color)
        Dim index As Integer = match.Index
        Dim length As Integer = match.Result("${return}").Length

        codeEditorBox.Select(index, length)
        codeEditorBox.SelectionColor = c
        codeEditorBox.SelectionStart = index + length
        codeEditorBox.SelectionColor = codeEditorBox.ForeColor
        codeEditorBox.SelectionStart = selPos
    End Sub


Что я уже пробовал:

Несколько генераторов регулярных выражений
-------------------------

2 Ответов

Рейтинг:
9

User 13204940

Решено! В итоге я использовал комбинацию регулярных выражений и старой доброй логики программирования.

Проблема заключалась в том, в каком порядке я раскрашивал разные струны. Если вы раскрасите разные части richtextbox таким образом, чтобы SelectionStart не был больше последнего, некоторые части потеряют свой цвет. Вот что меня смутило - поскольку на некоторых частях не было цвета, я предположил, что регулярное выражение не запечатлело все правильно.

Чтобы исправить проблему упорядочения, я сохранил индекс и SyntaxHighlightOperation, содержащие другие необходимые значения для моей функции выделения, в SortedDictionary. Это эффективно сортировало операции выделения по индексу, полностью решая проблему окраски.

Вот результат: [^]

И вот окончательный код:

Dim operations As New SortedDictionary(Of Integer, SyntaxHighlightOperation)

        Dim code As String = codeEditorBox.Text

        Dim anything As String = "[\(\);]"
        Dim codeElements As String = "Server|Files"
        Dim variableTypes As String = "string|int|bool|dec"
        Dim enumerations As String = "SyntaxRating|Mood"

        For Each match As Match In New Regex(anything).Matches(code)
            operations.Add(match.Index, New SyntaxHighlightOperation(match.Index, 1, selPos, SyntaxColours.Symbol))
        Next

        For Each match As Match In New Regex(variableTypes).Matches(code)
            If code.hasAnyAround(match.Value, match.Index, New String()() {
                                 New String() {"""", """"},
                                 New String() {"(", ")"}
                                 }) Then
                operations.Add(match.Index - 1, New SyntaxHighlightOperation(match.Index - 1, match.Value.Length + 2, selPos, SyntaxColours.Variable))
            ElseIf code.hasAround(match.Value, match.Index, New String() {"List<", ">"}) AndAlso code.hasAround(match.Value, match.Index, New String() {"<", ">"}) Then
                operations.Add(match.Index - 5, New SyntaxHighlightOperation(match.Index - 5, match.Value.Length + 2, selPos, SyntaxColours.List))
                operations.Add(match.Index, New SyntaxHighlightOperation(match.Index, match.Value.Length, selPos, SyntaxColours.Variable))
                operations.Add(match.Index + match.Value.Length, New SyntaxHighlightOperation(match.Index + match.Value.Length, 1, selPos, SyntaxColours.List))
            End If
        Next

        For Each match As Match In New Regex(codeElements).Matches(code)
            operations.Add(match.Index, New SyntaxHighlightOperation(match.Index, match.Value.Length, selPos, SyntaxColours.CodeElement))
        Next

        For Each match As Match In New Regex(enumerations).Matches(code)
            If code.hasAfter(match.Value, match.Index, ".") Then
                operations.Add(match.Index, New SyntaxHighlightOperation(match.Index, match.Value.Length, selPos, SyntaxColours.Enumeration))
            End If
        Next

        ''''''''''''''''''''''''''''''''''

        For Each operation As SyntaxHighlightOperation In operations.Values
            highlight(operation.getIndex(), operation.getLength(), operation.getSelPos(), operation.getColor())
        Next


Я также создал класс расширений, чтобы помочь с логикой требования определенных строк до, после или до и после матча:

Imports System.Runtime.CompilerServices

Module ExtensionMethods

    <Extension()>
    Public Function hasAround(code As String, text As String, index As Integer, around() As String) As Boolean
        Dim before As String = around(0)
        Dim after As String = around(1)

        If index - before.Length < 0 OrElse index + text.Length = code.Length Then Return False

        Return code.Substring(index - before.Length, before.Length) = before AndAlso code.Substring(index + text.Length, after.Length) = after
    End Function

    <Extension()>
    Public Function hasAnyAround(code As String, text As String, index As Integer, around()() As String) As Boolean
        For Each a As String() In around
            Dim before As String = a(0)
            Dim after As String = a(1)

            If index - before.Length < 0 OrElse index + text.Length >= code.Length Then Return False
            If code.Substring(index - before.Length, before.Length) = before AndAlso code.Substring(index + text.Length, after.Length) = after Then Return True
        Next

        Return False
    End Function

    <Extension()>
    Public Function hasBefore(code As String, text As String, index As Integer, before As String) As Boolean
        If index - before.Length < 0 Then Return False
        Return code.Substring(index - before.Length, before.Length) = before
    End Function

    <Extension()>
    Public Function hasAfter(code As String, text As String, index As Integer, after As String) As Boolean
        If index + text.Length >= code.Length Then Return False
        Return code.Substring(index + text.Length, after.Length) = after
    End Function

End Module


Рейтинг:
1

RickZeeland

Я когда-то сделал что-то подобное в C# для SQL-кода, может быть, это будет полезно для вас.
Полный исходный код можно найти на CodeProject: Учебник по СМО 3 н - сценариев[^]
Обратите внимание, что новая строка заменяются пробелами во временной строке для поиска, так как новые строки проблематичны для поиска регулярных выражений.

/// <summary>
/// Syntax coloring for rich text box.
/// </summary>
/// <param name="colorComments">Also color comments green.</param>
private void SyntaxColoring(bool colorComments)
{
    this.richTextScript.Focus();
    this.richTextScript.SelectionStart = 0;
    this.richTextScript.SelectionLength = 0;
    string richText = this.richTextScript.Text.Replace("\n", " ");

    // Check for keywords and apply blue color.
    foreach (var keyword in this.Keywords)
    {
        var reg = Regex.Matches(richText, @"([ ,()-])" + keyword + @"([ ,()-])");

        foreach (Match match in reg)
        {
            this.richTextScript.SelectionStart = match.Index;
            this.richTextScript.SelectionLength = match.Length;
            this.richTextScript.SelectionColor = Color.Blue;
            this.richTextScript.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
        }
    }

    // Find strings and apply red color.
    foreach (Match match in Regex.Matches(richText, @"([""'])(?:(?=(\\?))\2.)*?\1"))
    {
        this.richTextScript.SelectionStart = match.Index;
        this.richTextScript.SelectionLength = match.Length;
        this.richTextScript.SelectionColor = Color.IndianRed;
        this.richTextScript.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
    }

    // Check for comments and apply green color.
    if (colorComments)
    {
        foreach (Match match in Regex.Matches(richText, @"/\*.*?\*/"))
        {
            this.richTextScript.SelectionStart = match.Index;
            this.richTextScript.SelectionLength = match.Length;
            this.richTextScript.SelectionColor = Color.Green;
            this.richTextScript.SelectionFont = new Font("Courier New", 10, FontStyle.Regular);
        }
    }
}


[no name]

Я обязательно разберусь с этим, спасибо!

[no name]

У меня есть проблема, как вы можете видеть здесь https://www.debuggex.com/r/c7IRbxMbAlRz0-dN

Он не возвращает только int|String|boolean, как я хочу, он возвращает всю строку целиком.

RickZeeland

Я нахожу debuggex немного запутанным, но это, кажется, работает для строк:
([""'])(?:(?=(\\?))\2.)*?\1

RickZeeland

И это для ваших ключевых слов, пока они окружены пробелом или специальными символами:
([, ()- ]) int|bool / String([ ,()-])

[no name]

Я действительно борюсь здесь.

Для каждого ключевого слова в виде строки в переменных типах
На каждый матч, как матч нового регулярного выражения("((.* |(?&л;!.))" &ампер; сайта &ампер; "|(?&ЛТ;=.)&л;" &амп; сайта &ампер; "&ГТ;)( .*|(?!.))").Играм(codeEditorBox.Текст)
выделите (матч, selPos, цвет.FromArgb(64, 255, 64))
Следующий
Следующий

Private Sub highlight (match As Match, selPos As Integer, c As Color)
Тусклый индекс как целое число = совпадение.Индекс
Тусклая длина как целое число = совпадение.Длина

codeEditorBox.Выберите (индекс, длина)
codeEditorBox.SelectionColor = c
codeEditorBox.Объект selectionstart = индекс + длина
codeEditorBox.SelectionColor = codeEditorBox.Цвет
codeEditorBox.Объект selectionstart = селпос
Конец Подводной Лодки

Если я запускаю это на "String goaway", он возвращает" String goaway", а не просто" String", который я хочу.

RickZeeland

Может быть.Select-это проблема здесь, см. мой код, который использует .Вместо этого SelectionLength.

[no name]

совпадение.Значение - " String goaway"

RickZeeland

Я думаю, что во второй части вам не хватает космического символа:
"|(? <=.)<"

[no name]

Мне не нужен космический шар. Мне нужно сказать ему, чтобы он дал мне "String", а не " String goaway"

RickZeeland

Попробуйте это сделать, вам нужно проверить наличие пробела в конце строки:
((|(? <!.)))строка ((|(? <!.)))

[no name]

Жаль, что какой-то грустный человек проголосовал за 1. Похоже, они тоже проголосовали за мое решение, как будто ... оно не сработало..

Ну что ж, это разрушило 5-звездочный рейтинг, который я вам дал

RickZeeland

Случалось со мной и раньше, я подавал жалобу по этому поводу на форуме. Кажется, в прошлом было обязательно заполнять причину низких голосов, но по какой-то причине это было отменено. Так что теперь нам придется с этим смириться ...