summaryrefslogtreecommitdiff
path: root/script/core/hover/label.lua
blob: 8224e9d3e71b98eb3ce8eaf1336a3feab83b1093 (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
212
213
214
215
216
217
218
219
220
221
local buildName   = require 'core.hover.name'
local buildArgs   = require 'core.hover.args'
local buildReturn = require 'core.hover.return'
local buildTable  = require 'core.hover.table'
local infer       = require 'vm.infer'
local vm          = require 'vm'
local util        = require 'utility'
local lang        = require 'language'
local config      = require 'config'
local files       = require 'files'
local guide       = require 'parser.guide'

local function asFunction(source, oop)
    local name  = buildName(source, oop)
    local args  = buildArgs(source)
    local rtn   = buildReturn(source)
    local lines = {}

    lines[1] = string.format('%s%s %s(%s)'
        , vm.isAsync(source) and '(async) ' or ''
        , oop and '(method)' or 'function'
        , name or ''
        , oop and table.concat(args, ', ', 2) or table.concat(args, ', ')
    )
    lines[2] = rtn

    return table.concat(lines, '\n')
end

local function asDocTypeName(source)
    local defs = vm.getDefs(source)
    for _, doc in ipairs(defs) do
        if doc.type == 'doc.class' then
            return '(class) ' .. doc.class[1]
        end
        if doc.type == 'doc.alias' then
            return '(alias) ' .. doc.alias[1] .. ' ' .. lang.script('HOVER_EXTENDS', infer.getInfer(doc.extends):view())
        end
    end
end

---@async
local function asValue(source, title)
    local name    = buildName(source, false) or ''
    local ifr     = infer.getInfer(source)
    local type    = ifr:view()
    local literal = ifr:viewLiterals()
    local cont    = buildTable(source)
    local pack = {}
    pack[#pack+1] = title
    pack[#pack+1] = name .. ':'
    if vm.isAsync(source, true) then
        pack[#pack+1] = 'async'
    end
    if  cont
    and (  type == 'table'
        or type == 'any'
        or type == 'unknown'
        or type == 'nil') then
        type = nil
    end
    pack[#pack+1] = type
    if literal then
        pack[#pack+1] = '='
        pack[#pack+1] = literal
    end
    if cont then
        pack[#pack+1] = cont
    end
    return table.concat(pack, ' ')
end

---@async
local function asLocal(source)
    local node
    if source.type == 'local'
    or source.type == 'self' then
        node = source
    else
        node = source.node
    end
    if node.type == 'self' then
        return asValue(source, '(self)')
    end
    if node.parent.type == 'funcargs' then
        return asValue(source, '(parameter)')
    elseif guide.getParentFunction(source) ~= guide.getParentFunction(node) then
        return asValue(source, '(upvalue)')
    else
        return asValue(source, 'local')
    end
end

---@async
local function asGlobal(source)
    return asValue(source, '(global)')
end

local function isGlobalField(source)
    if source.type == 'field'
    or source.type == 'method' then
        source = source.parent
    end
    if     source.type == 'setfield'
    or     source.type == 'getfield'
    or     source.type == 'setmethod'
    or     source.type == 'getmethod' then
        local node = source.node
        if node.type == 'setglobal'
        or node.type == 'getglobal' then
            return true
        end
        return isGlobalField(node)
    elseif source.type == 'tablefield' then
        local parent = source.parent
        if parent.type == 'setglobal'
        or parent.type == 'getglobal' then
            return true
        end
        return isGlobalField(parent)
    else
        return false
    end
end

---@async
local function asField(source)
    if isGlobalField(source) then
        return asGlobal(source)
    end
    return asValue(source, '(field)')
end

local function asDocFieldName(source)
    local name     = source.field[1]
    local class
    for _, doc in ipairs(source.bindGroup) do
        if doc.type == 'doc.class' then
            class = doc
            break
        end
    end
    local view = infer.getInfer(source.extends):view()
    if not class then
        return ('(field) ?.%s: %s'):format(name, view)
    end
    return ('(field) %s.%s: %s'):format(class.class[1], name, view)
end

local function asString(source)
    local str = source[1]
    if type(str) ~= 'string' then
        return ''
    end
    local len = #str
    local charLen = util.utf8Len(str, 1, -1)
    if len == charLen then
        return lang.script('HOVER_STRING_BYTES', len)
    else
        return lang.script('HOVER_STRING_CHARACTERS', len, charLen)
    end
end

local function formatNumber(n)
    local str = ('%.10f'):format(n)
    str = str:gsub('%.?0*$', '')
    return str
end

local function asNumber(source)
    if not config.get(guide.getUri(source), 'Lua.hover.viewNumber') then
        return nil
    end
    local num = source[1]
    if type(num) ~= 'number' then
        return nil
    end
    local uri  = guide.getUri(source)
    local text = files.getText(uri)
    if not text then
        return nil
    end
    local raw = text:sub(source.start, source.finish)
    if not raw or not raw:find '[^%-%d%.]' then
        return nil
    end
    return formatNumber(num)
end

---@async
return function (source, oop)
    if     source.type == 'function'
    or     source.type == 'doc.type.function' then
        return asFunction(source, oop)
    elseif source.type == 'local'
    or     source.type == 'self'
    or     source.type == 'getlocal'
    or     source.type == 'setlocal' then
        return asLocal(source)
    elseif source.type == 'setglobal'
    or     source.type == 'getglobal' then
        return asGlobal(source)
    elseif source.type == 'getfield'
    or     source.type == 'setfield'
    or     source.type == 'getmethod'
    or     source.type == 'setmethod'
    or     source.type == 'tablefield'
    or     source.type == 'field'
    or     source.type == 'method' then
        return asField(source)
    elseif source.type == 'string' then
        return asString(source)
    elseif source.type == 'number'
    or     source.type == 'integer' then
        return asNumber(source)
    elseif source.type == 'doc.type.name' then
        return asDocTypeName(source)
    elseif source.type == 'doc.field' then
        return asDocFieldName(source)
    end
end