[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] master b05124b 03/13: Sort candidates by previous completion choi
From: |
Dmitry Gutov |
Subject: |
[elpa] master b05124b 03/13: Sort candidates by previous completion choices |
Date: |
Wed, 28 Jan 2015 13:09:06 +0000 |
branch: master
commit b05124bbbbc4d98f86bbcdb5c7cd95c742a5a71d
Author: Ingo Lohmar <address@hidden>
Commit: Ingo Lohmar <address@hidden>
Sort candidates by previous completion choices
---
company-statistics.el | 206 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 206 insertions(+), 0 deletions(-)
diff --git a/company-statistics.el b/company-statistics.el
new file mode 100644
index 0000000..eadd49e
--- /dev/null
+++ b/company-statistics.el
@@ -0,0 +1,206 @@
+;;; company-statistics.el --- history scoring using company-transformers
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+
+;; Author: Ingo Lohmar
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+
+;;; Commentary:
+;; - backends decide on available candidates --- depends on prefix
+;; - we store how often a candidate is chosen --- independent of prefixes
+;; - for sorted candidates: stable sort keeps incoming order if same/no score
+;; - TODO add ert tests
+;; - TODO how to treat case, use backend's ignore-case?
+;; - TODO maybe later depend on the mode, file, project: all in score functions
+
+;;; Code:
+
+(require 'company)
+
+(defgroup company-statistics nil
+ "Completion candidates ranking by historical statistics."
+ :group 'company)
+
+(defcustom company-statistics-size 400
+ "Number of completion choices that `company-statistics' keeps track of.
+As this is a global cache, making it too small defeats the purpose."
+ :group 'company-statistics
+ :type 'integer
+ :initialize (lambda (option init-size) (setq company-statistics-size
init-size))
+ :set 'company-statistics--history-resize)
+
+(defcustom company-statistics-file
+ (concat user-emacs-directory "company-statistics-cache.el")
+ "File to save company-statistics state."
+ :group 'company-statistics
+ :type 'string)
+
+(defcustom company-statistics-auto-save t
+ "Whether to save the statistics when leaving emacs."
+ :group 'company-statistics
+ :type 'boolean)
+
+(defcustom company-statistics-auto-restore t
+ "Whether to restore statistics when company-statistics is enabled and has
+not been used before."
+ :group 'company-statistics
+ :type 'boolean)
+
+;; internal vars, persistence
+
+(defvar company-statistics--scores nil
+ "Store selection frequency of candidates.")
+
+(defvar company-statistics--history nil
+ "Ring keeping the history of chosen candidates.")
+
+(defvar company-statistics--history-replace nil
+ "Index into the completion history.")
+
+(defun company-statistics--init ()
+ "Initialize company-statistics."
+ (setq company-statistics--scores
+ (make-hash-table :test 'equal :size company-statistics-size))
+ (setq company-statistics--history (make-vector company-statistics-size nil)
+ company-statistics--history-replace 0))
+
+(defun company-statistics--initialized-p ()
+ (hash-table-p company-statistics--scores))
+
+(defun company-statistics--history-resize (option new-size)
+ (when (company-statistics--initialized-p)
+ ;; hash scoresheet auto-resizes, but history does not
+ (let ((new-hist (make-vector new-size nil))
+ ;; use actual length, to also work for freshly restored history
+ (company-statistics-size (length company-statistics--history)))
+ ;; copy newest entries (possibly nil) to new-hist
+ (dolist (i (number-sequence 0 (1- (min new-size
company-statistics-size))))
+ (let ((old-i (mod (+ (- company-statistics--history-replace new-size)
i)
+ company-statistics-size)))
+ (aset new-hist i (aref company-statistics--history old-i))))
+ ;; remove discarded history (when shrinking) from scores
+ (when (< new-size company-statistics-size)
+ (dolist (i (number-sequence
+ company-statistics--history-replace
+ (+ company-statistics-size
+ company-statistics--history-replace
+ (1- new-size))))
+ (company-statistics--score-down
+ (aref company-statistics--history (mod i
company-statistics-size)))))
+ (setq company-statistics--history new-hist)
+ (setq company-statistics--history-replace (if (<= new-size
company-statistics-size)
+ 0
+ company-statistics-size))))
+ (setq company-statistics-size new-size))
+
+(defun company-statistics--save ()
+ "Save statistics."
+ (with-temp-buffer
+ (let (print-level print-length)
+ (insert
+ (format
+ "%S"
+ `(setq
+ company-statistics--scores ,company-statistics--scores
+ company-statistics--history ,company-statistics--history
+ company-statistics--history-replace
,company-statistics--history-replace))))
+ (write-file company-statistics-file)))
+
+(defun company-statistics--maybe-save ()
+ (when company-statistics-auto-save
+ (company-statistics--save)))
+
+(defun company-statistics--load ()
+ "Restore statistics."
+ (load company-statistics-file 'noerror nil 'nosuffix))
+
+;; score manipulation in one place
+
+(defun company-statistics--score-get (cand)
+ (gethash cand company-statistics--scores 0))
+
+(defun company-statistics--score-up (cand)
+ (puthash cand
+ (1+ (company-statistics--score-get cand))
+ company-statistics--scores))
+
+(defun company-statistics--score-down (cand)
+ (when cand ;ignore nil
+ (let ((old-score (company-statistics--score-get cand)))
+ ;; on scoresheet, decrease corresponding score or remove entry
+ (if (> old-score 1)
+ (puthash cand (1- old-score) company-statistics--scores)
+ (remhash cand company-statistics--scores)))))
+
+;; core functions: actual sorting transformer, statistics updater
+
+(defun company-sort-by-statistics (candidates)
+ "Sort candidates by historical statistics."
+ (setq candidates
+ (sort candidates
+ (lambda (cand1 cand2)
+ (> (company-statistics--score-get cand1)
+ (company-statistics--score-get cand2))))))
+
+(defun company-statistics--finished (result)
+ "After completion, update scores and history."
+ (setq result (substring-no-properties result)) ;on the safe side
+ (company-statistics--score-up result)
+ ;; update cyclic completion history
+ (let ((replace-result
+ (aref company-statistics--history
company-statistics--history-replace)))
+ (company-statistics--score-down replace-result)) ;void if nil
+ ;; insert new result
+ (aset company-statistics--history company-statistics--history-replace result)
+ (setq company-statistics--history-replace
+ (mod (1+ company-statistics--history-replace)
company-statistics-size)))
+
+;;;###autoload
+(define-minor-mode company-statistics-mode
+ "Statistical sorting for company-mode. Ranks completion candidates by
+the frequency with which they have been chosen in recent (as given by
+`company-statistics-size') history.
+
+Turning this mode on and off preserves the statistics. They are also
+preserved automatically between Emacs sessions in the default
+configuration. You can customize this behavior with
+`company-statistics-auto-save', `company-statistics-auto-restore' and
+`company-statistics-file'."
+ nil nil nil
+ :global t
+ (if company-statistics-mode
+ (progn
+ (unless (company-statistics--initialized-p)
+ (if company-statistics-auto-restore
+ (progn
+ (company-statistics--load) ;maybe of different size
+ (company-statistics--history-resize nil
company-statistics-size))
+ (company-statistics--init)))
+ (add-to-list 'company-transformers
+ 'company-sort-by-statistics 'append)
+ (add-hook 'company-completion-finished-hook
+ 'company-statistics--finished))
+ (setq company-transformers
+ (delq 'company-sort-by-statistics company-transformers))
+ (remove-hook 'company-completion-finished-hook
+ 'company-statistics--finished)))
+
+(add-hook 'kill-emacs-hook 'company-statistics--maybe-save)
+
+(provide 'company-statistics)
+;;; company-statistics.el ends here
- [elpa] master updated (0d834ff -> 2452ff7), Dmitry Gutov, 2015/01/28
- [elpa] master bc3f1ec 02/13: Basic files, Dmitry Gutov, 2015/01/28
- [elpa] master d131452 01/13: Initial commit, Dmitry Gutov, 2015/01/28
- [elpa] master e5553b5 07/13: Fix enabling mode without saved cache file, Dmitry Gutov, 2015/01/28
- [elpa] master b05124b 03/13: Sort candidates by previous completion choices,
Dmitry Gutov <=
- [elpa] master 3babb6c 08/13: Pass de-propertized candidates to sorting, Dmitry Gutov, 2015/01/28
- [elpa] master 1c8bf18 04/13: Flexible context handling, refactoring, Dmitry Gutov, 2015/01/28
- [elpa] master b9bc7f0 05/13: Tests and stubs, Dmitry Gutov, 2015/01/28
- [elpa] master c9395db 06/13: First round of typos, Dmitry Gutov, 2015/01/28
- [elpa] master f8d15c7 12/13: Fix elpa URL in docs, Dmitry Gutov, 2015/01/28
- [elpa] master b736562 09/13: Keep properties for score change/calc, Dmitry Gutov, 2015/01/28
- [elpa] master 1ec2351 10/13: Fix default score calc function for missing global score, Dmitry Gutov, 2015/01/28
- [elpa] master 7b9f171 11/13: Update metadata and documentation for ELPA, Dmitry Gutov, 2015/01/28
- [elpa] master 2452ff7 13/13: Add 'packages/company-statistics/' from commit 'f8d15c7edb2a182f484c5e6eb86f322df473e763', Dmitry Gutov, 2015/01/28