summaryrefslogtreecommitdiff
path: root/script-beta/core/semantic-tokens.lua
blob: fa0376108a7f4aa2d4306a59203555b21872a07a (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
local files          = require 'files'
local guide          = require 'parser.guide'
local await          = require 'await'
local define         = require 'proto.define'
local vm             = require 'vm'
local util           = require 'utility'

local Care = {}
Care['setglobal'] = function (source, results)
    results[#results+1] = {
        start      = source.start,
        finish     = source.finish,
        type       = define.TokenTypes.namespace,
        modifieres = define.TokenModifiers.deprecated,
    }
end
Care['getglobal'] = function (source, results)
    local lib = vm.getLibrary(source)
    if lib then
        if source[1] == '_G' then
            return
        else
            results[#results+1] =  {
                start      = source.start,
                finish     = source.finish,
                type       = define.TokenTypes.namespace,
                modifieres = define.TokenModifiers.static,
            }
        end
    else
        results[#results+1] =  {
            start      = source.start,
            finish     = source.finish,
            type       = define.TokenTypes.namespace,
            modifieres = define.TokenModifiers.deprecated,
        }
    end
end
Care['tablefield'] = function (source, results)
    local field = source.field
    if not field then
        return
    end
    results[#results+1] = {
        start      = field.start,
        finish     = field.finish,
        type       = define.TokenTypes.property,
        modifieres = define.TokenModifiers.declaration,
    }
end
Care['getlocal'] = function (source, results)
    local loc = source.node
    -- 1. 函数的参数
    if loc.parent and loc.parent.type == 'funcargs' then
        results[#results+1] = {
            start      = source.start,
            finish     = source.finish,
            type       = define.TokenTypes.parameter,
            modifieres = define.TokenModifiers.declaration,
        }
        return
    end
    -- 2. 特殊变量
    if source[1] == '_ENV'
    or source[1] == 'self' then
        return
    end
    -- 3. 不是函数的局部变量
    local hasFunc
    for _, def in ipairs(vm.getDefs(loc)) do
        if def.type == 'function'
        or (def.type == 'library' and def.value.type == 'function') then
            hasFunc = true
            break
        end
    end
    if hasFunc then
        results[#results+1] = {
            start      = source.start,
            finish     = source.finish,
            type       = define.TokenTypes.interface,
            modifieres = define.TokenModifiers.declaration,
        }
        return
    end
    -- 4. 其他
    results[#results+1] = {
        start      = source.start,
        finish     = source.finish,
        type       = define.TokenTypes.variable,
    }
end
Care['setlocal'] = Care['getlocal']

local function buildTokens(results, text, lines)
    local tokens = {}
    local lastLine = 0
    local lastStartChar = 0
    for i, source in ipairs(results) do
        local row, col = guide.positionOf(lines, source.start)
        local start    = guide.lineRange(lines, row)
        local ucol     = util.utf8Len(text, start, start + col - 1)
        local line = row - 1
        local startChar = ucol - 1
        local deltaLine = line - lastLine
        local deltaStartChar
        if deltaLine == 0 then
            deltaStartChar = startChar - lastStartChar
        else
            deltaStartChar = startChar
        end
        lastLine = line
        lastStartChar = startChar
        -- see https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#textDocument_semanticTokens
        local len = i * 5 - 5
        tokens[len + 1] = deltaLine
        tokens[len + 2] = deltaStartChar
        tokens[len + 3] = source.finish - source.start + 1 -- length
        tokens[len + 4] = source.type
        tokens[len + 5] = source.modifieres or 0
    end
    return tokens
end

return function (uri, start, finish)
    local ast   = files.getAst(uri)
    local lines = files.getLines(uri)
    local text  = files.getText(uri)
    if not ast then
        return nil
    end

    local results = {}
    local count = 0
    guide.eachSourceBetween(ast.ast, start, finish, function (source)
        local method = Care[source.type]
        if not method then
            return
        end
        method(source, results)
        count = count + 1
        if count % 100 == 0 then
            await.delay()
        end
    end)

    table.sort(results, function (a, b)
        return a.start < b.start
    end)

    local tokens = buildTokens(results, text, lines)

    return tokens
end