Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inlay hints #14

Open
wants to merge 5 commits into
base: steel-event-system
Choose a base branch
from

Conversation

merisbahti
Copy link

Suggestion for an interface for displaying "inline ghost text" from the scripting engine.

Tested with the following plugin (make sure you've an env variable called OPENAI_API_KEY.)

(require (prefix-in helix. "helix/commands.scm"))
(require (prefix-in helix.static. "helix/static.scm"))
(require "helix/misc.scm")
(require "component.scm")
(require "helix/editor.scm")
(require-builtin helix/components)
(require-builtin helix/core/text as text.)
(require "cogs/helix-ext.scm")

(define (run-cmd cmd . args)
  (let
    ([builder (command cmd args)])
    (set-piped-stdout! builder)
    (~> builder spawn-process Ok->value wait->stdout Ok->value)))
(define api-key (trim (run-cmd "printenv" "OPENAI_API_KEY")))
(define (get-document-as-slice)
  (let* ([focus (editor-focus)]
         [focus-doc-id (editor->doc-id focus)])
    (text.rope->string (editor->text focus-doc-id))))
(define openapi-server "https://api.openai.com/v1/chat/completions")
(define (make-request code-with-cursor callback)
  (spawn-native-thread
    (fn ()
      (define output
        (run-cmd
          "curl"
          openapi-server
          "-d"
          (value->jsexpr-string
            (hash
              'model
              "gpt-4o-mini"
              'messages
              (list
                (hash
                  'role
                  "user"
                  'content
                  (string-join
                    (list "Only answer in code without wrapping into markdown, what would you put at the position of <CURSOR>, don't include text before or after <CURSOR>:
"
                      code-with-cursor))))
              'prediction
              (hash 'type "content" 'content code-with-cursor)
              'temperature
              0.1))
          "-H"
          "Content-Type: application/json"
          "-H"
          (string-join (list "Authorization: Bearer " api-key))))
      (define parsed-output
        (~>
          (string->jsexpr output)
          (hash-ref 'choices)
          (list-ref 0)
          (hash-ref 'message)
          (hash-ref 'content)))
      (hx.with-context
        (fn ()
          (callback parsed-output))))))

(define *latest-inlay-hint* (None))
(provide ask-prompt)

;; accepts the current ai generated hint
(provide accept-current-hint)
(define (accept-current-hint)
  (when (not (None? *latest-inlay-hint*))
    (helix.static.insert_string (cadr *latest-inlay-hint*))
    (clear-inlay-hint)))

(define (clear-inlay-hint)
  (when (not (None? *latest-inlay-hint*))
    (apply remove-inlay-hint (cons *helix.cx* *latest-inlay-hint*))
    (set! *latest-inlay-hint* (None))))

(define *latest-debounce-id* 0) ;; each character should'nt send a request
(define (ask-prompt)
  (define current-debounce-id (+ 1 *latest-debounce-id*))
  (set! *latest-debounce-id* current-debounce-id)
  (define doc (get-document-as-slice))
  (define cursor-pos (hx.cx->pos))
  (define code-with-cursor
    (string-join
      (list
        (substring doc 0 cursor-pos)
        "<CURSOR>"
        (substring doc cursor-pos))))
  (enqueue-thread-local-callback-with-delay 300
    (fn ()
      (when
        (not (equal? current-debounce-id *latest-debounce-id*))
        (return! void))
      (make-request code-with-cursor
        (fn (x)
          (when (not (equal? current-debounce-id *latest-debounce-id*)) (return! void))
          (hx.with-context
            (fn ()
              (clear-inlay-hint)
              (set! *latest-inlay-hint* (list cursor-pos x))
              (add-inlay-hint *helix.cx* cursor-pos x))))))))
(register-hook! "post-command"
  (fn (_)
    (displayln *latest-inlay-hint*)
    (clear-inlay-hint)))
(register-hook! "post-insert-char"
  (fn (_)
    (displayln *latest-inlay-hint* _)
    (clear-inlay-hint)
    (ask-prompt)))
(register-hook! "on-mode-switch"
  (fn (_)
    (set! *latest-debounce-id* (+ 1 *latest-debounce-id*))
    (clear-inlay-hint)))

@merisbahti merisbahti changed the title wip inlay hints inlay hints Dec 24, 2024
@Talia-12
Copy link

I've tested this a bit and it seems to be working on my machine as well.

This matches the layout of `shell_impl_async` in `commands.rs` and
avoids a hang or maybe deadlock in `to_writer`'s calls to
`tokio::io::AsyncWriteExt::write_all`. I don't really understand the
underlying cause of the hang but it seems it's necessary to spawn a
new tokio task to provide input to stdin. This is shown in an example
in `tokio::process::Child::wait` but not documented explicitly.
This should help debug formatting failures when using external
formatters in the future. Previously we didn't log anything when an
external formatter failed despite having a custom error type for it.
@merisbahti merisbahti force-pushed the meris.inlay-hints-steel branch from 9be095d to 05d0801 Compare February 11, 2025 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants