Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Full Sublime Text 3 compatibility. #69

Merged
merged 3 commits into from
@cluther

I've been trying to port some of my favorite Sublime Text 2 plugins to Sublime Text 3.

I believe I've tested all functionality and have everything working in ST2. However, it isn't backwards compatible with ST2.

I'll let you decide how you want to handle compatibility. I see that you were making some initial attempt at importing from future to attempt to keep it compatible. I plan on keeping this in a python3 branch for now, and certainly wouldn't mind if you did the same.

Thanks for the plugin!

@plukevdh

:+1: please pull

@mhenrixon

Amen! Would love this

@JustSid

+1

@jcontonio

This is what I get in the error log

File "/Users/.../Sublime Text 3/Packages/Gist/gist.py", line 337, in api_request_native
request.add_header('Authorization', 'token ' + token_auth_string())
TypeError: Can't convert 'bytes' object to str implicitly
error: Gist: unknown error (please, report a bug!)

@cluther

@jcontonio This is one of the many fixes already in the pull request.

@jcontonio

Ah cool thanks @cluther

@huglester

any chance it would get merged? at least to different branch?

@ninnypants

I get this error whenever trying to do anything with the plugin. My .../User/Gist.sublime-settings file has a token and username and password in it.

Gist: GitHub username or password isn't provided in  Gist.sublime-settings file

Current Build is: 3021

@cluther

I just amended the pull request with a fix to the problem @ninnypants reported. I don't know how this was working for me previously. I had to move all of the initialization code out of the module scope to comply with the new Sublime Text 3 API, specifically the "Restricted API Usage at Startup" section of the following document.

http://www.sublimetext.com/docs/3/porting_guide.html

@huglester

thank you @cluther, for now I've cloned your python3 repo and it's working.

@ptim

thanks @cluther i can create private gists with your fork, though I can't open gists.. good enough!

@cluther

@ptim: If you open the Sublime Text console and attempt to open a gist, do you see any errors? This works for me, but it's pretty slow and I only have ~50 gists.

@ptim

@cluther i see:

Traceback (most recent call last):
File "~/Library/Application Support/Sublime Text 3/Packages/Gist/gist.py", line 197, in _fn
return fn(*args, **kwargs)
File "~/Library/Application Support/Sublime Text 3/Packages/Gist/gist.py", line 669, in run
self.get_window().show_quick_panel(gist_names, on_gist_num)
File "/Applications/Sublime Text.app/Contents/MacOS/sublime.py", line 301, in show_quick_panel
items_per_row, on_select, on_highlight, flags, selected_index)
TypeError: String required
error: Gist: unknown error (please, report a bug!)

(I've substituted ~)

@cluther

@ptim Do you use any non-default parameters in your Gist.sublime-settings? Such as include_orgs, include_users, show_authors or other? Can you show your Gist.sublime-settings with credentials removed?

I can't reproduce this, and am having a hard time guessing what could be going on just from that traceback and the code.

Do you have any gists? Considering that you're able to create private gists, I'm assuming you do. But it can't hurt to ask.

@ptim

thanks for the help @cluther - i think that include_orgs was the culprit - i set it to false and I can now create, open, update - awesome! thanks!

For reference, my user settings:

{
    // Your username on GitHub
    "username": "ptim",

    // Your password on GitHub
    "password": "###",

    // Your GitHub API token
    // see: https://github.com/condemil/Gist#generating-access-token
    "token": "###",

    // Show GitHub organizations
    "include_orgs": false,
    // "include_orgs": ["company1", "company2"],
}
@nathankot

@ptim you probably want to remove your access credentials from those settings...

@ptim

how embarrassing!!! tx for the heads up @nathankot ... I had replaced them with ### as above, but looks like there was a markdown preview/save glitch :(

PW changed, API regenerated.

@ninnypants

@cluther So I forgot to come back and let you know that cleared up the problem for me. Though I do have a problem when listing gists. Sublime Text will spin and actually go unresponsive for a second then pop up the error.

error: Gist: unknown error (please, report a bug!)
@Tyderion

@cluther If I use your python3 branch I get the following error (the same as a commentor earlier, though you told him this is fixed in the pull request):

[...]Packages/Gist/gist.py", line 337, in api_request_native
    request.add_header('Authorization', 'token ' + token_auth_string())
TypeError: Can't convert 'bytes' object to str implicitly
@cluther

@Tyderion: The file and line number in that error don't correspond to what's in my python3 branch. Are you sure you have the latest of the python3 branch of the cluther/Gist repository?

@Tyderion

@cluther HA! thanks for the tip, I forgot to checkout the python3 branch xD now that is a stupid mistake :)
Thanks for answering this fast! :+1:

@jonschlinkert

Any chance this will be merged soon?

@mhenrixon

take a look at sublime-github instead. Works great in sublime text 3.

@cluther

Thanks @mhenrixon. That looks like the way to go.

@pgolm

The python version for Linux isn't compiled with SSL :cry:, so i get this error:

Traceback (most recent call last):
  File "/home/pgolm/.config/sublime-text-3/Packages/Gist/gist.py", line 197, in _fn
    return fn(*args, **kwargs)
  File "/home/pgolm/.config/sublime-text-3/Packages/Gist/gist.py", line 620, in run
    filtered = gists_filter(get_gists())
  File "/home/pgolm/.config/sublime-text-3/Packages/Gist/gist.py", line 305, in get_gists
    return api_request(GISTS_URL)
  File "/home/pgolm/.config/sublime-text-3/Packages/Gist/gist.py", line 382, in api_request_native
    with contextlib.closing(urllib.urlopen(request)) as response:
  File "X/urllib/request.py", line 160, in urlopen
  File "X/urllib/request.py", line 473, in open
  File "X/urllib/request.py", line 496, in _open
  File "X/urllib/request.py", line 451, in _call_chain
  File "X/urllib/request.py", line 1314, in unknown_open
urllib.error.URLError: <urlopen error unknown url type: https>
error: Gist: Error while contacting GitHub
@condemil condemil merged commit c4e8cf9 into condemil:master
@condemil
Owner

@cluther where you found the documentation regarding

view.run_command('append', {
    'characters': gist['files'][gist_filename]['content'],
})

and

view.run_command('insert', {
    'characters': gist['files'][gist_filename]['content'],
})

@condemil: I found it in the Sublime Text 3 porting guide. In the Restricted begin_edit() and end_edit() section.

http://www.sublimetext.com/docs/3/porting_guide.html

Owner

@cluther thank you, but I know about this article and read it several times. I mean where I can get a list of commands, like append or delete and parameters of this commands, like characters.

@condemil: Ah, that is very hard information to find. I learn about these mostly by opening the console and running sublime.log_commands(True) then performing the action I want my plugin to do. This is quite useful. Other than that I find a plugin that does something similar to what I want to do and look at its source.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 6, 2013
  1. @cluther
Commits on Feb 7, 2013
  1. @cluther
Commits on Mar 25, 2013
  1. @cluther
This page is out of date. Refresh to see the latest.
Showing with 99 additions and 50 deletions.
  1. +99 −50 gist.py
View
149 gist.py
@@ -1,4 +1,4 @@
-from __future__ import print_function
+
import sublime
import sublime_plugin
import os
@@ -13,40 +13,69 @@
import contextlib
import shutil
import re
+import codecs
try:
import urllib2 as urllib
except ImportError: # Python 3
import urllib.request as urllib
-DEFAULT_CREATE_PUBLIC_VALUE = 'false'
-DEFAULT_USE_PROXY_VALUE = 'false'
-settings = sublime.load_settings('Gist.sublime-settings')
-GISTS_URL = 'https://api.github.com/gists'
-USER_GISTS_URL = 'https://api.github.com/users/%s/gists'
-ORGS_URL = 'https://api.github.com/user/orgs'
-ORG_MEMBERS_URL = 'https://api.github.com/orgs/%s/members'
-
-#Enterprise support:
-if settings.get('enterprise'):
- GISTS_URL = settings.get('url')
- if not GISTS_URL:
- raise MissingCredentialsException()
- GISTS_URL += '/api/v3/gists'
-
-#Per page support (max 100)
-if settings.get('max_gists'):
- if settings.get('use_starred'):
- GISTS_URL += '/starred'
- USER_GISTS_URL += '/starred'
-
- if settings.get('max_gists') <= 100:
- MAX_GISTS = '?per_page=%d' % settings.get('max_gists')
- GISTS_URL += MAX_GISTS
- USER_GISTS_URL += MAX_GISTS
- else:
- settings.set( "max_gists",100 )
- sublime.status_message("Gist: GitHub API does not support a value of higher than 100")
+
+global settings
+global DEFAULT_CREATE_PUBLIC_VALUE
+global DEFAULT_USE_PROXY_VALUE
+global GISTS_URL
+global USER_GISTS_URL
+global ORGS_URL
+global ORG_MEMBERS_URL
+
+
+def initialize_globals():
+ '''
+ Initialize globals. In Sublime Text 3 this can no longer me done in
+ the module scope.
+
+ See "Restricted API Usage at Startup" in the following document.
+ http://www.sublimetext.com/docs/3/porting_guide.html
+ '''
+ global settings
+ global DEFAULT_CREATE_PUBLIC_VALUE
+ global DEFAULT_USE_PROXY_VALUE
+ global GISTS_URL
+ global USER_GISTS_URL
+ global ORGS_URL
+ global ORG_MEMBERS_URL
+
+ settings = sublime.load_settings('Gist.sublime-settings')
+ DEFAULT_CREATE_PUBLIC_VALUE = 'false'
+ DEFAULT_USE_PROXY_VALUE = 'false'
+ GISTS_URL = 'https://api.github.com/gists'
+ USER_GISTS_URL = 'https://api.github.com/users/%s/gists'
+ ORGS_URL = 'https://api.github.com/user/orgs'
+ ORG_MEMBERS_URL = 'https://api.github.com/orgs/%s/members'
+
+ #Enterprise support:
+ if settings.get('enterprise'):
+ GISTS_URL = settings.get('url')
+ if not GISTS_URL:
+ raise MissingCredentialsException()
+ GISTS_URL += '/api/v3/gists'
+
+ #Per page support (max 100)
+ if settings.get('max_gists'):
+ if settings.get('use_starred'):
+ GISTS_URL += '/starred'
+ USER_GISTS_URL += '/starred'
+
+ if settings.get('max_gists') <= 100:
+ MAX_GISTS = '?per_page=%d' % settings.get('max_gists')
+ GISTS_URL += MAX_GISTS
+ USER_GISTS_URL += MAX_GISTS
+ else:
+ settings.set('max_gists', 100)
+ sublime.status_message(
+ "Gist: GitHub API does not support a value of higher than 100")
+
class MissingCredentialsException(Exception):
pass
@@ -70,8 +99,8 @@ def get_credentials():
return (username, password)
def basic_auth_string():
- auth_string = u'%s:%s' % get_credentials()
- return auth_string.encode('utf-8')
+ auth_string = '%s:%s' % get_credentials()
+ return bytes(auth_string, 'ascii')
def get_token():
token = settings.get('token')
@@ -80,13 +109,13 @@ def get_token():
return token
def token_auth_string():
- auth_string = u'%s' % get_token()
- return auth_string.encode('utf-8')
+ auth_string = '%s' % get_token()
+ return auth_string
if sublime.platform() == 'osx':
# Keychain support
# Instead of Gist.sublime-settings, fetch username and password from the user's github.com keychain entry
- SERVER = 'github.com'
+ SERVER = b'github.com'
def create_keychain_accessor():
from ctypes import cdll, util, c_uint32, c_int, c_char_p, c_void_p, POINTER, pointer, byref, Structure, string_at
@@ -187,7 +216,7 @@ def _fn(*args, **kwargs):
except SimpleHTTPError as err:
msg = "Gist: GitHub returned error %d" % err.code
try:
- response_json = json.loads(err.response)
+ response_json = json.loads(err.response.decode('ascii'))
response_msg = response_json.get('message')
if response_msg:
msg += ": " + response_msg
@@ -200,7 +229,7 @@ def _fn(*args, **kwargs):
return _fn
def create_gist(public, description, files):
- file_data = dict((filename, {'content': text}) for filename, text in files.items())
+ file_data = dict((filename, {'content': text}) for filename, text in list(files.items()))
data = json.dumps({'description': description, 'public': public, 'files': file_data})
gist = api_request(GISTS_URL, data)
return gist
@@ -243,9 +272,9 @@ def open_gist(gist_url):
gistify_view(view, gist, gist_filename)
- edit = view.begin_edit()
- view.insert(edit, 0, gist['files'][gist_filename]['content'])
- view.end_edit(edit)
+ view.run_command('append', {
+ 'characters': gist['files'][gist_filename]['content'],
+ })
if not "language" in gist['files'][gist_filename]: continue
@@ -268,12 +297,9 @@ def insert_gist(gist_url):
files = sorted(gist['files'].keys())
for gist_filename in files:
view = sublime.active_window().active_view()
- edit = view.begin_edit()
- for region in view.sel():
-
- view.replace(edit, region, gist['files'][gist_filename]['content'])
-
- view.end_edit(edit)
+ view.run_command('insert', {
+ 'characters': gist['files'][gist_filename]['content'],
+ })
def get_gists():
return api_request(GISTS_URL)
@@ -336,12 +362,15 @@ def api_request_native(url, data=None, method=None):
try:
request.add_header('Authorization', 'token ' + token_auth_string())
except MissingTokenException:
- request.add_header('Authorization', 'Basic ' + base64.urlsafe_b64encode(basic_auth_string()))
+ request.add_header(
+ 'Authorization',
+ 'Basic ' + str(base64.urlsafe_b64encode(basic_auth_string())))
+
request.add_header('Accept', 'application/json')
request.add_header('Content-Type', 'application/json')
if data is not None:
- request.add_data(data)
+ request.add_data(bytes(data, 'ASCII'))
if settings.get('https_proxy'):
opener = urllib.build_opener(urllib.HTTPHandler(), urllib.HTTPSHandler(),
@@ -354,7 +383,8 @@ def api_request_native(url, data=None, method=None):
if response.code == 204: # No Content
return None
else:
- return json.loads(response.read())
+ return json.loads(response.read().decode('ascii'))
+
except urllib.HTTPError as err:
with contextlib.closing(err):
raise SimpleHTTPError(err.code, err.read())
@@ -411,7 +441,7 @@ def api_request_curl(url, data=None, method=None):
if responsecode == 204: # No Content
return None
elif 200 <= responsecode < 300 or responsecode == 100: # Continue
- return json.loads(response)
+ return json.loads(response.decode('ascii'))
else:
raise SimpleHTTPError(responsecode, response)
@@ -425,6 +455,8 @@ def mode(self):
@catch_errors
def run(self, edit):
+ initialize_globals()
+
try:
get_token()
except MissingTokenException:
@@ -470,7 +502,7 @@ def on_gist_filename(filename):
sublime.status_message("%s Gist: %s" % (self.mode(), gist_html_url))
if gistify:
- gistify_view(self.view, gist, gist['files'].keys()[0])
+ gistify_view(self.view, gist, list(gist['files'].keys())[0])
# else:
# open_gist(gist['url'])
@@ -483,6 +515,9 @@ class GistViewCommand(object):
def is_enabled(self):
return self.gist_url() is not None
+ def run(self, edit):
+ initialize_globals()
+
def gist_url(self):
return self.view.settings().get("gist_url")
@@ -497,14 +532,18 @@ def gist_description(self):
class GistCopyUrl(GistViewCommand, sublime_plugin.TextCommand):
def run(self, edit):
+ GistViewCommand.run(self, edit)
sublime.set_clipboard(self.gist_html_url())
class GistOpenBrowser(GistViewCommand, sublime_plugin.TextCommand):
def run(self, edit):
+ GistViewCommand.run(self, edit)
webbrowser.open(self.gist_html_url())
class GistRenameFileCommand(GistViewCommand, sublime_plugin.TextCommand):
def run(self, edit):
+ GistViewCommand.run(self, edit)
+
old_filename = self.gist_filename()
@catch_errors
@@ -520,6 +559,8 @@ def on_filename(filename):
class GistChangeDescriptionCommand(GistViewCommand, sublime_plugin.TextCommand):
def run(self, edit):
+ GistViewCommand.run(self, edit)
+
@catch_errors
def on_gist_description(description):
if description and description != self.gist_description():
@@ -536,6 +577,8 @@ def on_gist_description(description):
class GistUpdateFileCommand(GistViewCommand, sublime_plugin.TextCommand):
@catch_errors
def run(self, edit):
+ GistViewCommand.run(self, edit)
+
text = self.view.substr(sublime.Region(0, self.view.size()))
changes = {self.gist_filename(): {'content': text}}
update_gist(self.gist_url(), changes)
@@ -544,6 +587,8 @@ def run(self, edit):
class GistDeleteFileCommand(GistViewCommand, sublime_plugin.TextCommand):
@catch_errors
def run(self, edit):
+ GistViewCommand.run(self, edit)
+
changes = {self.gist_filename(): None}
update_gist(self.gist_url(), changes)
ungistify_view(self.view)
@@ -552,6 +597,8 @@ def run(self, edit):
class GistDeleteCommand(GistViewCommand, sublime_plugin.TextCommand):
@catch_errors
def run(self, edit):
+ GistViewCommand.run(self, edit)
+
gist_url = self.gist_url()
api_request(gist_url, method='DELETE')
for window in sublime.windows():
@@ -568,6 +615,8 @@ class GistListCommandBase(object):
@catch_errors
def run(self, *args):
+ initialize_globals()
+
filtered = gists_filter(get_gists())
self.gists = filtered[0]
gist_names = filtered[1]
Something went wrong with that request. Please try again.