Kolejny etap obsługi plików Tom's Obvious, Minimal Language.: Mam już ropoznawanie typu pliku oraz [kolorowanie składni](/2020-03-01-netbeans-toml-plugin-syntax-color//, a nawet breadcrums Pora na "zwijanie" kodu.

Zwijanie

Funkcjonalność zwijania, jak już wspominałem wcześniej należy do klasy implementującej metodę folds z interfejsu StructureScanner.

Ponieważ strukturę dokumentu mamy już rozpoznaną, jedyne czego nam potrzeba, to rekursywnie przejść przez nią i
każdy napotkany element typu tabela, zamienić na "zwijalny" obszar:

    @Override
    public Map<String, List<OffsetRange>> folds(ParserResult parserResult) {
        if (parserResult == null) {
            return Collections.emptyMap();
        }
        if (!(parserResult instanceof TomlParserResult)) {
            Logger.getLogger(TomlStructureScanner.class.getName()).log(Level.WARNING, "parser result of unexpected type "+parserResult.getClass().getName());
            return Collections.emptyMap();

        }
        TomlParserResult tomlResult = (TomlParserResult) parserResult;
        List<AbstractTomlStructureItem> listOfRoots = tomlResult.getStructure();
        List<OffsetRange> ranges = listOfRoots.stream()
                .flatMap(AbstractTomlStructureItem::flattened)
                .filter(item -> item instanceof TableStructureItem)
                .map(item -> new OffsetRange((int) item.getPosition(), (int) item.getEndPosition()))
                .collect(Collectors.toList());
        return Collections.singletonMap("tags", ranges);
    }

flattened to nowa metoda w AbstractTomlStructureItem zapewniająca łatwą obsługę rekurencji: zwraca nowy stream składający się z aktualnej instancji i streamu będącego wynikiem wywołań flattened na dzieciach:

   public Stream<? extends AbstractTomlStructureItem> flattened() {
       return Stream.concat(
               Stream.of(this),
               children.stream().flatMap(AbstractTomlStructureItem::flattened));
   }

i... widać linie pokazujące "zwijalne" rejony:

Zwijalne rejony

działa też zwijanie:

Zwinięty obszar

Czyli działa, ale... (jak zwykle jest jakieś ale), nie wygląda to aż tak dobrze jak np. w przypadku xml (czyli nie widać "co" faktycznie zwinęliśmy):

Zwinięty tag xml

Konfiguracja

Rozwiązaniem powyższego problemu jest dodanie własnej konfiguracji zwijań.

Własny typ zwijania

Dla każdego typu mime (w tym naszego application/toml) każda wtyczka może sobie zdefiniować jakie typy "zwinięć" będą obsługiwane. Trzeba zaimplementować interfejs FoldTypeProvider i zarejestrować go dla wskazanego typu mime:

@MimeRegistration(mimeType = "application/toml", service = FoldTypeProvider.class)
public class TomlFoldTypeProvider implements FoldTypeProvider {

   @NbBundle.Messages("FoldType_TomlTables=Tables")
   public static final FoldType TOML_TABLE = FoldType.TAG.override(Bundle.FoldType_TomlTables(),
           new org.netbeans.api.editor.fold.FoldTemplate(1, 1, FoldTemplate.CONTENT_PLACEHOLDER)); // NOI18N

   List<FoldType> foldTypes = Collections.singletonList(TOML_TABLE);

   @Override
   public Collection getValues(Class type) {
       return foldTypes;
   }

   @Override
   public boolean inheritable() {
       return false;
   }

}

po kolei:

  • @MimeRegistration czyli rejestrujemy daną klasę jako service (określonego typu) dla podanego typu mime.
  • @Messages, które już wcześniej wykorzystywałem do definicji tekstów, które mogą być tłumaczone na inne języki.
  public static final FoldType TOML_TABLE = FoldType.TAG.override(Bundle.FoldType_TomlTables(),
          new org.netbeans.api.editor.fold.FoldTemplate(1, 1, FoldTemplate.CONTENT_PLACEHOLDER)); // NOI18N
  • definicja typu ( FoldType) zwijania. W moim przypadku nadpisuję istniejący typ TAGS (bo już z niego skorzystałem w StructureScanner). Używam FoldTemplate.CONTENT_PLACEHOLDER) jako wzorca, ponieważ, jeśli dodamy potem zwracanie własnego tekstu, to zawartość tego szablonu zostanie podmieniona na "nasz" tekst.

pozostałe metody/pola chyba nie wymagają objaśnień.

klasa wymaga oczywiście nowych wpisów w pom.xml do kompilacji:

       <dependency>
           <groupId>org.netbeans.api</groupId>
           <artifactId>org-netbeans-modules-editor-fold</artifactId>
           <version>${netbeans.version}</version>
       </dependency>

Zarejestrowany typ pojawia się już w konfiguracji edytora: Konfiguracja zwijania

Ponieważ nadpisaliśmy istniejący typ, to domyślne ustawienia zwijania dla Tags będą również obowiązywać dla naszego typu.

Wyświetlana zawartość po zwinięciu

Domyślnie po zwinięciu obszaru wyświetlana jest zawartość ustawiona jako szablon w definicji typu zwinięcia. Jeśli chcemy wyświetlić tam jakąś inną wartość (podobnie jak np. w przypadku xml), potrzeba dla każdego obsługiwanego typu zwinięcia zaimplementować interfejs ContentReader oraz Factory które będzie tworzyć instancje tych ContentReader:

public class TomlFoldContentReader implements ContentReader {

    @Override
    public CharSequence read(Document document, Fold fold, FoldTemplate foldTemplate) throws BadLocationException {
        int lineEnd = LineDocumentUtils.getLineEnd((LineDocument) document, fold.getStartOffset());
        String text = document.getText(fold.getStartOffset(), lineEnd - fold.getStartOffset());
        int indexOf = text.indexOf(']');
        if (indexOf > -1) {
            return text.substring(0, indexOf + 1);
        }
        return null;
    }

    @MimeRegistration(mimeType = "application/toml", service = ContentReader.Factory.class)
    public static class TomlFoldContentReaderFactory implements ContentReader.Factory {
        @Override
        public ContentReader createReader(FoldType ft) {
            if (ft == TomlFoldTypeProvider.TOML_TABLE) {
                return new TomlFoldContentReader();
            }
            return null;
        }
    }
}
  • read czyli metoda, która zwraca to co powinno pokazać się zamiast ... - jak widać znajduję najbliższy znak końca tabeli i cały tekst od początku zwinięcia (czyli nazwę tabeli) zwracam jako to co ma się pokazać.
  • TomlFoldContentReaderFactory - czyli spięcie naszego TomlFoldContentReader ze zdefiniowanym wcześniej typem zwijania TomlFoldTypeProvider.TOML_TABLE

...i kolejna zależność w pom.xml:

        <dependency>
            <groupId>org.netbeans.api</groupId>
            <artifactId>org-netbeans-modules-editor-document</artifactId>
            <version>${netbeans.version}</version>
        </dependency>

Podsumowanie

Efekt końcowy:

Widok zwinięcia

Repozytorium z aktualnym kodem: https://gitlab.com/ihsahn/netbeans-toml

Literatura

  1. Tom's Obvious, Minimal Language. oficjalne repo Toml
  2. Rich Client Programming: Plugging Into the NetBeans Platform książka o programowaniu z wykorzystaniem platformy Netbeans
  3. NetBeans api javadoc