summaryrefslogtreecommitdiff
path: root/config/essentials/vis/vis-ultisnips/ultisnips-parser.lua
blob: a4240b8c5d37b1bed586d749c7c80d3d58e91012 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
--------------------------------------------------------------------------------
-- Module table

local M = {}

local lpeg = require('lpeg')



--------------------------------------------------------------------------------
-- lpeg rules

local tsep               = lpeg.S' \t'
local tws                = tsep ^ 1
local tnewline           = lpeg.S'\n'
local tlowcasedword      = lpeg.R'az' ^ 1
local tdigit             = lpeg.locale()['digit']
-- local talphanum          = lpeg.locale()['alnum']
local tanyprintable      = lpeg.locale()['print']
local tcontrol           = lpeg.locale()['cntrl']
local function surrounded(ch, p) return lpeg.S(ch) * p * lpeg.S(ch) end
local function anythingbut(ch) return (tanyprintable + tcontrol) - lpeg.S(ch) end

local ttabtriggercomplex = surrounded ('"',
                              tlowcasedword * lpeg.S'()[]?0123456789-'^1
                           )
-- TODO This is just retarded
--      Check the actual grammar and see what special starting chars are
--      then relax the grammar a bit
local ttabtriggerweird   = surrounded('!',
                             (lpeg.R'az' + lpeg.S'?()') ^ 1
                           )
local ttabtriggerweird2  = lpeg.P'#!'
local ttabtriggerweird3  = surrounded('/',
                             (anythingbut'/') ^1
                           )
local ttabtrigger        = ttabtriggercomplex
                         + ttabtriggerweird
                         + ttabtriggerweird2
                         + ttabtriggerweird3
                         + (tlowcasedword + lpeg.S'.')
local tdescription       = surrounded ('"',
                              lpeg.Cg( (tanyprintable - lpeg.S'"')^1, 'description')
                           )
local toption            = lpeg.R'az'

local tstartsnippet = lpeg.P'snippet'
                    * tws
                    * lpeg.Cg(ttabtrigger, 'tabtrigger')
                    * tws
                    * tdescription
                    * tws ^ 0
                    * lpeg.Cg(toption^0, 'options')
local tendsnippet   = lpeg.P'endsnippet'

-- The content parsing needs cleanup, its really convoluted due to me learning
-- lpeg while using it
--tcontent      = ((tanyprintable + tcontrol)^1 - tendsnippet) * tnewline
local tcontent = ((lpeg.S' \t' + tanyprintable)^1 - tendsnippet)
               * tnewline
local tsnippet = tstartsnippet
               * tnewline
               * ((tendsnippet * tnewline) + lpeg.Cg(tcontent ^ 1, 'content'))

-- local tcomment  = lpeg.S'#'
--                 * tanyprintable^0
--                 * tnewline
-- local tpriority = lpeg.P'priority'
--                 * tws
--                 * lpeg.Cg(lpeg.S('-')^0 * tdigit^1, 'priority')

-- TODO doesn't work
-- local tsnippetsfile = (lpeg.Ct(tsnippet) + tpriority + tcomment + tnewline) ^ 1


-- TODO does parse values correctly, but parsing out nested tags will
--      require recursion at the callsite since I have no clue how to do it
local ttag = { 'T'
       ; Expr = lpeg.C((lpeg.V'T' + ((tanyprintable + tcontrol) - lpeg.S'}'))^1)
       , Tnum = lpeg.Cg(tdigit ^ 1, 'tagnum')
       , Ps   = lpeg.Cg(lpeg.Cp(), 'selstart')
       , Pe   = lpeg.Cg(lpeg.Cp(), 'selend')
       , Tc   = lpeg.V'Ps'
                * lpeg.P'${'
                * lpeg.V'Tnum'
                * lpeg.S(':')
                * lpeg.Cg(lpeg.V'Expr', 'expr')
                * lpeg.V'Pe'
                * lpeg.S'}'
       , Ts   = lpeg.V'Ps' * lpeg.S'$' * lpeg.V'Pe' * lpeg.V'Tnum'
       , T    = lpeg.V'Tc' + lpeg.V'Ts'
       }



--------------------------------------------------------------------------------
-- Functions

-- Parses the snippet's content to create a table we later use
-- to corrently insert the text, the selections, and the default values
local function create_content(str)
  local content = {}
  content.str   = str
  content.tags  = {}

  local p = vis.lpeg.Ct((lpeg.Ct(ttag) + tanyprintable + tcontrol) ^ 1)
  local m = p:match(str)

  local s = 1 -- We start from 1 to adjust position from $^0 to ^$0
  for k,v in ipairs(m) do
    content.tags[k] = v
    -- TODO recurse over tag.expr to extract nested tags
    --      Of course this will actually have to be used later on, depending
    --      on whether the tag is added or not

    -- We need to keep track of how much we remove, and readjust all
    -- subsequent selection points
    -- Note to self, I hate all this bookkeeping
    local tagtext = string.sub(str, v.selstart, v.selend)
    if v.expr ~= nil then
      content.str = string.gsub(content.str, tagtext, v.expr)
      content.tags[k].selstart = content.tags[k].selstart - s
      content.tags[k].selend   = content.tags[k].selstart + #v.expr
      s = s + #'${' + #tostring(k) + #':' + 1
    else
      content.str = string.gsub(content.str, tagtext, '')
      content.tags[k].selstart = content.tags[k].selstart - s
      content.tags[k].selend   = content.tags[k].selstart
      s = s + #'$' + 1
    end
  end

  return content
end



-- Takes a line starting with 'snippet' and a lines iterator, and creates
-- a 'snippet' table to be used
-- If it fails it returns nil, otherwise returns two values, a tabtrigger
-- and a snippet
local function create_snippet(start_line, linesit)
  local snippetstr = start_line .. '\n'
  -- Read content into list of lines until we hit `endsnippet`
  for line in linesit do
    local s, _ = string.find(line, 'endsnippet')
    if s == 1 then
      snippetstr = snippetstr .. 'endsnippet' .. '\n'
      break
    else
      snippetstr = snippetstr .. line .. '\n'
    end
  end

  local p = vis.lpeg.Ct(tsnippet)
  local m = p:match(snippetstr)

  if not m then
    -- Enable this when debugging, otherwise it nukes whole app
    vis:info('Failed to parse some snippets!')
    -- vis:message('Failed to parse snippet: ' .. snippetstr)
    return nil
  else
    local tabtrigger = m.tabtrigger
    local snippet = {}
    snippet.description = m.description
    snippet.options = m.options
    snippet.content = create_content(m.content)
    return tabtrigger, snippet
  end
end



-- Loads all snippets from passed '.snippets' file. Should probably be
-- triggered when new file is loaded or when syntax is set/changed
M.load_snippets = function(snippetfile)
  local snippets = {}

  local f = io.open(snippetfile, 'r')
  if f then
    io.input(f)
    local linesit = io.lines()

    for line in linesit do
      -- TODO read whole file, then apply lpeg grammar that parses all
      -- snippets out rather than being pedestrian about it like this
      local s, _ = string.find(line, 'snippet')
      -- Find lines that start with 'snippet' and enter
      -- snippet reading loop
      if s == 1 then
        local tabtrigger, snippet = create_snippet(line, linesit)
        if tabtrigger then
          snippets[tabtrigger] = snippet
        end
      end
    end

    io.close(f)
    return snippets, true
  else
    return snippets, false
  end
end



--------------------------------------------------------------------------------
-- End module

return M