Porting vim-copycat Python 2 Code to Python 3

用了 3 年多的 LinuxMint 前陣子宣佈 LinuxMint 18.3 是 KDE edition 最後一個版本,然後 .. 就沒有然後了 T_T

索性昨天把系統重新裝成 Kubuntu 18.04 (真心很喜歡 KDE Plasma),不得不說重裝 Unix-like 系統感覺真的超好,重裝 / 保留 /home 的情況,Firefox / Chrome / Opera 全部都不需要重新設定 (設定檔保留於 /home)。

不過裝完 vim 8 之後,遇到一個很大的問題,一直在使用的 vim-copycat(georgefs/vim-copycat)

E319: Sorry, the command isn’t available in this version: python

vim-copycat(georgefs/vim-copycat) 是用於同步系統跟 vim 的剪貼板 (clipboard)

輸入指令:

1
~$ vim --version | grep python

顯示:

1
2
+comments          +libcall           -python            +vreplace
+conceal +linebreak +python3 +wildignore

在 Kubuntu 18.04 使用 apt-get 安裝 Ubuntu repository 中的 vim 已經拿掉了 Python 2 的支援,導致 .vim/bundle/vim-copycat/plugin/copycat.vim 使用 Python 2 出現問題

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
Error detected while processing /home/ming/.vim/bundle/vim-copycat/plugin/copycat.vim:
line 23:
E319: Sorry, the command is not available in this version: python << EOF
line 24:
E492: Not an editor command: import sys
line 25:
E492: Not an editor command: import vim
line 26:
E492: Not an editor command: sys.path.append(vim.eval('expand("<sfile>:p:h:h")'))
line 27:
E492: Not an editor command: import copycat_plugin
line 28:
E492: Not an editor command: EOF

我決定採用解決方式是將 Python 2 的代碼改成 Python 3,下面將一步一步紀錄這個過程。

事前準備:

1
2
sudo pip3 install copycat-clipboard
sudo apt-get install xclip

首先最直觀的根據錯誤紀錄將 .vim/bundle/vim-copycat/plugin/copycat.vim 中的 python 全部取代為 python 3

1
\:1,$ s/python/python3/g

儲存之後,接下來打開 vim 錯誤變成:

1
2
3
4
5
6
7
8
9
10
Error detected while processing /home/ming/.vim/bundle/vim-copycat/plugin/copycat.vim:
line 28:
Traceback (most recent call last):
File "<string>", line 4, in <module>
File "/home/ming/.vim/bundle/vim-copycat/copycat_plugin.py", line 11, in <module>
import copycat
File "/usr/local/lib/python3.6/dist-packages/copycat.py", line 73
print 'no reg {}'.format(name)
^
SyntaxError: invalid syntax

很明顯是 Python 2 的 print 用法,解決的方法很簡單,將所有 copycat.py 中的 print “” 改成 print(“”)

1
:1,$ s/python/python3/g

接下來錯誤變成

1
2
3
4
5
6
7
8
9
10
11
12
13
Error detected while processing function <SNR>36_push_into_clip:                                                                                                                                
line 4:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/home/ming/.vim/bundle/vim-copycat/copycat_plugin.py", line 37, in deco
result = func(*args, **kwargs)
File "/home/ming/.vim/bundle/vim-copycat/copycat_plugin.py", line 53, in copy
copycat.copy(name=name, value=value)
File "/usr/local/lib/python3.6/dist-packages/copycat.py", line 96, in copy
value = smart_str(value)
File "/usr/local/lib/python3.6/dist-packages/copycat.py", line 20, in smart_str
if not isinstance(s, basestring):
NameError: name 'basestring' is not defined

一樣是 copycat.py,將 copycat.py 中的 smart_str 函數改為:

1
2
3
4
5
6
7
8
9
19 def smart_str(s, encoding='utf-8', errors='strict'):
20 #if not isinstance(s, basestring):
21 # s = str(s)
22 #elif isinstance(s, unicode):
23 # return s.encode(encoding, errors)
24 #elif s and encoding != 'utf-8':
25 # return s.decode('utf-8', errors).encode(encoding, errors)
26 #else:
27 return s

錯誤變成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Error detected while processing function <SNR>36_push_into_clip:                                                                                                                                
line 4:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/home/ming/.vim/bundle/vim-copycat/copycat_plugin.py", line 37, in deco
result = func(*args, **kwargs)
File "/home/ming/.vim/bundle/vim-copycat/copycat_plugin.py", line 53, in copy
copycat.copy(name=name, value=value)
File "/usr/local/lib/python3.6/dist-packages/copycat.py", line 100, in copy
clipboard.copy(value)
File "/usr/local/lib/python3.6/dist-packages/clipboard.py", line 31, in copy
pipe.communicate(text)
File "/usr/lib/python3.6/subprocess.py", line 828, in communicate
self._stdin_write(input)
File "/usr/lib/python3.6/subprocess.py", line 781, in _stdin_write
self.stdin.write(input)
TypeError: a bytes-like object is required, not 'str'

將 copycat.py 中的 copy 函數改成:

1
2
3
4
5
6
7
8
def copy(value=None, name=None):
value = value or not sys.stdin.isatty() and sys.stdin.read()

#value = smart_str(value)
with Storage() as storage:
storage.save(value, name=name)
if not name:
clipboard.copy(value.encode('utf-8'))

copy 函數正常後,現在已經可以正常複製,接下來 paste 錯誤訊息:

1
2
3
4
5
6
7
8
9
10
11
12
Error detected while processing function <SNR>36_pop_from_clip:                                                                                                                                 
line 3:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/home/ming/.vim/bundle/vim-copycat/copycat_plugin.py", line 39, in deco
set_to_vim(result_reg, result)
File "/home/ming/.vim/bundle/vim-copycat/copycat_plugin.py", line 25, in set_to_vim
if not isinstance(name, basestring):
NameError: name 'basestring' is not defined
line 4:
E121: Undefined variable: l:result
E15: Invalid expression: l:result

將 copycat.py 中的 paste 函數修改為:

1
2
3
4
5
6
7
8
9
def paste(name=None):
with Storage() as storage:
if not name:
data = clipboard.paste() or storage.get()
else:
data = storage.get(name)
#data = smart_str(data)
clipboard.copy(data)
return data

打開 copycat_plugin.py 將 set_to_vim 函數改成:

1
2
3
4
5
6
7
8
9
def set_to_vim(name, value):
#if not isinstance(name, basestring):
# return False

#if isinstance(value, basestring):
value = re.sub(r'([\"\\])', r'\\\1', value.decode('utf-8'))
vim.command('let {}=\"{}\"'.format(name, value))
return True
#return False

完成!

取之於開源,回饋於開源

如果不想要手動改的話,可以參考:

https://github.com/iwdmb/vim-copycat
https://github.com/iwdmb/copycat

我 fork 了 vim-copycat and copycat(copycat-clipboard) 使其支援 Python 3 : )

copycat-clipboard3 pypi repository url:https://pypi.org/project/copycat-clipboard3/