无意中看到 telephone-line 有一种渐变颜色的效果,看起来很棒,但telephone-line的源码有些难懂,所以自己动手实现类似的效果
渐变颜色的实现可使用color-gradient
(color-gradient '(0 0 0) (color-name-to-rgb "red") 10)
实现原理是对红(R)、绿(G)、蓝(B)三个颜色通道分别取 n + 2 个过渡值,n 为中间颜色过渡状态,所以对于red
、#000
、#a0a0a0
等颜色需要转化为RGB色彩模式
XPM图片格式参考 https://en.wikipedia.org/wiki/X_PixMap(XPM3)
大概是这样的
/* XPM */ static char * XFACE[] = { /* <Values> */ /* <width/columns> <height/rows> <colors> <chars per pixel>*/ "48 4 2 1", /* <Colors> */ "a c #ffffff", "b c #000000", /* <Pixels> */ "abaabaababaaabaabababaabaabaababaabaaababaabaaab", "abaabaababaaabaabababaabaabaababaabaaababaabaaab", "abaabaababaaabaabababaabaabaababaabaaababaabaaab", "abaabaababaaabaabababaabaabaababaabaaababaabaaab" };
渐变颜色XPM图片原理就是使用不同的字符代表不同状态的渐变颜色,当所使用的字符数越多,渐变效果越好,但同时XPM图片的宽度也就越大
;; 0~9 (mapcar 'number-to-string (number-sequence 0 9)) ;; a~z (mapcar 'char-to-string (number-sequence ?a ?z))
(let ((number -1)) (mapconcat (lambda(x) (setq number (+ number 1)) (format "\"%s c %s\"," (nth number maple-xpm-chars) (apply 'color-rgb-to-hex x))) (color-gradient (color-name-to-rgb color1) (color-name-to-rgb color2) width) ""))
ivy默认的配置已经足够了,但还是可以通过一些技巧来提升使用体验
ivy提供了好几个有关补全的命令,分别是ivy-done, ivy-partial-or-done, ivy-immediate-done, ivy-dispatching-done, 但不幸,想要获得高效的补全,我不得不记住这n多个命令,这常常使效率变得更低,
所以,我自定义了一个 maple/ivy-done, 仅使用 tab
这一个按键就可以获得以上所有的体验
(defun maple/ivy-done() (interactive) (let ((dir ivy--directory)) (ivy-partial-or-done) (when (string= dir ivy--directory) (ivy-insert-current) (when (and (eq (ivy-state-collection ivy-last) #'read-file-name-internal) (setq dir (ivy-expand-file-if-directory (ivy-state-current ivy-last)))) (ivy--cd dir) (setq this-command 'ivy-cd)))))
dirs目录下有
init-a.el init-b.el init-c.el
等多个文件, 我需要把init-c.el重命名为init-c.el.bak, 默认的ivy会怎么做呢
C-M-j
(ivy-immediate-done) 插入当前选中项而使用 maple/ivy-done 后呢
tab
插入当前选中项有一个n级子目录,需要切换到第n个子目录下, 默认的ivy会怎么做呢
tab
选中tab
切换目录
而使用 maple/ivy-done 后呢
tab
选中并切换目录
由于我是邪恶的 evil 用户, 所以我正常情况会使用 C-j
, C-k
来选择待选项,同样的,我希望能够使用 C-h
来进行某些操作,比如在counsel-ag中使用C-h
代替backspace
删除输入的字符,而在counsel-find-file中使用C-h
切换到上一级目录
ivy默认提供了 counsel-up-directory 命令可以切换到上级目录, 我希望能更进一步,当有插入的字符时, 调用C-h
能够直接清除插入的字符,再次调用才切换到上一级目录
(defun maple/ivy-c-h () (interactive) (if (eq (ivy-state-collection ivy-last) #'read-file-name-internal) (if (string-equal (ivy--input) "") (counsel-up-directory) (delete-minibuffer-contents)) (ivy-backward-delete-char)))
ivy默认调用ivy-occur后, 还需要调用ivy-wgrep-change-to-wgrep-mode才能进行批量修改
;; ivy-occur custom (defun maple/ivy-edit () "Edit the current search results in a buffer using wgrep." (interactive) (run-with-idle-timer 0 nil 'ivy-wgrep-change-to-wgrep-mode) (ivy-occur))
预设场景: 当某个项目下有许多使用 aaa.py的文件,我需要把 aaa.py 全部修改为 bbb.py, 也许某个 aaa.py 需要修改成 ccc.py,我在使用 projectile-find-file 搜索完全部的 aaa.py 后,打开ivy默认的ivy-occur并不能对文件进行修改(提示Text read only
), 所以我采用 find-name-dired 来批量修改文件名(目前还有些问题)
(defun maple/ivy-dired-occur() (interactive) (find-name-dired (or (projectile-project-root) default-directory) (concat (ivy--input) "*")) (ivy-exit-with-action (lambda (_) (pop-to-buffer (get-buffer "*Find*")) (dired-hide-details-mode) (wdired-change-to-wdired-mode) (when (bound-and-true-p evil-local-mode) (evil-normal-state)))))
预设场景: 有一个dirs目录, 我需要在同级目录下创建一个 dirs-aa.el 的文件, 默认的ivy会怎么做呢
但还有一种方式
backspace
键删除目录末尾的 =/=不幸的是,ivy中 backspace
的按键绑定了 ivy-backward-delete-char, 在 counsel-find-file 中使用 ivy会直接返回上级目录, 所以需要对 ivy-backward-delete-char 作简单的修改
(defun maple/ivy-backward-delete-char () (interactive) (let ((dir ivy--directory) (p (and ivy--directory (= (minibuffer-prompt-end) (point))))) (ivy-backward-delete-char) (when p (insert (file-name-nondirectory (directory-file-name dir))))))
不仅用于ivy, 其它文件操作命令也需要
(defun maple/ivy-make-directory-maybe () "Create parent directory if not exists while visiting file." (let ((dir (file-name-directory buffer-file-name))) (unless (file-exists-p dir) (if (y-or-n-p (format "Directory %s does not exist,do you want you create it? " dir)) (make-directory dir t) (keyboard-quit))))) (add-to-list 'find-file-not-found-functions 'maple/ivy-make-directory-maybe nil #'eq)
在使用 counsel-ag 或者 swiper, 我需要快速搜索选中的关键词,然而,ivy默认并不支持这么做,ivy需要打开counsel-ag后使用 M-n
插入选中的关键词,当然,也许是作者认为这样的方式很方便,但我并不认同,我为何需要重复一次操作呢,为何还需要记住 M-n
这样的按键呢?
(defun maple/region-string() "Get region string." (if (not (use-region-p)) "" (let* ((beg (region-beginning)) (end (region-end)) (eol (save-excursion (goto-char beg) (line-end-position)))) (deactivate-mark) (buffer-substring-no-properties beg (min end eol))))) ;; custom counsel-ag (defun maple/counsel-ag(-counsel-ag &optional initial-input initial-directory extra-ag-args ag-prompt) (funcall -counsel-ag (or initial-input (maple/region-string)) (or initial-directory default-directory) extra-ag-args ag-prompt)) (advice-add 'counsel-ag :around #'maple/counsel-ag)
这样,调用 counsel-ag 时,如何已经有选中的关键词,就会直接调用该关键词进行查找,避免重复操作, 同样的对于swiper 也可以使用另一种方式
(defun maple/ivy-search-at-point (func) (let ((ivy-initial-inputs-alist (list (cons func (maple/region-string))))) (funcall func))) (defun maple/swiper() (interactive) (maple/ivy-search-at-point 'swiper))
counsel-ag默认搜索当前目录,想要搜索某个目录,需要切换到该目录下调用counsel-ag,无疑的,这非常不方便,所以,我希望可以选择某个目录进行搜索
(defun maple/counsel-ag-directory() (interactive) (counsel-ag nil (read-directory-name "Search in directory: ")))
详见 自定义helm式的ivy.html 中的
吐嘈六: minibuffer
莫相逢
只道人生初见
寻归里
岂敢天涯
且听风雨且听云
庄周,意逍遥
当你不断超越前方的人
在更前方
总会有那么一盏红灯
也许是为了安全
也许是为了公平
谁知道呢
你,终归是只能等着
青霜长华旧离人
梅雨秋雪落缤纷
Flask的多语言国际化可以使用Flask-Babel插件,在此不再细述,但对于所谓的多语言站点(即形如example.com/zh/uri、example.com/en/uri或者zh.example.com、en.example.com)文档上却未作细述
有一个 Flask URL Processors 需要对所有的uri都额外增加一个lang_code的前缀,路由数较少时没什么问题,但路由数较多时太过麻烦
实现example.com/en/uri可以有多种方式,除了使用Flask URL Processors中介绍的外,还可以
这应该是各种方式里最简单的一种
location ~ ^/en/ { rewrite ^/en/(.*)$ /$1 last; } location = /en { rewrite ^/(.*)$ /index last; }
即增加一个url为/en/<path:uri>的路由,在此路由func中,根据<path:uri>信息获取已注册路由的view_function,不使用重定向,而是直接调用view_function返回实际响应
# https://stackoverflow.com/questions/38488134/get-the-flask-view-function-that-matches-a-url def get_view_function(url, method='GET'): adapter = current_app.url_map.bind('localhost') try: match = adapter.match(url, method=method) except RequestRedirect as e: # recursively match redirects return get_view_function(e.new_url, method) except (MethodNotAllowed, NotFound): # no match return None try: # return the view function and arguments return current_app.view_functions[match[0]], match[1] except KeyError: # no view is associated with the endpoint return None
提高查找对应view-function
的性能,增加缓存到内存中
FUNCTION = dict() def view_function_cache(func): @wraps(func) def _view_function(url, method='GET'): # 避免故意访问 if len(FUNCTION) > 100: for k, v in FUNCTION.items(): if v is None: FUNCTION.pop(k) key = method + url key = str(hashlib.md5(key.encode("UTF-8")).hexdigest()) if key in FUNCTION: return FUNCTION[key] FUNCTION[key] = func(url, method) return FUNCTION[key] return _view_function
这样就可以定义/en/<uri>实际的view_function
def redirect_en(uri): view_function = get_view_function( "/" + uri, request.method, ) if view_function is None: abort(404) # 注:因为我使用Flask-Babel是根据accept_language来区别不同语言 request.environ["HTTP_ACCEPT_LANGUAGE"] = "en-US,en;q=0.5" return view_function[0](**view_function[1])
原理同Flask URL Processors ,为所有的路由都额外增加/en前缀,并在before_request
中匹配到以/en开头的请求就修改对应accept_language信息
@app.before_request def before_request(): if request.path.startswith("/en/"): request.environ["HTTP_ACCEPT_LANGUAGE"] = "en-US,en;q=0.5" url_map = list(app.url_map.iter_rules()) for rule in url_map: app.add_url_rule("/en" + rule.rule, rule.endpoint, alias=True)
咦,感觉这种方式更简单一些,但最好还是能够对一些特殊的路由比如: static, admin, subdomain等进行特殊处理
;; 设置默认为左右分屏 (setq split-width-threshold 1)
(evil-leader/set-key "ws" 'split-window-below ;; 上下分屏 "wv" 'split-window-right) ;; 左右分屏
当设置了 (setq split-width-threshold 1)
后所有的窗口都会变成左右分屏,所以需要 shackle 或 popwin package对窗口进行管理, 对一些特殊的窗口或 buffer 比如 *Help*
和 flycheck error list
需要设置为显示在下方,并且默认选中
(setq shackle-rules '(("*Help*" :select t :align 'below :autoclose t) (flycheck-error-list-mode :select t :align 'below :autoclose t)))
设置默认的参数
(setq shackle-default-size 0.3 shackle-default-alignment 'below shackle-default-rule nil)
窗口黄金分割是一个非常有用的效果,当选中某个buffer时,这个buffer的window会自动按比例放大,这样就不用手动对窗口进行调整, 之前一直使用 golden-ratio 这个package, 但看github上最近一次提交已经是两年前,加上使用需要定义很多的 golden-ratio-extra-commands
, 所以切换到了 zoom
zoom 这个 package也有一些问题,比如我定义了
(setq zoom-size '(0.618 . 0.618) zoom-ignored-major-modes '(term-mode flycheck-error-list-mode))
但使用 flycheck-list-errors
时还是会自动缩放窗口大小,后来翻看源码时发现新建 window 是都会调用 balance-windows
,所以我稍微修改了一下
(defun maple/balance-windows(func &optional window-or-frame) (unless (zoom--window-ignored-p) (funcall func window-or-frame))) (advice-add 'balance-windows :around 'maple/balance-windows)
这样在调用 flycheck-list-errors
就不会再自动缩放窗口大小了
另外有一些命令需要临时关闭 zoom-mode ,所以有添加了一个 marco 临时关闭 zoom-mode
(defmacro with-zoom-disable(body) (declare (indent defun)) (let ((zoom-mode-p (when (featurep 'zoom) zoom-mode)) res) (when zoom-mode-p (zoom-mode -1)) (setq res `,@body) (when zoom-mode-p (zoom-mode zoom-mode-p)) res))
比如:
(with-zoom-disable (maple-imenu))
为了更方便的在窗口间进行跳转,避免重复使用 evil-window-down
, evil-window-right
等命令
(use-package window-numbering :hook (maple-theme . window-numbering-mode))
maple-theme-hook: 加载 theme 后执行hook
然后设置(这里只设置到5,因为我大概不会开10个窗口的)
(evil-leader/set-key "w0" 'select-window-0 "w1" 'select-window-1 "w2" 'select-window-2 "w3" 'select-window-3 "w4" 'select-window-4 "w5" 'select-window-5)
这样就可以使用leader + w + {window number}
或者 alt + {window number}
跳转
另外window-number的显示需要mode-line的支持,比如在maple-modeline里可以这样定义
(maple-modeline-define window-number :if (bound-and-true-p window-numbering-mode) :format (let ((color (face-attribute 'cursor :background))) (maple-modeline--add-text-property (maple-modeline--unicode-number (int-to-string (window-numbering-get-number))) 'face `(:foreground ,color :distant-foreground "white"))))
web-mode
有一个内置的web-mode-fold-or-unfold
函数,但这个函数有一个问题,当存在fold时,使用indent-region
会得到错误的缩进,想要得到正确的缩进,必须先 unfold
, 比如
<div> <div class="col-xs-3 col-sm-3" id="sidebar" role="navigation"> <button class="btn btn-primary">Submit</button> <br /> <span> <button class="btn btn-primary">Submit</button> <br /> </span> <span> <button class="btn btn-primary">Submit</button> <br /> </span> </div> </div> <button class="btn btn-primary">Submit</button> <br /> <a href="">as</a>
当把div#sidebar使用web-mode-fold-or-unfold
折叠起来, 然后使用
(defun maple/indent-buffer () "Format buffer with `indent-region`." (interactive) (save-excursion (indent-region (point-min) (point-max) nil)))
展开后就会变成
<!-- sidebar --> <div> <div class="col-xs-3 col-sm-3" id="sidebar" role="navigation"> <button class="btn btn-primary">Submit</button> <br /> <span> <button class="btn btn-primary">Submit</button> <br /> </span> <span> <button class="btn btn-primary">Submit</button> <br /> </span> </div> </div> <button class="btn btn-primary">Submit</button> <br /> <a href="">as</a>
这与期望的效果不符(不知道是不是只有我碰到,还是这可能是一个bug), 我去看了一下web-mode-fold-or-unfold
的实现, 它使用的是
(put-text-property beg-inside end-inside 'invisible t)
来隐藏折叠部分, 我不太清楚为什么使用put-text-property会使indent-region缩进有问题,有知道的可以告知一下,但我平时在其他项目中都是使用 hs-toggle-hiding
来折叠代码, hs-toggle-hiding
能很好的与indent-region
配合
由于web-mode-fold-or-unfold
这个函数太长,不想占用我自己的配置, 所以魔改了一下
(fset 'maple/put-text-property (symbol-function 'put-text-property)) (defun maple/web-mode-put-text(p q prop value) (if (and (eq prop 'invisible) value) (hs-make-overlay p q 'code) (maple/put-text-property p q prop value))) (defun maple/web-mode-fold-or-unfold() (interactive) (cl-letf (((symbol-function 'put-text-property) 'maple/web-mode-put-text)) (web-mode-fold-or-unfold)))
把put-text-property
临时修改成hs-make-overlay
,这样web-mode折叠后就能正确地使用maple/indent-buffer
了
之前一直在使用bootcdn.cn提供的CDN服务,没出过什么大问题,即使国庆第一天凌晨挂过,对它依旧信任。
但事与愿违,无意中打开查看源码,才发现竟然有那么多的css,js文件
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <link href="https://cdn.bootcss.com/fancybox/3.1.25/jquery.fancybox.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/lazysizes/4.0.1/lazysizes.min.js"></script> <script src="https://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script> <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script src="https://cdn.bootcss.com/particles.js/2.0.0/particles.min.js"></script> <script src="https://cdn.bootcss.com/fancybox/3.1.25/jquery.fancybox.min.js"></script>
作为强迫症有些受不了,想要把这些合并成一个css
文件或js
文件
首先找到熟悉的python的webassets
,但发现只能使用本地文件,又不想一个文件一个文件的下载下来,所以写了一段简单的代码,把下载文件并压缩文件合到一起
def write_to_file(name, url): resp = requests.get(url) path = os.path.join("/tmp", name) with open(path, "w") as f: f.write(resp.text) return name
from webassets import Bundle from webassets import Environment def asset_css(files): env = Environment(directory='/tmp', url='/media') css = Bundle(*files, filters='cssmin', output='/tmp/mine.css') env.register('css_all', css) print(env['css_all'].urls())
def asset_js(files): env = Environment(directory='/tmp', url='/media') css = Bundle(*files, filters='jsmin', output='/tmp/mine.js') env.register('js_all', css) print(env['js_all'].urls())
css_files = { "bootstrap.min.css": "https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css", "font-awesome.min.css": "https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css", "jquery.fancybox.min.css": "https://cdn.bootcss.com/fancybox/3.1.25/jquery.fancybox.min.css" } js_files = { "lazysizes.min.js": "https://cdn.bootcss.com/lazysizes/4.0.1/lazysizes.min.js", "jquery.min.js": "https://cdn.bootcss.com/jquery/2.1.4/jquery.min.js", "bootstrap.min.js": "https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js", "particles.min.js": "https://cdn.bootcss.com/particles.js/2.0.0/particles.min.js", "jquery.fancybox.min.js": "https://cdn.bootcss.com/fancybox/3.1.25/jquery.fancybox.min.js" } def main(): files = [] for name, url in css_files.items(): files.append(write_to_file(name, url)) asset_css(files) files = [] for name, url in js_files.items(): files.append(write_to_file(name, url)) asset_js(files) if __name__ == '__main__': main()
然后把压缩的mine.css
和mine.js
上传到云存储,测试后发现一切正常, 唯有font-awesome
缺少字体, 下载字体后手动上传到css
文件的相对目录../fonts
即可
为避免忘记,特此记录
这几天参考imenu-list
重写了一个类似 vim 中tagbar
的插件 maple-imenu, 其实之前我一直在使用另一个类似的插件—— imenu-list, 虽然imenu-list已经足够使用了, 但它和golden-ratio搭配起来很难受, golden-ratio 我是常开的, 但imenu-list也会偶尔使用(浏览及讲解代码必备,否则查找某个函数和变量时很难受的)
原本我想要自定义一下imenu-list,但看了源码之后, 发现很多地方都不符合我的需求, 而且需要改动的地方越来越多,最后不如自己重写
下载文件复制到 $HOME/.emacs.d/site-lisp/maple-imenu
目录, 然后
(use-package maple-imenu :load-path "site-lisp/maple-imenu" :commands (maple-imenu) :config (with-eval-after-load 'evil (evil-make-overriding-map maple-imenu-mode-map 'normal)))
M-x maple-imenu
maple-imenu-buffer
maple-imenu buffer name, default *maple-imenu*
maple-imenu-displayed-buffer
displayed buffer name, default nil
maple-imenu-overlays
save overlay info, toggle open or close entry
maple-imenu-width
maple-imenu window size, default 25
maple-imenu-padding
maple-imenu prefix padding, default 2
maple-imenu-position
maple-imenu window display position, default 'right
, or 'left
maple-imenu-arrow
open or close arrow, default ("▾" . "▸")
maple-imenu-auto-update
auto update when t
, after save every time
➊ 代码和逻辑我认为与 imenu-list 比起来更加通俗易懂
➋ window fixed, 至少与golden-ratio搭配不那么难受
➌ 更多我可以控制和自定义的地方