ai-completion-mode – a minimal setup to integrate OpenAI-based code completion directly into Emacs. It provides a minor mode, a keybinding, and a straightforward way to fetch AI-generated code.
This blog post demonstrates how to build an “AI code completion” feature in Emacs using the OpenAI API. With a single command (C-c C-a by default), you can send the surrounding code context (or the current region) to an LLM such as GPT-3.5-turbo or GPT-4, then insert the model’s completion right into your buffer.
Below is an abbreviated version of ai-completion.el, a minimal Emacs Lisp script:
;;; ai-completion.el --- Minimal AI code completion in Emacs using OpenAI API
(require 'request-deferred)
(require 'json)
(require 'subr-x)
(defgroup ai-completion nil
"AI code completion using OpenAI."
:group 'tools)
(defcustom ai-completion-openai-api-key ""
"OpenAI API key for AI code completion."
:type 'string
:group 'ai-completion)
(defcustom ai-completion-model "gpt-3.5-turbo"
"OpenAI model (e.g., gpt-3.5-turbo, gpt-4)."
:type 'string
:group 'ai-completion)
(defcustom ai-completion-max-tokens 128
"Max tokens to request."
:type 'integer
:group 'ai-completion)
(defcustom ai-completion-temperature 0.2
"Sampling temperature (0.0 = deterministic)."
:type 'float
:group 'ai-completion)
(defun ai-completion--get-context ()
(let* ((context-window 1000)
(start (max (point-min) (- (point) context-window)))
(end (min (point-max) (+ (point) context-window))))
(buffer-substring-no-properties start end)))
(defun ai-completion--extract-code (text)
"Extract code from TEXT, ignoring explanations.
Look for triple backticks; if none, assume the entire TEXT is code."
(let ((code-regex \"```\\([^`]*\\)```\"))
(if (string-match code-regex text)
(match-string 1 text)
text)))
(defun ai-completion--call-openai (prompt callback)
(let ((url \"https://api.openai.com/v1/chat/completions\")
(data (json-encode
`((\"model\" . ,ai-completion-model)
(\"messages\" . [
((\"role\" . \"system\")
(\"content\" . \"You are a coding assistant. Output code only.\"))
((\"role\" . \"user\")
(\"content\" . ,prompt))
])
(\"max_tokens\" . ,ai-completion-max-tokens)
(\"temperature\" . ,ai-completion-temperature))))
(headers `((\"Content-Type\" . \"application/json\")
(\"Authorization\" . ,(concat \"Bearer \" ai-completion-openai-api-key)))))
(request-deferred
url
:type \"POST\"
:data data
:headers headers
:parser 'json-read
:error (lambda (&rest args &key error-thrown &allow-other-keys)
(message \"ai-completion error: %S\" error-thrown))
:success (lambda (&key data &allow-other-keys)
(let* ((choices (assoc-default 'choices data))
(choice (and choices (aref choices 0)))
(msg (assoc-default 'message choice))
(content (assoc-default 'content msg)))
(when callback
(funcall callback (ai-completion--extract-code content))))))))
(defun ai-completion-insert ()
\"Fetch an AI completion and insert it at point.\"
(interactive)
(let ((context (ai-completion--get-context)))
(ai-completion--call-openai
context
(lambda (response)
(when response
(save-excursion
(insert (concat \"\\n\" response)))))))))
;;;###autoload
(define-minor-mode ai-completion-mode
\"Minor mode for AI code completion.\"
:lighter \" AI-Complete\"
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd \"C-c C-a\") 'ai-completion-insert)
map)
(if ai-completion-mode
(message \"ai-completion-mode enabled\")
(message \"ai-completion-mode disabled\")))
(provide 'ai-completion)
;;; ai-completion.el ends here
Key points:
chat/completions API.
C-c C-a by default, fetching
and inserting the LLM's response.
Installation:
;; In your init.el:
;; 1. Put ai-completion.el on your load path
(load-file \"~/.emacs.d/lisp/ai-completion.el\")
;; 2. Set your API key
(setq ai-completion-openai-api-key \"sk-XXXXXXXXXXXXXX\")
;; 3. Enable the mode
(ai-completion-mode 1)
Now, select some code or place the cursor within code context. Press C-c C-a and the system will insert GPT’s code completion below your cursor, discarding non-code text.
Back to Blog Index | Main Terminal
Refer to the official OpenAI docs for more info on the chat/completions endpoint. For advanced usage—like hooking it into an overlay or integrating with Company/Corfu—feel free to adapt the code further. But this minimal approach is enough to get started with a quick, workable AI code completion mode in Emacs.