|
10 | 10 | [clojure-mcp.tools.unified-read-file.file-timestamps :as file-timestamps] |
11 | 11 | [clojure-mcp.config :as config] |
12 | 12 | [rewrite-clj.zip :as z] |
13 | | - [rewrite-clj.parser :as p] |
14 | 13 | [clojure-mcp.linting :as linting] |
15 | 14 | [clojure-mcp.sexp.paren-utils :as paren-utils] |
16 | 15 | [clojure.spec.alpha :as s] |
|
72 | 71 | ctx |
73 | 72 | fns)) |
74 | 73 |
|
75 | | -(defn log-pipeline-ctx |
76 | | - ([message ctx] |
77 | | - (log-pipeline-ctx message ctx :debug)) |
78 | | - ([message ctx level] |
79 | | - (case level |
80 | | - :info (log/info (str "PIPELINE " message ": ") (pr-str ctx)) |
81 | | - :warn (log/warn (str "PIPELINE " message ": ") (pr-str ctx)) |
82 | | - :error (log/error (str "PIPELINE " message ": ") (pr-str ctx)) |
83 | | - (log/debug (str "PIPELINE " message ": ") (pr-str ctx))) |
84 | | - ctx)) |
85 | | - |
86 | 74 | (defn file-path->lang |
87 | 75 | "Extract language type from file path extension. |
88 | 76 | Returns :clj if file-path is nil." |
|
147 | 135 | "\nPlease read the file again before editing.")} |
148 | 136 | ctx)))) |
149 | 137 |
|
150 | | -(defn lint-code |
151 | | - "Lints the new source code to be inserted. |
152 | | - Adds ::lint-result to the context." |
153 | | - ([ctx] |
154 | | - (lint-code ctx ::new-source-code)) |
155 | | - ([ctx ky] |
156 | | - (let [lang (file-path->lang (::file-path ctx)) |
157 | | - lint-result (linting/lint (get ctx ky) {:lang lang})] |
158 | | - (if (and lint-result (:error? lint-result)) |
159 | | - {::error :lint-failure |
160 | | - ::lint-report (:report lint-result) |
161 | | - ::message (str "Syntax errors detected in Clojure code:\n" |
162 | | - (:report lint-result) |
163 | | - "\nPlease fix the syntax errors before saving.")} |
164 | | - (assoc ctx ::lint-result lint-result))))) |
165 | | - |
166 | 138 | (defn lint-repair-code |
167 | 139 | "Lints the new source code to be inserted, and attempts to fix delimiter errors. |
168 | 140 | If repair is successful, updates the source code in the context. |
|
330 | 302 | (str error-msg suggestion-msg) |
331 | 303 | error-msg)})))) |
332 | 304 |
|
333 | | -(defn edit-docstring |
334 | | - "Edits the docstring of a form. |
335 | | - Requires ::zloc, ::top-level-def-type, ::top-level-def-name, and ::docstring in the context. |
336 | | - Updates ::zloc with the edited zipper." |
337 | | - [ctx] |
338 | | - (let [result (core/edit-docstring |
339 | | - (::zloc ctx) |
340 | | - (::top-level-def-type ctx) |
341 | | - (::top-level-def-name ctx) |
342 | | - (::docstring ctx)) |
343 | | - updated-zloc (:zloc result) |
344 | | - similar-matches (:similar-matches result)] |
345 | | - (if updated-zloc |
346 | | - (assoc ctx ::zloc updated-zloc) |
347 | | - (let [error-msg (str "Could not find or edit docstring for form '" |
348 | | - (::top-level-def-name ctx) "'.") |
349 | | - suggestion-msg (format-similar-matches similar-matches)] |
350 | | - {::error true |
351 | | - ::message (if suggestion-msg |
352 | | - (str error-msg suggestion-msg) |
353 | | - error-msg)})))) |
354 | | - |
355 | | -(defn find-and-edit-comment |
356 | | - "Finds and edits a comment block. |
357 | | - Requires ::source, ::comment-substring, and ::new-content in the context. |
358 | | - Updates ::zloc with the edited zipper." |
359 | | - [ctx] |
360 | | - (let [source (::source ctx) |
361 | | - comment-substring (::comment-substring ctx) |
362 | | - new-content (::new-content ctx) |
363 | | - block (core/find-comment-block source comment-substring)] |
364 | | - (if (nil? block) |
365 | | - {::error true |
366 | | - ::message (str "Could not find comment containing: " comment-substring)} |
367 | | - (case (:type block) |
368 | | - ;; For comment forms, use zipper replacement |
369 | | - :comment-form |
370 | | - (let [zloc (:zloc block) |
371 | | - updated-zloc (z/replace zloc (p/parse-string new-content))] |
372 | | - (assoc ctx ::zloc updated-zloc)) |
373 | | - ;; For line comments, create a new zipper with the edited content |
374 | | - :line-comments |
375 | | - (let [lines (str/split-lines source) |
376 | | - start (:start block) |
377 | | - end (:end block) |
378 | | - new-lines (str/split-lines new-content) |
379 | | - replaced-lines (concat |
380 | | - (take start lines) |
381 | | - new-lines |
382 | | - (drop (inc end) lines)) |
383 | | - updated-source (str/join "\n" replaced-lines) |
384 | | - ;; Parse with track-position? to ensure positions are tracked correctly |
385 | | - updated-zloc (z/of-string updated-source {:track-position? true})] |
386 | | - (assoc ctx ::zloc updated-zloc)))))) |
387 | | - |
388 | 305 | (defn zloc->output-source |
389 | 306 | "Converts a zipper to a string output source. |
390 | 307 | Requires ::zloc in the context. |
|
608 | 525 | update-file-timestamp |
609 | 526 | highlight-form)))))) |
610 | 527 |
|
611 | | -(defn docstring-edit-pipeline |
612 | | - "Pipeline for editing a docstring in a file. |
613 | | - |
614 | | - Arguments: |
615 | | - - file-path: Path to the file containing the form |
616 | | - - form-name: Name of the form to edit |
617 | | - - form-type: Type of the form (e.g., \"defn\", \"def\") |
618 | | - - new-docstring: New docstring content |
619 | | - - nrepl-client-atom: Atom containing the nREPL client (optional) |
620 | | - - config: Optional tool configuration map |
621 | | - |
622 | | - Returns: |
623 | | - - A context map with the result of the operation" |
624 | | - [file-path form-name form-type new-docstring {:keys [nrepl-client-atom] :as config}] |
625 | | - (let [ctx {::file-path file-path |
626 | | - ::top-level-def-name form-name |
627 | | - ::top-level-def-type form-type |
628 | | - ::docstring new-docstring |
629 | | - ::edit-type :docstring |
630 | | - ::nrepl-client-atom nrepl-client-atom |
631 | | - ::config config}] |
632 | | - (thread-ctx |
633 | | - ctx |
634 | | - validate-form-type |
635 | | - emacs-buffer-modified-check |
636 | | - load-source |
637 | | - file-changes/capture-original-file-content |
638 | | - check-file-modified |
639 | | - enhance-defmethod-name |
640 | | - parse-source |
641 | | - find-form |
642 | | - edit-docstring |
643 | | - capture-edit-offsets |
644 | | - zloc->output-source |
645 | | - format-source |
646 | | - determine-file-type |
647 | | - generate-diff |
648 | | - save-file |
649 | | - update-file-timestamp |
650 | | - highlight-form))) |
651 | | - |
652 | | -(defn comment-block-edit-pipeline |
653 | | - "Pipeline for editing a comment block in a file. |
654 | | - |
655 | | - Arguments: |
656 | | - - file-path: Path to the file containing the comment |
657 | | - - comment-substring: Substring to identify the comment block |
658 | | - - new-content: New content for the comment block |
659 | | - - nrepl-client-atom: Atom containing the nREPL client (optional) |
660 | | - - config: Optional tool configuration map |
661 | | - |
662 | | - Returns: |
663 | | - - A context map with the result of the operation" |
664 | | - [file-path comment-substring new-content {:keys [nrepl-client-atom] :as config}] |
665 | | - (let [ctx {::file-path file-path |
666 | | - ::comment-substring comment-substring |
667 | | - ::new-content new-content |
668 | | - ::nrepl-client-atom nrepl-client-atom |
669 | | - ::config config}] |
670 | | - (thread-ctx |
671 | | - ctx |
672 | | - emacs-buffer-modified-check |
673 | | - load-source |
674 | | - file-changes/capture-original-file-content |
675 | | - check-file-modified |
676 | | - find-and-edit-comment |
677 | | - capture-edit-offsets |
678 | | - zloc->output-source |
679 | | - determine-file-type |
680 | | - generate-diff |
681 | | - ;; TODO this should probably be added |
682 | | - ;; #(lint-code % ::output-source) |
683 | | - |
684 | | - save-file |
685 | | - update-file-timestamp |
686 | | - highlight-form))) |
687 | | - |
688 | 528 | (defn edit-locations->offsets [ctx] |
689 | 529 | (try |
690 | 530 | (let [zloc (::zloc ctx) |
|
698 | 538 | (log/error e (str "Warning: Failed to capture edit offsets -" (ex-message e))) |
699 | 539 | ctx))) |
700 | 540 |
|
701 | | -(defn replace-sexp |
702 | | - [{:keys [::zloc ::match-form ::new-form ::replace-all ::whitespace-sensitive] :as ctx}] |
703 | | - (try |
704 | | - (if-let [result (core/find-and-edit-multi-sexp |
705 | | - zloc |
706 | | - match-form |
707 | | - new-form |
708 | | - (cond-> {:operation :replace} |
709 | | - replace-all (assoc :all? true)))] |
710 | | - (-> ctx |
711 | | - (assoc ::zloc (:zloc result)) |
712 | | - ;; not used |
713 | | - (assoc ::edit-locations (:locations result))) |
714 | | - {::error true |
715 | | - ::message (str "Could not find form: " match-form)}) |
716 | | - (catch Exception e |
717 | | - {::error true |
718 | | - ::message (str "Error replacing form: " (.getMessage e))}))) |
719 | | - |
720 | 541 | (defn edit-sexp |
721 | 542 | [{:keys [::zloc ::match-form ::new-form ::operation ::replace-all ::whitespace-sensitive] :as ctx}] |
722 | 543 | (try |
|
735 | 556 | {::error true |
736 | 557 | ::message (str "Error editing form: " (.getMessage e))}))) |
737 | 558 |
|
738 | | -(defn sexp-replace-pipeline |
739 | | - "Pipeline for replacing s-expressions in a file. |
740 | | - |
741 | | - Arguments: |
742 | | - - file-path: Path to the file containing the forms |
743 | | - - match-form: S-expression to find and replace |
744 | | - - new-form: Replacement s-expression |
745 | | - - replace-all: Whether to replace all occurrences |
746 | | - - whitespace-sensitive: Whether to match forms exactly as written |
747 | | - - nrepl-client-atom: Atom containing the nREPL client (optional) |
748 | | - - config: Optional tool configuration map |
749 | | - |
750 | | - Returns: |
751 | | - - A context map with the result of the operation" |
752 | | - [file-path match-form new-form replace-all whitespace-sensitive {:keys [nrepl-client-atom] :as config}] |
753 | | - (let [ctx {::file-path file-path |
754 | | - ::match-form match-form |
755 | | - ::new-form new-form |
756 | | - ::replace-all replace-all |
757 | | - ::whitespace-sensitive whitespace-sensitive |
758 | | - ::nrepl-client-atom nrepl-client-atom |
759 | | - ::config config}] |
760 | | - (thread-ctx |
761 | | - ctx |
762 | | - #(lint-repair-code % ::match-form) |
763 | | - #(lint-repair-code % ::new-form) |
764 | | - emacs-buffer-modified-check |
765 | | - load-source |
766 | | - file-changes/capture-original-file-content |
767 | | - check-file-modified |
768 | | - parse-source |
769 | | - replace-sexp |
770 | | - zloc->output-source |
771 | | - edit-locations->offsets |
772 | | - format-source |
773 | | - determine-file-type |
774 | | - generate-diff |
775 | | - save-file |
776 | | - update-file-timestamp |
777 | | - highlight-form))) |
778 | | - |
779 | 559 | (defn sexp-edit-pipeline |
780 | 560 | "Pipeline for editing s-expressions in a file with support for replace, insert-before, and insert-after operations. |
781 | 561 | |
|
826 | 606 |
|
827 | 607 | (comment |
828 | 608 | ;; Example usage of the pipelines |
829 | | - (sexp-replace-pipeline "test-sexp.clj" |
830 | | - "(* y y)" |
831 | | - "(+ x (* y y))" |
832 | | - false |
833 | | - false |
834 | | - {}) |
835 | | - |
836 | 609 | (def replace-result |
837 | 610 | (edit-form-pipeline "tmp/edit_file_created.clj" |
838 | 611 | "simple-fn" |
839 | 612 | "defn" |
840 | 613 | "(defn example-fn [x y]\n (* x y))" |
841 | 614 | :after |
842 | | - {:enable-emacs-notifications true})) |
843 | | - |
844 | | - (def docstring-result |
845 | | - (docstring-edit-pipeline "/path/to/file.clj" |
846 | | - "example-fn" |
847 | | - "defn" |
848 | | - "Updated docstring" |
849 | | - {})) |
850 | | - |
851 | | - (def comment-result |
852 | | - (comment-block-edit-pipeline "/path/to/file.clj" |
853 | | - "test comment" |
854 | | - ";; Updated comment" |
855 | | - {})) |
856 | | - |
857 | | - (def outline-result |
858 | | - (file-outline-pipeline "/path/to/file.clj" []))) |
| 615 | + nil |
| 616 | + {:enable-emacs-notifications true}))) |
0 commit comments