]> git.wincent.com - docvim.git/blob - lib/Text/Docvim/AST.hs
Add additional blank line above headings
[docvim.git] / lib / Text / Docvim / AST.hs
1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE RankNTypes #-}
3
4 module Text.Docvim.AST where
5
6 import Control.Lens.Fold
7 import Control.Lens.Getter
8 import Control.Lens.Plated
9 import Data.Char
10 import Data.Data
11 import Data.Data.Lens
12 import Data.Monoid
13
14 data Node
15     -- Roots
16     = Project [Node] -- list of translation units (files)
17     | Unit [Node] -- translation unit (file)
18
19     -- To remove any node from the tree, we can just replace it with this.
20     | Empty
21
22     -- VimL nodes
23     | FunctionDeclaration { functionBang :: Bool
24                           , functionName :: String
25                           , functionArguments :: ArgumentList
26                           , functionAttributes :: [String]
27                           , functionBody :: [Node]
28                           }
29     | LetStatement { letLexpr :: String
30                     , letValue :: String
31                     }
32     | LexprStatement { lexprBang :: Bool
33                       , lexprExpr :: String
34                       }
35     | LwindowStatement { lwindowHeight :: Maybe Int }
36     | UnletStatement { unletBang :: Bool
37                       , unletBody :: String
38                       }
39     | GenericStatement String -- ^ For the stuff we only crudely parse for now.
40
41     -- Docvim nodes: "block-level" elements
42     | DocBlock [Node]
43     | Paragraph [Node]
44     | LinkTargets [String]
45     | List [Node]
46     | ListItem [Node]
47     | Blockquote [Node]
48     | Fenced [String]
49     | Separator
50
51     -- Docvim nodes: "phrasing content" elements
52     | Plaintext String
53     | BreakTag
54     | Link String
55     | Code String
56     | Whitespace
57
58     -- Docvim nodes: annotations
59     | PluginAnnotation Name Description
60     | FunctionsAnnotation
61     | FunctionAnnotation Name -- not sure if I will want more here
62     | IndentAnnotation
63     | DedentAnnotation
64     | CommandsAnnotation
65     | CommandAnnotation Name (Maybe Parameters)
66     | FooterAnnotation
67     | MappingsAnnotation
68     | MappingAnnotation Name
69     | OptionsAnnotation
70     | OptionAnnotation Name Type (Maybe Default)
71     | HeadingAnnotation String
72     | SubheadingAnnotation String
73
74     -- Docvim nodes: synthesized nodes
75     | TOC [String]
76   deriving (Data, Eq, Show, Typeable)
77
78 -- The VimScript (VimL) grammar is embodied in the implementation of
79 -- https://github.com/vim/vim/blob/master/src/eval.c; there is no formal
80 -- specification for it, and there are many ambiguities that can only be
81 -- resolved at runtime. We aim to parse a loose subset.
82
83 -- TODO: deal with bar |
84 --       note that `function X() |` does not work, and `endf` must be on a line
85 --       of its own too (not a syntax error to do `| endf`, but it doesn't work
86 --       , so you need to add another `endf`, which will blow up at runtime.
87 -- TODO: validate name = CapitalLetter or s:foo or auto#loaded
88
89 data ArgumentList = ArgumentList [Argument]
90   deriving (Data, Eq, Show, Typeable)
91
92 data Argument = Argument String
93   deriving (Data, Eq, Show, Typeable)
94
95 instance Plated Node
96
97 type Default = String
98 type Description = String
99 type Name = String
100 type Type = String
101 type Parameters = String
102
103 -- | Walks an AST node calling the supplied visitor function.
104 --
105 -- This is an in-order traversal.
106 --
107 -- For example, to implement a visitor which counts all nodes:
108 --
109 -- >  import Data.Monoid
110 -- >  count = getSum $ walk (\_ -> 1) (Sum 0) tree
111 --
112 -- For comparison, here is a (probably inefficient) alternative way using
113 -- `Control.Lens.Plated` instead of `walk`:
114 --
115 -- > import Control.Lens.Operators
116 -- > import Control.Lens.Plated
117 -- > import Data.Data.Lens
118 -- > count = length $ tree ^.. cosmosOf uniplate
119 --
120 -- Another example; accumulating `SubheadingAnnotation` nodes into a list:
121 --
122 -- >  accumulator node@(SubheadingAnnotation _) = [node]
123 -- >  accumulator _ = [] -- skip everything else
124 -- >  nodes = walk accumulator [] tree
125 --
126 -- Again, for comparison, the same without `walk`, this time using a list
127 -- comprehension:
128 --
129 -- > import Control.Lens.Operators
130 -- > import Control.Lens.Plated
131 -- > import Data.Data.Lens
132 -- > [n | n@(SubheadingAnnotation _) <- tree ^.. cosmosOf uniplate]
133 --
134 walk :: Monoid a => (Node -> a) -> a -> Node -> a
135 walk f = foldlOf (cosmosOf uniplate . to f) (<>)
136 -- TODO: consider making it possible for `f` to return `Nothing` to
137 -- short-circuit traversal, or `Just a` to continue with the current `mappend`
138 -- behavior.
139
140 -- | Sanitizes a link target similar to the way that GitHub does:
141 --
142 --    - Downcase.
143 --    - Filter, keeping only letter, number, space, hyphen.
144 --    - Change spaces to hyphens.
145 --    - Uniquify by appending "-1", "-2", "-3" etc (not yet implemented).
146 --
147 -- We use this both for generating GitHub friendly link targets, and for
148 -- auto-generating new link targets for use inside Vim help files.
149 --
150 -- Source: https://gist.github.com/asabaylus/3071099#gistcomment-1593627
151 sanitizeAnchor :: String -> String
152 sanitizeAnchor = hyphenate . keepValid . downcase
153   where
154     hyphenate = map spaceToHyphen
155     spaceToHyphen c = if c == ' ' then '-' else c
156     keepValid = filter (`elem` (['a'..'z'] ++ ['0'..'9'] ++ " -"))
157     downcase = map toLower
158
159 invalidNode :: forall t. t
160 invalidNode = error "Invalid Node type"