sublime中为Markdown文档插入剪切板中的图片


sublime中为Markdown文档插入剪切板中的图片

sublime中为Markdown文档插入剪切板中的图片的插件没有合适的,找到一个不是很好用,几年前开发的,以及不维护了。于是,自己写一个吧。

自己写的好处就是,各种问题都可以自己去进行修复,进行定制化的配置。这个也是的,在Macos系统下面是Retina屏,截屏保存的图片的像素是普通屏幕的大概4倍,就是长度和宽度都变成了2倍。
所以,我在粘贴图片的时候就会在普通的![]() 后面再加上原始截屏时的长度和宽度![](){width="10" height="10"}。这样,即使在Retina屏下截取的图片也不影响,最终呈现的大小和原始的Retina一样,而不是4倍。

目前直接使用全路径,将所有的图片放在一个单独的文件夹,而不是一般的,放在.md文件的当前目录下面,这样,.md文档的目录看起来更干净一些。

TODO:
后面,可以再完善一个清理图片的功能,因为在粘贴的过程中,有可能会截图多次,或者文章中把图片删除了,但实际上的图片并没有删除。可以把图片对应的文章中的引用扫描一下,如果没有引用了,则可以删除掉。这样,可以把不需要的图片及时清理掉。

由于sublime的默认包里面没有PIL包,所以,使用了外置的python来执行,需要在环境变量中加入python的路径。并且安装PIL包。

pip3 install pillow
import sublime
import sublime_plugin
import os
import sys
import subprocess

class MarkdownImagePasteObject(object):
    settings_file = 'MarkdownImagePaste.sublime-settings'
    def __init__(self, *args, **kwgs):
        super(MarkdownImagePasteObject, self).__init__(*args, **kwgs)
        self.settings = sublime.load_settings(self.settings_file)
        self.image_dir = self.settings.get("image_dir", ".images")
        self.project_dir = self.settings.get("project_dir")
        # TODO: 
        if len(self.project_dir) == 0:
            self.project_dir = "~/md"
        self.project_dir = os.path.normpath(os.path.expanduser(self.project_dir))
        print(self.project_dir)

class MarkdownImagePasteCommand(MarkdownImagePasteObject, sublime_plugin.TextCommand):
    def run(self, edit):
        filename = self.get_filename()
        if filename is None:
            sublime.error_message('Please save the file first!')
            return
        size = self.paste_image(filename)
        # print("size:", size)
        if size:
            for pos in self.view.sel():
                if 'text.html.markdown' in self.view.scope_name(pos.begin()):
                    if sys.platform == 'darwin':
                        width = int(size[0]) // 2
                        hight = int(size[1]) // 2
                        if width > 900:
                            ratio = 900.0 / width
                            width = 900
                            hight = int(hight * ratio)
                        self.view.insert(edit, pos.begin(), '![](%s){width="%d" height="%d"}' % (filename, width, hight))
                        # self.view.insert(edit, pos.begin(), '' % (filename, size[0], size[1]))
                    else:
                        self.view.insert(edit, pos.begin(), "![](%s)" % filename)
                else:
                    self.view.insert(edit, pos.begin(), "%s" % filename)
                break               
        else:
            self.view.run_command("paste")

    def get_filename(self):
        view = self.view
        filename = view.file_name()
        if filename is None:
            # raise RuntimeError("Please save the file first!")
            return None
        else:
            filename = os.path.normpath(os.path.expanduser(filename))

        # create dir in current path with the name of current filename
        dirname, _ = os.path.splitext(filename)
        sub_dir = dirname[len(self.project_dir) + 1:]
        # print("sub_dir", sub_dir)

        # create new image file under currentdir/filename_without_ext/filename_without_ext%d.png
        fn_without_ext = os.path.basename(dirname)
        full_image_dir = os.path.join(self.project_dir, self.image_dir, sub_dir)
        # print("full_image_dir", full_image_dir)
        if not os.path.lexists(full_image_dir):
            os.makedirs(full_image_dir)
        
        i = 0
        while True:
            # absolute file path
            abs_filename = os.path.join(full_image_dir, "%s%d.png" % (fn_without_ext, i))
            if not os.path.exists(abs_filename):
                break
            i += 1

        # print("save file: " + abs_filename)
        return abs_filename


    def paste_image(self, filename):
        '''
        成功:返回格式为 (width, height),失败:返回 None
        '''
        # 内部没有pillow的lib,用外包python执行
        command = 'python3 "%s" save "%s"' % (os.path.join(os.path.dirname(__file__), 'bin/imageutils.py'), filename)
        # print(command)
        out = self.run_command(command)
        # print("out:" + out + ":end")
        if out and out[-2:-1] == "0":
            return out.split("\n")[0].split(",")
        else:
            return None

    def run_command(self, cmd):
        filename = self.view.file_name()
        if filename is None:
            cwd = "~"
        else:
            cwd = os.path.dirname(filename)
        # print("cmd %r" % cmd)
        proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=os.environ)
        
        try:
            outs, errs = proc.communicate(timeout=15)
            # print("outs %r %r" % (outs, proc))
        except Exception:
            proc.kill()
            outs, errs = proc.communicate()
        print("outs %r, errs %r" % (b'\n'.join(outs.split(b'\r\n')), errs))
        if errs is None or len(errs) == 0:
            return outs.decode()    

bin/imageutils.py

"""
pip install pillow
"""
import sys
import os
from PIL import Image, ImageGrab

def getSize(filename):
    with Image.open(filename) as im:
        # print("file: %s with size: %d %d" % (file1, im.width, im.height))
        return (im.width, im.height)

def saveImagefile(filename):
    im = ImageGrab.grabclipboard()
    if isinstance(im, Image.Image):
        # retina screen should scale 0.5
        # if sys.platform == 'darwin':
        #     im = im.resize((im.size[0] // 2, im.size[1] // 2), Image.ANTIALIAS)
        print("%d,%d" % (im.size[0], im.size[1]))
        im.save(filename)
        return 0
    else:
        return 1

if __name__ == '__main__':
    if len(sys.argv) == 3:
        # python imageutil.py size filename
        # print("begin %r" % sys.stdin.encoding)
        if sys.argv[1] == 'size':
            print("%d,%d" % getSize(sys.argv[2]))
        elif sys.argv[1] == 'save':
            print(saveImagefile(sys.argv[2]))