Documentation
¶
Index ¶
- Variables
- type Conflict
- type Delta
- type DeltaBlock
- type DeltaDoc
- type DiskChangedError
- type Doc
- type DocRef
- type SaveResult
- type Session
- func (s *Session) ApplyBlock(prefix string, i, version int, source []byte) (*Delta, error)
- func (s *Session) Events() http.Handler
- func (s *Session) Handler() http.Handler
- func (s *Session) Reload(prefix string) error
- func (s *Session) Save(force bool) (SaveResult, error)
- func (s *Session) Source(prefix string, i int) ([]byte, int, error)
- func (s *Session) State() State
- func (s *Session) Warnings() []string
- func (s *Session) Watch(ctx context.Context, interval time.Duration, warn func(string))
- type StaleVersionError
- type State
- type TOCEntry
Constants ¶
This section is empty.
Variables ¶
var ( // ErrStaleVersion is returned when the client edits a block of a version the // document has already moved past. Use errors.As with *StaleVersionError to // learn the version it is at now. ErrStaleVersion = errors.New("stale version") // ErrNotEditable is returned for a block with no source range of its own — // a thematic break. There is nothing to put into a textarea, and its range // is empty, so an edit would insert text rather than replace anything. ErrNotEditable = errors.New("block is not editable") )
var ( // ErrUnknownDoc is returned for a document prefix that is not part of the // session. ErrUnknownDoc = errors.New("unknown document") // ErrBlockIndex is returned for a block index outside the document. ErrBlockIndex = errors.New("block index out of range") // ErrReadOnly is returned by every writing entry point of a session opened // for reading. Such a session never gets the write API mounted in front of // it — a --tunnel page has no token and no routes to reach one — so this is // the guard against a future mounting mistake, not a refusal a browser can // provoke today. ErrReadOnly = errors.New("session is read-only") )
var ErrDiskChanged = errors.New("file changed on disk")
ErrDiskChanged is returned by Save when a file changed on disk since the session last read or wrote it. Use errors.As with *DiskChangedError to learn which documents diverged.
Functions ¶
This section is empty.
Types ¶
type Conflict ¶ added in v0.8.0
Conflict is one document the session refuses to re-read on its own. Name is the display form, never the path as typed: the same page goes out over --tunnel, where a directory is somebody else's business.
type Delta ¶
type Delta struct {
// Seq is the number of the change this delta carries. The same delta goes
// to the tab that made the edit and to every other one over the stream, so
// both paths dedupe on it through one entry point.
Seq uint64 `json:"seq"`
// Full marks the whole page rather than a patch — the answer to a client the
// session has fallen out of step with. It is on the wire because seq alone
// cannot say it: the client's counter moves on the answer to its own edit
// too, and that answer carries only the blocks that edit changed. A full
// sync numbered no higher would look like a repeat and be dropped by the
// very tab it was sent to catch up.
Full bool `json:"full,omitempty"`
// Dirty holds the prefixes of the documents with edits not yet on disk. It
// is normalised to an empty slice rather than left nil: null on the wire
// would make indexOf throw in the browser.
Dirty []string `json:"dirty"`
// Conflicts names the documents whose file diverged while they held unsaved
// edits. It travels as part of the state, not as a notification of its own:
// a client that reconnects gets the banner along with the full sync.
Conflicts []Conflict `json:"conflicts,omitempty"`
Docs []DeltaDoc `json:"docs"`
Warnings []string `json:"warnings,omitempty"`
HasMermaid bool `json:"hasMermaid"`
HasKatex bool `json:"hasKatex"`
}
Delta is what an accepted edit gives the browser back. It never carries the whole page: with images inlined as base64 a full re-send would make every blur cost megabytes. Every block reports its index and whether it can be
type DeltaBlock ¶
type DeltaBlock struct {
I int `json:"i"`
Editable bool `json:"editable"`
HTML *string `json:"html,omitempty"`
}
DeltaBlock is one block after the re-render. HTML is nil when the block rendered byte-for-byte the same as before and the DOM can be left alone.
type DeltaDoc ¶
type DeltaDoc struct {
Doc string `json:"doc"`
Version int `json:"version"`
TOC []TOCEntry `json:"toc"`
// Body is the whole article as one html string, sent instead of Blocks by a
// read-only session: a reading page has no block wrappers to address, so
// the article is replaced whole or not at all. It is nil for a document
// that did not change, and always nil on an editing page.
Body *string `json:"body,omitempty"`
Blocks []DeltaBlock `json:"blocks,omitempty"`
}
DeltaDoc is the post-edit state of one document on the page. The version is per document rather than per page: an edit elsewhere must not age out an open editor whose own block came back unchanged.
type DiskChangedError ¶
type DiskChangedError struct {
// Docs holds the prefixes of the diverged documents in page order — the
// name the browser addresses a document by.
Docs []string
// Names holds the same documents in the same order, named the way the page
// names them, so the message says which file to look at. A prefix is empty
// on a single-document page and says nothing there.
Names []string
}
DiskChangedError names the documents whose files no longer match what the session read.
func (*DiskChangedError) Error ¶
func (e *DiskChangedError) Error() string
func (*DiskChangedError) Unwrap ¶
func (e *DiskChangedError) Unwrap() error
Unwrap makes errors.Is(err, ErrDiskChanged) hold for the typed error.
type Doc ¶
type Doc struct {
// Prefix is the anchor/article prefix this document gets on the page; "" on
// a single-document page, where ids stay exactly what a stand-alone render
// would produce.
Prefix string
// Path is the absolute path with symlinks resolved: the file Save writes to.
Path string
// Name is the path as typed on the command line, used for rendering and in
// messages so they name the file the way the author does.
Name string
// contains filtered or unexported fields
}
Doc is one markdown file owned by a session, together with the state needed to splice edits into it and to write it back.
type DocRef ¶
type DocRef struct {
// Path is the path as typed on the command line. It is what rendering sees:
// relative images and links resolve against its directory, exactly as they
// do in a run without --edit.
Path string
// Display is the form of the path that goes into the page as the sidebar
// tooltip. Empty falls back to the file name alone, so a caller that forgets
// it cannot leak an absolute path — and with it the OS user name — into a
Display string
}
DocRef is one input file of a session as the caller knows it.
type SaveResult ¶
type SaveResult struct {
Saved []string `json:"saved"`
Errors map[string]string `json:"errors,omitempty"`
// Seq is the state of the page this answer describes: the number of the
// event the save broadcast, or the current one when it wrote nothing. The
// answer travels on its own connection and the broadcast goes out before
// it, so an event newer than this save can reach the browser first — the
// number is what lets the page tell that and leave the newer state alone.
Seq uint64 `json:"seq"`
}
SaveResult reports what one Save call did. Failures are collected per document rather than turned into a single verdict for the page: one file in a read-only directory should not cost the author the edits that did land in
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session holds every document of one rmd --edit run. All state changes go through mu: edits arrive from HTTP handlers, i.e. from several goroutines.
func NewReadOnly ¶ added in v0.8.0
NewReadOnly opens the same live session behind a page that only reads. It is what --tunnel keeps: the poller follows the files and the stream pushes what changed, but every write refuses with ErrReadOnly.
A file named twice is skipped here rather than refused. There is nothing to splice an edit into, and `rmd --tunnel a.md link-to-a.md` renders today — failing the run over a duplicate would take away a command that works.
func (*Session) ApplyBlock ¶
ApplyBlock replaces the source of one block with source and re-renders the page. version is the document version the client edited against; an edit against anything older is refused rather than merged — see the conflict
func (*Session) Events ¶ added in v0.8.0
Events returns the state stream alone, without the rest of the API. The keep-alive server mounts it on a bare /events and has nothing to mount the multiplexer under: its /done is shadowed by the root beacon of the server itself, and a reading page has no other route to reach anyway.
func (*Session) Handler ¶
Handler returns the HTTP API of the session: the markdown of a block, the submission of an edit, the write-back to disk, and the load beacon.
func (*Session) Reload ¶ added in v0.8.0
Reload takes one document as it is on disk, unsaved edits and all. It is the answer to the conflict banner and the only place on the page where text disappears: the session cannot merge two versions of a file, so the author is shown the choice and this is them making it.
func (*Session) Save ¶
func (s *Session) Save(force bool) (SaveResult, error)
Save writes every document holding unsaved edits back to disk. Clean documents are skipped: their bytes on disk are already what the session holds, and rewriting them would only move their mtime.
func (*Session) Source ¶
Source returns the markdown of one block together with the document version it was taken at. The two are read under the same lock on purpose: fetched separately, the client could pair source text with a version an edit has
func (*Session) Warnings ¶
Warnings returns the non-fatal render issues of the whole page, each prefixed with the document that produced it: on a multi-document page a bare "image … not found" leaves the reader guessing which file to fix.
func (*Session) Watch ¶ added in v0.8.0
Watch keeps the session in step with the files behind it until ctx is cancelled. Every interval the poller compares what is on disk with what it saw last time and re-reads what has settled, so an edit made in another editor reaches the open page on its own.
warn takes the render issues of the re-read documents. They cannot ride the event alone: the author who edits in a terminal is looking at that terminal, and a broken image in the file they just wrote belongs there too.
type StaleVersionError ¶
StaleVersionError carries the version the document actually holds, so the refusal the browser gets can tell it how far behind it is instead of just saying no.
func (*StaleVersionError) Error ¶
func (e *StaleVersionError) Error() string
func (*StaleVersionError) Unwrap ¶
func (e *StaleVersionError) Unwrap() error
Unwrap makes errors.Is(err, ErrStaleVersion) hold for the typed error.
type State ¶
type State struct {
// Docs are the documents in navigation order.
Docs []page.Doc
// Mermaid and Katex are the page-level asset flags, ORed over every
// document: one diagram or formula anywhere pulls the bundle in for the
// whole page.
Mermaid bool
Katex bool
// Warnings are the render issues of the whole page, each named by document.
Warnings []string
// Dirty holds the prefixes of the documents with edits not yet on disk. A
// page built mid-session has to say so: the session it is rendered from is
// ahead of the files, and a page that showed the edits but no way to write
Dirty []string
// Conflicts names the documents the session refused to re-read: their file
// moved under unsaved edits. It travels with the page for the same reason
// Dirty does, and one more of its own — the conflict was announced by an
// event that is long past, and a page built afterwards has no other way to
// learn of it. Without it a reload takes the "Перечитать" button away and
// leaves the author with the overwrite as the only answer.
Conflicts []Conflict
// Seq is the number of the last change the session made. A page carries it
// so the stream it opens afterwards can say where it already is: without it
// a client would either re-apply the state it was built from or miss a
// change that landed between the render and the subscription.
Seq uint64
// Epoch names the run of the CLI this state came from. Seq counts from zero
// in every run, so it says where a client is only once the run it counts in
// is known — the page carries both back, and a cursor minted in another run
// is exactly what a full sync is for.
Epoch string
}
State is the whole session as the page builder sees it. It is one type rather than a call per part because a page has to be built from one moment in time: an edit landing between two reads would pair the blocks of one render with the