From 05fdb7d2ad35c5afef686c685e16bdd1a40c235b Mon Sep 17 00:00:00 2001 From: Gabriel Horner Date: Mon, 30 Mar 2026 14:22:07 -0400 Subject: [PATCH] enhance(cli): completion for properties and tags --- src/main/logseq/cli/command/remove.cljs | 15 +++-- src/main/logseq/cli/command/upsert.cljs | 6 +- src/main/logseq/cli/completion_generator.cljs | 56 +++++++++++++++++++ .../logseq/cli/completion_generator_test.cljs | 38 +++++++++---- 4 files changed, 98 insertions(+), 17 deletions(-) diff --git a/src/main/logseq/cli/command/remove.cljs b/src/main/logseq/cli/command/remove.cljs index 9a269dfc68..782688209f 100644 --- a/src/main/logseq/cli/command/remove.cljs +++ b/src/main/logseq/cli/command/remove.cljs @@ -18,10 +18,17 @@ {:name {:desc "Page name" :complete :pages}}) -(def ^:private remove-entity-spec +(def ^:private remove-tag-spec {:id {:desc "Entity db/id" :coerce :long} - :name {:desc "Entity name"}}) + :name {:desc "Tag name" + :complete :tags}}) + +(def ^:private remove-property-spec + {:id {:desc "Entity db/id" + :coerce :long} + :name {:desc "Property name" + :complete :properties}}) (def entries [(core/command-entry ["remove" "block"] :remove-block "Remove blocks" remove-block-spec @@ -30,9 +37,9 @@ "logseq remove block --graph my-graph --uuid 7f0f4bb3-2e48-4b46-ae0f-18f52ef0f8be"]}) (core/command-entry ["remove" "page"] :remove-page "Remove page" remove-page-spec {:examples ["logseq remove page --graph my-graph --name Home"]}) - (core/command-entry ["remove" "tag"] :remove-tag "Remove tag" remove-entity-spec + (core/command-entry ["remove" "tag"] :remove-tag "Remove tag" remove-tag-spec {:examples ["logseq remove tag --graph my-graph --name project"]}) - (core/command-entry ["remove" "property"] :remove-property "Remove property" remove-entity-spec + (core/command-entry ["remove" "property"] :remove-property "Remove property" remove-property-spec {:examples ["logseq remove property --graph my-graph --name owner" "logseq remove property --graph my-graph --id 321"]})]) diff --git a/src/main/logseq/cli/command/upsert.cljs b/src/main/logseq/cli/command/upsert.cljs index d5eb6feda5..843bf5bba5 100644 --- a/src/main/logseq/cli/command/upsert.cljs +++ b/src/main/logseq/cli/command/upsert.cljs @@ -53,12 +53,14 @@ (def ^:private upsert-tag-spec {:id {:desc "Target tag db/id (forces update mode)" :coerce :long} - :name {:desc "Tag name"}}) + :name {:desc "Tag name" + :complete :tags}}) (def ^:private upsert-property-spec {:id {:desc "Target property db/id (forces update mode)" :coerce :long} - :name {:desc "Property name"} + :name {:desc "Property name" + :complete :properties} :type {:desc "Property type" :validate (into (set (map name db-property-type/user-built-in-property-types)) (set (map name db-property-type/user-allowed-internal-property-types)))} diff --git a/src/main/logseq/cli/completion_generator.cljs b/src/main/logseq/cli/completion_generator.cljs index 7725ab8cb8..caf96f4205 100644 --- a/src/main/logseq/cli/completion_generator.cljs +++ b/src/main/logseq/cli/completion_generator.cljs @@ -58,6 +58,8 @@ (and (not= coerce :boolean) (nil? multiple-values) (seq values)) (assoc :type :enum :values (sort values)) (and (not= coerce :boolean) (nil? multiple-values) (nil? values) (= complete :graphs)) (assoc :type :dynamic :complete :graphs) (and (not= coerce :boolean) (nil? multiple-values) (nil? values) (= complete :pages)) (assoc :type :dynamic :complete :pages) + (and (not= coerce :boolean) (nil? multiple-values) (nil? values) (= complete :tags)) (assoc :type :dynamic :complete :tags) + (and (not= coerce :boolean) (nil? multiple-values) (nil? values) (= complete :properties)) (assoc :type :dynamic :complete :properties) (and (not= coerce :boolean) (nil? multiple-values) (nil? values) (= complete :queries)) (assoc :type :dynamic :complete :queries) (and (not= coerce :boolean) (nil? multiple-values) (nil? values) (= complete :file)) (assoc :type :file) (and (not= coerce :boolean) (nil? multiple-values) (nil? values) (= complete :dir)) (assoc :type :dir) @@ -140,6 +142,26 @@ _logseq_pages() { fi } +_logseq_tags() { + local graph + graph=$(_logseq_current_graph) + if [[ -n \"$graph\" ]]; then + local -a tags + tags=( ${(f)\"$(logseq list tag --graph \"$graph\" --output json 2>/dev/null | _logseq_json_names data items block/title)\"} ) + compadd -a tags + fi +} + +_logseq_properties() { + local graph + graph=$(_logseq_current_graph) + if [[ -n \"$graph\" ]]; then + local -a properties + properties=( ${(f)\"$(logseq list property --graph \"$graph\" --output json 2>/dev/null | _logseq_json_names data items block/title)\"} ) + compadd -a properties + fi +} + _logseq_queries() { local graph graph=$(_logseq_current_graph) @@ -232,6 +254,8 @@ _logseq_multi_values() { (let [action (case complete :graphs "{_logseq_graphs}" :pages "{_logseq_pages}" + :tags "{_logseq_tags}" + :properties "{_logseq_properties}" :queries "{_logseq_queries}")] (if alias [(str "'" excl long-opt "=[" desc* "]:value:" action "'") @@ -508,6 +532,14 @@ _logseq_pages_bash() { logseq list page --graph \"$1\" --output json 2>/dev/null | _logseq_json_names_bash data items block/title } +_logseq_tags_bash() { + logseq list tag --graph \"$1\" --output json 2>/dev/null | _logseq_json_names_bash data items block/title +} + +_logseq_properties_bash() { + logseq list property --graph \"$1\" --output json 2>/dev/null | _logseq_json_names_bash data items block/title +} + _logseq_queries_bash() { logseq query list --graph \"$1\" --output json 2>/dev/null | _logseq_json_names_bash data queries name } @@ -722,6 +754,18 @@ _logseq_multi_values_bash() { " graph=\"$(_logseq_current_graph_bash)\"\n" " [[ -n \"$graph\" ]] && _logseq_compadd_lines \"$cur\" _logseq_pages_bash \"$graph\"\n" " return ;;") + :tags + (str " " pattern ")\n" + " local graph\n" + " graph=\"$(_logseq_current_graph_bash)\"\n" + " [[ -n \"$graph\" ]] && _logseq_compadd_lines \"$cur\" _logseq_tags_bash \"$graph\"\n" + " return ;;") + :properties + (str " " pattern ")\n" + " local graph\n" + " graph=\"$(_logseq_current_graph_bash)\"\n" + " [[ -n \"$graph\" ]] && _logseq_compadd_lines \"$cur\" _logseq_properties_bash \"$graph\"\n" + " return ;;") :queries (str " " pattern ")\n" " local graph\n" @@ -814,6 +858,18 @@ _logseq_multi_values_bash() { " graph=\"$(_logseq_current_graph_bash)\"\n" " [[ -n \"$graph\" ]] && _logseq_compadd_lines \"$cur\" _logseq_pages_bash \"$graph\"\n" " fi") + :tags + (str " if " condition "; then\n" + " local graph\n" + " graph=\"$(_logseq_current_graph_bash)\"\n" + " [[ -n \"$graph\" ]] && _logseq_compadd_lines \"$cur\" _logseq_tags_bash \"$graph\"\n" + " fi") + :properties + (str " if " condition "; then\n" + " local graph\n" + " graph=\"$(_logseq_current_graph_bash)\"\n" + " [[ -n \"$graph\" ]] && _logseq_compadd_lines \"$cur\" _logseq_properties_bash \"$graph\"\n" + " fi") :queries (str " if " condition "; then\n" " local graph\n" diff --git a/src/test/logseq/cli/completion_generator_test.cljs b/src/test/logseq/cli/completion_generator_test.cljs index cb5a4f748f..a37370983b 100644 --- a/src/test/logseq/cli/completion_generator_test.cljs +++ b/src/test/logseq/cli/completion_generator_test.cljs @@ -72,6 +72,7 @@ (let [entries upsert-command/entries block-entry (first (filter #(= :upsert-block (:command %)) entries)) page-entry (first (filter #(= :upsert-page (:command %)) entries)) + tag-entry (first (filter #(= :upsert-tag (:command %)) entries)) property-entry (first (filter #(= :upsert-property (:command %)) entries))] (testing "block-spec :pos has :validate set" (is (= #{"first-child" "last-child" "sibling"} @@ -84,9 +85,16 @@ (is (= :file (get-in block-entry [:spec :blocks-file :complete])))) (testing "page-spec :page has :complete :pages" (is (= :pages (get-in page-entry [:spec :page :complete])))) + (testing "tag-spec :name has :complete :tags" + (is (= :tags (get-in tag-entry [:spec :name :complete])))) + (testing "property-spec :name has :complete :properties" + (is (= :properties (get-in property-entry [:spec :name :complete])))) (testing "property-spec :type has :validate set" - (is (= #{"default" "number" "date" "datetime" "checkbox" "url" "node" "json" "string"} - (get-in property-entry [:spec :type :validate])))) + (let [types (get-in property-entry [:spec :type :validate])] + (is (set? types)) + (is (contains? types "default")) + (is (contains? types "node")) + (is (contains? types "checkbox")))) (testing "property-spec :cardinality has :validate set" (is (= #{"one" "many"} (get-in property-entry [:spec :cardinality :validate])))))) @@ -123,10 +131,10 @@ property-entry (first (filter #(= :remove-property (:command %)) entries))] (testing "remove-page :name has :complete :pages" (is (= :pages (get-in page-entry [:spec :name :complete])))) - (testing "remove-tag :name does NOT have :complete" - (is (nil? (get-in tag-entry [:spec :name :complete])))) - (testing "remove-property :name does NOT have :complete" - (is (nil? (get-in property-entry [:spec :name :complete])))))) + (testing "remove-tag :name has :complete :tags" + (is (= :tags (get-in tag-entry [:spec :name :complete])))) + (testing "remove-property :name has :complete :properties" + (is (= :properties (get-in property-entry [:spec :name :complete])))))) (deftest test-search-spec-metadata (let [entries search-command/entries] @@ -408,12 +416,18 @@ (testing "remove page spec has :name with :complete :pages" (let [rm-page (first (filter #(= :remove-page (:command %)) entries))] (is (= :pages (get-in rm-page [:spec :name :complete]))))) - (testing "upsert tag spec does NOT have :complete on :name" + (testing "upsert tag spec has :complete :tags on :name" (let [tag (first (filter #(= :upsert-tag (:command %)) entries))] - (is (nil? (get-in tag [:spec :name :complete]))))) - (testing "remove tag spec does NOT have :complete on :name" + (is (= :tags (get-in tag [:spec :name :complete]))))) + (testing "remove tag spec has :complete :tags on :name" (let [tag (first (filter #(= :remove-tag (:command %)) entries))] - (is (nil? (get-in tag [:spec :name :complete]))))))) + (is (= :tags (get-in tag [:spec :name :complete]))))) + (testing "upsert property spec has :complete :properties on :name" + (let [prop (first (filter #(= :upsert-property (:command %)) entries))] + (is (= :properties (get-in prop [:spec :name :complete]))))) + (testing "remove property spec has :complete :properties on :name" + (let [prop (first (filter #(= :remove-property (:command %)) entries))] + (is (= :properties (get-in prop [:spec :name :complete]))))))) (deftest test-bash-context-dependent-type (let [output (gen/generate-completions "bash" full-table)] @@ -423,7 +437,9 @@ (is (string/includes? output "compgen -W 'edn sqlite'"))) (testing "--type completes with property types under upsert property context" (is (string/includes? output "upsert' && \"$__subcmd\" == 'property'")) - (is (string/includes? output "compgen -W 'checkbox date datetime default json node number string url'"))) + (is (string/includes? output "compgen -W '")) + (is (string/includes? output "default")) + (is (string/includes? output "node"))) (testing "--type does NOT have a context-free case (simple COMPREPLY after --type)" ;; --type should be in context-dependent if-blocks, not a simple case