Несколько приемов работы с окнами FAR.

Александр Назаренко [corwinnt@yahoo.com]corwinnt@yahoo.com

О написании плагинов к редактору написано много, но большинство примеров подобных плагинов работает только с одним окном редактора. А как быть, если нужно переключаться между несколькими открытыми редакторами? Как сохранить все измененные файлы например? Или как перейти на известную позицию в уже открытом на редактирование файле? Поскольку я, похоже, первый наступил на эти «грабли», то мне и приводить рецепты.

FAR Manager до 1.70 beta 3 (включительно)

К сожалению здесь нормально реализовать работу с несколькими окнами редактора не получится потому, что стандартному API не хватает функциональности, а именно нормального способа переключится в нужное окно. Единственный "нормальный" путь - вызов функции Info.Editor(...,EF_NONMODAL,...);. При этом, если файл уже открыт в редакторе, FAR «переспрашивает» у пользователя, хочет ли он использовать уже открытый файл, перегрузить его или отказаться от открытия файла. При использовании плагина, обеспечивающего переход в нужное место исходного текста на основании анализа листинга компилятора, при таком подходе нетрудно предугадать реакцию пользователя. Выхода здесь два:

Первое - просить FAR Group переписать API с поддержкой дополнительных флагов типа Info.Editor(...,EF_USEEXISTING,...);, которые подавляли бы это предупреждение.
Второе - использовать модальный редактор и таким образом просто запретить переключение между окнами.

FAR Manager 1.70 beta 4 (и выше)

Но не о том здесь речь. Третье решение - использовать оконное API от «четверки» с ее расширенным набором функций ACTL_*. Покажу это на примере реализации сохранения всех файлов, открытых на редактирование, и перехода на известную позицию в возможно уже открытом на редактирование файле.

В примере, приведенном ниже, показано как реализовать отсутствующую в стандартном API функцию Info.EditorControl(ECTL_SAVEALL,&rec).

void EditorSaveAll(void)
{
  struct WindowInfo wi;
  // получим информацию о окне текущего файла
  wi.Pos = -1;
  Info.AdvControl(Info.ModuleNumber, ACTL_GETWINDOWINFO, (void*)&wi);
  int home = wi.Pos;
  int n = Info.AdvControl(Info.ModuleNumber, ACTL_GETWINDOWCOUNT, NULL);
  for ( int i = 0 ; i < n ; i++ )
  {
    wi.Pos = i;
    Info.AdvControl(Info.ModuleNumber, ACTL_GETWINDOWINFO, (void*)&wi);
    if ( ( wi.Type == WTYPE_EDITOR ) && wi.Modified )
    {
      // сохраним каждый измененный файл
      Info.AdvControl(Info.ModuleNumber, ACTL_SETCURRENTWINDOW, (void*)i);
      Info.AdvControl(Info.ModuleNumber, ACTL_COMMIT, NULL);
      Info.EditorControl(ECTL_SAVEFILE, NULL);
    }
  }
  // восстановим сохраненное окно редактора
  Info.AdvControl(Info.ModuleNumber, ACTL_SETCURRENTWINDOW, (void*)home);
  Info.AdvControl(Info.ModuleNumber, ACTL_COMMIT, NULL);
}

Все достаточно просто, но эта простота обманчива:

Во первых, при написании этого куска кода я достаточно долго искал причину того, что приведенный кусок не работал. Сохранялся только текущий файл. Причина оказалась в том, что я не использовал Info.AdvControl(Info.ModuleNumber, ACTL_COMMIT, NULL) и фактического переключения в нужное окно не происходило.

Во вторых, вместо использования wi.Pos = -1 я сначала получал имя текущего редактируемого файла через Info.EditorControl(ECTL_GETINFO,&ei); и проверял его совпадение с wi.Name в цикле перебора окон FAR.
Надеюсь, что на эти «грабли» теперь уже никто не наступит.

Теперь о переходе на «нужный» редактор. То есть реализуем отсутствующую в стандартном API функцию Info.Editor(...,EF_NONMODAL|EF_USEEXISTING,...).

Исходные данные, которые мы имеем, это полное имя нужного файла, строка и столбец в нем. Здесь тоже есть тонкие моменты. Нужно отследить два момента.
Во первых, нужно проверить, нужно ли вообще куда-то переходить. То есть, не редактируем ли мы этот файл в данный момент.
Во вторых, нужно проверить, открыт ли этот файл на редактирование вообще или нужно открывать новое окно редактора.

// переход на нужную позицию в текущем редакторе

void EditorSetPos(int line, int col, int topline = -1, int leftcol = -1)
{
  EditorSetPosition sp;
  sp.CurLine = line;
  sp.CurPos = col;
  sp.TopScreenLine = topline;
  sp.LeftPos = leftcol;
  sp.CurTabPos = sp.Overtype = -1;
  Info.EditorControl(ECTL_SETPOSITION, &sp);
}

bool OpenEditor(const char *FileName, int line, int col)
{
  if ( ( FileName != NULL) && ( *FileName != 0 ) )
  {
    struct WindowInfo wi;
    // получим информацию о имени текущего файла
    wi.Pos = -1;
    Info.AdvControl(Info.ModuleNumber, ACTL_GETWINDOWINFO, (void*)&wi);
    if ( !FSF.LStricmp(wi.Name, FileName) )
      EditorSetPos(line, col);
    else
    {
      // ищем нужный файл среди открытых
      int n = Info.AdvControl(Info.ModuleNumber, ACTL_GETWINDOWCOUNT, NULL);
      for ( int i = 0 ; i < n ; i++ )
      {
        wi.Pos = i;
        Info.AdvControl(Info.ModuleNumber, ACTL_GETWINDOWINFO, (void*)&wi);
        if ( ( wi.Type == WTYPE_EDITOR ) && ( !FSF.LStricmp(wi.Name, FileName) ) )
        {
          Info.AdvControl(Info.ModuleNumber, ACTL_SETCURRENTWINDOW, (void*)i);
          Info.AdvControl(Info.ModuleNumber, ACTL_COMMIT, NULL);
          EditorSetPos(line, col);
          return true;
        }
      }
      // ничего не поделаешь, придется открывать
      Info.Editor(FileName, NULL, 0, 0, -1, -1, EF_NONMODAL, line, col);
      return true;
    }
  }
  return false;
}

Возможно это многим покажется очевидным, но лично к этому коду я пришел далеко не сразу :)


 
13.01.2002
наверх