diff options
| author | ruki <[email protected]> | 2022-12-25 11:38:09 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-12-25 11:38:09 +0800 |
| commit | c0a6a5ef5a125b6c40dd5c7973f40888fb210e86 (patch) | |
| tree | 8c8e3dee0c2dc51f99ca1eeaa63e68b0bac0cecd | |
| parent | e05bb0fceae1a6f73d03cbcbc929ca25aad2479a (diff) | |
| parent | de72555619940eddf4dbe613b68741d28b425081 (diff) | |
Merge pull request #3201 from glcraft/completer
Add completer and xrepo complete
| -rw-r--r-- | xmake/modules/private/utils/complete.lua | 299 | ||||
| -rw-r--r-- | xmake/modules/private/utils/completer.lua | 329 | ||||
| -rw-r--r-- | xmake/modules/private/xrepo/complete.lua | 63 |
3 files changed, 404 insertions, 287 deletions
diff --git a/xmake/modules/private/utils/complete.lua b/xmake/modules/private/utils/complete.lua index 072c69704..d8956086f 100644 --- a/xmake/modules/private/utils/complete.lua +++ b/xmake/modules/private/utils/complete.lua @@ -14,301 +14,20 @@ -- -- Copyright (C) 2015-present, TBOOX Open Source Group. -- --- @author OpportunityLiu +-- @author OpportunityLiu, glcraft -- @file complete.lua -- -- imports 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 +46,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..2d882c9e3 --- /dev/null +++ b/xmake/modules/private/utils/completer.lua @@ -0,0 +1,329 @@ +--!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, glcraft +-- @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 diff --git a/xmake/modules/private/xrepo/complete.lua b/xmake/modules/private/xrepo/complete.lua new file mode 100644 index 000000000..be4b04ced --- /dev/null +++ b/xmake/modules/private/xrepo/complete.lua @@ -0,0 +1,63 @@ +--!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 glcraft +-- @file complete.lua +-- + +import("private.utils.completer") + +function main(pos, config, ...) + + local comp = completer.new(pos, config, {...}) + + local word = table.concat(comp:words(), " ") or "" + position = tonumber(pos) or 0 + + local has_space = word:endswith(" ") or position > #word + word = word:trim() + + local argv = os.argv(word) + + if argv[1] then + + -- normailize word to remove "xrepo" + if is_host("windows") and argv[1]:lower() == "xrepo.exe" then + argv[1] = "xrepo" + end + if argv[1] == "xrepo" then + table.remove(argv, 1) + end + end + + local items = {} + for _, scriptfile in ipairs(os.files(path.join(os.scriptdir(), "action", "*.lua"))) do + local action_name = path.basename(scriptfile) + local action = import("private.xrepo.action." .. action_name, {anonymous = true}) + local options, _, description = action.menu_options() + items[action_name] = { + description = description, + options = options, + } + end + + if has_space then + comp:complete(items, argv, "") + else + local completing = table.remove(argv) + comp:complete(items, argv, completing or "") + end +end
\ No newline at end of file |
