Разработка простого синтаксического анализатора, страница 7

Рассмотрим возможные схемы реализации. В реакции на нажатие Ctrl+Space вызываем метод CompleteWord.

private void CompleteWord ()

{

string match = ShowPrompt ();

if (match != null)    // Найден единственный вариант: завершаем слово

{

SelectionStart = start;

SelectionLength = end - start;

SelectedText = match;

// Работа со стеком undo

}

}

Приведу схему методов ShowPrompt, GetWordLeftToCursor и GetMatchingKeyword.

private string ShowPrompt ()

{

string word = GetWordLeftToCursor (),  // Ищем слово слева от курсора

match = null;

if (word != string.Empty)

match = GetMatchingKeyword (word);    // Ищем точное совпадение

if (match == null)

ShowKeywords (word);      // Отображаем список со словами, начинающимися с word

return match;

}

private string GetWordLeftToCursor (out int start, out int end)

{

start = end = -1;

if (SelectionStart == 0) // TextLength==0 или курсор в начале текста

return string.Empty;

char[] what = highlighter.Separators.ToCharArray ();     

// Используя свойство SelectionStart и метод LastIndexOfAny, найдите разделитель слева

// Используя метод Substring, выделите слово слева от курсора (res = . . .)

// Используя метод IndexOfAny, уточните позицию конца слова (end)

return res;

}

private string GetMatchingKeyword (string word)

{

string match = null;

// В цикле по всем ключевым словам

{

if (//ключевое слово начинается с word)

{

// Программная логика, смысл которой: еще одно совпадение -> нет совпадения

}

}

return match;

}

Метод демонстрации окна с отфильтрованным списком подсказок довольно прост.

public void ShowKeywords (string filter)

{

promptList.BeginUpdate ();

// Очистите список (свойство Items элемента ListBox)

// В цикле по всем ключевым словам

{

if (ключевое слово.StartsWith (filter, StringComparison.InvariantCultureIgnoreCase))

// Добавьте его в коллекцию Items

}

// Если filter == "", то StartsWith возвращает true и все ключевые слова попадут список

if (список не пуст)

{

SetAutoCompleteSize ();

SetAutoCompleteLocation (true);

}

promptList.EndUpdate ();

if (список не пуст)

// Покажите его

}

В методах SetAutoCompleteSize и SetAutoCompleteLocation надо вычислить размер и местоположение окна со списком (или элемента типа ListBox).

Возможные проблемы

Во время работы над проектом вам придется решить ряд задач, которые не были упомянуты здесь. Самой трудной из них является реализация функций Undo и Redo. Эти функции работали в элементе RichTextBox, но утрачены в нашем классе, так как переопределен виртуальный метод OnTextChanged (см. Help по этому вопросу).

Для сдачи проекта не нужно реализовывать функций Undo и Redo, но если вы это сделаете, то получите удовлетворение от сознания своей компетенции. Эта тема рассмотрена в одной из статей RSDN. Вы найдете ее в журнале #2-2003 по заголовку: "Back/Forward и Undo/Redo в .NET-приложениях".

Второй проблемой является нарушение позиции прокрутки (scrolling), которое происходит в результате того, что мы заново генерируем весь текст разметки. Она решается с помощью пары методов: GetScrollPos и SetScrollPos, которые надо ввести в состав класса SyntaxBox.

private unsafe POINT GetScrollPos ()

{

POINT res = new POINT ();

SendMessage (Handle, EM_GETSCROLLPOS, 0, new IntPtr (&res));

return res;

}

private unsafe void SetScrollPos (POINT point)

{

SendMessage (Handle, EM_SETSCROLLPOS, 0, new IntPtr (&point));

}

Вызов первого метода осуществите перед синтаксическим анализом (обращением к методу Parse), а вызов второго после этой процедуры. Запомните текущую позицию прокрутки перед обращением к Parse и восстановите ее после отработки алгоритма анализа и подсветки текста.

Проблему "убегания" курсора ввода (к этому моменту вы должны были ее обнаружить) можно решить сходным образом. Запомните позицию курсора перед обращением к Parse:

int cursorPos = SelectionStart;

и восстановите ее после завершения Parse:

SelectionStart = cursorPos;

Желаю удачи и жду красивых решений.