254 lines
8.1 KiB
EmacsLisp
254 lines
8.1 KiB
EmacsLisp
;;; oni-python.el --- Extra Python commands -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2015 Tom Willemse
|
|
|
|
;; Author: Tom Willemse <tom@ryuslash.org>
|
|
;; Keywords:
|
|
|
|
;; 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:
|
|
|
|
;; Here are some extra Emacs commands for working with Python source
|
|
;; code.
|
|
|
|
;;; Code:
|
|
|
|
(require 'auto-complete)
|
|
(require 'fill-column-indicator)
|
|
(require 'python)
|
|
(require 'whitespace)
|
|
|
|
;;;###autoload
|
|
(defun oni:add-import-from (package import)
|
|
"Add a Python import statement at the beginning of the module."
|
|
(interactive
|
|
(list (completing-read "From package: " (oni:collect-from-imports))
|
|
(read-string "Import: ")))
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(search-forward (concat "from " package " import ("))
|
|
(insert "\n " import ",")
|
|
(oni:sort-imports)))
|
|
|
|
(defun oni:collect-from-imports ()
|
|
"Find all modules from which names are imported in the current file."
|
|
(let (results)
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(while (re-search-forward "from \\(.+\\) import" nil :noerror)
|
|
(push (buffer-substring-no-properties (match-beginning 1)
|
|
(match-end 1)) results)))
|
|
results))
|
|
|
|
;;;###autoload
|
|
(defun oni:clear-electric-indent-chars ()
|
|
"Clear the `electric-indent-chars' which don't work well in Python."
|
|
(set (make-local-variable 'electric-indent-chars) nil))
|
|
|
|
;;;###autoload
|
|
(defun oni:make-import-multiline (from-point to-point)
|
|
"Turn an import statement into a multi-line import statement."
|
|
(interactive (list (line-beginning-position)
|
|
(line-end-position)))
|
|
(goto-char from-point)
|
|
(search-forward "import" to-point)
|
|
(insert " (\n")
|
|
(delete-horizontal-space)
|
|
(let ((imports-start (point)) imports-end)
|
|
(while (search-forward "," to-point :noeror)
|
|
(insert "\n")
|
|
(delete-horizontal-space))
|
|
(end-of-line)
|
|
(insert ",\n")
|
|
(setf imports-end (point))
|
|
(insert ")")
|
|
(python-indent-shift-right imports-start imports-end)
|
|
(forward-line -1)
|
|
(oni:sort-imports)))
|
|
|
|
;;;###autoload
|
|
(defun oni:set-keys-for-python ()
|
|
"Set Python mode-specific keybindings."
|
|
(define-key python-mode-map (kbd "C->") #'python-indent-shift-right)
|
|
(define-key python-mode-map (kbd "C-<") #'python-indent-shift-left))
|
|
|
|
;;;###autoload
|
|
(defun oni:set-pep8-fill-column ()
|
|
"Set the `fill-column' to the PEP 8 recommendation."
|
|
(setq-local fill-column 72))
|
|
|
|
;;;###autoload
|
|
(defun oni:set-pep8-margin ()
|
|
"Set the `fci-rule-column' the the PEP 8 recommendation."
|
|
(setq-local fci-rule-column 79))
|
|
|
|
;;;###autoload
|
|
(defun oni:set-python-completion-sources ()
|
|
"Set `ac-sources' to python-specific sources."
|
|
(setq ac-sources '(ac-source-jedi-direct)))
|
|
|
|
;;;###autoload
|
|
(defun oni:set-python-imenu-function ()
|
|
"Set the `imenu-create-index-function' variable.
|
|
|
|
For `python-mode' I prefer `python-imenu-create-flat-index'."
|
|
(setq imenu-create-index-function #'python-imenu-create-flat-index))
|
|
|
|
;;;###autoload
|
|
(defun oni:set-python-symbols ()
|
|
"Set a few extra UTF-8 symbols for use in python."
|
|
(when (boundp 'prettify-symbols-alist)
|
|
(setq prettify-symbols-alist
|
|
'(("lambda" . ?λ)
|
|
("<=" . ?≤)
|
|
(">=" . ?≥)
|
|
("!=" . ?≠)))))
|
|
|
|
;;;###autoload
|
|
(defun oni:set-whitespace-tab-display ()
|
|
"Set the `whitespace-style' to show only tabs."
|
|
(setq-local whitespace-style '(tab-mark)))
|
|
|
|
;;;###autoload
|
|
(defun oni:sort-imports ()
|
|
"Sort python multiline imports using `()'."
|
|
(interactive)
|
|
(save-excursion
|
|
(sort-lines nil (1+ (search-backward "("))
|
|
(1- (search-forward ")")))))
|
|
|
|
;;; Tests
|
|
|
|
(ert-deftest oni:add-import-from ()
|
|
(with-temp-buffer
|
|
(python-mode)
|
|
(insert "from myaethon2.core.administration.models import (
|
|
Client,
|
|
Contact,
|
|
Individual,
|
|
Location,
|
|
)")
|
|
(oni:add-import-from "myaethon2.core.administration.models" "Debtor")
|
|
(should (equal (buffer-substring-no-properties (point-min) (point-max))
|
|
"from myaethon2.core.administration.models import (
|
|
Client,
|
|
Contact,
|
|
Debtor,
|
|
Individual,
|
|
Location,
|
|
)"))))
|
|
|
|
(ert-deftest oni:collect-from-imports ()
|
|
(with-temp-buffer
|
|
(python-mode)
|
|
(insert "import calendar as cal
|
|
import datetime
|
|
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import login_required, permission_required
|
|
from django.core.context_processors import csrf
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import transaction
|
|
from django.db.models import Q
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.utils import formats
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from myaethon2.core.business_units import BU
|
|
from myaethon2.core.forms import MultiFormWrapper
|
|
from myaethon2.core.models import AUser, Service
|
|
from myaethon2.core.planning.models import Booking, JOB_TYPES
|
|
from myaethon2.core.util import simplify_timedelta
|
|
from myaethon2.core.views import SearchAndSortListView
|
|
from myaethon2.jobs import forms, status
|
|
from myaethon2.jobs.models import Assignment, Job, JobGroup
|
|
from myaethon2.jobs.util import JobFactory
|
|
from myaethon2.workers.models import Worker
|
|
from myaethon2.export import Exporter, XLSGenerator
|
|
|
|
from django.http import (
|
|
HttpResponseForbidden,
|
|
HttpResponseNotAllowed,
|
|
HttpResponseRedirect,
|
|
)
|
|
|
|
from django.views.generic import (
|
|
CreateView,
|
|
DeleteView,
|
|
DetailView,
|
|
ListView,
|
|
UpdateView,
|
|
)
|
|
|
|
from myaethon2.core.administration.models import (
|
|
Client,
|
|
Contact,
|
|
Debtor,
|
|
Individual,
|
|
Location,
|
|
)
|
|
|
|
from myaethon2.core.decorators import (
|
|
json_response,
|
|
protect_with,
|
|
with_help_text,
|
|
)")
|
|
(should (equal (sort (oni:collect-from-imports) #'string-lessp)
|
|
'("django.conf"
|
|
"django.contrib"
|
|
"django.contrib.auth.decorators"
|
|
"django.core.context_processors"
|
|
"django.core.exceptions"
|
|
"django.core.paginator"
|
|
"django.core.urlresolvers"
|
|
"django.db"
|
|
"django.db.models"
|
|
"django.http"
|
|
"django.shortcuts"
|
|
"django.utils"
|
|
"django.utils.translation"
|
|
"django.views.generic"
|
|
"myaethon2.core.administration.models"
|
|
"myaethon2.core.business_units"
|
|
"myaethon2.core.decorators"
|
|
"myaethon2.core.forms"
|
|
"myaethon2.core.models"
|
|
"myaethon2.core.planning.models"
|
|
"myaethon2.core.util"
|
|
"myaethon2.core.views"
|
|
"myaethon2.export"
|
|
"myaethon2.jobs"
|
|
"myaethon2.jobs.models"
|
|
"myaethon2.jobs.util"
|
|
"myaethon2.workers.models")))))
|
|
|
|
(ert-deftest oni:make-import-multiline ()
|
|
(with-temp-buffer
|
|
(python-mode)
|
|
(insert "from myaethon2.core.administration.models import Contact, Individual, Client, Location")
|
|
(oni:make-import-multiline (line-beginning-position) (line-end-position))
|
|
(should (equal (buffer-substring-no-properties (point-min) (point-max))
|
|
"from myaethon2.core.administration.models import (
|
|
Client,
|
|
Contact,
|
|
Individual,
|
|
Location,
|
|
)"))))
|
|
|
|
(provide 'oni-python)
|
|
;;; oni-python.el ends here
|