summaryrefslogtreecommitdiffstats
path: root/fcomp.el
blob: e76516260b23d0de28db20e6f17d39af768a0f8b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
;;; fcomp.el --- fcomp front-end                     -*- lexical-binding: t; -*-

;; Copyright (C) 2019  Anastasis Grammenos

;; Author: Anastasis Grammenos <anastasis.gramm2@gmail.com>
;; Keywords: lisp
;; Version: 0.0.1

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; Emacs front-end for fcomp. Get it from https://ubuntos.dynu.net/git/fcomp

;;; Install:

;; git clone https://ubuntos.dynu.net/git/fcomp && cd fcomp
;; make && make install # install copies the binary under /usr/local/bin

;; In the emacs init file:
;; (add-to-list 'load-path "/your/custom/path")
;; (require 'fcomp)
;; (setq fcomp-path "/your/custom/path/fcomp")
;; (global-set-key (kbd "C-<tab>") 'fcomp-autocomplete)

;; or with use-package
;; (use-package fcomp
;;   :load-path "/your/custom/path"
;;   :bind (("C-<tab>" . fcomp-autocomplete))
;;   :config
;;   (setq fcomp-path "/your/custom/path/fcomp"))


;;; Code:

(require 'ido)
(require 'subr-x)

;; User variables

(defvar fcomp-path "/usr/local/bin/fcomp"
  "Path to fcomp executable.")
(defvar fcomp-min-word-length 3
  "Minimum word length to be counted as token.")
(defvar fcomp-extra-valid-chars ""
  "Extra valid chars for the tokenization.")
(defvar fcomp-extra-invalid-chars ""
  "Extra invalid chars for the tokenization.")
(defvar fcomp-completing-read-func 'completing-read
  "Function to call when showing results to user.")

(defun empty-string-p (string)
  "Return true if the string is empty or nil. Expects string."
  (or (null string)
      (zerop (length (string-trim string)))))

(defun fcomp--start (query)
  "Initiaize fcomp variables"
  (setq fcomp--query query)
  (setq fcomp--output ""))

(defun fcomp--filter (proc str)
  "Fill the output buffer"
  (let* ((fcomp-pre
          (if (empty-string-p (process-get proc 'input-word))
              nil
            (format "%s" (process-get proc 'input-word)))))
    (setq fcomp--output (concat fcomp--output str))))

(defun fcomp--handle-output ()
  "Handle output buffer"
  (let ((initial-input fcomp--query)
        (out-string fcomp--output)
        (result nil))
    (if (empty-string-p fcomp--output)
        nil
      (setq result
            (funcall fcomp-completing-read-func
                     "Complete:"
                     (remove "" (split-string fcomp--output "\n"))
                     nil nil
                     initial-input)))
  (if (empty-string-p result)
        (message "%s" "No completion candidates")
      (insert (string-remove-prefix
               (if (empty-string-p initial-input)
                   (format "%s" "")
                 (format "%s" initial-input))
               result)))
    (setq fcomp--query "")
    (setq fcomp--output "")))

(defun fcomp--autocomplete (word point &optional x)
  "Start fcomp process and orchistrate it's output."
  (let ((term (if (empty-string-p word)
                  (format "%s" "-au")
                (format "%s" word)))
        (input-len (buffer-size)))
    (fcomp--start word)
    (setq proc
          (start-process
           "fcomp"
           nil
           (format "%s" fcomp-path)
           "-w" (format "%s" fcomp-min-word-length)
           "-v" (format "\"%s\"" fcomp-extra-valid-chars)
           "-i" (format "\"%s\"" fcomp-extra-invalid-chars)
           term))
    (set-process-filter proc #'fcomp--filter)
    (set-process-coding-system proc 'raw-text-unix 'raw-text-unix)
    (process-put proc 'input-word word)
    (if (< input-len 50000)
        (process-send-region proc (point-min) (point-max))
      (process-send-region proc
                           (if (< (- point 25000) 0) (point-min) (- point 25000))
                           (if (> (+ point 25000) input-len) (point-max) (+ point 25000))))
    (process-send-string proc "\n")
    (process-send-eof proc)
    (if (accept-process-output proc nil nil t)
        (unless x
            (fcomp--handle-output))
      (progn
        (unless x
          (message "No completion for %s" word))
        (delete-process proc)))))

(defun fcomp--make-syntax-table ()
  "Modifies the current active sytanx table to include '_', '-'
and `fcomp-extra-valid-chars' as part of a word."
  ;; Thanks Dan for the syntax-table trick
  ;; https://emacs.stackexchange.com/questions/9583/how-to-treat-underscore-as-part-of-the-word
  (let ((table (copy-syntax-table (syntax-table))))
    (when (not (empty-string-p fcomp-extra-valid-chars))
      (mapc (lambda (char) (modify-syntax-entry char "w" table)) fcomp-extra-valid-chars))
    (modify-syntax-entry ?- "w" table)
    (modify-syntax-entry ?_ "w" table)
    (copy-syntax-table table)))

(defun fcomp-get-canditates (word)
  "Return a list of candidates for WORD curated by fcomp."
  (progn
    (fcomp--autocomplete word (point) t))
    (remove "" (split-string fcomp--output "\n")))

(defun fcomp-autocomplete ()
  "Autocompete using fcomp.c

When `thing-at-point' is a word, autocomplete it based on buffer contents.

When invoked with no `thing-at-point' show a list of all possible candidates.

The syntax-table is temporarily modified and includes '_', '-' and
`fcomp-extra-valid-chars' as part of a word.

Requires fcomp. Get it at URL `https://git.eyesin.space/git/fcomp'"
  (interactive)
  (with-syntax-table (fcomp--make-syntax-table)
    (fcomp--autocomplete (thing-at-point 'word 'no-properties) (point))))

(provide 'fcomp)
;;; fcomp.el ends here