/* Copyright (c) 2016 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Gabriel Ebner, Leonardo de Moura, Sebastian Ullrich */ #if defined(LEAN_JSON) #include #include #include #include #include "util/lean_path.h" #include "util/sexpr/option_declarations.h" #include "util/bitap_fuzzy_search.h" #include "library/protected.h" #include "library/util.h" #include "library/attribute_manager.h" #include "library/scoped_ext.h" #include "library/class.h" #include "frontends/lean/completion.h" #include "frontends/lean/util.h" #ifndef LEAN_DEFAULT_AUTO_COMPLETION_MAX_RESULTS #define LEAN_DEFAULT_AUTO_COMPLETION_MAX_RESULTS 100 #endif namespace lean { static name * g_auto_completion_max_results = nullptr; unsigned get_auto_completion_max_results(options const & o) { return o.get_unsigned(*g_auto_completion_max_results, LEAN_DEFAULT_AUTO_COMPLETION_MAX_RESULTS); } #define LEAN_FUZZY_MAX_ERRORS 3 #define LEAN_FUZZY_MAX_ERRORS_FACTOR 3 /** \brief Return an (atomic) name if \c n can be referenced by this atomic name in the given environment. */ optional is_essentially_atomic(environment const & env, name const & n) { if (n.is_atomic()) return optional(n); list const & ns_list = get_namespaces(env); for (name const & ns : ns_list) { if (is_prefix_of(ns, n)) { auto n_prime = n.replace_prefix(ns, name()); if (n_prime.is_atomic() && !is_protected(env, n)) return optional(n_prime); break; } } if (auto it = is_uniquely_aliased(env, n)) if (it->is_atomic()) return it; return optional(); } unsigned get_fuzzy_match_max_errors(unsigned prefix_sz) { unsigned r = (prefix_sz / LEAN_FUZZY_MAX_ERRORS_FACTOR); if (r > LEAN_FUZZY_MAX_ERRORS) return LEAN_FUZZY_MAX_ERRORS; return r; } optional exact_prefix_match(environment const & env, std::string const & pattern, declaration const & d) { if (auto it = is_essentially_atomic(env, d.get_name())) { std::string it_str = it->to_string(); // if pattern "perfectly" matches beginning of declaration name, we just display d on the top of the list if (it_str.compare(0, pattern.size(), pattern) == 0) return it; } return optional(); } template void filter_completions(std::string const & pattern, std::vector> & selected, std::vector & completions, unsigned max_results, std::function serialize) { unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::vector> exact_matches; bitap_fuzzy_search matcher(pattern, max_errors); unsigned num_results = 0; unsigned sz = selected.size(); if (sz == 1) { completions.push_back(serialize(selected[0].second)); } else if (sz > 1) { std::sort(selected.begin(), selected.end()); auto it = std::unique(selected.begin(), selected.end(), [](pair const & s1, pair const & s2) { return s1.first == s2.first; }); selected.resize(it - selected.begin()); std::vector> next_selected; auto process = [&](pair const & s, bool select) { if (select) { completions.push_back(serialize(s.second)); num_results++; if (num_results >= max_results) return false; } else { next_selected.push_back(s); } return true; }; // 1. exact prefix matches for (auto const & s : selected) { if (!process(s, s.first.compare(0, pattern.size(), pattern) == 0)) break; } std::swap(selected, next_selected); next_selected.clear(); // 2. fuzzy matches by increasing error count for (unsigned k = 0; k <= max_errors && num_results < max_results; k++) { bitap_fuzzy_search matcher(pattern, k); for (auto const & s : selected) { if (!process(s, matcher.match(s.first))) break; } std::swap(selected, next_selected); next_selected.clear(); } } } std::vector get_decl_completions(std::string const & pattern, environment const & env, options const & opts) { std::vector completions; unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::vector> exact_matches; std::vector> selected; bitap_fuzzy_search matcher(pattern, max_errors); env.for_each_declaration([&](declaration const & d) { if (is_projection(env, d.get_name())) { auto s_name = d.get_name().get_prefix(); if (is_class(env, s_name)) return; } if (is_internal_name(d.get_name())) { return; } if (auto it = exact_prefix_match(env, pattern, d)) { exact_matches.emplace_back(*it, d.get_name()); } else { std::string text = d.get_name().to_string(); if (matcher.match(text)) selected.emplace_back(text, d.get_name()); } }); unsigned num_results = 0; if (!exact_matches.empty()) { std::sort(exact_matches.begin(), exact_matches.end(), [](pair const & p1, pair const & p2) { return p1.first.size() < p2.first.size(); }); for (pair const & p : exact_matches) { completions.push_back(serialize_decl(p.first, p.second, env, opts)); num_results++; if (num_results >= max_results) break; } } filter_completions(pattern, selected, completions, max_results - num_results, [&](name const & n) { return serialize_decl(n, env, opts); }); return completions; } void search_decls(std::string const & pattern, std::vector> const & envs, options const & opts, std::vector & completions) { unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); name_map> name2env; std::vector> exact_matches; std::vector> selected; bitap_fuzzy_search matcher(pattern, max_errors); for (auto & env_file : envs) { auto & env = env_file.second; env.for_each_declaration([&](declaration const & d) { if (name2env.find(d.get_name())) return; name2env.insert(d.get_name(), env_file); if (is_projection(env, d.get_name())) { auto s_name = d.get_name().get_prefix(); } if (is_internal_name(d.get_name())) { return; } if (auto it = exact_prefix_match(env, pattern, d)) { exact_matches.emplace_back(*it, d.get_name()); } else { std::string text = d.get_name().to_string(); if (matcher.match(text)) selected.emplace_back(text, d.get_name()); } }); } unsigned num_results = 0; if (!exact_matches.empty()) { std::sort(exact_matches.begin(), exact_matches.end(), [](pair const & p1, pair const & p2) { return p1.first.size() < p2.first.size(); }); for (pair const & p : exact_matches) { auto j = serialize_decl(p.first, p.second, name2env.find(p.second)->second, opts); if (!j["source"].count("file")) j["source"]["file"] = name2env.find(p.second)->first; completions.push_back(j); num_results++; if (num_results >= max_results) break; } } filter_completions(pattern, selected, completions, max_results - num_results, [&](name const & n) { auto j = serialize_decl(n, name2env.find(n)->second, opts); if (!j["source"].count("file")) j["source"]["file"] = name2env.find(n)->first; return j; }); } std::vector get_field_completions(name const & s, std::string const & pattern, environment const & env, options const & opts) { std::vector completions; unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::string new_pattern; new_pattern = s.to_string(); if (!pattern.empty()) { new_pattern += "."; new_pattern += pattern; } std::vector> exact_matches; std::vector> selected; bitap_fuzzy_search matcher(new_pattern, max_errors); env.for_each_declaration([&](declaration const & d) { if (d.get_name() == s || !is_prefix_of(s, d.get_name()) || is_internal_name(d.get_name())) { return; } if (auto it = exact_prefix_match(env, new_pattern, d)) { exact_matches.emplace_back(*it, d.get_name()); } else { std::string text = d.get_name().to_string(); if (matcher.match(text)) selected.emplace_back(text, d.get_name()); } }); unsigned num_results = 0; if (!exact_matches.empty()) { std::sort(exact_matches.begin(), exact_matches.end(), [](pair const & p1, pair const & p2) { return p1.first.size() < p2.first.size(); }); for (pair const & p : exact_matches) { name fname = p.second.replace_prefix(s, name()); completions.push_back(serialize_decl(fname, p.second, env, opts)); num_results++; if (num_results >= max_results) break; } } filter_completions(pattern, selected, completions, max_results - num_results, [&](name const & n) { name fname = n.replace_prefix(s, name()); return serialize_decl(fname, n, env, opts); }); return completions; } std::vector get_option_completions(std::string const & pattern, options const & opts) { unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::vector> selected; bitap_fuzzy_search matcher(pattern, max_errors); std::vector completions; get_option_declarations().for_each([&](name const & n, option_declaration const &) { std::string text = n.to_string(); if (matcher.match(text)) selected.emplace_back(text, n); }); filter_completions(pattern, selected, completions, max_results, [&](name const & n) { json completion; completion["text"] = n.to_string(); std::stringstream ss; auto const & decl = *get_option_declarations().find(n); decl.display_value(ss, opts); completion["type"] = ss.str(); completion["doc"] = decl.get_description(); return completion; }); return completions; } pair, std::string> parse_import(std::string s) { if (s.size() && s[0] == '.') { unsigned i = 1; while (i < s.size() && s[i] == '.') i++; return {some(i - 1), s.substr(i)}; } return {{}, s}; } std::vector get_import_completions(std::string const & pattern, std::string const & curr_dir, search_path const & path, options const & opts) { unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::vector>> selected; bitap_fuzzy_search matcher(pattern, max_errors); std::vector completions; optional depth = parse_import(pattern).first; std::vector> imports; find_imports(path, curr_dir, depth, imports); for (auto const & candidate : imports) { if (matcher.match(candidate.first)) selected.emplace_back(candidate.first, candidate); } filter_completions>(pattern, selected, completions, max_results, [&](pair const & c) { json completion; completion["text"] = c.first; completion["type"] = c.second; completion["source"]["file"] = c.second; completion["source"]["line"] = 1; completion["source"]["column"] = 0; return completion; }); return completions; } std::vector get_interactive_tactic_completions(std::string const & pattern, name const & tac_class, environment const & env, options const & opts) { std::vector completions; unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::vector> selected; bitap_fuzzy_search matcher(pattern, max_errors); name namespc = tac_class + name("interactive"); env.for_each_declaration([&](declaration const & d) { auto const & n = d.get_name(); if (n.get_prefix() == namespc && n.is_string() && matcher.match(n.get_string())) { selected.emplace_back(n.get_string(), n); } }); filter_completions(pattern, selected, completions, max_results, [&](name const & n) { return serialize_decl(n.get_string(), n, env, opts); }); return completions; } std::vector get_attribute_completions(std::string const & pattern, environment const & env, options const & opts) { unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::vector> selected; bitap_fuzzy_search matcher(pattern, max_errors); std::vector completions; buffer attrs; get_attributes(env, attrs); for (auto const & attr : attrs) { if (!is_internal_name(attr->get_name())) { auto s = attr->get_name().to_string(); if (matcher.match(s)) selected.emplace_back(s, attr->get_name()); } } filter_completions(pattern, selected, completions, max_results, [&](name const & n) { json completion; completion["text"] = n.to_string(); completion["doc"] = get_attribute(env, n).get_description(); add_source_info(env, n, completion); return completion; }); return completions; } std::vector get_namespace_completions(std::string const & pattern, environment const & env, options const & opts) { unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::vector> selected; bitap_fuzzy_search matcher(pattern, max_errors); std::vector completions; for (auto const & ns : get_namespace_completion_candidates(env)) { if (ns.is_anonymous()) continue; auto s = ns.to_string(); if (matcher.match(s)) selected.emplace_back(s, ns); } filter_completions(pattern, selected, completions, max_results, [&](name const & n) { json completion; completion["text"] = n.to_string(); return completion; }); return completions; } void initialize_completion() { g_auto_completion_max_results = new name{"auto_completion", "max_results"}; register_unsigned_option(*g_auto_completion_max_results, LEAN_DEFAULT_AUTO_COMPLETION_MAX_RESULTS, "(auto-completion) maximum number of results returned"); } void finalize_completion() { delete g_auto_completion_max_results; } } #endif