Jak prawidłowo napisać iterator zwracający fragmenty stringa wejściowego?

Jak prawidłowo napisać iterator zwracający fragmenty stringa wejściowego?
elwis
  • Rejestracja:ponad 18 lat
  • Ostatnio:2 dni
0

Cześć, ćwiczebnie próbuję napisać klasę, która zapewni dopasowywanie stringów. Ze względu na naukę, chciałbym w miarę możliwości używać referencji &str. Dopóki robię proste, sekwencyjne dopasowywanie wszystko jest łatwo. Mam po prostu w strukturze referencję do tego co zostało mi do przeczytania i nie mam z tym problemów. Problem pojawia się gdy chcę powtórzyć wielokrotnie to samo dopasowanie. Jedyny pomysł, jaki przyszedł mi do głowy to stworzyć metodę, która przyjmie funkcję/lambdę robiącą poszczególne działania i stworzę iterator, który będzie robić co trzeba. Ostatecznie, chciałbym uzyskać taką składnię:

Kopiuj
let mut it = matcher("foo=1 bar=2 coo zza=3")
              .many(|m1|
                       m1.until_word()
                         .const_str("=")
                         .merge::<u64,(&str, u64),_>(|name, val| (name,val)));

Wszystko działa dobrze, dopóki nie próbuję zwracać referencji na stringa, bo wtedy kompilator marudzi, że nie może mi zagwarantować, że ta referencja nie przeżyje obiektu, na który pokazuje. Tak więc jeśli zwracam cokolwiek innego niż referencję (liczby, String, etc) jest OK i jest to jakieś rozwiązanie. Z drugiej strony nie wydaje mi się to rozsądne, bo przecież jest dla mnie oczywiste, że nie mam się czego obawiać, a alokować miejsce na stercie ilekroć chcę pokazać kawałek tekstu z iteratora wydaje się być co najmniej nieładne i niefektywne. Tak wiec pewnie ten problem ma dobre rozwiązanie o którym nie wiem.

Coś przeoczyłem, że kompilator nie jest w stanie podążyć za cyklem życia tych referencji?
Powinienem inaczej użyć anotacji lifetimeów (wszędzie praktycznie jest 'a -> 'a, więc teoretycznie chyba mógłby załapać…)?
Może jest jakiś magiczny obiekt, który w magiczny sposób odróżni taki bezpieczny przypadek jak mam tutaj, a potencjalnie niebezpiecznym przypadkiem kiedy rzeczywście referencja przeżyje to na co pokazuje.

Poglądowo wklejam kod użytych tu klas:

Kopiuj
trait Matchable {
    type Out;
    fn scan(inp: &str) -> Option<(&str, Self::Out)>;
}

impl Matchable for u64 {
    type Out=u64;

    fn scan(inp: &str) -> Option<(&str, u64)> {
        let it = inp.chars();
        let mut ans : u64 = 0;
        let mut i = 0;
        for c in it {
            match c.to_digit(10) {
                Some(x) => {
                    ans = ans*10 + x as u64;
                    i += 1;
                },
                None => break
            }
        }

        if i == 0 {
            None
        } else {
            Some((&inp[i..], ans))
        }
    }
}

#[cfg(test)]
struct MatcherLoop<'a, R, F>
where
    F: Fn(Matcher<()>) -> Matcher<R>,
{
    tail: Option<&'a str>,
    f: F,
}

#[cfg(test)]
impl<'a, R, F> Iterator for MatcherLoop<'a, R, F>
where
    F: Fn(Matcher<()>) -> Matcher<R>,
{
    type Item = R;

    fn next(&mut self) -> Option<Self::Item> {
        let tail = self.tail.take();
        let m = (self.f)(matcher(tail?));
        self.tail = Some(m.tail);
        m.val
    }
}

struct Matcher<'a,T> {
    tail: &'a str,
    val: Option<T>
}


impl<'a,T> Matcher <'a,T> {
    fn then<F, R> (self, f: F) -> Matcher<'a, R>
            where F: Fn(&'a str, T) -> Matcher<'a, R> {
        match self.val {
            Some(v) => f(self.tail, v),
            None => Matcher { tail: self.tail, val: None }
        }
    }

    #[cfg(test)]
    fn after(mut self, refs: &'a str) -> Self {
        let mut finder = matcher(self.tail);
        while finder.tail.len() > refs.len() {
            finder = finder.const_str(refs);
            if finder.val.is_some() {
                self.tail = finder.tail;
                return self;
            }
            finder.tail = &finder.tail[1..];
            finder.val = Some(());
        }

        self.val = None;
        return self;
    }

    #[cfg(test)]
    fn until_word(self) -> Matcher<'a, &'a str> {
        let mut cur = self.tail;
        let mut ans;
        while cur.len() > 0 {
            ans = matcher(cur).word();
            if ans.val.is_some() {
                return ans;
            }
            cur = &cur[1..];
        }

        Matcher { tail: self.tail, val: None }
    }

    #[cfg(test)]
    fn until<R: Matchable>(self) -> Matcher<'a, <R as Matchable>::Out> {
        let mut cur = self.tail;
        let mut ans;
        while cur.len() > 0 {
            ans = matcher(cur).value::<R>();
            if ans.val.is_some() {
                return ans;
            }
            cur = &cur[1..];
        }

        Matcher { tail: self.tail, val: None }
    }

    #[cfg(test)]
    fn many<R,F>(self, f:F) -> MatcherLoop<'a,R,F>
            where F: Fn(Matcher<()>) -> Matcher<R> {
        MatcherLoop { tail: Some(self.tail), f }
    }

    #[cfg(test)]
    fn word(self) -> Matcher<'a, &'a str> {
        let mut i: usize = 0;
        let mut cur = self.tail.chars();
        while cur.next().map(|c| c.is_alphabetic()).unwrap_or(false) {
            i += 1;
        }

        if i > 0 {
            Matcher { tail: &self.tail[i..], val: Some(&self.tail[0..i]) }
        } else {
            Matcher { tail: self.tail, val: None }
        }
    }

    fn const_str(self, refs: &'a str) -> Self {
        self.then(|tail, val| {
            let mut it = tail.chars();
            for refc in refs.chars() {
                match it.next() {
                    Some(c) => match c == refc {
                                    true => (),
                                    false => return Matcher{ tail, val: None }
                            },
                    None => return Matcher { tail, val: None }
                }
            }

            Matcher { tail: it.as_str(), val: Some(val) }
        })
    }

    fn value<R: Matchable> (self) -> Matcher<'a,<R as Matchable>::Out> {
        self.then(|tail, _| {
            match R::scan(tail) {
                Some((t2, val)) => Matcher { tail: t2, val: Some(val) },
                None => Matcher { tail, val: None }
            }
        })
    }

    fn merge<R, V, F> (self, f: F) -> Matcher<'a, V>
            where R: Matchable,
                  F: Fn(T, <R as Matchable>::Out) -> V {
        self.then(|tail, v1| {
            match R::scan(tail) {
                Some((t2, v2)) => Matcher { tail: t2, val: Some(f(v1, v2)) },
                None => Matcher { tail, val: None }
            }
        })
    }

    fn result(self) -> Option<T> {
        self.val
    }

    #[cfg(test)]
    fn result_ref(&self) -> &Option<T> {
        &self.val
    }
}

fn matcher<'a>(src: &'a str) -> Matcher<'a, ()> {
    Matcher { tail: src, val: Some(()) }
}

LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:4 minuty
  • Postów:8422
0

Dużo tego kodu. A jakbyś napisał jakiś bardziej minimalny przykład i wypisał konkretny błąd do konkretnej linijki?

wtedy kompilator marudzi, że nie może mi zagwarantować, że ta referencja nie przeżyje obiektu, na który pokazuje

Ale ten błąd dotyczy którego miejsca w kodzie i których dokładnie referencji?


elwis
  • Rejestracja:ponad 18 lat
  • Ostatnio:2 dni
0

No to minimalny przykład będzie taki:

Kopiuj
        let mut it = matcher("foo=1 bar=2 zza=3")
                       .many(|m1|
                             m1.until_word());

Wtedy komunikat błędu będzie taki:

Kopiuj
|| error: lifetime may not live long enough
src/main.rs|370 col 6 error| 
||     |
|| 369 |                        .many(|m1|
||     |                               --- return type of closure is Matcher<'_, &'2 str>
||     |                               |
||     |                               has type `Matcher<'1, ()>`
|| 370 |                              m1.until_word());
||     |                              ^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
|| 
|| error: could not compile `vidvid` due to previous error


TU
  • Rejestracja:ponad rok
  • Ostatnio:ponad rok
  • Postów:122
0
elwis napisał(a):

a alokować miejsce na stercie ilekroć chcę pokazać kawałek tekstu z iteratora wydaje się być co najmniej nieładne i niefektywne. Tak wiec pewnie ten problem ma dobre rozwiązanie o którym nie wiem.

Nie rozumiem tu czegoś jak chcesz zwrócić wartość bez alokowania tego na heapie?

Normalnie z funkcji możesz zwrócić jeden adres w RAX rejestrze, ten adres to może być jakiś obiekt, który będzie enkapsulował więcej danych.
W C++ byś stworzył obiekt na heapie tego stringa, a potem za pomocą std::move konstruktora przeniósł prawa własności do obiektu i jego życia do innej funkcji.
W dół możesz przenieść samą referencję, gdyż rodzic funkcja będzie żyła dłużej od dziecka, a w górę funkcji musisz przenieść prawa własności obiektu, gdyż funkcja dziecka umrze, tzn. jego ramka stosu zostanie zniszczona.
I teraz funkcja, która odebrała obiekt nie musi się obawiać, że obiekt zginie, gdyż teraz jest jej i dopiero zostanie usunięty gdy skończy się funkcja, chyba że znowu komuś odda prawa własności do obiektu.

Nie wiem jak w rust jest to rozwiązanie, ale jeśli alokuje na stosie, to nie jest w stanie tych danych przekazać, musi przekopiować, przez heap idzie bez problemowo.
Rust mógł także nie tworzyć nowego stringa, a po prostu odwoływać się do tego statycznego kawałka tekstu, to akurat łatwo pójdzie sprawdzić w listingu assemblera.

Nieładne i nieefektywne?
Jak zwracasz przez heap to jest efektywne, bo tylko przekazujesz adres i zmieniasz właściciela obiektu, jeśli masz na stosie to musisz i tak wykonać przekopiowywanie, gdyż nie da się fizycznie tego inaczej przenieść.
I odwoływanie się do statycznego stringa w formie indeksów to jest nieładne, bo używasz jednego długiego stringa jako odniesienia do 3 innych, w C się takie coś robiło w strtok, co oczywiście nie powinno się robić na const static, ale pozwala zaoszczędzić na pamięci gdyż ją mutujemy.

LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:4 minuty
  • Postów:8422
0

A to nie chodzi o to, że wyrzucasz to z domknięcia i że te dwa lifetime'y ze sobą nie współgrają?

W końcu masz jakiś lifetime w tej strukturze i spoko, ale jak dajesz niezależne od struktury domknięcie, to generuje ono kolejny lifetime i kompilator nie wie, że to to samo?

Nie ogarniam tego kodu do końca i nie wiem, czy moje rozumowanie jest poprawne, ale tak intuicyjnie bym w tym kierunku popróbował.


edytowany 1x, ostatnio: LukeJL
elwis
  • Rejestracja:ponad 18 lat
  • Ostatnio:2 dni
0
tumor napisał(a):
elwis napisał(a):

a alokować miejsce na stercie ilekroć chcę pokazać kawałek tekstu z iteratora wydaje się być co najmniej nieładne i niefektywne. Tak wiec pewnie ten problem ma dobre rozwiązanie o którym nie wiem.

Nie rozumiem tu czegoś jak chcesz zwrócić wartość bez alokowania tego na heapie?

W tym przypadku to akurat nie ma sensu niczego alokować na stercie, bo ten string żyje sobie pewnie w sekcji .rodata i jest tam przez cały czas wykonywania programu. Stringi w Ruscie są reprezentowane przez wskaźnik+długość, więc można spokojnie mnożyć dowolne podciągi w oparciu o ten sam fragment pamięci. Gdyby nawet nie był, to przecież nie wyrzucam tej referencji nigdzie poza blok, w którym ten string jest zaalokowany, więc nie widzę problemu.

LukeJL napisał(a):

A to nie chodzi o to, że wyrzucasz to z domknięcia i że te dwa lifetime'y ze sobą nie współgrają?

W końcu masz jakiś lifetime w tej strukturze i spoko, ale jak dajesz niezależne od struktury domknięcie, to generuje ono kolejny lifetime i kompilator nie wie, że to to samo?

Nie ogarniam tego kodu do końca i nie wiem, czy moje rozumowanie jest poprawne, ale tak intuicyjnie bym w tym kierunku popróbował.

Myślę, że kompilator to właśnie sugeruje, tylko nie wiem czemu uważa, że wartość wyjściowa ma inny lifetime niż wejściowa, skoro postać funkcji until_word jednoznacznie stwierdza, że lifetime zostanie ten sam: fn until_word(self) -> Matcher<'a, &'a str> .


edytowany 1x, ostatnio: elwis
TU
  • Rejestracja:ponad rok
  • Ostatnio:ponad rok
  • Postów:122
1

Też nie do końca tak jest, że zawsze dane są w .rodata, kompilator często wrzuca statyczny string za pomocą movabs [adres], constant, nawet kilka pod rząd tak jak się nie mieści w jednym rejestrze, ma to dwie zalety, nie musi oddzielnie cachować stringa, za jednym razem cachuje i instrukcje i stałą, często wysokich optymalizacjach kompilator stosuje ten trik.

Możne tak zrobić sobie miejsce na stosie i przekopiować za pomocą movabs qword [rsp], 'asdfghij', a potem po wyjściu z funkcji dane są tracone, może też tego użyć żeby do heapu wpisać.

Podejrzyj w listingu assemblera, jak przechowywany jest ten string, żeby dopuścić do kompilacji możesz nie zwracać go tymczasowo.

elwis
Dzięki za wyjaśnienie. Może się kiedyś przydać. :) Tylko to nie zmienia faktu, że chyba powinien móc sobie wyliczyć, że lifetimy się zgadzają w tym przypadku. Tylko z jakiegoś powodu nie rozumie, że lifetime nigdzie tam się nie zmienia.
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:4 minuty
  • Postów:8422
0

Myślę, że kompilator to właśnie sugeruje, tylko nie wiem czemu uważa, że wartość wyjściowa ma inny lifetime niż wejściowa, skoro postać funkcji until_word jednoznacznie stwierdza, że lifetime zostanie ten sam: fn until_word(self) -> Matcher<'a, &'a str> .

Ale dla struktury, w której jest to zdefiniowane. A jak robisz tak:

Kopiuj
 .many(|m1|
   m1.until_word());

to 'a odnosi się do m1, a m1 jest tak definiowane:

Kopiuj
    fn many<R,F>(self, f:F) -> MatcherLoop<'a,R,F>
            where F: Fn(Matcher<()>) -> Matcher<R> {

czyli jest tu jakiś przeskok i kompilator nie wie, że to jest to samo. Czyli stąd to drugie lifetime.

Ale tak ogólnie, to nie rozumiem tego kodu na poziomie koncepcyjnym. Do czego służy funkcja many? Co ma zwracać callback:

Kopiuj
    .many(|m1|
          m1.until_word());

?
o co chodzi z Matcher<R, F>?


edytowany 1x, ostatnio: LukeJL
elwis
  • Rejestracja:ponad 18 lat
  • Ostatnio:2 dni
0
LukeJL napisał(a):

Ale dla struktury, w której jest to zdefiniowane. A jak robisz tak:

Kopiuj
 .many(|m1|
   m1.until_word());

to 'a odnosi się do m1, a m1 jest tak definiowane:

Kopiuj
    fn many<R,F>(self, f:F) -> MatcherLoop<'a,R,F>
            where F: Fn(Matcher<()>) -> Matcher<R> {

czyli jest tu jakiś przeskok i kompilator nie wie, że to jest to samo. Czyli stąd to drugie lifetime.

To jest fakt, że powinno być F: for <'b> Fn(Matcher<'b, ()>) -> Matcher<'b, R> i analogicznie w definicji MatcherLoop. To jednak nie naprawia problemu.

Ale tak ogólnie, to nie rozumiem tego kodu na poziomie koncepcyjnym. Do czego służy funkcja many? Co ma zwracać callback:

Kopiuj
    .many(|m1|
          m1.until_word());

?
o co chodzi z Matcher<R, F>?

many zapewnia wielokrotne dopasowanie. Funkcja f:F dostaje matchera i robi dopasowanie, bo jak inaczej dobrze zamodelować listę działań? Tym bardziej, że chcę mieć kroki z typem (jak value::<u64>), chyba nie mogę tak zrobić z enumem. W związku z tym Many zwraca MatcherLoop<R,F>, który jest iteratorem, który dopasowuje, zwraca wartość i w następnym zaczyna dopasowanie od miejsca w którym skończył poprzednie. R jest typem dopasowania, a F jest typem funkcji, co jest konieczne ze względu na to, że każda lambda ma swój typ, więc jeśli chcę je wspierać to tak trzeba zrobić. Przynajmniej nie znam innego sposobu.


elwis
  • Rejestracja:ponad 18 lat
  • Ostatnio:2 dni
0
LukeJL napisał(a):

Ale dla struktury, w której jest to zdefiniowane. A jak robisz tak:

Kopiuj
 .many(|m1|
   m1.until_word());

to 'a odnosi się do m1, a m1 jest tak definiowane:

Kopiuj
    fn many<R,F>(self, f:F) -> MatcherLoop<'a,R,F>
            where F: Fn(Matcher<()>) -> Matcher<R> {

czyli jest tu jakiś przeskok i kompilator nie wie, że to jest to samo. Czyli stąd to drugie lifetime.

To jest fakt, że powinno być F: for <'b> Fn(Matcher<'b, ()>) -> Matcher<'b, R> i analogicznie w definicji MatcherLoop. To jednak nie naprawia problemu.

Ale tak ogólnie, to nie rozumiem tego kodu na poziomie koncepcyjnym. Do czego służy funkcja many? Co ma zwracać callback:

Kopiuj
    .many(|m1|
          m1.until_word());

?
o co chodzi z Matcher<R, F>?

many zapewnia wielokrotne dopasowanie. Funkcja f:F dostaje matchera i robi dopasowanie, bo jak inaczej dobrze zamodelować listę działań? Tym bardziej, że chcę mieć kroki z typem (jak value::<u64>), chyba nie mogę tak zrobić z enumem. W związku z tym Many zwraca MatcherLoop<R,F>, który jest iteratorem, który dopasowuje, zwraca wartość i w następnym zaczyna dopasowanie od miejsca w którym skończył poprzednie. R jest typem dopasowania, a F jest typem funkcji, co jest konieczne ze względu na to, że każda lambda ma swój typ, więc jeśli chcę je wspierać to tak trzeba zrobić. Przynajmniej nie znam innego sposobu.


elwis
  • Rejestracja:ponad 18 lat
  • Ostatnio:2 dni
1

Znalazłem rozwiązanie. Problem tkwił w definicji MatcherLoop i funkcji Many. Było:

Kopiuj
struct MatcherLoop<'a, R, F>
where
    F: Fn(Matcher<()>) -> Matcher<R>,

//...

impl<'a, R, F> Iterator for MatcherLoop<'a, R, F>
where
    F: Fn(Matcher<()>) -> Matcher<R>,

//...

fn many<R,F>(self, f:F) -> MatcherLoop<'a,R,F>
            where F: Fn(Matcher<()>) -> Matcher<R> {

należało wszędzie w definicjach typu funkcji konsekwentnie podawać lifetime objektu:

Kopiuj
struct MatcherLoop<'a, R, F>
where
    F: Fn(Matcher<'a, ()>) -> Matcher<'a, R>,

//...

impl<'a, R, F> Iterator for MatcherLoop<'a, R, F>
where
    F: Fn(Matcher<'a, ()>) -> Matcher<'a, R>,

//...

fn many<R,F>(self, f:F) -> MatcherLoop<'a,R,F>
            where F: Fn(Matcher<'a, ()>) -> Matcher<'a, R> {

I wtedy wszystko gra, pozwala zwracać &str bez problemu.


Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)