Skip to content

Commit fa222cc

Browse files
authored
Merge pull request #317 from tninja/kang_feat
Feat: Support Org TODO headlines in implement-todo
2 parents d5b59c5 + 9f45724 commit fa222cc

2 files changed

Lines changed: 147 additions & 3 deletions

File tree

ai-code-change.el

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
(require 'cl-lib)
1414
(require 'magit)
1515
(require 'flycheck nil t)
16+
(require 'org)
1617

1718
(require 'ai-code-input)
1819
(require 'ai-code-prompt-mode)
@@ -24,6 +25,8 @@
2425
(declare-function ai-code--get-git-relative-paths "ai-code-discussion")
2526
(declare-function ai-code--get-region-location-info "ai-code-discussion")
2627
(declare-function ai-code--format-repo-context-info "ai-code-file")
28+
(declare-function dired-get-marked-files "dired"
29+
(&optional localp arg filter distinguish-one-marked error-if-none-p))
2730
(declare-function flycheck-error-pos "flycheck")
2831
(declare-function flycheck-error-line "flycheck")
2932
(declare-function flycheck-error-column "flycheck")
@@ -262,6 +265,7 @@ line, with proper indentation. If cursor is inside a function, implement
262265
comments for that function.
263266
Otherwise implement comments for the entire current file.
264267
Argument ARG is the prefix argument."
268+
;; DONE: I want to implement the idea inside https://github.com/tninja/ai-code-interface.el/issues/316, it could to either code change or ask question, given user's input with completing-read selection. The difference of this org-mode section TODO, with the existing comment todo is, it won't replace the TODO section with implementation. It just use the section headline and content inside this section as part of prompt, and send to AI.
265269
(interactive "P")
266270
(if (not buffer-file-name)
267271
(user-error "Error: buffer-file-name must be available")
@@ -330,6 +334,50 @@ Returns non-nil if handled and the caller should exit."
330334
(indent-according-to-mode)))
331335
t))
332336

337+
(defun ai-code--implement-todo--get-org-todo-section-info ()
338+
"Return current Org TODO section info as a plist, or nil.
339+
The plist contains `:heading-line', `:content', and `:line-number'."
340+
(when (derived-mode-p 'org-mode)
341+
(save-excursion
342+
(when (and (org-at-heading-p)
343+
(ignore-errors (org-back-to-heading t)))
344+
(let* ((line-number (line-number-at-pos (point)))
345+
(heading-line (buffer-substring-no-properties
346+
(line-beginning-position)
347+
(line-end-position)))
348+
(todo-state (org-get-todo-state))
349+
(todo-prefix-p
350+
(ai-code--implement-todo--org-todo-headline-p heading-line)))
351+
(when (and (or todo-state todo-prefix-p)
352+
(not (org-entry-is-done-p)))
353+
(let* ((content-start (save-excursion
354+
(forward-line 1)
355+
(point)))
356+
(content-end (save-excursion
357+
(org-end-of-subtree t t)
358+
(point)))
359+
(content (string-trim-right
360+
(buffer-substring-no-properties
361+
content-start
362+
content-end))))
363+
(list :heading-line heading-line
364+
:content content
365+
:line-number line-number))))))))
366+
367+
(defun ai-code--implement-todo--format-org-section-block (org-todo-section-info)
368+
"Return formatted Org section text from ORG-TODO-SECTION-INFO."
369+
(when org-todo-section-info
370+
(let ((heading-line (plist-get org-todo-section-info :heading-line))
371+
(content (plist-get org-todo-section-info :content)))
372+
(concat heading-line
373+
(unless (string-blank-p content)
374+
(concat "\n" content))))))
375+
376+
(defun ai-code--implement-todo--org-todo-headline-p (heading-line)
377+
"Return non-nil when HEADING-LINE begins with a TODO-style Org headline."
378+
(string-match-p "^[[:space:]]*\\*+[[:space:]]+TODO:?\\(?:[[:space:]]\\|$\\)"
379+
heading-line))
380+
333381
(defun ai-code--implement-todo--build-and-send-prompt (arg)
334382
"Build the TODO implementation prompt and insert it.
335383
ARG is the prefix argument for clipboard context."
@@ -341,6 +389,11 @@ ARG is the prefix argument for clipboard context."
341389
(function-name (if is-comment
342390
(ai-code--get-function-name-for-comment)
343391
(which-function)))
392+
(org-todo-section-info (ai-code--implement-todo--get-org-todo-section-info))
393+
(org-line-number (plist-get org-todo-section-info :line-number))
394+
(org-section-block
395+
(ai-code--implement-todo--format-org-section-block
396+
org-todo-section-info))
344397
(function-context (if function-name
345398
(format "\nFunction: %s" function-name)
346399
""))
@@ -366,8 +419,8 @@ ARG is the prefix argument for clipboard context."
366419
(region-comment-block-p (or (not region-text)
367420
(ai-code--is-comment-block region-text)))
368421
;; Validate scenario before prompting user
369-
(_ (unless (or region-text is-comment)
370-
(user-error "Current line is not a TODO comment and cannot proceed with `ai-code-implement-todo'. Please select a TODO comment (not DONE), a region of comments, or activate on a blank line")))
422+
(_ (unless (or org-todo-section-info region-text is-comment)
423+
(user-error "Current line is not a TODO comment or Org TODO headline and cannot proceed with `ai-code-implement-todo'. Please select a TODO comment (not DONE), an Org TODO headline, a region of comments, or activate on a blank line")))
371424
(_ (unless region-comment-block-p
372425
(user-error "Selected region must be a comment block")))
373426
(action-intent (completing-read "Select action: "
@@ -376,29 +429,45 @@ ARG is the prefix argument for clipboard context."
376429
(ask-question-p (string= action-intent "Ask question"))
377430
(prompt-label
378431
(cond
432+
((and ask-question-p org-todo-section-info)
433+
(if (and clipboard-context (string-match-p "\\S-" clipboard-context))
434+
"Question about Org TODO headline (clipboard context): "
435+
"Question about Org TODO headline: "))
379436
(ask-question-p
380437
(if (and clipboard-context (string-match-p "\\S-" clipboard-context))
381438
"Question about TODO comment (clipboard context): "
382439
"Question about TODO comment: "))
440+
((and org-todo-section-info
441+
clipboard-context
442+
(string-match-p "\\S-" clipboard-context))
443+
"TODO implementation instruction for Org TODO headline (clipboard context): ")
383444
((and clipboard-context
384445
(string-match-p "\\S-" clipboard-context))
385446
(cond
386447
(region-text "TODO implementation instruction (clipboard context): ")
387448
(is-comment "TODO implementation instruction (clipboard context): ")
388449
(function-name (format "TODO implementation instruction for function %s (clipboard context): " function-name))
389450
(t "TODO implementation instruction (clipboard context): ")))
451+
(org-todo-section-info "TODO implementation instruction for Org TODO headline: ")
390452
(region-text "TODO implementation instruction: ")
391453
(is-comment "TODO implementation instruction: ")
392454
(function-name (format "TODO implementation instruction for function %s: " function-name))
393455
(t "TODO implementation instruction: ")))
394456
(initial-input
395457
(cond
458+
((and ask-question-p org-todo-section-info)
459+
(format "Regarding this Org TODO headline on line %d:\n%s%s%s"
460+
org-line-number org-section-block function-context files-context-string))
396461
((and ask-question-p region-text)
397462
(format "Regarding this TODO comment block in the selected region:\n%s\n%s%s%s"
398463
region-location-line region-text function-context files-context-string))
399464
((and ask-question-p is-comment)
400465
(format "Regarding this TODO comment on line %d: '%s'%s%s"
401466
current-line-number current-line function-context files-context-string))
467+
(org-todo-section-info
468+
(format "Please implement code for this Org TODO headline first. After implementing, keep the Org TODO headline in place and use the headline and content as prompt context.\nLine %d:\n%s%s%s"
469+
org-line-number org-section-block function-context
470+
files-context-string))
402471
(region-text
403472
(format
404473
"Please implement code for this requirement comment block in the selected region first. After implementing, keep the comment in place and ensure it begins with a DONE prefix (change TODO to DONE or prepend DONE if no prefix). If this is a pure new code block, place it after the comment; otherwise keep the existing structure and make corresponding change for the context.\n%s\n%s%s%s"

test/test_ai-code-change.el

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ is between the function definition and its body."
339339
(should (looking-at-p " ;; TODO: indented task"))))))
340340

341341
(ert-deftest ai-code-test-implement-todo-action-choice-is-presented ()
342-
"Test that build-and-send-prompt presents action choice via completing-read."
342+
"Test that build-and-send-prompt presents action choice via `completing-read'."
343343
(with-temp-buffer
344344
(setq buffer-file-name "test.el")
345345
(setq-local comment-start ";")
@@ -506,6 +506,81 @@ is between the function definition and its body."
506506
(should (string-match-p "[Qq]uestion" captured-label))
507507
(should-not (string-match-p "implementation" captured-label))))))
508508

509+
(ert-deftest ai-code-test-ai-code-implement-todo-org-section-includes-heading-and-content ()
510+
"Test Org TODO section is used as prompt context without requiring comment syntax."
511+
(with-temp-buffer
512+
(require 'org)
513+
(setq buffer-file-name "todo.org")
514+
(insert "* TODO Build backend switcher\n")
515+
(insert "Use Codex for implementation.\n")
516+
(insert "Keep the UI untouched.\n")
517+
(org-mode)
518+
(goto-char (point-min))
519+
520+
(let (captured-prompt)
521+
(cl-letf (((symbol-function 'completing-read)
522+
(lambda (&rest _) "Code change"))
523+
((symbol-function 'ai-code-read-string)
524+
(lambda (_label input) input))
525+
((symbol-function 'ai-code--get-clipboard-text) (lambda () nil))
526+
((symbol-function 'ai-code--get-context-files-string) (lambda () ""))
527+
((symbol-function 'ai-code--format-repo-context-info) (lambda () ""))
528+
((symbol-function 'which-function) (lambda () nil))
529+
((symbol-function 'region-active-p) (lambda () nil))
530+
((symbol-function 'ai-code--insert-prompt)
531+
(lambda (prompt) (setq captured-prompt prompt))))
532+
533+
(ai-code-implement-todo nil)
534+
535+
(should (stringp captured-prompt))
536+
(should (string-match-p "Please implement code" captured-prompt))
537+
(should (string-match-p "\\* TODO Build backend switcher" captured-prompt))
538+
(should (string-match-p "Use Codex for implementation\\." captured-prompt))
539+
(should (string-match-p "Keep the UI untouched\\." captured-prompt))))))
540+
541+
(ert-deftest ai-code-test-ai-code-implement-todo-org-body-line-is-not-headline ()
542+
"Test Org body lines do not count as the TODO entry for implementation."
543+
(with-temp-buffer
544+
(require 'org)
545+
(setq buffer-file-name "todo.org")
546+
(insert "** TODO my task description\n")
547+
(insert "Supporting details live here.\n")
548+
(org-mode)
549+
(goto-char (point-min))
550+
(forward-line 1)
551+
552+
(cl-letf (((symbol-function 'region-active-p) (lambda () nil)))
553+
(should-error (ai-code-implement-todo nil) :type 'user-error))))
554+
555+
(ert-deftest ai-code-test-ai-code-implement-todo-org-headline-with-colon-prefix ()
556+
"Test Org headline with `TODO:' prefix is accepted."
557+
(with-temp-buffer
558+
(require 'org)
559+
(setq buffer-file-name "todo.org")
560+
(insert "** TODO: what is the most important verse in Bible\n")
561+
(org-mode)
562+
(goto-char (point-min))
563+
564+
(let (captured-prompt)
565+
(cl-letf (((symbol-function 'completing-read)
566+
(lambda (&rest _) "Ask question"))
567+
((symbol-function 'ai-code-read-string)
568+
(lambda (_label input) input))
569+
((symbol-function 'ai-code--get-clipboard-text) (lambda () nil))
570+
((symbol-function 'ai-code--get-context-files-string) (lambda () ""))
571+
((symbol-function 'ai-code--format-repo-context-info) (lambda () ""))
572+
((symbol-function 'which-function) (lambda () nil))
573+
((symbol-function 'region-active-p) (lambda () nil))
574+
((symbol-function 'ai-code--insert-prompt)
575+
(lambda (prompt) (setq captured-prompt prompt))))
576+
577+
(ai-code-implement-todo nil)
578+
579+
(should (stringp captured-prompt))
580+
(should (string-match-p "Regarding this Org TODO headline" captured-prompt))
581+
(should (string-match-p "\\*\\* TODO: what is the most important verse in Bible"
582+
captured-prompt))))))
583+
509584
(provide 'test_ai-code-change)
510585

511586
;;; test_ai-code-change.el ends here

0 commit comments

Comments
 (0)