diff options
| author | reptee <[email protected]> | 2025-09-15 12:45:28 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-09-15 12:45:28 +0200 |
| commit | a0cfb3fd31bc3729266cc3a7aaac1416df183445 (patch) | |
| tree | 188945541576851557ac34e3b19c01cd45bd0197 /src/Text | |
| parent | 0ce85b0cfa8e5d3155dddc77b1408b4d7d7890fa (diff) | |
Vimdoc writer (#11132)
Support for vimdoc, documentation format used by vim in its help pages.
Relies heavily on definition lists and precise text alignment to generate tags.
Diffstat (limited to 'src/Text')
| -rw-r--r-- | src/Text/Pandoc/Writers.hs | 3 | ||||
| -rw-r--r-- | src/Text/Pandoc/Writers/Vimdoc.hs | 615 |
2 files changed, 618 insertions, 0 deletions
diff --git a/src/Text/Pandoc/Writers.hs b/src/Text/Pandoc/Writers.hs index 164bf6d0f..556ff5ddf 100644 --- a/src/Text/Pandoc/Writers.hs +++ b/src/Text/Pandoc/Writers.hs @@ -79,6 +79,7 @@ module Text.Pandoc.Writers , writeXML , writeXWiki , writeZimWiki + , writeVimdoc , getWriter ) where @@ -132,6 +133,7 @@ import Text.Pandoc.Writers.Typst import Text.Pandoc.Writers.XML import Text.Pandoc.Writers.XWiki import Text.Pandoc.Writers.ZimWiki +import Text.Pandoc.Writers.Vimdoc data Writer m = TextWriter (WriterOptions -> Pandoc -> m Text) | ByteStringWriter (WriterOptions -> Pandoc -> m BL.ByteString) @@ -206,6 +208,7 @@ writers = [ ,("djot" , TextWriter writeDjot) ,("ansi" , TextWriter writeANSI) ,("xml" , TextWriter writeXML) + ,("vimdoc" , TextWriter writeVimdoc) ] -- | Retrieve writer, extensions based on formatSpec (format+extensions). diff --git a/src/Text/Pandoc/Writers/Vimdoc.hs b/src/Text/Pandoc/Writers/Vimdoc.hs new file mode 100644 index 000000000..f20b99b6c --- /dev/null +++ b/src/Text/Pandoc/Writers/Vimdoc.hs @@ -0,0 +1,615 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE Strict #-} + +module Text.Pandoc.Writers.Vimdoc (writeVimdoc) where + +import Control.Applicative (optional, (<|>)) +import Control.Monad (forM) +import Control.Monad.Reader (MonadReader (..), ReaderT (..), asks) +import Control.Monad.State (MonadState (..), StateT, evalStateT, gets, modify) +import Data.Default (Default (..)) +import Data.List (intercalate, intersperse, transpose) +import Data.List.NonEmpty (NonEmpty (..), nonEmpty) +import Data.Maybe (fromMaybe) +import Data.Text (Text) +import qualified Data.Text as T +import Text.DocLayout hiding (char, link, text) +import Text.Pandoc.Class.PandocMonad ( report, PandocMonad ) +import Text.Pandoc.Definition +import Text.Pandoc.Error (PandocError) +import Text.Pandoc.Logging (LogMessage (..)) +import Text.Pandoc.Options (WrapOption (..), WriterOptions (..)) +import Text.Pandoc.Parsing.General (many1Till, many1TillChar, readWith) +import Text.Pandoc.Shared (capitalize, onlySimpleTableCells, orderedListMarkers, isTightList, makeSections, removeFormatting, tshow) +import Text.Pandoc.Templates (renderTemplate) +import Text.Pandoc.URI (escapeURI, isURI) +import Text.Pandoc.Writers.Shared (defField, metaToContext, toLegacyTable) +import Text.Parsec (anyChar, char, eof, string, try) +import Text.Read (readMaybe) +import Text.Pandoc.Chunks (toTOCTree, SecInfo (..)) +import Data.Tree (Tree(..)) +import Data.Functor ((<&>)) +import Data.Sequence (Seq, (|>), (<|)) +import qualified Data.Sequence as Seq +import Data.Foldable (toList) + +data WriterState = WriterState + { indentLevel :: Int -- How much to indent the block. Inlines shouldn't + -- be concerned with indent level (I guess?) + , shiftWidth :: Int -- spaces per indentation level + , writerOptions :: WriterOptions + , vimdocPrefix :: Maybe Text + } + +instance Default WriterState where + def = + WriterState + { indentLevel = 0 + , shiftWidth = 4 + , writerOptions = def + , vimdocPrefix = Nothing + } + +indent :: (Monad m) => Int -> (VW m a) -> (VW m a) +indent n = local (\s -> s{indentLevel = indentLevel s + n}) + +type VW m = StateT (Seq (Doc Text)) (ReaderT WriterState m) + +runRR :: (Monad m) => Seq (Doc Text) -> WriterState -> VW m a -> m a +runRR footnotes opts action = runReaderT (evalStateT action footnotes) opts + +docShiftWidth :: Meta -> Maybe Int +docShiftWidth meta = case lookupMeta "shiftwidth" meta of + Just (MetaInlines [Str sw]) -> readMaybe (T.unpack sw) + Just (MetaString sw) -> readMaybe (T.unpack sw) + _ -> Nothing + +docVimdocPrefix :: Meta -> Maybe Text +docVimdocPrefix meta = case lookupMeta "vimdoc-prefix" meta of + Just (MetaInlines [Str pref]) -> Just pref + Just (MetaString pref) -> Just pref + _ -> Nothing + +{- | Build a vim modeline +>>> makeModeLine def +"vim:tw=72:sw=4:ts=4:ft=help:norl:et:" +-} +makeModeLine :: WriterState -> Text +makeModeLine ws = + T.pack . intercalate ":" $ + [ "vim" + , "tw=" <> show tw + , "sw=" <> show sw + , "ts=" <> show sw + , "ft=help" + , "norl" -- left-to-right text + , "et:" -- expandtab and finishing ":" + ] + where + tw = writerColumns . writerOptions $ ws + sw = shiftWidth ws + +-- | Build a single formatted TOC line +tocEntryToLine :: (PandocMonad m) => SecInfo -> VW m Text +tocEntryToLine secinfo = do + rightRef <- mkVimdocRef (secId secinfo) + let numberStr = case secNumber secinfo of + Nothing -> "" + Just x | '.' `T.elem` x -> x <> " " + Just x -> x <> ". " + title <- inlineListToVimdoc $ removeFormatting (secTitle secinfo) + let titlePlain = render Nothing (title <> " ") + + -- length sub 2 because vertical bars are concealed + let rightRefLen = max 0 (T.length rightRef - 2) + let numberLen = T.length numberStr + let leftLen = numberLen + T.length titlePlain + let padForRight = 1 + textWidth <- asks (writerColumns . writerOptions) + il <- asks indentLevel + + -- positive when we lack space (i.e. content is too long) + let lack = (il + leftLen + padForRight + rightRefLen) - textWidth + + -- when lacking, truncate title reserving 3+ chars for ellipsis + let finalTitle = + if lack >= 0 + then + let trunc = T.dropEnd (lack + 3) titlePlain + stripped = T.stripEnd trunc + ellipsis = + T.replicate (3 + T.length trunc - T.length stripped) "." + in stripped <> ellipsis + else titlePlain + + -- Negative lack means we have an excess of space, so we fill it with dots + let dots = T.replicate (negate lack) "." + + pure . T.concat $ [numberStr, finalTitle, dots, " ", rightRef] + +vimdocTOC :: (PandocMonad m) => WriterState -> [Block] -> VW m (Doc Text) +vimdocTOC (WriterState{writerOptions = opts}) blocks = do + let (Node _ subtrees) = + toTOCTree $ makeSections (writerNumberSections opts) Nothing blocks + let tocDepth = writerTOCDepth opts + let isBelowTocDepth (Node sec _) = secLevel sec <= tocDepth + + let makeItem :: (PandocMonad m) => Tree SecInfo -> VW m (Doc Text) + makeItem (Node secinfo xs) = do + line <- tocEntryToLine secinfo + -- When unnumbered, indent constantly by two, + -- otherwise indent by (length of marker + 1) + let markerLen = 1 + maybe 1 T.length (secNumber secinfo) + childItems <- + indent markerLen $ + traverse makeItem (filter isBelowTocDepth xs) + pure (literal line $$ nest markerLen (vcat childItems)) + + items <- traverse makeItem (filter isBelowTocDepth subtrees) + pure $ vcat items + +writeVimdoc :: (PandocMonad m) => WriterOptions -> Pandoc -> m Text +writeVimdoc opts document@(Pandoc meta _) = + let + sw = fromMaybe (shiftWidth def) $ docShiftWidth meta + vp = docVimdocPrefix meta + footnotes = Seq.empty + initialEnv = def{shiftWidth = sw, writerOptions = opts, vimdocPrefix = vp} + in + runRR footnotes initialEnv $ pandocToVimdoc document + +pandocToVimdoc :: (PandocMonad m) => Pandoc -> VW m Text +pandocToVimdoc (Pandoc meta body) = do + st <- ask + let opts = writerOptions st + + metadata <- metaToContext opts blockListToVimdoc inlineListToVimdoc meta + main <- do + body' <- blockListToVimdoc body + footnotes <- get + rule <- blockToVimdoc HorizontalRule + let footnotes' = if Seq.null footnotes + then Empty + else vsep (toList $ rule <| footnotes) + pure $ body' <> blankline <> footnotes' + + title <- inlineListToVimdoc $ docTitle meta + authors <- traverse inlineListToVimdoc $ docAuthors meta + let authors' = mconcat $ intersperse ("," <> space) (fmap nowrap authors) + let tw = writerColumns . writerOptions $ st + + let combinedTitle = + render (Just tw) . cblock tw $ + (title <> space) + <> (if null authors' then "" else "by" <> space <> authors') + + -- This is placed here because I couldn't find a way to right-align text + -- inside template to the width specified by a variable + let toc_reminder = + render Nothing . rblock tw $ + ("Type |gO| to see the table of contents." :: Doc Text) + + toc <- render (Just tw) <$> vimdocTOC st body + + let modeline = makeModeLine st + let context = + defField "body" main + . defField "toc" (if writerTableOfContents opts then toc else "") + . defField "modeline" modeline + . defField "combined-title" combinedTitle + . defField "toc-reminder" toc_reminder + $ metadata + + pure $ + case writerTemplate opts of + Just tpl -> render (Just tw) $ renderTemplate tpl context + Nothing -> render (Just tw) main + +blockListToVimdoc :: (PandocMonad m) => [Block] -> VW m (Doc Text) +blockListToVimdoc blocks = vcat <$> mapM blockToVimdoc blocks + +blockToVimdoc :: (PandocMonad m) => Block -> VW m (Doc Text) + +blockToVimdoc (Plain inlines) = inlineListToVimdoc inlines + +blockToVimdoc (Para inlines) = do + contents <- inlineListToVimdoc inlines + pure $ contents <> blankline + +blockToVimdoc (LineBlock inliness) = vcat <$> mapM inlineListToVimdoc inliness + +blockToVimdoc (CodeBlock (_, cls, _) code) = do + sw <- asks shiftWidth + let lang = case cls of + (lang' : _) -> lang' + _ -> "" + -- NOTE: No blankline after the codeblock because closing `<` is concealed + pure . vcat $ + [ ">" <> literal lang + , nest sw (literal code) + , flush "<" + ] + +blockToVimdoc block@(RawBlock format raw) = case format of + "vimdoc" -> pure $ literal raw + _ -> "" <$ report (BlockNotRendered block) + +blockToVimdoc (BlockQuote blocks) = do + content <- blockListToVimdoc blocks + pure $ nest 2 content <> blankline + +blockToVimdoc (OrderedList listAttr items) = do + let itemSpacer = if isTightList items then empty else blankline + let itemsWithMarkers = zip (orderedListMarkers listAttr) items + items' <- forM itemsWithMarkers $ \(marker, blocks) -> do + let markerLen = T.length marker + + item' <- indent (markerLen + 1) $ blockListToVimdoc blocks + pure $ literal marker <> space <> nest (markerLen + 1) item' <> itemSpacer + pure $ vcat items' <> blankline + +blockToVimdoc (BulletList items) = do + let itemSpacer = if isTightList items then empty else blankline + items' <- forM items $ \blocks -> do + let marker = "-" + item <- indent 2 $ blockListToVimdoc blocks + pure $ marker <> " " <> nest 2 item <> itemSpacer + pure $ vcat items' <> blankline + +blockToVimdoc (DefinitionList items) = do + sw <- asks shiftWidth + let sepAll = if all (isTightList . snd) items then vcat else vsep + items' <- forM items $ \(term, definitions) -> do + let sepCur = if isTightList definitions then vcat else vsep + labeledTerm <- mkVimdocDefinitionTerm term + definitions' <- indent sw $ traverse blockListToVimdoc definitions + pure $ labeledTerm <> cr <> nest sw (sepCur definitions') + pure $ sepAll items' <> blankline + +blockToVimdoc (Header level (ref, _, _) inlines) = do + tw <- asks (writerColumns . writerOptions) + let rule = case level of + 1 -> T.replicate tw "=" + 2 -> T.replicate tw "-" + _ -> "" + title <- fmap (render Nothing) . inlineListToVimdoc $ case level of + 3 -> capitalize inlines + _ -> inlines + + label <- mkVimdocTag ref + -- One manual space that ensures that even if spaceLeft is 0, title and ref + -- don't touch each other + let label' = " " <> label + -- (+ 2) due to stars concealment + let spaceLeft = tw - T.length title + 2 + + pure $ vcat + [ blankline + , literal rule + , literal $ title <> T.justifyRight spaceLeft ' ' label' + , blankline + ] + +blockToVimdoc HorizontalRule = do + tw <- asks (writerColumns . writerOptions) + pure $ literal (T.replicate (tw `div` 2) " *") <> blankline + +-- Based on blockToMarkdown' from Text.Pandoc.Writers.Markdown +blockToVimdoc t@(Table (_, _, _) blkCapt specs thead tbody tfoot) = do + let isColRowSpans (Cell _ _ rs cs _) = rs > 1 || cs > 1 + let rowHasColRowSpans (Row _ cs) = any isColRowSpans cs + let tbodyHasColRowSpans (TableBody _ _ rhs rs) = + any rowHasColRowSpans rhs || any rowHasColRowSpans rs + let theadHasColRowSpans (TableHead _ rs) = any rowHasColRowSpans rs + let tfootHasColRowSpans (TableFoot _ rs) = any rowHasColRowSpans rs + let hasColRowSpans = + theadHasColRowSpans thead + || any tbodyHasColRowSpans tbody + || tfootHasColRowSpans tfoot + let (caption, aligns, widths, headers, rows) = + toLegacyTable blkCapt specs thead tbody tfoot + let numcols = + maximum $ + length aligns :| length widths : map length (headers : rows) + caption' <- inlineListToVimdoc caption + let caption'' + | null caption = blankline + | otherwise = blankline $$ caption' $$ blankline + let hasSimpleCells = onlySimpleTableCells $ headers : rows + let isSimple = hasSimpleCells && all (== 0) widths && not hasColRowSpans + let isPlainBlock (Plain _) = True + isPlainBlock _ = False + let hasBlocks = not (all (all (all isPlainBlock)) $ headers : rows) + let padRow r = r ++ replicate x empty + where + x = numcols - length r + let aligns' = aligns ++ replicate x AlignDefault + where + x = numcols - length aligns + let widths' = widths ++ replicate x 0.0 + where + x = numcols - length widths + sw <- asks shiftWidth + rawHeaders <- padRow <$> mapM blockListToVimdoc headers + rawRows <- mapM (fmap padRow . mapM blockListToVimdoc) rows + let hasHeader = all null headers + if + | isSimple -> do + -- Simple table + tbl <- + indent sw $ + vimdocTable False hasHeader aligns' widths' rawHeaders rawRows + pure $ nest sw (tbl $$ caption'') $$ blankline + | not (hasBlocks || hasColRowSpans) -> do + -- Multiline table + tbl <- + indent sw $ + vimdocTable True hasHeader aligns' widths' rawHeaders rawRows + pure $ nest sw (tbl $$ caption'') $$ blankline + | otherwise -> ("[TABLE]" $$ caption'') <$ report (BlockNotRendered t) + +blockToVimdoc (Figure _ _ blocks) = blockListToVimdoc blocks + +blockToVimdoc (Div _ blocks) = blockListToVimdoc blocks + +{- | Create a vimdoc tag. Tag is prefixed with "$vimdocPrefix-" if vimdocPrefix +is a Just value. +>>> runReader (mkVimdocTag "abc") def +"*abc*" +>>> runReader (mkVimdocTag "abc") (def{vimdocPrefix = Just "myCoolProject"}) +"*myCoolProject-abc*" +-} +mkVimdocTag :: (Monad m) => Text -> VW m Text +mkVimdocTag tag = do + asks vimdocPrefix <&> \case + _ | T.null tag -> "" + Nothing -> "*" <> tag <> "*" + Just pref' -> "*" <> pref' <> "-" <> tag <> "*" + +{- | Create a hotlink for a tag, ie. a followable vimdoc link. Tag is prefixed + - with "$vimdocPrefix-" if vimdocPrefix is a Just value +>>> runReader (mkVimdocRef "abc") def +"|abc|" +>>> runReader (mkVimdocRef "abc") (def{vimdocPrefix = Just "myCoolProject"}) +"|myCoolProject-abc|" +-} +mkVimdocRef :: (Monad m) => Text -> VW m Text +mkVimdocRef ref = + asks vimdocPrefix <&> \case + _ | T.null ref -> "" + Nothing -> "|" <> ref <> "|" + Just pref' -> "|" <> pref' <> "-" <> ref <> "|" + +mkVimdocDefinitionTerm :: + (PandocMonad m) => + [Inline] -> + VW m (Doc Text) +mkVimdocDefinitionTerm inlines = do + il <- asks indentLevel + tw <- asks (writerColumns . writerOptions) + label <- case inlines of + -- NOTE: commands in vim are unique, so they get no prefix + [Code (ref, _, _) code] + | T.isPrefixOf ":" code -> + pure . Just $ "*" <> ref <> "*" + [Code (ref, _, _) _] | not (T.null ref) -> Just <$> mkVimdocTag ref + [Span (ref, _, _) _] | not (T.null ref) -> Just <$> mkVimdocTag ref + _ -> pure Nothing + + term <- case inlines of + [Code _ code] | T.isPrefixOf ":" code -> pure $ literal code + _ -> inlineListToVimdoc inlines + let termLen = offset term + let labelLen = maybe 0 T.length label + + if il + termLen + labelLen > tw + then + pure . mconcat $ + [ case label of + Nothing -> empty + -- (+2) due to stars concealment + Just l -> flush (rblock (tw + 2) $ literal l) <> cr + , term + ] + else + pure . mconcat $ + [ -- Since we calculated that label fits on the same line as + -- term and since label actually must exceed textwidth to align + -- properly, we disable wrapping. + -- vvvvvvvv + nowrap term + , case label of + Nothing -> empty + -- (+2) due to stars concealment + Just l -> rblock (tw - termLen - il + 2) (literal l) + ] + +-- | Write a vimdoc table +vimdocTable :: + (Monad m) => + -- | whether this is a multiline table + Bool -> + -- | whether the table has a header + Bool -> + -- | column alignments + [Alignment] -> + -- | column widths + [Double] -> + -- | table header cells + [Doc Text] -> + -- | table body rows + [[Doc Text]] -> + VW m (Doc Text) +vimdocTable multiline headless aligns widths rawHeaders rawRows = do + let isSimple = all (== 0) widths + let alignHeader alignment = case alignment of + AlignLeft -> lblock + AlignCenter -> cblock + AlignRight -> rblock + AlignDefault -> lblock + -- Number of characters per column necessary to output every cell + -- without requiring a line break. + -- The @+2@ is needed for specifying the alignment. + let numChars = (+ 2) . maybe 0 maximum . nonEmpty . map offset + -- Number of characters per column necessary to output every cell + -- without requiring a line break *inside a word*. + -- The @+2@ is needed for specifying the alignment. + let minNumChars = (+ 2) . maybe 0 maximum . nonEmpty . map minOffset + let columns = transpose (rawHeaders : rawRows) + + il <- asks indentLevel + + -- x = (2 * length columns) -- spaces for specifying the alignment + -- y = (length columns - 1) -- spaces between the columns + -- x + y = (3 * length columns - 1) -- total needed correction + tw <- asks (writerColumns . writerOptions) + let tw' = tw - il - 3 * length columns + 1 + wrap <- asks (writerWrapText . writerOptions) + + -- minimal column width without wrapping a single word + let relWidth w col = + max + (floor $ fromIntegral (tw' - 1) * w) + ( if wrap == WrapAuto + then minNumChars col + else numChars col + ) + let widthsInChars + | isSimple = map numChars columns + | otherwise = zipWith relWidth widths columns + let makeRow = + hcat + . intersperse (lblock 1 (literal " ")) + . zipWith3 alignHeader aligns widthsInChars + let rows' = map makeRow rawRows + -- TODO: reduce tw in case head is not empty + let head' = makeRow rawHeaders <> " ~" + let head'' = + if headless + then empty + else head' + let body = + if multiline + then + vsep rows' + $$ if length rows' < 2 + then blankline + else empty + else vcat rows' + return $ + blankline + $$ head'' + $$ (if multiline then blankline else empty) + $$ body + +-- | Replace Unicode characters with their ASCII representation +replaceSpecialStrings :: Text -> Text +replaceSpecialStrings = + let expand c = case c of + '\x00ad' -> "" + '\x2013' -> "--" + '\x2014' -> "---" + '\x2019' -> "'" + '\x2026' -> "..." + _ -> T.singleton c + in T.concatMap expand + +inlineListToVimdoc :: (PandocMonad m) => [Inline] -> VW m (Doc Text) +inlineListToVimdoc inlines = hcat <$> mapM inlineToVimdoc inlines + +inlineToVimdoc :: (PandocMonad m) => Inline -> VW m (Doc Text) + +inlineToVimdoc (Str str) = pure . literal $ replaceSpecialStrings str + +-- Neither `:h help-writing`, nor neovim's grammar.js for vimdoc and +-- highlights.scm say anything about styling text, so we strip all the +-- formatting +inlineToVimdoc (Emph inlines) = inlineListToVimdoc inlines +inlineToVimdoc (Underline inlines) = inlineListToVimdoc inlines +inlineToVimdoc (Strong inlines) = inlineListToVimdoc inlines +inlineToVimdoc (Strikeout inlines) = inlineListToVimdoc inlines +inlineToVimdoc (Superscript inlines) = inlineListToVimdoc inlines +inlineToVimdoc (Subscript inlines) = inlineListToVimdoc inlines +inlineToVimdoc (SmallCaps inlines) = inlineListToVimdoc inlines + +inlineToVimdoc (Quoted typ inlines) = + let quote = case typ of SingleQuote -> "'"; DoubleQuote -> "\"" + in inlineListToVimdoc inlines >>= \text -> pure (quote <> text <> quote) + +inlineToVimdoc (Cite _citations inlines) = inlineListToVimdoc inlines + +inlineToVimdoc (Code (_, cls, _) code) = do + let hasNoLang = null cls + pure . literal $ case T.words code of + [":help", ref] | hasNoLang -> "|" <> ref <> "|" + [":h", ref] | hasNoLang -> "|" <> ref <> "|" + _ -> "`" <> code <> "`" + +inlineToVimdoc Space = pure space +inlineToVimdoc SoftBreak = + asks (writerWrapText . writerOptions) >>= \case + WrapAuto -> pure space + WrapNone -> pure " " + WrapPreserve -> pure "\n" + +inlineToVimdoc LineBreak = pure "\n" + +inlineToVimdoc (Math _ math) = pure . literal $ "`$" <> math <> "$`" + +inlineToVimdoc inline@(RawInline (Format format) text) = case format of + "vimdoc" -> pure $ literal text + _ -> "" <$ report (InlineNotRendered inline) + +inlineToVimdoc (Link _ txt (src, _)) = do + let srcSuffix = fromMaybe src (T.stripPrefix "mailto:" src) + linkText <- render Nothing <$> inlineListToVimdoc txt + + let isAutolink = case txt of + [Str x] | escapeURI x `elem` [src, srcSuffix] -> True + _ -> False + + pure $ case refdocLinkToLink src of + Right link | isAutolink -> "|" <> literal link <> "|" + Right link -> + literal (T.stripEnd linkText) <> space <> "|" <> literal link <> "|" + Left _ | isURI src, isAutolink -> literal srcSuffix + Left _ -> literal (T.stripEnd linkText) <> space <> literal srcSuffix + +inlineToVimdoc (Image {}) = pure "" + +inlineToVimdoc (Note blocks) = do + newN <- gets (succ . Seq.length) + contents <- blockListToVimdoc blocks + tag <- mkVimdocTag ("footnote" <> tshow newN) + tw <- asks (writerColumns . writerOptions) + + -- (+2) due to concealment of stars + -- vvvvvvvv + let taggedContents = rblock (tw + 2) (literal tag) $$ contents + modify (|> taggedContents) + + ref <- mkVimdocRef ("footnote" <> tshow newN) + pure $ space <> literal ref + +inlineToVimdoc (Span _ inlines) = inlineListToVimdoc inlines + + +refdocLinkToLink :: Text -> Either PandocError Text +refdocLinkToLink x = (\parser -> readWith parser Nothing x) $ do + string "http" >> optional (char 's') >> string "://" + + let vimhelpP = do + try (string "vimhelp.org/") <|> string "neo.vimhelp.org/" + + try (many1Till anyChar (char '#') >> many1TillChar anyChar eof) + <|> many1TillChar anyChar (try $ string ".html" >> eof) + + let neovimP = do + string "neovim.io/doc/user/" + try (many1Till anyChar (char '#') >> many1TillChar anyChar eof) + <|> do base <- many1TillChar anyChar (try $ string ".html" >> eof) + pure $ base <> ".txt" + + try vimhelpP <|> neovimP |
