summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGly <[email protected]>2022-12-24 15:09:17 +0100
committerGly <[email protected]>2022-12-24 15:09:17 +0100
commit216c126bd8378e1b96159955fa00d647e50ec73b (patch)
tree5f5f5eb1be9ad8072e67c7261527c9062235c277
parent2e5134b08e41ba3afb7975473601f4407a7334d5 (diff)
make completer into "class"
-rw-r--r--xmake/modules/private/utils/complete.lua295
-rw-r--r--xmake/modules/private/utils/completer.lua331
2 files changed, 342 insertions, 284 deletions
diff --git a/xmake/modules/private/utils/complete.lua b/xmake/modules/private/utils/complete.lua
index 072c69704..a93d24776 100644
--- a/xmake/modules/private/utils/complete.lua
+++ b/xmake/modules/private/utils/complete.lua
@@ -23,292 +23,13 @@ import("core.base.option")
import("core.base.task")
import("core.base.cli")
import("core.base.json")
-
-local raw_words = {}
-local raw_config
-local position = 0
-local no_json = false
-local use_spaces = true
-local no_key = false
-local reenter = false
-
-function _print_candidate(candidate)
- if candidate.value and #candidate.value ~= 0 then
- printf(candidate.value)
- if use_spaces and candidate.is_complete then
- print(" ")
- else
- print("")
- end
- end
-end
-
-function _print_candidates(candidates)
- if to_json then
- print(json.encode(candidates))
- else
- for _, v in ipairs(candidates) do
- _print_candidate(v)
- end
- end
-end
-
-function _find_candidates(candidates, find)
-
- if type(candidates) ~= 'table' then
- return {}
- end
-
- local has_candidate = false
- local results = table.new(#candidates, 0)
-
- -- find candidate starts with find str
- for _, v in ipairs(candidates) do
- if tostring(v):startswith(find) then
- table.insert(results, v)
- has_candidate = true
- end
- end
-
- -- stop searching if found any
- if has_candidate then
- return results
- end
-
- -- find candidate contains find str
- for _, v in ipairs(candidates) do
- if tostring(v):find(find, 1, true) then
- table.insert(results, v)
- end
- end
-
- return results
-end
-
-function _complete_task(tasks, name)
- local found_candidates = {}
- for i, v in ipairs(_find_candidates((table.keys(tasks)), name)) do
- table.insert(found_candidates, { value = v, is_complete = true, description = tasks[v].description })
- end
- _print_candidates(found_candidates)
- return #found_candidates > 0
-end
-
--- complete values of kv
-function _complete_option_kv_v(options, current, completing, name, value)
-
- -- find completion option
- local opt
- for _, v in ipairs(options) do
- if v[3] == "kv" and (v[1] == name or v[2] == name) then
- opt = v
- break
- end
- end
- if not opt then
- return false
- end
-
- -- show candidates of values
- local values = opt.values
- if type(values) == "function" then
- values = values(value, current)
- end
- if values == nil and type(opt[4]) == "boolean" then
- values = { "yes", "no" }
- -- ignore existing input
- value = ""
- end
-
- -- match values starts with value first
- local found_candidates = {}
- for _, v in ipairs(_find_candidates(values, value)) do
- if no_key then
- table.insert(found_candidates, { value = v, is_complete = true })
- else
- table.insert(found_candidates, { value = format("--%s=%s", name, v), is_complete = true })
- end
- end
- _print_candidates(found_candidates)
-
- -- whether any candidates has been found, finish complete since we don't have more info
- return true
-end
-
--- complete keys of kv
-function _complete_option_kv_k(options, current, completing, name)
-
- local opcandi = table.new(0, 10)
- for _, opt in ipairs(options) do
- if opt[2] and current[opt[2]] == nil and (opt[3] == "kv" or opt[3] == "k") then
- opcandi[opt[2]] = opt
- end
- end
- local found_candidates = {}
- for _, k in ipairs(_find_candidates((table.keys(opcandi)), name)) do
- local opt = opcandi[k]
- local name = format((opt[3] == "k") and "--%s" or "--%s=", opt[2])
- table.insert(found_candidates, { value = name, description = opt[5], is_complete = (opt[3] == "k") })
- end
- _print_candidates(found_candidates)
-
- return true
-end
-
-function _complete_option_kv(options, current, completing)
-
- local name, value
- if completing == "-" or completing == "--" then
- name = ""
- elseif completing:startswith("--") then
- local parg = cli.parsev({completing})[1]
- if parg.type == "option" then
- name, value = parg.key, parg.value
- elseif parg.type == "flag" then
- name = parg.key
- end
- elseif completing:startswith("-") then
- -- search full names only
- return true
- end
-
- if value then
- -- complete values
- return _complete_option_kv_v(options, current, completing, name, value)
- else
- -- complete keys
- return _complete_option_kv_k(options, current, completing, name)
- end
-end
-
--- complete options v and vs
-function _complete_option_v(options, current, completing)
- -- find completion option
- local opt
- local optvs
- for _, v in ipairs(options) do
- if v[3] == "v" and current[v[2] or v[1]] == nil then
- opt = v
- end
- if v[3] == "vs" and not optvs then
- optvs = v
- end
- end
- local found_candidates = {}
- if opt then
- -- show candidates of values
- local values = opt.values
- if type(values) == "function" then
- values = values(completing, current)
- end
- for _, v in ipairs(values) do
- if tostring(v):startswith(completing) then
- table.insert(found_candidates, { value = v, is_complete = true })
- end
- end
- end
-
- if optvs and #found_candidates == 0 then
- -- show candidates of values
- local values = optvs.values
- if type(values) == "function" then
- values = values(completing)
- end
- for _, v in ipairs(values) do
- if tostring(v):startswith(completing) then
- table.insert(found_candidates, { value = v, is_complete = true })
- end
- end
- end
- _print_candidates(found_candidates)
- return #found_candidates > 0
-end
-
-function _complete_option(options, segs, completing)
- local current_options = try
- {
- function()
- return option.raw_parse(segs, options, { populate_defaults = false, allow_unknown = true })
- end
- }
- -- current options is invalid
- if not current_options then return false end
-
- -- current context is wrong
- if not reenter and (current_options.file or current_options.project) then
- local args = {"lua", "private.utils.complete", tostring(position), raw_config .. "-reenter", table.unpack(raw_words) }
- if current_options.file then
- table.insert(args, 2, "--file=" .. current_options.file)
- end
- if current_options.project then
- table.insert(args, 2, "--project=" .. current_options.project)
- end
- os.execv("xmake", args)
- return true
- end
-
- if completing:startswith("-") then
- return _complete_option_kv(options, current_options, completing)
- else
- return _complete_option_v(options, current_options, completing)
- end
-end
-
-function _complete(argv, completing)
-
- local tasks = table.new(10, 0)
- local shortnames = table.new(0, 10)
- for _, v in ipairs(task.names()) do
- local menu = option.taskmenu(v)
- tasks[v] = menu
- if menu.shortname then
- shortnames[menu.shortname] = v
- end
- end
-
- if #argv == 0 then
- if _complete_task(tasks, completing) then return end
- end
-
- local task_name = "build"
- if argv[1] and not argv[1]:startswith("-") then
- task_name = table.remove(argv, 1)
- end
-
- if shortnames[task_name] then task_name = shortnames[task_name] end
-
- local options
- if tasks[task_name] then
- options = tasks[task_name].options
- else
- options = tasks["build"].options
- end
-
- _complete_option(options, argv, completing)
-end
+import("private.utils.completer")
function main(pos, config, ...)
- raw_words = {...}
-
- raw_config = (config or ""):trim()
- if raw_config:find("nospace", 1, true) then
- use_spaces = false
- end
- if raw_config:find("reenter", 1, true) then
- reenter = true
- end
- if raw_config:find("nokey", 1, true) then
- no_key = true
- end
- if raw_config:find("debug", 1, true) then
- debug = true
- end
- if raw_config:find("json", 1, true) then
- to_json = true
- end
+ local comp = completer.new(pos, config, {...})
- local word = table.concat(raw_words, " ") or ""
+ local word = table.concat(comp:words(), " ") or ""
position = tonumber(pos) or 0
local has_space = word:endswith(" ") or position > #word
@@ -327,11 +48,17 @@ function main(pos, config, ...)
end
end
+ local items = {}
+ local tasks = task.names()
+ for _, name in ipairs(tasks) do
+ items[name] = option.taskmenu(name)
+ end
+
if has_space then
- _complete(argv, "")
+ comp:complete(items, argv, "")
else
local completing = table.remove(argv)
- _complete(argv, completing or "")
+ comp:complete(items, argv, completing or "")
end
end
diff --git a/xmake/modules/private/utils/completer.lua b/xmake/modules/private/utils/completer.lua
new file mode 100644
index 000000000..d0c74e93f
--- /dev/null
+++ b/xmake/modules/private/utils/completer.lua
@@ -0,0 +1,331 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author OpportunityLiu
+-- @file complete.lua
+--
+
+-- imports
+import("core.base.option")
+import("core.base.cli")
+
+
+
+local completer = {}
+
+
+function completer.new(pos, config, words)
+ local instance = table.inherit(completer)
+ instance._POSITION = 0
+ instance._CONFIG = {}
+ instance._WORDS = {}
+ instance._DEFAULT_COMMAND = ""
+ instance:set_config(config)
+ instance:set_position(pos)
+ instance:set_words(words)
+ return instance
+end
+
+function completer:_print_candidate(candidate)
+ if candidate.value and #candidate.value ~= 0 then
+ printf(candidate.value)
+ if not self:config("nospace") and candidate.is_complete then
+ print(" ")
+ else
+ print("")
+ end
+ end
+end
+
+function completer:_print_candidates(candidates)
+ if self:config("json") then
+ import("core.base.json")
+ print(json.encode(candidates))
+ else
+ for _, v in ipairs(candidates) do
+ self:_print_candidate(v)
+ end
+ end
+end
+
+function completer:_find_candidates(candidates, find)
+
+ if type(candidates) ~= 'table' then
+ return {}
+ end
+
+ local has_candidate = false
+ local results = table.new(#candidates, 0)
+
+ -- find candidate starts with find str
+ for _, v in ipairs(candidates) do
+ if tostring(v):startswith(find) then
+ table.insert(results, v)
+ has_candidate = true
+ end
+ end
+
+ -- stop searching if found any
+ if has_candidate then
+ return results
+ end
+
+ -- find candidate contains find str
+ for _, v in ipairs(candidates) do
+ if tostring(v):find(find, 1, true) then
+ table.insert(results, v)
+ end
+ end
+
+ return results
+end
+
+function completer:_complete_item(items, name)
+ local found_candidates = {}
+ for _, v in ipairs(self:_find_candidates(table.keys(items), name)) do
+ table.insert(found_candidates, { value = v, is_complete = true, description = items[v].description })
+ end
+ self:_print_candidates(found_candidates)
+ return #found_candidates > 0
+end
+
+-- complete values of kv
+function completer:_complete_option_kv_v(options, current, completing, name, value)
+
+ -- find completion option
+ local opt
+ for _, v in ipairs(options) do
+ if v[3] == "kv" and (v[1] == name or v[2] == name) then
+ opt = v
+ break
+ end
+ end
+ if not opt then
+ return false
+ end
+
+ -- show candidates of values
+ local values = opt.values
+ if type(values) == "function" then
+ values = values(value, current)
+ end
+ if values == nil and type(opt[4]) == "boolean" then
+ values = { "yes", "no" }
+ -- ignore existing input
+ value = ""
+ end
+
+ -- match values starts with value first
+ local found_candidates = {}
+ local nokey = self:config("nokey")
+ for _, v in ipairs(_find_candidates(values, value)) do
+ if nokey then
+ table.insert(found_candidates, { value = v, is_complete = true })
+ else
+ table.insert(found_candidates, { value = format("--%s=%s", name, v), is_complete = true })
+ end
+ end
+ completer:_print_candidates(found_candidates)
+
+ -- whether any candidates has been found, finish complete since we don't have more info
+ return true
+end
+
+-- complete keys of kv
+function completer:_complete_option_kv_k(options, current, completing, name)
+
+ local opcandi = table.new(0, 10)
+ for _, opt in ipairs(options) do
+ if opt[2] and current[opt[2]] == nil and (opt[3] == "kv" or opt[3] == "k") then
+ opcandi[opt[2]] = opt
+ end
+ end
+ local found_candidates = {}
+ for _, k in ipairs(self:_find_candidates((table.keys(opcandi)), name)) do
+ local opt = opcandi[k]
+ local name = format((opt[3] == "k") and "--%s" or "--%s=", opt[2])
+ table.insert(found_candidates, { value = name, description = opt[5], is_complete = (opt[3] == "k") })
+ end
+ self:_print_candidates(found_candidates)
+
+ return true
+end
+
+function completer:_complete_option_kv(options, current, completing)
+
+ local name, value
+ if completing == "-" or completing == "--" then
+ name = ""
+ elseif completing:startswith("--") then
+ local parg = cli.parsev({completing})[1]
+ if parg.type == "option" then
+ name, value = parg.key, parg.value
+ elseif parg.type == "flag" then
+ name = parg.key
+ end
+ elseif completing:startswith("-") then
+ -- search full names only
+ return true
+ end
+
+ if value then
+ -- complete values
+ return self:_complete_option_kv_v(options, current, completing, name, value)
+ else
+ -- complete keys
+ return self:_complete_option_kv_k(options, current, completing, name)
+ end
+end
+
+-- complete options v and vs
+function completer:_complete_option_v(options, current, completing)
+ -- find completion option
+ local opt
+ local optvs
+ for _, v in ipairs(options) do
+ if v[3] == "v" and current[v[2] or v[1]] == nil then
+ opt = v
+ end
+ if v[3] == "vs" and not optvs then
+ optvs = v
+ end
+ end
+ local found_candidates = {}
+ if opt then
+ -- show candidates of values
+ local values = opt.values
+ if type(values) == "function" then
+ values = values(completing, current)
+ end
+ for _, v in ipairs(values) do
+ if tostring(v):startswith(completing) then
+ table.insert(found_candidates, { value = v, is_complete = true })
+ end
+ end
+ end
+
+ if optvs and #found_candidates == 0 then
+ -- show candidates of values
+ local values = optvs.values
+ if type(values) == "function" then
+ values = values(completing)
+ end
+ for _, v in ipairs(values) do
+ if tostring(v):startswith(completing) then
+ table.insert(found_candidates, { value = v, is_complete = true })
+ end
+ end
+ end
+ self:_print_candidates(found_candidates)
+ return #found_candidates > 0
+end
+
+function completer:_complete_option(options, segs, completing)
+ local current_options = try
+ {
+ function()
+ return option.raw_parse(segs, options, { populate_defaults = false, allow_unknown = true })
+ end
+ }
+ -- current options is invalid
+ if not current_options then return false end
+
+ -- current context is wrong
+ if not self:config("reenter") and (current_options.file or current_options.project) then
+ local args = {"lua", "private.utils.complete", tostring(position), table.concat(self._CONFIG, "-") .. "-reenter", table.unpack(raw_words) }
+ if current_options.file then
+ table.insert(args, 2, "--file=" .. current_options.file)
+ end
+ if current_options.project then
+ table.insert(args, 2, "--project=" .. current_options.project)
+ end
+ os.execv("xmake", args)
+ return true
+ end
+
+ if completing:startswith("-") then
+ return self:_complete_option_kv(options, current_options, completing)
+ else
+ return self:_complete_option_v(options, current_options, completing)
+ end
+end
+
+function completer:complete(items, argv, completing)
+
+ local shortnames = table.new(0, 10)
+ for v, menu in pairs(items) do
+ -- local menu = items[v]
+ if menu.shortname then
+ shortnames[menu.shortname] = v
+ end
+ end
+
+ if #argv == 0 then
+ if self:_complete_item(items, completing) then return end
+ end
+
+ local item_name = self:default_command()
+ if argv[1] and not argv[1]:startswith("-") then
+ item_name = table.remove(argv, 1)
+ end
+
+ if shortnames[item_name] then item_name = shortnames[item_name] end
+
+ local options
+ if items[item_name] then
+ options = items[item_name].options
+ end
+
+ if options then
+ self:_complete_option(options, argv, completing)
+ end
+end
+
+function completer:set_words(raw_words)
+ self._WORDS = raw_words
+end
+
+function completer:words()
+ return self._WORDS
+end
+
+function completer:set_position(pos)
+ self._POSITION = pos
+end
+
+function completer:position()
+ return self._POSITION
+end
+
+function completer:set_default_command(command)
+ self._DEFAULT_COMMAND = command
+end
+
+function completer:default_command()
+ return self._DEFAULT_COMMAND
+end
+
+function completer:set_config(config)
+ self._CONFIG = (config or ""):trim():split("-")
+end
+
+function completer:config(key)
+ return table.find_first(self._CONFIG, key)
+end
+
+function new(...)
+ return completer.new(...)
+end \ No newline at end of file