我正在构建一个Sublime Text 3插件,以使用goo.gl API缩短网址。请记住,以下代码是从其他插件和教程代码一起被黑客攻击的。我对Python有没有经验。
插件确实实际上是按原样工作的。 URL缩短并替换为内联。这是插件代码:
import sublime
import sublime_plugin
import urllib.request
import urllib.error
import json
import threading
class ShortenUrlCommand(sublime_plugin.TextCommand):
def run(self, edit):
sels = self.view.sel()
threads = []
for sel in sels:
url = self.view.substr(sel)
thread = GooglApiCall(sel, url, 5) # Send the selection, the URL and timeout to the class
threads.append(thread)
thread.start()
# Wait for threads
for thread in threads:
thread.join()
self.view.sel().clear()
self.handle_threads(edit, threads, sels)
def handle_threads(self, edit, threads, sels, offset=0, i=0, dir=1):
next_threads = []
for thread in threads:
sel = thread.sel
result = thread.result
if thread.is_alive():
next_threads.append(thread)
continue
if thread.result == False:
continue
offset = self.replace(edit, thread, sels, offset)
thread = next_threads
if len(threads):
before = i % 8
after = (7) - before
if not after:
dir = -1
if not before:
dir = 1
i += dir
self.view.set_status("shorten_url", "[%s=%s]" % (" " * before, " " * after))
sublime.set_timeout(lambda: self.handle_threads(edit, threads, sels, offset, i, dir), 100)
return
self.view.erase_status("shorten_url")
selections = len(self.view.sel())
sublime.status_message("URL shortener successfully ran on %s URL%s" %
(selections, "" if selections == 1 else "s"))
def replace(self, edit, thread, sels, offset):
sel = thread.sel
result = thread.result
if offset:
sel = sublime.Region(edit, thread.sel.begin() + offset, thread.sel.end() + offset)
self.view.replace(edit, sel, result)
return
class GooglApiCall(threading.Thread):
def __init__(self, sel, url, timeout):
self.sel = sel
self.url = url
self.timeout = timeout
self.result = None
threading.Thread.__init__(self)
def run(self):
try:
apiKey = "xxxxxxxxxxxxxxxxxxxxxxxx"
requestUrl = "https://www.googleapis.com/urlshortener/v1/url"
data = json.dumps({"longUrl": self.url})
binary_data = data.encode("utf-8")
headers = {
"User-Agent": "Sublime URL Shortener",
"Content-Type": "application/json"
}
request = urllib.request.Request(requestUrl, binary_data, headers)
response = urllib.request.urlopen(request, timeout=self.timeout)
self.result = json.loads(response.read().decode())
self.result = self.result["id"]
return
except (urllib.error.HTTPError) as e:
err = "%s: HTTP error %s contacting API. %s." % (__name__, str(e.code), str(e.reason))
except (urllib.error.URLError) as e:
err = "%s: URL error %s contacting API" % (__name__, str(e.reason))
sublime.error_message(err)
self.result = False
问题是每次插件运行时我都会在控制台中收到以下错误:
Traceback (most recent call last):
File "/Users/joejoinerr/Library/Application Support/Sublime Text 3/Packages/URL Shortener/url_shortener.py", line 51, in <lambda>
sublime.set_timeout(lambda: self.handle_threads(edit, threads, sels, offset, i, dir), 100)
File "/Users/joejoinerr/Library/Application Support/Sublime Text 3/Packages/URL Shortener/url_shortener.py", line 39, in handle_threads
offset = self.replace(edit, thread, sels, offset)
File "/Users/joejoinerr/Library/Application Support/Sublime Text 3/Packages/URL Shortener/url_shortener.py", line 64, in replace
self.view.replace(edit, sel, result)
File "/Applications/Sublime Text.app/Contents/MacOS/sublime.py", line 657, in replace
raise ValueError("Edit objects may not be used after the TextCommand's run method has returned")
ValueError: Edit objects may not be used after the TextCommand's run method has returned
我不确定该错误是什么问题。我做了一些研究,我理解解决方案可以在this question的答案中进行,但由于我缺乏Python知识,我无法弄清楚如何使其适应我的用例。
答案 0 :(得分:2)
我正在为Sublime搜索Python自动完成插件并找到了这个问题。我喜欢你的插件想法。你有没有得到它的工作? ValueError
告诉您在edit
返回后,您尝试将ShortenUrlCommand.run
参数用于ShortenUrlCommand.run
。我认为您可以使用begin_edit
和end_edit
在Sublime Text 2中执行此操作,但在3中,您的插件必须在run
返回(https://www.sublimetext.com/docs/3/porting_guide.html)之前完成所有编辑。
在您的代码中,handle_threads
函数每100毫秒检查GoogleApiCall
个线程,并对已完成的任何线程执行替换。但是handle_threads
有一个错字导致它永远运行:thread = next_threads
它应该是threads = next_threads
。这意味着永远不会从活动线程列表中删除已完成的线程,并且在handle_threads
的每次调用中都会处理所有线程(最终抛出您看到的异常)。
您实际上并不需要担心GoogleApiCall
踏板是否已在handle_threads
中完成,因为您在调用join
之前在每个踏板上调用handle_threads
(有关threading
:https://docs.python.org/2/library/threading.html)的详细信息,请参阅python join
文档。您知道线程已完成,因此您可以执行以下操作:
def handle_threads(self, edit, threads, sels):
offset = 0
for thread in threads:
if thread.result:
offset = self.replace(edit, thread, sels, offset)
selections = len(threads)
sublime.status_message("URL shortener successfully ran on %s URL%s" %
(selections, "" if selections == 1 else "s"))
这仍有问题:它无法正确处理多个选择,并且它会阻止Sublime中的UI线程。
当您替换多个选择时,您必须考虑替换文本的长度可能与它替换的文本的长度不同。这会将文本移到后面,您必须调整后续选定区域的索引。例如,假设在以下文本中选择了URL,并且您要用缩短的URL替换它们:
1 2 3 4 5 6 7
01234567890123456789012345678901234567890123456789012345678901234567890123
blah blah http://example.com/long blah blah http://example.com/longer blah
第二个URL占用索引44到68.替换第一个URL后,我们有:
1 2 3 4 5 6 7
01234567890123456789012345678901234567890123456789012345678901234567890123
blah blah http://goo.gl/abc blah blah http://example.com/longer blah
现在第二个URL占用了索引38到62.它被移动了-6:我们刚刚替换的字符串长度与我们替换它的字符串长度之间的差异。您需要跟踪这些差异,并在每次更换后更新它。看起来您已经考虑过offset
这个问题了,但是从来没有实现它。
def handle_threads(self, edit, threads, sels):
offset = 0
for thread in threads:
if thread.result:
offset = self.replace(edit, thread.sel, thread.result, offset)
selections = len(threads)
sublime.status_message("URL shortener successfully ran on %s URL%s" %
(selections, "" if selections == 1 else "s"))
def replace(self, edit, selection, replacement_text, offset):
# Adjust the selection region to account for previous replacements
adjusted_selection = sublime.Region(selection.begin() + offset,
selection.end() + offset)
self.view.replace(edit, adjusted_selection, replacement_text)
# Update the offset for the next replacement
old_len = selection.size()
new_len = len(replacement_text)
delta = new_len - old_len
new_offset = offset + delta
return new_offset
我不熟悉Sublime插件,因此我查看了如何在Gist插件中处理它(https://github.com/condemil/Gist)。它们在HTTP请求期间阻止UI线程。这似乎是不可取的,但我认为如果您不阻止可能会出现问题:用户可能会在插件完成更新之前更改文本缓冲区并使选择索引无效。如果您想沿着这条路前进,可以尝试将网址缩短调用移至WindowCommand
。然后,一旦有了替换文本,就可以在每个当前视图上执行替换命令。此示例获取当前视图并在其上执行ShortenUrlCommand
。您必须将收集缩短网址的代码移至ShortenUrlWrapperCommand.run
:
class ShortenUrlWrapperCommand(sublime_plugin.WindowCommand):
def run(self):
view = self.window.active_view()
view.run_command("shorten_url")