feat(SublimeText2.GitPackages): cache packages
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
1
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/EditorConfig/.gitattributes
vendored
Normal file
1
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/EditorConfig/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto
|
@@ -0,0 +1,71 @@
|
|||||||
|
import sublime_plugin
|
||||||
|
from editorconfig import get_properties, EditorConfigError
|
||||||
|
|
||||||
|
|
||||||
|
LINE_ENDINGS = {
|
||||||
|
'lf': 'unix',
|
||||||
|
'crlf': 'windows',
|
||||||
|
'cr': 'cr'
|
||||||
|
}
|
||||||
|
|
||||||
|
CHARSETS = {
|
||||||
|
'latin1': 'Western (ISO 8859-1)',
|
||||||
|
'utf-8': 'utf-8',
|
||||||
|
'utf-8-bom': 'utf-8 with bom',
|
||||||
|
'utf-16be': 'utf-16 be',
|
||||||
|
'utf-16le': 'utf-16 le'
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditorConfig(sublime_plugin.EventListener):
|
||||||
|
def on_load(self, view):
|
||||||
|
self.init(view, False)
|
||||||
|
|
||||||
|
def on_pre_save(self, view):
|
||||||
|
self.init(view, True)
|
||||||
|
|
||||||
|
def init(self, view, pre_save):
|
||||||
|
path = view.file_name()
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
config = get_properties(path)
|
||||||
|
except EditorConfigError:
|
||||||
|
print 'Error occurred while getting EditorConfig properties'
|
||||||
|
else:
|
||||||
|
if config:
|
||||||
|
if pre_save:
|
||||||
|
self.apply_charset(view, config)
|
||||||
|
else:
|
||||||
|
self.apply_config(view, config)
|
||||||
|
|
||||||
|
def apply_charset(self, view, config):
|
||||||
|
charset = config.get('charset')
|
||||||
|
if charset in CHARSETS:
|
||||||
|
view.set_encoding(CHARSETS[charset])
|
||||||
|
|
||||||
|
def apply_config(self, view, config):
|
||||||
|
settings = view.settings()
|
||||||
|
indent_style = config.get('indent_style')
|
||||||
|
indent_size = config.get('indent_size')
|
||||||
|
end_of_line = config.get('end_of_line')
|
||||||
|
trim_trailing_whitespace = config.get('trim_trailing_whitespace')
|
||||||
|
insert_final_newline = config.get('insert_final_newline')
|
||||||
|
if indent_style == 'space':
|
||||||
|
settings.set('translate_tabs_to_spaces', True)
|
||||||
|
elif indent_style == 'tab':
|
||||||
|
settings.set('translate_tabs_to_spaces', False)
|
||||||
|
if indent_size:
|
||||||
|
try:
|
||||||
|
settings.set('tab_size', int(indent_size))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if end_of_line in LINE_ENDINGS:
|
||||||
|
view.set_line_endings(LINE_ENDINGS[end_of_line])
|
||||||
|
if trim_trailing_whitespace == 'true':
|
||||||
|
settings.set('trim_trailing_white_space_on_save', True)
|
||||||
|
elif trim_trailing_whitespace == 'false':
|
||||||
|
settings.set('trim_trailing_white_space_on_save', False)
|
||||||
|
if insert_final_newline == 'true':
|
||||||
|
settings.set('ensure_newline_at_eof_on_save', True)
|
||||||
|
elif insert_final_newline == 'false':
|
||||||
|
settings.set('ensure_newline_at_eof_on_save', False)
|
@@ -0,0 +1,19 @@
|
|||||||
|
"""EditorConfig Python Core"""
|
||||||
|
|
||||||
|
from editorconfig.versiontools import join_version
|
||||||
|
|
||||||
|
VERSION = (0, 11, 1, "final")
|
||||||
|
|
||||||
|
__all__ = ['get_properties', 'EditorConfigError', 'exceptions']
|
||||||
|
|
||||||
|
__version__ = join_version(VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
def get_properties(filename):
|
||||||
|
"""Locate and parse EditorConfig files for the given filename"""
|
||||||
|
handler = EditorConfigHandler(filename)
|
||||||
|
return handler.get_configurations()
|
||||||
|
|
||||||
|
|
||||||
|
from editorconfig.handler import EditorConfigHandler
|
||||||
|
from editorconfig.exceptions import *
|
@@ -0,0 +1,18 @@
|
|||||||
|
"""EditorConfig Python2/Python3/Jython compatibility utilities"""
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
__all__ = ['slice', 'u']
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
slice = types.SliceType
|
||||||
|
else:
|
||||||
|
slice = slice
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
import codecs
|
||||||
|
u = lambda s: codecs.unicode_escape_decode(s)[0]
|
||||||
|
else:
|
||||||
|
u = lambda s: s
|
@@ -0,0 +1,27 @@
|
|||||||
|
"""EditorConfig exception classes
|
||||||
|
|
||||||
|
Licensed under PSF License (see LICENSE.txt file).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class EditorConfigError(Exception):
|
||||||
|
"""Parent class of all exceptions raised by EditorConfig"""
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ConfigParser import ParsingError as _ParsingError
|
||||||
|
except:
|
||||||
|
from configparser import ParsingError as _ParsingError
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingError(_ParsingError, EditorConfigError):
|
||||||
|
"""Error raised if an EditorConfig file could not be parsed"""
|
||||||
|
|
||||||
|
|
||||||
|
class PathError(ValueError, EditorConfigError):
|
||||||
|
"""Error raised if invalid filepath is specified"""
|
||||||
|
|
||||||
|
|
||||||
|
class VersionError(ValueError, EditorConfigError):
|
||||||
|
"""Error raised if invalid version number is specified"""
|
@@ -0,0 +1,126 @@
|
|||||||
|
"""Filename matching with shell patterns.
|
||||||
|
|
||||||
|
fnmatch(FILENAME, PATTERN) matches according to the local convention.
|
||||||
|
fnmatchcase(FILENAME, PATTERN) always takes case in account.
|
||||||
|
|
||||||
|
The functions operate by translating the pattern into a regular
|
||||||
|
expression. They cache the compiled regular expressions for speed.
|
||||||
|
|
||||||
|
The function translate(PATTERN) returns a regular expression
|
||||||
|
corresponding to PATTERN. (It does not compile it.)
|
||||||
|
|
||||||
|
Based on code from fnmatch.py file distributed with Python 2.6.
|
||||||
|
|
||||||
|
Licensed under PSF License (see LICENSE.txt file).
|
||||||
|
|
||||||
|
Changes to original fnmatch module:
|
||||||
|
- translate function supports ``*`` and ``**`` similarly to fnmatch C library
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
__all__ = ["fnmatch", "fnmatchcase", "translate"]
|
||||||
|
|
||||||
|
_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def fnmatch(name, pat):
|
||||||
|
"""Test whether FILENAME matches PATTERN.
|
||||||
|
|
||||||
|
Patterns are Unix shell style:
|
||||||
|
|
||||||
|
- ``*`` matches everything except path separator
|
||||||
|
- ``**`` matches everything
|
||||||
|
- ``?`` matches any single character
|
||||||
|
- ``[seq]`` matches any character in seq
|
||||||
|
- ``[!seq]`` matches any char not in seq
|
||||||
|
- ``{s1,s2,s3}`` matches any of the strings given (separated by commas)
|
||||||
|
|
||||||
|
An initial period in FILENAME is not special.
|
||||||
|
Both FILENAME and PATTERN are first case-normalized
|
||||||
|
if the operating system requires it.
|
||||||
|
If you don't want this, use fnmatchcase(FILENAME, PATTERN).
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = os.path.normcase(name).replace(os.sep, "/")
|
||||||
|
return fnmatchcase(name, pat)
|
||||||
|
|
||||||
|
|
||||||
|
def fnmatchcase(name, pat):
|
||||||
|
"""Test whether FILENAME matches PATTERN, including case.
|
||||||
|
|
||||||
|
This is a version of fnmatch() which doesn't case-normalize
|
||||||
|
its arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not pat in _cache:
|
||||||
|
res = translate(pat)
|
||||||
|
_cache[pat] = re.compile(res)
|
||||||
|
return _cache[pat].match(name) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def translate(pat):
|
||||||
|
"""Translate a shell PATTERN to a regular expression.
|
||||||
|
|
||||||
|
There is no way to quote meta-characters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
i, n = 0, len(pat)
|
||||||
|
res = ''
|
||||||
|
escaped = False
|
||||||
|
while i < n:
|
||||||
|
c = pat[i]
|
||||||
|
i = i + 1
|
||||||
|
if c == '*':
|
||||||
|
j = i
|
||||||
|
if j < n and pat[j] == '*':
|
||||||
|
res = res + '.*'
|
||||||
|
else:
|
||||||
|
res = res + '[^/]*'
|
||||||
|
elif c == '?':
|
||||||
|
res = res + '.'
|
||||||
|
elif c == '[':
|
||||||
|
j = i
|
||||||
|
if j < n and pat[j] == '!':
|
||||||
|
j = j + 1
|
||||||
|
if j < n and pat[j] == ']':
|
||||||
|
j = j + 1
|
||||||
|
while j < n and (pat[j] != ']' or escaped):
|
||||||
|
escaped = pat[j] == '\\' and not escaped
|
||||||
|
j = j + 1
|
||||||
|
if j >= n:
|
||||||
|
res = res + '\\['
|
||||||
|
else:
|
||||||
|
stuff = pat[i:j]
|
||||||
|
i = j + 1
|
||||||
|
if stuff[0] == '!':
|
||||||
|
stuff = '^' + stuff[1:]
|
||||||
|
elif stuff[0] == '^':
|
||||||
|
stuff = '\\' + stuff
|
||||||
|
res = '%s[%s]' % (res, stuff)
|
||||||
|
elif c == '{':
|
||||||
|
j = i
|
||||||
|
groups = []
|
||||||
|
while j < n and pat[j] != '}':
|
||||||
|
k = j
|
||||||
|
while k < n and (pat[k] not in (',', '}') or escaped):
|
||||||
|
escaped = pat[k] == '\\' and not escaped
|
||||||
|
k = k + 1
|
||||||
|
group = pat[j:k]
|
||||||
|
for char in (',', '}', '\\'):
|
||||||
|
group = group.replace('\\' + char, char)
|
||||||
|
groups.append(group)
|
||||||
|
j = k
|
||||||
|
if j < n and pat[j] == ',':
|
||||||
|
j = j + 1
|
||||||
|
if j < n and pat[j] == '}':
|
||||||
|
groups.append('')
|
||||||
|
if j >= n or len(groups) < 2:
|
||||||
|
res = res + '\\{'
|
||||||
|
else:
|
||||||
|
res = '%s(%s)' % (res, '|'.join(map(re.escape, groups)))
|
||||||
|
i = j + 1
|
||||||
|
else:
|
||||||
|
res = res + re.escape(c)
|
||||||
|
return res + '\Z(?ms)'
|
@@ -0,0 +1,125 @@
|
|||||||
|
"""EditorConfig file handler
|
||||||
|
|
||||||
|
Provides ``EditorConfigHandler`` class for locating and parsing
|
||||||
|
EditorConfig files relevant to a given filepath.
|
||||||
|
|
||||||
|
Licensed under PSF License (see LICENSE.txt file).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from editorconfig import VERSION
|
||||||
|
from editorconfig.ini import EditorConfigParser
|
||||||
|
from editorconfig.exceptions import PathError, VersionError
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['EditorConfigHandler']
|
||||||
|
|
||||||
|
|
||||||
|
def get_filenames(path, filename):
|
||||||
|
"""Yield full filepath for filename in each directory in and above path"""
|
||||||
|
path_list = []
|
||||||
|
while True:
|
||||||
|
path_list.append(os.path.join(path, filename))
|
||||||
|
newpath = os.path.dirname(path)
|
||||||
|
if path == newpath:
|
||||||
|
break
|
||||||
|
path = newpath
|
||||||
|
return path_list
|
||||||
|
|
||||||
|
|
||||||
|
class EditorConfigHandler(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Allows locating and parsing of EditorConfig files for given filename
|
||||||
|
|
||||||
|
In addition to the constructor a single public method is provided,
|
||||||
|
``get_configurations`` which returns the EditorConfig options for
|
||||||
|
the ``filepath`` specified to the constructor.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, filepath, conf_filename='.editorconfig', version=None):
|
||||||
|
"""Create EditorConfigHandler for matching given filepath"""
|
||||||
|
self.filepath = filepath
|
||||||
|
self.conf_filename = conf_filename
|
||||||
|
self.version = version
|
||||||
|
self.options = None
|
||||||
|
|
||||||
|
def get_configurations(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Find EditorConfig files and return all options matching filepath
|
||||||
|
|
||||||
|
Special exceptions that may be raised by this function include:
|
||||||
|
|
||||||
|
- ``VersionError``: self.version is invalid EditorConfig version
|
||||||
|
- ``PathError``: self.filepath is not a valid absolute filepath
|
||||||
|
- ``ParsingError``: improperly formatted EditorConfig file found
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.check_assertions()
|
||||||
|
path, filename = os.path.split(self.filepath)
|
||||||
|
conf_files = get_filenames(path, self.conf_filename)
|
||||||
|
|
||||||
|
# Attempt to find and parse every EditorConfig file in filetree
|
||||||
|
for filename in conf_files:
|
||||||
|
parser = EditorConfigParser(self.filepath)
|
||||||
|
parser.read(filename)
|
||||||
|
|
||||||
|
# Merge new EditorConfig file's options into current options
|
||||||
|
old_options = self.options
|
||||||
|
self.options = parser.options
|
||||||
|
if old_options:
|
||||||
|
self.options.update(old_options)
|
||||||
|
|
||||||
|
# Stop parsing if parsed file has a ``root = true`` option
|
||||||
|
if parser.root_file:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.preprocess_values()
|
||||||
|
return self.options
|
||||||
|
|
||||||
|
def check_assertions(self):
|
||||||
|
|
||||||
|
"""Raise error if filepath or version have invalid values"""
|
||||||
|
|
||||||
|
# Raise ``PathError`` if filepath isn't an absolute path
|
||||||
|
if not os.path.isabs(self.filepath):
|
||||||
|
raise PathError("Input file must be a full path name.")
|
||||||
|
|
||||||
|
# Raise ``VersionError`` if version specified is greater than current
|
||||||
|
if self.version is not None and self.version[:3] > VERSION[:3]:
|
||||||
|
raise VersionError(
|
||||||
|
"Required version is greater than the current version.")
|
||||||
|
|
||||||
|
def preprocess_values(self):
|
||||||
|
|
||||||
|
"""Preprocess option values for consumption by plugins"""
|
||||||
|
|
||||||
|
opts = self.options
|
||||||
|
|
||||||
|
# Lowercase option value for certain options
|
||||||
|
for name in ["end_of_line", "indent_style", "indent_size",
|
||||||
|
"insert_final_newline", "trim_trailing_whitespace", "charset"]:
|
||||||
|
if name in opts:
|
||||||
|
opts[name] = opts[name].lower()
|
||||||
|
|
||||||
|
# Set indent_size to "tab" if indent_size is unspecified and
|
||||||
|
# indent_style is set to "tab".
|
||||||
|
if (opts.get("indent_style") == "tab" and
|
||||||
|
not "indent_size" in opts and self.version >= VERSION[:3]):
|
||||||
|
opts["indent_size"] = "tab"
|
||||||
|
|
||||||
|
# Set tab_width to indent_size if indent_size is specified and
|
||||||
|
# tab_width is unspecified
|
||||||
|
if ("indent_size" in opts and "tab_width" not in opts and
|
||||||
|
opts["indent_size"] != "tab"):
|
||||||
|
opts["tab_width"] = opts["indent_size"]
|
||||||
|
|
||||||
|
# Set indent_size to tab_width if indent_size is "tab"
|
||||||
|
if ("indent_size" in opts and "tab_width" in opts and
|
||||||
|
opts["indent_size"] == "tab"):
|
||||||
|
opts["indent_size"] = opts["tab_width"]
|
@@ -0,0 +1,150 @@
|
|||||||
|
"""EditorConfig file parser
|
||||||
|
|
||||||
|
Based on code from ConfigParser.py file distributed with Python 2.6.
|
||||||
|
|
||||||
|
Licensed under PSF License (see LICENSE.txt file).
|
||||||
|
|
||||||
|
Changes to original ConfigParser:
|
||||||
|
|
||||||
|
- Special characters can be used in section names
|
||||||
|
- Octothorpe can be used for comments (not just at beginning of line)
|
||||||
|
- Only track INI options in sections that match target filename
|
||||||
|
- Stop parsing files with when ``root = true`` is found
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from codecs import open
|
||||||
|
import posixpath
|
||||||
|
from os import sep
|
||||||
|
from os.path import normcase, dirname
|
||||||
|
|
||||||
|
from editorconfig.exceptions import ParsingError
|
||||||
|
from editorconfig.fnmatch import fnmatch
|
||||||
|
from editorconfig.odict import OrderedDict
|
||||||
|
from editorconfig.compat import u
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["ParsingError", "EditorConfigParser"]
|
||||||
|
|
||||||
|
|
||||||
|
class EditorConfigParser(object):
|
||||||
|
|
||||||
|
"""Parser for EditorConfig-style configuration files
|
||||||
|
|
||||||
|
Based on RawConfigParser from ConfigParser.py in Python 2.6.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Regular expressions for parsing section headers and options.
|
||||||
|
# Allow ``]`` and escaped ``;`` and ``#`` characters in section headers
|
||||||
|
SECTCRE = re.compile(
|
||||||
|
r'\s*\[' # [
|
||||||
|
r'(?P<header>([^#;]|\\#|\\;)+)' # very permissive!
|
||||||
|
r'\]' # ]
|
||||||
|
)
|
||||||
|
# Regular expression for parsing option name/values.
|
||||||
|
# Allow any amount of whitespaces, followed by separator
|
||||||
|
# (either ``:`` or ``=``), followed by any amount of whitespace and then
|
||||||
|
# any characters to eol
|
||||||
|
OPTCRE = re.compile(
|
||||||
|
r'\s*(?P<option>[^:=\s][^:=]*)'
|
||||||
|
r'\s*(?P<vi>[:=])\s*'
|
||||||
|
r'(?P<value>.*)$'
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.filename = filename
|
||||||
|
self.options = OrderedDict()
|
||||||
|
self.root_file = False
|
||||||
|
|
||||||
|
def matches_filename(self, config_filename, glob):
|
||||||
|
"""Return True if section glob matches filename"""
|
||||||
|
config_dirname = normcase(dirname(config_filename)).replace(sep, '/')
|
||||||
|
glob = glob.replace("\\#", "#")
|
||||||
|
glob = glob.replace("\\;", ";")
|
||||||
|
if '/' in glob:
|
||||||
|
if glob.find('/') == 0:
|
||||||
|
glob = glob[1:]
|
||||||
|
glob = posixpath.join(config_dirname, glob)
|
||||||
|
else:
|
||||||
|
glob = posixpath.join('**/', glob)
|
||||||
|
return fnmatch(self.filename, glob)
|
||||||
|
|
||||||
|
def read(self, filename):
|
||||||
|
"""Read and parse single EditorConfig file"""
|
||||||
|
try:
|
||||||
|
fp = open(filename, encoding='utf-8')
|
||||||
|
except IOError:
|
||||||
|
return
|
||||||
|
self._read(fp, filename)
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
def _read(self, fp, fpname):
|
||||||
|
"""Parse a sectioned setup file.
|
||||||
|
|
||||||
|
The sections in setup file contains a title line at the top,
|
||||||
|
indicated by a name in square brackets (`[]'), plus key/value
|
||||||
|
options lines, indicated by `name: value' format lines.
|
||||||
|
Continuations are represented by an embedded newline then
|
||||||
|
leading whitespace. Blank lines, lines beginning with a '#',
|
||||||
|
and just about everything else are ignored.
|
||||||
|
"""
|
||||||
|
in_section = False
|
||||||
|
matching_section = False
|
||||||
|
optname = None
|
||||||
|
lineno = 0
|
||||||
|
e = None # None, or an exception
|
||||||
|
while True:
|
||||||
|
line = fp.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
if lineno == 0 and line.startswith(u('\ufeff')):
|
||||||
|
line = line[1:] # Strip UTF-8 BOM
|
||||||
|
lineno = lineno + 1
|
||||||
|
# comment or blank line?
|
||||||
|
if line.strip() == '' or line[0] in '#;':
|
||||||
|
continue
|
||||||
|
# a section header or option header?
|
||||||
|
else:
|
||||||
|
# is it a section header?
|
||||||
|
mo = self.SECTCRE.match(line)
|
||||||
|
if mo:
|
||||||
|
sectname = mo.group('header')
|
||||||
|
in_section = True
|
||||||
|
matching_section = self.matches_filename(fpname, sectname)
|
||||||
|
# So sections can't start with a continuation line
|
||||||
|
optname = None
|
||||||
|
# an option line?
|
||||||
|
else:
|
||||||
|
mo = self.OPTCRE.match(line)
|
||||||
|
if mo:
|
||||||
|
optname, vi, optval = mo.group('option', 'vi', 'value')
|
||||||
|
if ';' in optval or '#' in optval:
|
||||||
|
# ';' and '#' are comment delimiters only if
|
||||||
|
# preceeded by a spacing character
|
||||||
|
m = re.search('(.*?) [;#]', optval)
|
||||||
|
if m:
|
||||||
|
optval = m.group(1)
|
||||||
|
optval = optval.strip()
|
||||||
|
# allow empty values
|
||||||
|
if optval == '""':
|
||||||
|
optval = ''
|
||||||
|
optname = self.optionxform(optname.rstrip())
|
||||||
|
if not in_section and optname == 'root':
|
||||||
|
self.root_file = (optval.lower() == 'true')
|
||||||
|
if matching_section:
|
||||||
|
self.options[optname] = optval
|
||||||
|
else:
|
||||||
|
# a non-fatal parsing error occurred. set up the
|
||||||
|
# exception but keep going. the exception will be
|
||||||
|
# raised at the end of the file and will contain a
|
||||||
|
# list of all bogus lines
|
||||||
|
if not e:
|
||||||
|
e = ParsingError(fpname)
|
||||||
|
e.append(lineno, repr(line))
|
||||||
|
# if any parsing errors occurred, raise an exception
|
||||||
|
if e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def optionxform(self, optionstr):
|
||||||
|
return optionstr.lower()
|
@@ -0,0 +1,76 @@
|
|||||||
|
"""EditorConfig command line interface
|
||||||
|
|
||||||
|
Licensed under PSF License (see LICENSE.txt file).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import getopt
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from editorconfig import __version__, VERSION
|
||||||
|
from editorconfig.versiontools import split_version
|
||||||
|
from editorconfig.handler import EditorConfigHandler
|
||||||
|
from editorconfig.exceptions import ParsingError, PathError, VersionError
|
||||||
|
|
||||||
|
|
||||||
|
def version():
|
||||||
|
print("Version %s" % __version__)
|
||||||
|
|
||||||
|
|
||||||
|
def usage(command, error=False):
|
||||||
|
if error:
|
||||||
|
out = sys.stderr
|
||||||
|
else:
|
||||||
|
out = sys.stdout
|
||||||
|
out.write("%s [OPTIONS] FILENAME\n" % command)
|
||||||
|
out.write('-f '
|
||||||
|
'Specify conf filename other than ".editorconfig".\n')
|
||||||
|
out.write("-b "
|
||||||
|
"Specify version (used by devs to test compatibility).\n")
|
||||||
|
out.write("-h OR --help Print this help message.\n")
|
||||||
|
out.write("-v OR --version Display version information.\n")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
command_name = sys.argv[0]
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "vhb:f:", ["version", "help"])
|
||||||
|
except getopt.GetoptError:
|
||||||
|
print(str(sys.exc_info()[1])) # For Python 2/3 compatibility
|
||||||
|
usage(command_name, error=True)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
version_tuple = VERSION
|
||||||
|
conf_filename = '.editorconfig'
|
||||||
|
|
||||||
|
for option, arg in opts:
|
||||||
|
if option in ('-h', '--help'):
|
||||||
|
usage(command_name)
|
||||||
|
sys.exit()
|
||||||
|
if option in ('-v', '--version'):
|
||||||
|
version()
|
||||||
|
sys.exit()
|
||||||
|
if option == '-f':
|
||||||
|
conf_filename = arg
|
||||||
|
if option == '-b':
|
||||||
|
version_tuple = split_version(arg)
|
||||||
|
if version_tuple is None:
|
||||||
|
sys.exit("Invalid version number: %s" % arg)
|
||||||
|
|
||||||
|
if len(args) < 1:
|
||||||
|
usage(command_name, error=True)
|
||||||
|
sys.exit(2)
|
||||||
|
filenames = args
|
||||||
|
multiple_files = len(args) > 1
|
||||||
|
|
||||||
|
for filename in filenames:
|
||||||
|
handler = EditorConfigHandler(filename, conf_filename, version_tuple)
|
||||||
|
try:
|
||||||
|
options = handler.get_configurations()
|
||||||
|
except (ParsingError, PathError, VersionError):
|
||||||
|
print(str(sys.exc_info()[1])) # For Python 2/3 compatibility
|
||||||
|
sys.exit(2)
|
||||||
|
if multiple_files:
|
||||||
|
print("[%s]" % filename)
|
||||||
|
for key, value in options.items():
|
||||||
|
print("%s=%s" % (key, value))
|
@@ -0,0 +1,899 @@
|
|||||||
|
"""odict.py: An Ordered Dictionary object"""
|
||||||
|
# Copyright (C) 2005 Nicola Larosa, Michael Foord
|
||||||
|
# E-mail: nico AT tekNico DOT net, fuzzyman AT voidspace DOT org DOT uk
|
||||||
|
# Copyright (c) 2003-2010, Michael Foord
|
||||||
|
# E-mail : fuzzyman AT voidspace DOT org DOT uk
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer in the documentation and/or other materials provided
|
||||||
|
# with the distribution.
|
||||||
|
#
|
||||||
|
# * Neither the name of Michael Foord nor the name of Voidspace
|
||||||
|
# may be used to endorse or promote products derived from this
|
||||||
|
# software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import generators
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from editorconfig.compat import slice
|
||||||
|
|
||||||
|
|
||||||
|
__docformat__ = "restructuredtext en"
|
||||||
|
__all__ = ['OrderedDict']
|
||||||
|
|
||||||
|
|
||||||
|
INTP_VER = sys.version_info[:2]
|
||||||
|
if INTP_VER < (2, 2):
|
||||||
|
raise RuntimeError("Python v.2.2 or later required")
|
||||||
|
|
||||||
|
|
||||||
|
class OrderedDict(dict):
|
||||||
|
"""
|
||||||
|
A class of dictionary that keeps the insertion order of keys.
|
||||||
|
|
||||||
|
All appropriate methods return keys, items, or values in an ordered way.
|
||||||
|
|
||||||
|
All normal dictionary methods are available. Update and comparison is
|
||||||
|
restricted to other OrderedDict objects.
|
||||||
|
|
||||||
|
Various sequence methods are available, including the ability to explicitly
|
||||||
|
mutate the key ordering.
|
||||||
|
|
||||||
|
__contains__ tests:
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3),))
|
||||||
|
>>> 1 in d
|
||||||
|
1
|
||||||
|
>>> 4 in d
|
||||||
|
0
|
||||||
|
|
||||||
|
__getitem__ tests:
|
||||||
|
|
||||||
|
>>> OrderedDict(((1, 3), (3, 2), (2, 1)))[2]
|
||||||
|
1
|
||||||
|
>>> OrderedDict(((1, 3), (3, 2), (2, 1)))[4]
|
||||||
|
Traceback (most recent call last):
|
||||||
|
KeyError: 4
|
||||||
|
|
||||||
|
__len__ tests:
|
||||||
|
|
||||||
|
>>> len(OrderedDict())
|
||||||
|
0
|
||||||
|
>>> len(OrderedDict(((1, 3), (3, 2), (2, 1))))
|
||||||
|
3
|
||||||
|
|
||||||
|
get tests:
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.get(1)
|
||||||
|
3
|
||||||
|
>>> d.get(4) is None
|
||||||
|
1
|
||||||
|
>>> d.get(4, 5)
|
||||||
|
5
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (3, 2), (2, 1)])
|
||||||
|
|
||||||
|
has_key tests:
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.has_key(1)
|
||||||
|
1
|
||||||
|
>>> d.has_key(4)
|
||||||
|
0
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, init_val=(), strict=False):
|
||||||
|
"""
|
||||||
|
Create a new ordered dictionary. Cannot init from a normal dict,
|
||||||
|
nor from kwargs, since items order is undefined in those cases.
|
||||||
|
|
||||||
|
If the ``strict`` keyword argument is ``True`` (``False`` is the
|
||||||
|
default) then when doing slice assignment - the ``OrderedDict`` you are
|
||||||
|
assigning from *must not* contain any keys in the remaining dict.
|
||||||
|
|
||||||
|
>>> OrderedDict()
|
||||||
|
OrderedDict([])
|
||||||
|
>>> OrderedDict({1: 1})
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: undefined order, cannot get items from dict
|
||||||
|
>>> OrderedDict({1: 1}.items())
|
||||||
|
OrderedDict([(1, 1)])
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (3, 2), (2, 1)])
|
||||||
|
>>> OrderedDict(d)
|
||||||
|
OrderedDict([(1, 3), (3, 2), (2, 1)])
|
||||||
|
"""
|
||||||
|
self.strict = strict
|
||||||
|
dict.__init__(self)
|
||||||
|
if isinstance(init_val, OrderedDict):
|
||||||
|
self._sequence = init_val.keys()
|
||||||
|
dict.update(self, init_val)
|
||||||
|
elif isinstance(init_val, dict):
|
||||||
|
# we lose compatibility with other ordered dict types this way
|
||||||
|
raise TypeError('undefined order, cannot get items from dict')
|
||||||
|
else:
|
||||||
|
self._sequence = []
|
||||||
|
self.update(init_val)
|
||||||
|
|
||||||
|
### Special methods ###
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> del d[3]
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (2, 1)])
|
||||||
|
>>> del d[3]
|
||||||
|
Traceback (most recent call last):
|
||||||
|
KeyError: 3
|
||||||
|
>>> d[3] = 2
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (2, 1), (3, 2)])
|
||||||
|
>>> del d[0:1]
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(2, 1), (3, 2)])
|
||||||
|
"""
|
||||||
|
if isinstance(key, slice):
|
||||||
|
# FIXME: efficiency?
|
||||||
|
keys = self._sequence[key]
|
||||||
|
for entry in keys:
|
||||||
|
dict.__delitem__(self, entry)
|
||||||
|
del self._sequence[key]
|
||||||
|
else:
|
||||||
|
# do the dict.__delitem__ *first* as it raises
|
||||||
|
# the more appropriate error
|
||||||
|
dict.__delitem__(self, key)
|
||||||
|
self._sequence.remove(key)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d == OrderedDict(d)
|
||||||
|
True
|
||||||
|
>>> d == OrderedDict(((1, 3), (2, 1), (3, 2)))
|
||||||
|
False
|
||||||
|
>>> d == OrderedDict(((1, 0), (3, 2), (2, 1)))
|
||||||
|
False
|
||||||
|
>>> d == OrderedDict(((0, 3), (3, 2), (2, 1)))
|
||||||
|
False
|
||||||
|
>>> d == dict(d)
|
||||||
|
False
|
||||||
|
>>> d == False
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
if isinstance(other, OrderedDict):
|
||||||
|
# FIXME: efficiency?
|
||||||
|
# Generate both item lists for each compare
|
||||||
|
return (self.items() == other.items())
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
|
||||||
|
>>> c < d
|
||||||
|
True
|
||||||
|
>>> d < c
|
||||||
|
False
|
||||||
|
>>> d < dict(c)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: Can only compare with other OrderedDicts
|
||||||
|
"""
|
||||||
|
if not isinstance(other, OrderedDict):
|
||||||
|
raise TypeError('Can only compare with other OrderedDicts')
|
||||||
|
# FIXME: efficiency?
|
||||||
|
# Generate both item lists for each compare
|
||||||
|
return (self.items() < other.items())
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
|
||||||
|
>>> e = OrderedDict(d)
|
||||||
|
>>> c <= d
|
||||||
|
True
|
||||||
|
>>> d <= c
|
||||||
|
False
|
||||||
|
>>> d <= dict(c)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: Can only compare with other OrderedDicts
|
||||||
|
>>> d <= e
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
if not isinstance(other, OrderedDict):
|
||||||
|
raise TypeError('Can only compare with other OrderedDicts')
|
||||||
|
# FIXME: efficiency?
|
||||||
|
# Generate both item lists for each compare
|
||||||
|
return (self.items() <= other.items())
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d != OrderedDict(d)
|
||||||
|
False
|
||||||
|
>>> d != OrderedDict(((1, 3), (2, 1), (3, 2)))
|
||||||
|
True
|
||||||
|
>>> d != OrderedDict(((1, 0), (3, 2), (2, 1)))
|
||||||
|
True
|
||||||
|
>>> d == OrderedDict(((0, 3), (3, 2), (2, 1)))
|
||||||
|
False
|
||||||
|
>>> d != dict(d)
|
||||||
|
True
|
||||||
|
>>> d != False
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
if isinstance(other, OrderedDict):
|
||||||
|
# FIXME: efficiency?
|
||||||
|
# Generate both item lists for each compare
|
||||||
|
return not (self.items() == other.items())
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d > c
|
||||||
|
True
|
||||||
|
>>> c > d
|
||||||
|
False
|
||||||
|
>>> d > dict(c)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: Can only compare with other OrderedDicts
|
||||||
|
"""
|
||||||
|
if not isinstance(other, OrderedDict):
|
||||||
|
raise TypeError('Can only compare with other OrderedDicts')
|
||||||
|
# FIXME: efficiency?
|
||||||
|
# Generate both item lists for each compare
|
||||||
|
return (self.items() > other.items())
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
|
||||||
|
>>> e = OrderedDict(d)
|
||||||
|
>>> c >= d
|
||||||
|
False
|
||||||
|
>>> d >= c
|
||||||
|
True
|
||||||
|
>>> d >= dict(c)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: Can only compare with other OrderedDicts
|
||||||
|
>>> e >= d
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
if not isinstance(other, OrderedDict):
|
||||||
|
raise TypeError('Can only compare with other OrderedDicts')
|
||||||
|
# FIXME: efficiency?
|
||||||
|
# Generate both item lists for each compare
|
||||||
|
return (self.items() >= other.items())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""
|
||||||
|
Used for __repr__ and __str__
|
||||||
|
|
||||||
|
>>> r1 = repr(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
|
||||||
|
>>> r1
|
||||||
|
"OrderedDict([('a', 'b'), ('c', 'd'), ('e', 'f')])"
|
||||||
|
>>> r2 = repr(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd'))))
|
||||||
|
>>> r2
|
||||||
|
"OrderedDict([('a', 'b'), ('e', 'f'), ('c', 'd')])"
|
||||||
|
>>> r1 == str(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
|
||||||
|
True
|
||||||
|
>>> r2 == str(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd'))))
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
return '%s([%s])' % (self.__class__.__name__, ', '.join(
|
||||||
|
['(%r, %r)' % (key, self[key]) for key in self._sequence]))
|
||||||
|
|
||||||
|
def __setitem__(self, key, val):
|
||||||
|
"""
|
||||||
|
Allows slice assignment, so long as the slice is an OrderedDict
|
||||||
|
>>> d = OrderedDict()
|
||||||
|
>>> d['a'] = 'b'
|
||||||
|
>>> d['b'] = 'a'
|
||||||
|
>>> d[3] = 12
|
||||||
|
>>> d
|
||||||
|
OrderedDict([('a', 'b'), ('b', 'a'), (3, 12)])
|
||||||
|
>>> d[:] = OrderedDict(((1, 2), (2, 3), (3, 4)))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> d[::2] = OrderedDict(((7, 8), (9, 10)))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(7, 8), (2, 3), (9, 10)])
|
||||||
|
>>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)))
|
||||||
|
>>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
|
||||||
|
>>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)), strict=True)
|
||||||
|
>>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
|
||||||
|
|
||||||
|
>>> a = OrderedDict(((0, 1), (1, 2), (2, 3)), strict=True)
|
||||||
|
>>> a[3] = 4
|
||||||
|
>>> a
|
||||||
|
OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a
|
||||||
|
OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)])
|
||||||
|
Traceback (most recent call last):
|
||||||
|
ValueError: slice assignment must be from unique keys
|
||||||
|
>>> a = OrderedDict(((0, 1), (1, 2), (2, 3)))
|
||||||
|
>>> a[3] = 4
|
||||||
|
>>> a
|
||||||
|
OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a
|
||||||
|
OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a
|
||||||
|
OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a[::-1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> a
|
||||||
|
OrderedDict([(3, 4), (2, 3), (1, 2), (0, 1)])
|
||||||
|
|
||||||
|
>>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> d[:1] = 3
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: slice assignment requires an OrderedDict
|
||||||
|
|
||||||
|
>>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||||
|
>>> d[:1] = OrderedDict([(9, 8)])
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(9, 8), (1, 2), (2, 3), (3, 4)])
|
||||||
|
"""
|
||||||
|
if isinstance(key, slice):
|
||||||
|
if not isinstance(val, OrderedDict):
|
||||||
|
# FIXME: allow a list of tuples?
|
||||||
|
raise TypeError('slice assignment requires an OrderedDict')
|
||||||
|
keys = self._sequence[key]
|
||||||
|
# NOTE: Could use ``range(*key.indices(len(self._sequence)))``
|
||||||
|
indexes = range(len(self._sequence))[key]
|
||||||
|
if key.step is None:
|
||||||
|
# NOTE: new slice may not be the same size as the one being
|
||||||
|
# overwritten !
|
||||||
|
# NOTE: What is the algorithm for an impossible slice?
|
||||||
|
# e.g. d[5:3]
|
||||||
|
pos = key.start or 0
|
||||||
|
del self[key]
|
||||||
|
newkeys = val.keys()
|
||||||
|
for k in newkeys:
|
||||||
|
if k in self:
|
||||||
|
if self.strict:
|
||||||
|
raise ValueError('slice assignment must be from '
|
||||||
|
'unique keys')
|
||||||
|
else:
|
||||||
|
# NOTE: This removes duplicate keys *first*
|
||||||
|
# so start position might have changed?
|
||||||
|
del self[k]
|
||||||
|
self._sequence = (self._sequence[:pos] + newkeys +
|
||||||
|
self._sequence[pos:])
|
||||||
|
dict.update(self, val)
|
||||||
|
else:
|
||||||
|
# extended slice - length of new slice must be the same
|
||||||
|
# as the one being replaced
|
||||||
|
if len(keys) != len(val):
|
||||||
|
raise ValueError('attempt to assign sequence of size %s '
|
||||||
|
'to extended slice of size %s' % (len(val), len(keys)))
|
||||||
|
# FIXME: efficiency?
|
||||||
|
del self[key]
|
||||||
|
item_list = zip(indexes, val.items())
|
||||||
|
# smallest indexes first - higher indexes not guaranteed to
|
||||||
|
# exist
|
||||||
|
item_list.sort()
|
||||||
|
for pos, (newkey, newval) in item_list:
|
||||||
|
if self.strict and newkey in self:
|
||||||
|
raise ValueError('slice assignment must be from unique'
|
||||||
|
' keys')
|
||||||
|
self.insert(pos, newkey, newval)
|
||||||
|
else:
|
||||||
|
if key not in self:
|
||||||
|
self._sequence.append(key)
|
||||||
|
dict.__setitem__(self, key, val)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""
|
||||||
|
Allows slicing. Returns an OrderedDict if you slice.
|
||||||
|
>>> b = OrderedDict([(7, 0), (6, 1), (5, 2), (4, 3), (3, 4), (2, 5), (1, 6)])
|
||||||
|
>>> b[::-1]
|
||||||
|
OrderedDict([(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1), (7, 0)])
|
||||||
|
>>> b[2:5]
|
||||||
|
OrderedDict([(5, 2), (4, 3), (3, 4)])
|
||||||
|
>>> type(b[2:4])
|
||||||
|
<class '__main__.OrderedDict'>
|
||||||
|
"""
|
||||||
|
if isinstance(key, slice):
|
||||||
|
# FIXME: does this raise the error we want?
|
||||||
|
keys = self._sequence[key]
|
||||||
|
# FIXME: efficiency?
|
||||||
|
return OrderedDict([(entry, self[entry]) for entry in keys])
|
||||||
|
else:
|
||||||
|
return dict.__getitem__(self, key)
|
||||||
|
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
"""
|
||||||
|
Implemented so that accesses to ``sequence`` raise a warning and are
|
||||||
|
diverted to the new ``setkeys`` method.
|
||||||
|
"""
|
||||||
|
if name == 'sequence':
|
||||||
|
warnings.warn('Use of the sequence attribute is deprecated.'
|
||||||
|
' Use the keys method instead.', DeprecationWarning)
|
||||||
|
# NOTE: doesn't return anything
|
||||||
|
self.setkeys(value)
|
||||||
|
else:
|
||||||
|
# FIXME: do we want to allow arbitrary setting of attributes?
|
||||||
|
# Or do we want to manage it?
|
||||||
|
object.__setattr__(self, name, value)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
"""
|
||||||
|
Implemented so that access to ``sequence`` raises a warning.
|
||||||
|
|
||||||
|
>>> d = OrderedDict()
|
||||||
|
>>> d.sequence
|
||||||
|
[]
|
||||||
|
"""
|
||||||
|
if name == 'sequence':
|
||||||
|
warnings.warn('Use of the sequence attribute is deprecated.'
|
||||||
|
' Use the keys method instead.', DeprecationWarning)
|
||||||
|
# NOTE: Still (currently) returns a direct reference. Need to
|
||||||
|
# because code that uses sequence will expect to be able to
|
||||||
|
# mutate it in place.
|
||||||
|
return self._sequence
|
||||||
|
else:
|
||||||
|
# raise the appropriate error
|
||||||
|
raise AttributeError("OrderedDict has no '%s' attribute" % name)
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo):
|
||||||
|
"""
|
||||||
|
To allow deepcopy to work with OrderedDict.
|
||||||
|
|
||||||
|
>>> from copy import deepcopy
|
||||||
|
>>> a = OrderedDict([(1, 1), (2, 2), (3, 3)])
|
||||||
|
>>> a['test'] = {}
|
||||||
|
>>> b = deepcopy(a)
|
||||||
|
>>> b == a
|
||||||
|
True
|
||||||
|
>>> b is a
|
||||||
|
False
|
||||||
|
>>> a['test'] is b['test']
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
from copy import deepcopy
|
||||||
|
return self.__class__(deepcopy(self.items(), memo), self.strict)
|
||||||
|
|
||||||
|
### Read-only methods ###
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
"""
|
||||||
|
>>> OrderedDict(((1, 3), (3, 2), (2, 1))).copy()
|
||||||
|
OrderedDict([(1, 3), (3, 2), (2, 1)])
|
||||||
|
"""
|
||||||
|
return OrderedDict(self)
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
"""
|
||||||
|
``items`` returns a list of tuples representing all the
|
||||||
|
``(key, value)`` pairs in the dictionary.
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.items()
|
||||||
|
[(1, 3), (3, 2), (2, 1)]
|
||||||
|
>>> d.clear()
|
||||||
|
>>> d.items()
|
||||||
|
[]
|
||||||
|
"""
|
||||||
|
return zip(self._sequence, self.values())
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
"""
|
||||||
|
Return a list of keys in the ``OrderedDict``.
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.keys()
|
||||||
|
[1, 3, 2]
|
||||||
|
"""
|
||||||
|
return self._sequence[:]
|
||||||
|
|
||||||
|
def values(self, values=None):
|
||||||
|
"""
|
||||||
|
Return a list of all the values in the OrderedDict.
|
||||||
|
|
||||||
|
Optionally you can pass in a list of values, which will replace the
|
||||||
|
current list. The value list must be the same len as the OrderedDict.
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.values()
|
||||||
|
[3, 2, 1]
|
||||||
|
"""
|
||||||
|
return [self[key] for key in self._sequence]
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
"""
|
||||||
|
>>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iteritems()
|
||||||
|
>>> ii.next()
|
||||||
|
(1, 3)
|
||||||
|
>>> ii.next()
|
||||||
|
(3, 2)
|
||||||
|
>>> ii.next()
|
||||||
|
(2, 1)
|
||||||
|
>>> ii.next()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
StopIteration
|
||||||
|
"""
|
||||||
|
def make_iter(self=self):
|
||||||
|
keys = self.iterkeys()
|
||||||
|
while True:
|
||||||
|
key = keys.next()
|
||||||
|
yield (key, self[key])
|
||||||
|
return make_iter()
|
||||||
|
|
||||||
|
def iterkeys(self):
|
||||||
|
"""
|
||||||
|
>>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iterkeys()
|
||||||
|
>>> ii.next()
|
||||||
|
1
|
||||||
|
>>> ii.next()
|
||||||
|
3
|
||||||
|
>>> ii.next()
|
||||||
|
2
|
||||||
|
>>> ii.next()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
StopIteration
|
||||||
|
"""
|
||||||
|
return iter(self._sequence)
|
||||||
|
|
||||||
|
__iter__ = iterkeys
|
||||||
|
|
||||||
|
def itervalues(self):
|
||||||
|
"""
|
||||||
|
>>> iv = OrderedDict(((1, 3), (3, 2), (2, 1))).itervalues()
|
||||||
|
>>> iv.next()
|
||||||
|
3
|
||||||
|
>>> iv.next()
|
||||||
|
2
|
||||||
|
>>> iv.next()
|
||||||
|
1
|
||||||
|
>>> iv.next()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
StopIteration
|
||||||
|
"""
|
||||||
|
def make_iter(self=self):
|
||||||
|
keys = self.iterkeys()
|
||||||
|
while True:
|
||||||
|
yield self[keys.next()]
|
||||||
|
return make_iter()
|
||||||
|
|
||||||
|
### Read-write methods ###
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.clear()
|
||||||
|
>>> d
|
||||||
|
OrderedDict([])
|
||||||
|
"""
|
||||||
|
dict.clear(self)
|
||||||
|
self._sequence = []
|
||||||
|
|
||||||
|
def pop(self, key, *args):
|
||||||
|
"""
|
||||||
|
No dict.pop in Python 2.2, gotta reimplement it
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.pop(3)
|
||||||
|
2
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (2, 1)])
|
||||||
|
>>> d.pop(4)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
KeyError: 4
|
||||||
|
>>> d.pop(4, 0)
|
||||||
|
0
|
||||||
|
>>> d.pop(4, 0, 1)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: pop expected at most 2 arguments, got 3
|
||||||
|
"""
|
||||||
|
if len(args) > 1:
|
||||||
|
raise TypeError('pop expected at most 2 arguments, got %s' %
|
||||||
|
(len(args) + 1))
|
||||||
|
if key in self:
|
||||||
|
val = self[key]
|
||||||
|
del self[key]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
val = args[0]
|
||||||
|
except IndexError:
|
||||||
|
raise KeyError(key)
|
||||||
|
return val
|
||||||
|
|
||||||
|
def popitem(self, i=-1):
|
||||||
|
"""
|
||||||
|
Delete and return an item specified by index, not a random one as in
|
||||||
|
dict. The index is -1 by default (the last item).
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.popitem()
|
||||||
|
(2, 1)
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (3, 2)])
|
||||||
|
>>> d.popitem(0)
|
||||||
|
(1, 3)
|
||||||
|
>>> OrderedDict().popitem()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
KeyError: 'popitem(): dictionary is empty'
|
||||||
|
>>> d.popitem(2)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
IndexError: popitem(): index 2 not valid
|
||||||
|
"""
|
||||||
|
if not self._sequence:
|
||||||
|
raise KeyError('popitem(): dictionary is empty')
|
||||||
|
try:
|
||||||
|
key = self._sequence[i]
|
||||||
|
except IndexError:
|
||||||
|
raise IndexError('popitem(): index %s not valid' % i)
|
||||||
|
return (key, self.pop(key))
|
||||||
|
|
||||||
|
def setdefault(self, key, defval=None):
|
||||||
|
"""
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.setdefault(1)
|
||||||
|
3
|
||||||
|
>>> d.setdefault(4) is None
|
||||||
|
True
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (3, 2), (2, 1), (4, None)])
|
||||||
|
>>> d.setdefault(5, 0)
|
||||||
|
0
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (3, 2), (2, 1), (4, None), (5, 0)])
|
||||||
|
"""
|
||||||
|
if key in self:
|
||||||
|
return self[key]
|
||||||
|
else:
|
||||||
|
self[key] = defval
|
||||||
|
return defval
|
||||||
|
|
||||||
|
def update(self, from_od):
|
||||||
|
"""
|
||||||
|
Update from another OrderedDict or sequence of (key, value) pairs
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 0), (0, 1)))
|
||||||
|
>>> d.update(OrderedDict(((1, 3), (3, 2), (2, 1))))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (0, 1), (3, 2), (2, 1)])
|
||||||
|
>>> d.update({4: 4})
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: undefined order, cannot get items from dict
|
||||||
|
>>> d.update((4, 4))
|
||||||
|
Traceback (most recent call last):
|
||||||
|
TypeError: cannot convert dictionary update sequence element "4" to a 2-item sequence
|
||||||
|
"""
|
||||||
|
if isinstance(from_od, OrderedDict):
|
||||||
|
for key, val in from_od.items():
|
||||||
|
self[key] = val
|
||||||
|
elif isinstance(from_od, dict):
|
||||||
|
# we lose compatibility with other ordered dict types this way
|
||||||
|
raise TypeError('undefined order, cannot get items from dict')
|
||||||
|
else:
|
||||||
|
# FIXME: efficiency?
|
||||||
|
# sequence of 2-item sequences, or error
|
||||||
|
for item in from_od:
|
||||||
|
try:
|
||||||
|
key, val = item
|
||||||
|
except TypeError:
|
||||||
|
raise TypeError('cannot convert dictionary update'
|
||||||
|
' sequence element "%s" to a 2-item sequence' % item)
|
||||||
|
self[key] = val
|
||||||
|
|
||||||
|
def rename(self, old_key, new_key):
|
||||||
|
"""
|
||||||
|
Rename the key for a given value, without modifying sequence order.
|
||||||
|
|
||||||
|
For the case where new_key already exists this raise an exception,
|
||||||
|
since if new_key exists, it is ambiguous as to what happens to the
|
||||||
|
associated values, and the position of new_key in the sequence.
|
||||||
|
|
||||||
|
>>> od = OrderedDict()
|
||||||
|
>>> od['a'] = 1
|
||||||
|
>>> od['b'] = 2
|
||||||
|
>>> od.items()
|
||||||
|
[('a', 1), ('b', 2)]
|
||||||
|
>>> od.rename('b', 'c')
|
||||||
|
>>> od.items()
|
||||||
|
[('a', 1), ('c', 2)]
|
||||||
|
>>> od.rename('c', 'a')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
ValueError: New key already exists: 'a'
|
||||||
|
>>> od.rename('d', 'b')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
KeyError: 'd'
|
||||||
|
"""
|
||||||
|
if new_key == old_key:
|
||||||
|
# no-op
|
||||||
|
return
|
||||||
|
if new_key in self:
|
||||||
|
raise ValueError("New key already exists: %r" % new_key)
|
||||||
|
# rename sequence entry
|
||||||
|
value = self[old_key]
|
||||||
|
old_idx = self._sequence.index(old_key)
|
||||||
|
self._sequence[old_idx] = new_key
|
||||||
|
# rename internal dict entry
|
||||||
|
dict.__delitem__(self, old_key)
|
||||||
|
dict.__setitem__(self, new_key, value)
|
||||||
|
|
||||||
|
def setitems(self, items):
|
||||||
|
"""
|
||||||
|
This method allows you to set the items in the dict.
|
||||||
|
|
||||||
|
It takes a list of tuples - of the same sort returned by the ``items``
|
||||||
|
method.
|
||||||
|
|
||||||
|
>>> d = OrderedDict()
|
||||||
|
>>> d.setitems(((3, 1), (2, 3), (1, 2)))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(3, 1), (2, 3), (1, 2)])
|
||||||
|
"""
|
||||||
|
self.clear()
|
||||||
|
# FIXME: this allows you to pass in an OrderedDict as well :-)
|
||||||
|
self.update(items)
|
||||||
|
|
||||||
|
def setkeys(self, keys):
|
||||||
|
"""
|
||||||
|
``setkeys`` all ows you to pass in a new list of keys which will
|
||||||
|
replace the current set. This must contain the same set of keys, but
|
||||||
|
need not be in the same order.
|
||||||
|
|
||||||
|
If you pass in new keys that don't match, a ``KeyError`` will be
|
||||||
|
raised.
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.keys()
|
||||||
|
[1, 3, 2]
|
||||||
|
>>> d.setkeys((1, 2, 3))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 3), (2, 1), (3, 2)])
|
||||||
|
>>> d.setkeys(['a', 'b', 'c'])
|
||||||
|
Traceback (most recent call last):
|
||||||
|
KeyError: 'Keylist is not the same as current keylist.'
|
||||||
|
"""
|
||||||
|
# FIXME: Efficiency? (use set for Python 2.4 :-)
|
||||||
|
# NOTE: list(keys) rather than keys[:] because keys[:] returns
|
||||||
|
# a tuple, if keys is a tuple.
|
||||||
|
kcopy = list(keys)
|
||||||
|
kcopy.sort()
|
||||||
|
self._sequence.sort()
|
||||||
|
if kcopy != self._sequence:
|
||||||
|
raise KeyError('Keylist is not the same as current keylist.')
|
||||||
|
# NOTE: This makes the _sequence attribute a new object, instead
|
||||||
|
# of changing it in place.
|
||||||
|
# FIXME: efficiency?
|
||||||
|
self._sequence = list(keys)
|
||||||
|
|
||||||
|
def setvalues(self, values):
|
||||||
|
"""
|
||||||
|
You can pass in a list of values, which will replace the
|
||||||
|
current list. The value list must be the same len as the OrderedDict.
|
||||||
|
|
||||||
|
(Or a ``ValueError`` is raised.)
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.setvalues((1, 2, 3))
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 1), (3, 2), (2, 3)])
|
||||||
|
>>> d.setvalues([6])
|
||||||
|
Traceback (most recent call last):
|
||||||
|
ValueError: Value list is not the same length as the OrderedDict.
|
||||||
|
"""
|
||||||
|
if len(values) != len(self):
|
||||||
|
# FIXME: correct error to raise?
|
||||||
|
raise ValueError('Value list is not the same length as the '
|
||||||
|
'OrderedDict.')
|
||||||
|
self.update(zip(self, values))
|
||||||
|
|
||||||
|
### Sequence Methods ###
|
||||||
|
|
||||||
|
def index(self, key):
|
||||||
|
"""
|
||||||
|
Return the position of the specified key in the OrderedDict.
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.index(3)
|
||||||
|
1
|
||||||
|
>>> d.index(4)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
ValueError: 4 is not in list
|
||||||
|
"""
|
||||||
|
return self._sequence.index(key)
|
||||||
|
|
||||||
|
def insert(self, index, key, value):
|
||||||
|
"""
|
||||||
|
Takes ``index``, ``key``, and ``value`` as arguments.
|
||||||
|
|
||||||
|
Sets ``key`` to ``value``, so that ``key`` is at position ``index`` in
|
||||||
|
the OrderedDict.
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.insert(0, 4, 0)
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(4, 0), (1, 3), (3, 2), (2, 1)])
|
||||||
|
>>> d.insert(0, 2, 1)
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2)])
|
||||||
|
>>> d.insert(8, 8, 1)
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2), (8, 1)])
|
||||||
|
"""
|
||||||
|
if key in self:
|
||||||
|
# FIXME: efficiency?
|
||||||
|
del self[key]
|
||||||
|
self._sequence.insert(index, key)
|
||||||
|
dict.__setitem__(self, key, value)
|
||||||
|
|
||||||
|
def reverse(self):
|
||||||
|
"""
|
||||||
|
Reverse the order of the OrderedDict.
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
|
||||||
|
>>> d.reverse()
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(2, 1), (3, 2), (1, 3)])
|
||||||
|
"""
|
||||||
|
self._sequence.reverse()
|
||||||
|
|
||||||
|
def sort(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Sort the key order in the OrderedDict.
|
||||||
|
|
||||||
|
This method takes the same arguments as the ``list.sort`` method on
|
||||||
|
your version of Python.
|
||||||
|
|
||||||
|
>>> d = OrderedDict(((4, 1), (2, 2), (3, 3), (1, 4)))
|
||||||
|
>>> d.sort()
|
||||||
|
>>> d
|
||||||
|
OrderedDict([(1, 4), (2, 2), (3, 3), (4, 1)])
|
||||||
|
"""
|
||||||
|
self._sequence.sort(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# turn off warnings for tests
|
||||||
|
warnings.filterwarnings('ignore')
|
||||||
|
# run the code tests in doctest format
|
||||||
|
import doctest
|
||||||
|
m = sys.modules.get('__main__')
|
||||||
|
globs = m.__dict__.copy()
|
||||||
|
globs.update({
|
||||||
|
'INTP_VER': INTP_VER,
|
||||||
|
})
|
||||||
|
doctest.testmod(m, globs=globs)
|
@@ -0,0 +1,35 @@
|
|||||||
|
"""EditorConfig version tools
|
||||||
|
|
||||||
|
Provides ``join_version`` and ``split_version`` classes for converting
|
||||||
|
__version__ strings to VERSION tuples and vice versa.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['join_version', 'split_version']
|
||||||
|
|
||||||
|
|
||||||
|
_version_re = re.compile(r'^(\d+)\.(\d+)\.(\d+)(\..*)?$', re.VERBOSE)
|
||||||
|
|
||||||
|
|
||||||
|
def join_version(version_tuple):
|
||||||
|
"""Return a string representation of version from given VERSION tuple"""
|
||||||
|
version = "%s.%s.%s" % version_tuple[:3]
|
||||||
|
if version_tuple[3] != "final":
|
||||||
|
version += "-%s" % version_tuple[3]
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def split_version(version):
|
||||||
|
"""Return VERSION tuple for given string representation of version"""
|
||||||
|
match = _version_re.search(version)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
split_version = list(match.groups())
|
||||||
|
if split_version[3] is None:
|
||||||
|
split_version[3] = "final"
|
||||||
|
split_version = list(map(int, split_version[:3])) + split_version[3:]
|
||||||
|
return tuple(split_version)
|
@@ -0,0 +1 @@
|
|||||||
|
{"url": "http://sindresorhus.com", "version": "2013.03.18.18.13.22", "description": "Sublime Text plugin for EditorConfig - helps developers define and maintain consistent coding styles between different editors and IDEs"}
|
@@ -0,0 +1,74 @@
|
|||||||
|
# EditorConfig - Sublime Text plugin
|
||||||
|
|
||||||
|
> [EditorConfig](http://editorconfig.org) helps developers define and maintain consistent coding styles between different editors and IDEs. The EditorConfig project consists of a file format for defining coding styles and a collection of text editor plugins that enable editors to read the file format and adhere to defined styles. EditorConfig files are easily readibly and they work nicely with version control systems.
|
||||||
|
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
|
||||||
|
### Sublime Text 2
|
||||||
|
|
||||||
|
Install with [Package Control](http://wbond.net/sublime_packages/package_control)
|
||||||
|
|
||||||
|
|
||||||
|
### Sublime Text 3
|
||||||
|
|
||||||
|
[Download](https://github.com/sindresorhus/editorconfig-sublime/archive/st3.zip), unzip, and put the contents in `~/Library/Application Support/Sublime Text 3/Packages/EditorConfig`.
|
||||||
|
Will be easier when Package Control is fully compatible.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
See the [EditorConfig site][] for documentation.
|
||||||
|
|
||||||
|
|
||||||
|
## Supported properties
|
||||||
|
|
||||||
|
- root
|
||||||
|
- indent_style
|
||||||
|
- indent_size
|
||||||
|
- end\_of\_line
|
||||||
|
- charset
|
||||||
|
- trim_trailing_whitespace
|
||||||
|
- insert_final_newline
|
||||||
|
|
||||||
|
Explanation of the properties can be found on the [EditorConfig site][].
|
||||||
|
|
||||||
|
|
||||||
|
## Example file
|
||||||
|
|
||||||
|
*My recommended default settings*
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
### Trailing whitespace
|
||||||
|
|
||||||
|
Even though there is a `trim_trailing_whitespace` property. I would still recommend you set `"draw_white_space": "all"` in your Sublime preferences to prevent you from accidentally committing whitespace garbage whenever a project is missing a .editorconfig file.
|
||||||
|
|
||||||
|
|
||||||
|
### Show changes
|
||||||
|
|
||||||
|
This plugin does its changes transparently in the background. I would recommend the excellent [Modific](https://github.com/gornostal/Modific) plugin if you would like to see what changed.
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT License](http://en.wikipedia.org/wiki/MIT_License)
|
||||||
|
(c) [Sindre Sorhus](http://sindresorhus.com)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[EditorConfig site]: http://editorconfig.org
|
4
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/Git/.gitignore
vendored
Normal file
4
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/Git/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.tmLanguage.cache
|
||||||
|
.DS_Store
|
||||||
|
package-metadata.json
|
@@ -0,0 +1,28 @@
|
|||||||
|
These are the people who helped make this plugin:
|
||||||
|
|
||||||
|
David Lynch <kemayo@gmail.com>
|
||||||
|
Sheldon Els <sheldon.els@gmail.com>
|
||||||
|
Nick Fisher <spadgos@gmail.com>
|
||||||
|
Can Yilmaz <can@potatolondon.com>
|
||||||
|
Stefan Buhrmester <buhrmi@gmail.com>
|
||||||
|
Rafal Chlodnicki <rchlodnicki@opera.com>
|
||||||
|
Daniël de Kok <me@danieldk.eu>
|
||||||
|
David Baumgold <david@davidbaumgold.com>
|
||||||
|
Iuri de Silvio <iurisilvio@gmail.com>
|
||||||
|
joshuacc <josh@designpepper.com>
|
||||||
|
misfo <tedwardo2@gmail.com>
|
||||||
|
Kevin Smith <kevin@ilovecode.de>
|
||||||
|
Κώστας Καραχάλιος <kostas.karachalios@me.com>
|
||||||
|
Dominique Wahli <dominique.wahli@solvaxis.com>
|
||||||
|
Fraser Graham <frasergraham@me.com>
|
||||||
|
Hamid Nazari <hamidnazari@ymail.com>
|
||||||
|
Jeff Sandberg <paradox460@gmail.com>
|
||||||
|
Joshua Clanton <joshua.clanton@gmail.com>
|
||||||
|
Maxim Sukharev <max@smacker.ru>
|
||||||
|
Niklas Hambüchen <mail@nh2.me>
|
||||||
|
Patrik Ring <me@patrikring.se>
|
||||||
|
Scott Bowers <sbbowers@gmail.com>
|
||||||
|
Weslly Honorato <weslly.honorato@gmail.com>
|
||||||
|
brcooley <brcooley@cs.wm.edu>
|
||||||
|
jdc0589 <jdc0589@gmail.com>
|
||||||
|
Adam Venturella <aventurella@gmail.com>
|
@@ -0,0 +1,213 @@
|
|||||||
|
[
|
||||||
|
{ "caption": "Git: Init",
|
||||||
|
"command": "git_init"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Blame",
|
||||||
|
"command": "git_blame"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: New Tag",
|
||||||
|
"command": "git_new_tag"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Show Tags",
|
||||||
|
"command": "git_show_tags"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Push Tags",
|
||||||
|
"command": "git_push_tags"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Log Current File",
|
||||||
|
"command": "git_log"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Log All",
|
||||||
|
"command": "git_log_all"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Graph Current File",
|
||||||
|
"command": "git_graph"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Graph All",
|
||||||
|
"command": "git_graph_all"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Diff Current File",
|
||||||
|
"command": "git_diff"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Diff All",
|
||||||
|
"command": "git_diff_all"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Diff Staged",
|
||||||
|
"command": "git_diff_commit"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Diff Tool Current File",
|
||||||
|
"command": "git_diff_tool"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Diff Tool All",
|
||||||
|
"command": "git_diff_tool_all"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Commit",
|
||||||
|
"command": "git_commit"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Amend Commit",
|
||||||
|
"command": "git_commit_amend"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Quick Commit",
|
||||||
|
"command": "git_quick_commit"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Status",
|
||||||
|
"command": "git_status"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Open Modified Files",
|
||||||
|
"command": "git_open_modified_files"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: New Branch",
|
||||||
|
"command": "git_new_branch"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Change Branch",
|
||||||
|
"command": "git_branch"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Merge Branch",
|
||||||
|
"command": "git_merge"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Delete Branch",
|
||||||
|
"command": "git_delete_branch"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Stash Changes",
|
||||||
|
"command": "git_stash"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Stash Pop",
|
||||||
|
"command": "git_stash_pop"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Stash Apply",
|
||||||
|
"command": "git_stash_apply"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Stash Drop",
|
||||||
|
"command": "git_stash_drop"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Add Current File",
|
||||||
|
"command": "git_add"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Add...",
|
||||||
|
"command": "git_add_choice"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Checkout Current File",
|
||||||
|
"command": "git_checkout"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Fetch",
|
||||||
|
"command": "git_fetch"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Pull",
|
||||||
|
"command": "git_pull"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Pull Current Branch",
|
||||||
|
"command": "git_pull_current_branch"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Push",
|
||||||
|
"command": "git_push"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Push Current Branch",
|
||||||
|
"command": "git_push_current_branch"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Show Current File",
|
||||||
|
"command": "git_show"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Toggle Annotations",
|
||||||
|
"command": "git_toggle_annotations"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Custom Command",
|
||||||
|
"command": "git_custom"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git Flow: Feature Start",
|
||||||
|
"command": "git_flow_feature_start"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git Flow: Feature Finish",
|
||||||
|
"command": "git_flow_feature_finish"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git Flow: Release Start",
|
||||||
|
"command": "git_flow_release_start"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git Flow: Release Finish",
|
||||||
|
"command": "git_flow_release_finish"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git Flow: Hotfix Start",
|
||||||
|
"command": "git_flow_hotfix_start"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git Flow: Hotfix Finish",
|
||||||
|
"command": "git_flow_hotfix_finish"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Open...",
|
||||||
|
"command": "git_open_file"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Reset (unstage) Current File",
|
||||||
|
"command": "git_reset_head"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Reset (unstage) All",
|
||||||
|
"command": "git_reset_head_all"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Reset (hard) HEAD",
|
||||||
|
"command": "git_reset_hard_head"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Add Selected Hunk",
|
||||||
|
"command": "git_add_selected_hunk"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Commit Selected Hunk",
|
||||||
|
"command": "git_commit_selected_hunk"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Gui",
|
||||||
|
"command": "git_gui"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Gitk",
|
||||||
|
"command": "git_gitk"
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Git: Commit history",
|
||||||
|
"command": "git_commit_history"
|
||||||
|
}
|
||||||
|
]
|
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{"keys": ["enter"], "command": "git_goto_diff",
|
||||||
|
"context": [{"key": "selector", "operand": "markup.inserted.diff"}]},
|
||||||
|
{"keys": ["enter"], "command": "git_goto_diff",
|
||||||
|
"context": [{"key": "selector", "operand": "markup.deleted.diff"}]}
|
||||||
|
]
|
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"rulers": [70]
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
// save before running commands
|
||||||
|
"save_first": true
|
||||||
|
|
||||||
|
// if present, use this command instead of plain "git"
|
||||||
|
// e.g. "/Users/kemayo/bin/git" or "C:\bin\git.exe"
|
||||||
|
,"git_command": false
|
||||||
|
|
||||||
|
// point this the installation location of git-flow
|
||||||
|
,"git_flow_command": "/usr/local/bin/git-flow"
|
||||||
|
|
||||||
|
// use the panel for diff output, rather than a new scratch window (new tab)
|
||||||
|
,"diff_panel": false
|
||||||
|
|
||||||
|
// affects blame command when no selection is made
|
||||||
|
// true: blame whole file
|
||||||
|
// false: blame only current line
|
||||||
|
,"blame_whole_file": true
|
||||||
|
|
||||||
|
// If you'd rather have your status command open files instead of show you a
|
||||||
|
// diff, set this to true. You can still do `Git: Status` followed by
|
||||||
|
// 'Git: Diff Current File' to get a file diff
|
||||||
|
,"status_opens_file": false
|
||||||
|
|
||||||
|
// Use --verbose flag for commit messages
|
||||||
|
,"verbose_commits": true
|
||||||
|
|
||||||
|
// How many commit messages to store in the history. Set to 0 to disable.
|
||||||
|
,"history_size": 5
|
||||||
|
|
||||||
|
// Show git flow commands
|
||||||
|
,"flow": false
|
||||||
|
|
||||||
|
// Annotations default to being on for all files. Can be slow in some cases.
|
||||||
|
,"annotations": false
|
||||||
|
|
||||||
|
// statusbar
|
||||||
|
,"statusbar_branch": true
|
||||||
|
// Symbols for quick git status in status bar
|
||||||
|
,"statusbar_status": true
|
||||||
|
,"statusbar_status_symbols" : {"modified": "≠", "added": "+", "deleted": "×", "untracked": "?", "conflicts": "‼", "renamed":"R", "copied":"C", "clean": "√", "separator": " "}
|
||||||
|
}
|
@@ -0,0 +1,125 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "tools",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Git",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "This file",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Log", "command": "git_log" }
|
||||||
|
,{ "caption": "Graph", "command": "git_graph" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Diff", "command": "git_diff" }
|
||||||
|
,{ "caption": "DiffTool", "command": "git_diff_tool" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Add", "command": "git_add" }
|
||||||
|
,{ "caption": "Add Selected Hunk", "command": "git_add_selected_hunk" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Reset", "command": "git_reset_head" }
|
||||||
|
,{ "caption": "Checkout (Discard Changes)", "command": "git_checkout" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Quick Commit Current File", "command": "git_quick_commit" }
|
||||||
|
,{ "caption": "Commit Selected Hunk", "command": "git_commit_selected_hunk" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Blame", "command": "git_blame" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Toggle Annotations", "command": "git_toggle_annotations" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Whole repo",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Log", "command": "git_log_all" }
|
||||||
|
,{ "caption": "Graph", "command": "git_graph_all" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Diff", "command": "git_diff_all" }
|
||||||
|
,{ "caption": "Diff Staged", "command": "git_diff_commit" }
|
||||||
|
,{ "caption": "Diff Tool", "command": "git_diff_tool_all" }
|
||||||
|
,{ "caption": "Reset Hard", "command": "git_reset_hard_head" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Add...", "command": "git_add_choice" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Reset", "command": "git_reset_head_all" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Commit", "command": "git_commit" }
|
||||||
|
,{ "caption": "Amend Last Commit", "command": "git_commit_amend" }
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Open...", "command": "git_open_file" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"caption": "Stash",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Save", "command": "git_stash" }
|
||||||
|
,{ "caption": "Pop", "command": "git_stash_pop" }
|
||||||
|
,{ "caption": "Apply", "command": "git_stash_apply" }
|
||||||
|
,{ "caption": "Drop", "command": "git_stash_drop" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{
|
||||||
|
"caption": "Flow",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Feature Start", "command": "git_flow_feature_start"}
|
||||||
|
,{ "caption": "Feature Finish", "command": "git_flow_feature_finish"}
|
||||||
|
,{ "caption": "-"}
|
||||||
|
,{ "caption": "Release Start", "command": "git_flow_release_start"}
|
||||||
|
,{ "caption": "Release Finish", "command": "git_flow_release_finish"}
|
||||||
|
,{ "caption": "-"}
|
||||||
|
,{ "caption": "Hotfix Start", "command": "git_flow_hotfix_start"}
|
||||||
|
,{ "caption": "Hotfix Finish", "command": "git_flow_hotfix_finish"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
,{ "caption": "-" }
|
||||||
|
,{ "caption": "Init", "command": "git_init"}
|
||||||
|
,{ "caption": "Status...", "command": "git_status" }
|
||||||
|
,{ "caption": "Branches...", "command": "git_branch" }
|
||||||
|
,{ "caption": "Merge...", "command": "git_merge" }
|
||||||
|
,{ "caption": "See commit history...", "command": "git_commit_history"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
,{
|
||||||
|
"caption": "Preferences",
|
||||||
|
"mnemonic": "n",
|
||||||
|
"id": "preferences",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Package Settings",
|
||||||
|
"mnemonic": "P",
|
||||||
|
"id": "package-settings",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Git",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {"file": "${packages}/Git/Git.sublime-settings"},
|
||||||
|
"caption": "Settings – Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {"file": "${packages}/User/Git.sublime-settings"},
|
||||||
|
"caption": "Settings – User"
|
||||||
|
},
|
||||||
|
{ "caption": "-" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@@ -0,0 +1,22 @@
|
|||||||
|
# Sublime Text 2 plugin: git
|
||||||
|
|
||||||
|
Git integration: it's pretty handy. Who knew, right?
|
||||||
|
|
||||||
|
For more information about what's supported, and how to install this, [check the wiki](https://github.com/kemayo/sublime-text-2-git/wiki).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
### Package Control
|
||||||
|
|
||||||
|
The easiest way to install this is with [Package Control](http://wbond.net/sublime\_packages/package\_control).
|
||||||
|
|
||||||
|
* If you just went and installed Package Control, you probably need to restart Sublime Text 2 before doing this next bit.
|
||||||
|
* Bring up the Command Palette (Command+Shift+p on OS X, Control+Shift+p on Linux/Windows).
|
||||||
|
* Select "Package Control: Install Package" (it'll take a few seconds)
|
||||||
|
* Select Git when the list appears.
|
||||||
|
|
||||||
|
Package Control will automatically keep Git up to date with the latest version.
|
||||||
|
|
||||||
|
### The rest
|
||||||
|
|
||||||
|
If you don't want to use Package Control, [check the wiki](https://github.com/kemayo/sublime-text-2-git/wiki) for other installation methods on various platforms.
|
@@ -0,0 +1,115 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
from git import GitTextCommand, GitWindowCommand, git_root
|
||||||
|
import status
|
||||||
|
|
||||||
|
|
||||||
|
class GitAddChoiceCommand(status.GitStatusCommand):
|
||||||
|
def status_filter(self, item):
|
||||||
|
return super(GitAddChoiceCommand, self).status_filter(item) and not item[1].isspace()
|
||||||
|
|
||||||
|
def show_status_list(self):
|
||||||
|
self.results = [[" + All Files", "apart from untracked files"], [" + All Files", "including untracked files"]] + self.results
|
||||||
|
return super(GitAddChoiceCommand, self).show_status_list()
|
||||||
|
|
||||||
|
def panel_followup(self, picked_status, picked_file, picked_index):
|
||||||
|
working_dir = git_root(self.get_working_dir())
|
||||||
|
|
||||||
|
if picked_index == 0:
|
||||||
|
command = ['git', 'add', '--update']
|
||||||
|
elif picked_index == 1:
|
||||||
|
command = ['git', 'add', '--all']
|
||||||
|
else:
|
||||||
|
command = ['git']
|
||||||
|
picked_file = picked_file.strip('"')
|
||||||
|
if os.path.isfile(working_dir + "/" + picked_file):
|
||||||
|
command += ['add']
|
||||||
|
else:
|
||||||
|
command += ['rm']
|
||||||
|
command += ['--', picked_file]
|
||||||
|
|
||||||
|
self.run_command(command, self.rerun,
|
||||||
|
working_dir=working_dir)
|
||||||
|
|
||||||
|
def rerun(self, result):
|
||||||
|
self.run()
|
||||||
|
|
||||||
|
|
||||||
|
class GitAdd(GitTextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
self.run_command(['git', 'add', self.get_file_name()])
|
||||||
|
|
||||||
|
|
||||||
|
class GitAddSelectedHunkCommand(GitTextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
self.run_command(['git', 'diff', '--no-color', '-U1', self.get_file_name()], self.cull_diff)
|
||||||
|
|
||||||
|
def cull_diff(self, result):
|
||||||
|
selection = []
|
||||||
|
for sel in self.view.sel():
|
||||||
|
selection.append({
|
||||||
|
"start": self.view.rowcol(sel.begin())[0] + 1,
|
||||||
|
"end": self.view.rowcol(sel.end())[0] + 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
hunks = [{"diff":""}]
|
||||||
|
i = 0
|
||||||
|
matcher = re.compile('^@@ -([0-9]*)(?:,([0-9]*))? \+([0-9]*)(?:,([0-9]*))? @@')
|
||||||
|
for line in result.splitlines():
|
||||||
|
if line.startswith('@@'):
|
||||||
|
i += 1
|
||||||
|
match = matcher.match(line)
|
||||||
|
start = int(match.group(3))
|
||||||
|
end = match.group(4)
|
||||||
|
if end:
|
||||||
|
end = start + int(end)
|
||||||
|
else:
|
||||||
|
end = start
|
||||||
|
hunks.append({"diff": "", "start": start, "end": end})
|
||||||
|
hunks[i]["diff"] += line + "\n"
|
||||||
|
|
||||||
|
diffs = hunks[0]["diff"]
|
||||||
|
hunks.pop(0)
|
||||||
|
selection_is_hunky = False
|
||||||
|
for hunk in hunks:
|
||||||
|
for sel in selection:
|
||||||
|
if sel["end"] < hunk["start"]:
|
||||||
|
continue
|
||||||
|
if sel["start"] > hunk["end"]:
|
||||||
|
continue
|
||||||
|
diffs += hunk["diff"] # + "\n\nEND OF HUNK\n\n"
|
||||||
|
selection_is_hunky = True
|
||||||
|
|
||||||
|
if selection_is_hunky:
|
||||||
|
self.run_command(['git', 'apply', '--cached'], stdin=diffs)
|
||||||
|
else:
|
||||||
|
sublime.status_message("No selected hunk")
|
||||||
|
|
||||||
|
|
||||||
|
# Also, sometimes we want to undo adds
|
||||||
|
|
||||||
|
|
||||||
|
class GitResetHead(object):
|
||||||
|
def run(self, edit=None):
|
||||||
|
self.run_command(['git', 'reset', 'HEAD', self.get_file_name()])
|
||||||
|
|
||||||
|
def generic_done(self, result):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitResetHeadCommand(GitResetHead, GitTextCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitResetHeadAllCommand(GitResetHead, GitWindowCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitResetHardHeadCommand(GitWindowCommand):
|
||||||
|
may_change_files = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if sublime.ok_cancel_dialog("Warning: this will reset your index and revert all files, throwing away all your uncommitted changes with no way to recover. Consider stashing your changes instead if you'd like to set them aside safely.", "Continue"):
|
||||||
|
self.run_command(['git', 'reset', '--hard', 'HEAD'])
|
@@ -0,0 +1,130 @@
|
|||||||
|
import tempfile
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
from git import git_root, GitTextCommand
|
||||||
|
|
||||||
|
|
||||||
|
class GitClearAnnotationCommand(GitTextCommand):
|
||||||
|
def run(self, view):
|
||||||
|
self.active_view().settings().set('live_git_annotations', False)
|
||||||
|
self.view.erase_regions('git.changes.x')
|
||||||
|
self.view.erase_regions('git.changes.+')
|
||||||
|
self.view.erase_regions('git.changes.-')
|
||||||
|
|
||||||
|
|
||||||
|
class GitToggleAnnotationsCommand(GitTextCommand):
|
||||||
|
def run(self, view):
|
||||||
|
if self.active_view().settings().get('live_git_annotations'):
|
||||||
|
self.view.run_command('git_clear_annotation')
|
||||||
|
else:
|
||||||
|
self.view.run_command('git_annotate')
|
||||||
|
|
||||||
|
|
||||||
|
class GitAnnotationListener(sublime_plugin.EventListener):
|
||||||
|
def on_modified(self, view):
|
||||||
|
if not view.settings().get('live_git_annotations'):
|
||||||
|
return
|
||||||
|
view.run_command('git_annotate')
|
||||||
|
|
||||||
|
def on_load(self, view):
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
if s.get('annotations'):
|
||||||
|
view.run_command('git_annotate')
|
||||||
|
|
||||||
|
|
||||||
|
class GitAnnotateCommand(GitTextCommand):
|
||||||
|
# Unfortunately, git diff does not support text from stdin, making a *live*
|
||||||
|
# annotation difficult. Therefore I had to resort to the system diff
|
||||||
|
# command.
|
||||||
|
# This works as follows:
|
||||||
|
# 1. When the command is run for the first time for this file, a temporary
|
||||||
|
# file with the current state of the HEAD is being pulled from git.
|
||||||
|
# 2. All consecutive runs will pass the current buffer into diffs stdin.
|
||||||
|
# The resulting output is then parsed and regions are set accordingly.
|
||||||
|
def run(self, view):
|
||||||
|
# If the annotations are already running, we dont have to create a new
|
||||||
|
# tmpfile
|
||||||
|
if hasattr(self, "tmp"):
|
||||||
|
self.compare_tmp(None)
|
||||||
|
return
|
||||||
|
self.tmp = tempfile.NamedTemporaryFile()
|
||||||
|
self.active_view().settings().set('live_git_annotations', True)
|
||||||
|
root = git_root(self.get_working_dir())
|
||||||
|
repo_file = os.path.relpath(self.view.file_name(), root)
|
||||||
|
self.run_command(['git', 'show', 'HEAD:{0}'.format(repo_file)], show_status=False, no_save=True, callback=self.compare_tmp, stdout=self.tmp)
|
||||||
|
|
||||||
|
def compare_tmp(self, result, stdout=None):
|
||||||
|
all_text = self.view.substr(sublime.Region(0, self.view.size())).encode("utf-8")
|
||||||
|
self.run_command(['diff', '-u', self.tmp.name, '-'], stdin=all_text, no_save=True, show_status=False, callback=self.parse_diff)
|
||||||
|
|
||||||
|
# This is where the magic happens. At the moment, only one chunk format is supported. While
|
||||||
|
# the unified diff format theoritaclly supports more, I don't think git diff creates them.
|
||||||
|
def parse_diff(self, result, stdin=None):
|
||||||
|
lines = result.splitlines()
|
||||||
|
matcher = re.compile('^@@ -([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@')
|
||||||
|
diff = []
|
||||||
|
for line_index in range(0, len(lines)):
|
||||||
|
line = lines[line_index]
|
||||||
|
if not line.startswith('@'):
|
||||||
|
continue
|
||||||
|
match = matcher.match(line)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
line_before, len_before, line_after, len_after = [int(match.group(x)) for x in [1, 2, 3, 4]]
|
||||||
|
chunk_index = line_index + 1
|
||||||
|
tracked_line_index = line_after - 1
|
||||||
|
deletion = False
|
||||||
|
insertion = False
|
||||||
|
while True:
|
||||||
|
line = lines[chunk_index]
|
||||||
|
if line.startswith('@'):
|
||||||
|
break
|
||||||
|
elif line.startswith('-'):
|
||||||
|
if not line.strip() == '-':
|
||||||
|
deletion = True
|
||||||
|
tracked_line_index -= 1
|
||||||
|
elif line.startswith('+'):
|
||||||
|
if deletion and not line.strip() == '+':
|
||||||
|
diff.append(['x', tracked_line_index])
|
||||||
|
insertion = True
|
||||||
|
elif not deletion:
|
||||||
|
insertion = True
|
||||||
|
diff.append(['+', tracked_line_index])
|
||||||
|
else:
|
||||||
|
if not insertion and deletion:
|
||||||
|
diff.append(['-', tracked_line_index])
|
||||||
|
insertion = deletion = False
|
||||||
|
tracked_line_index += 1
|
||||||
|
chunk_index += 1
|
||||||
|
if chunk_index >= len(lines):
|
||||||
|
break
|
||||||
|
|
||||||
|
self.annotate(diff)
|
||||||
|
|
||||||
|
# Once we got all lines with their specific change types (either x, +, or - for
|
||||||
|
# modified, added, or removed) we can create our regions and do the actual annotation.
|
||||||
|
def annotate(self, diff):
|
||||||
|
self.view.erase_regions('git.changes.x')
|
||||||
|
self.view.erase_regions('git.changes.+')
|
||||||
|
self.view.erase_regions('git.changes.-')
|
||||||
|
typed_diff = {'x': [], '+': [], '-': []}
|
||||||
|
for change_type, line in diff:
|
||||||
|
if change_type == '-':
|
||||||
|
full_region = self.view.full_line(self.view.text_point(line - 1, 0))
|
||||||
|
position = full_region.begin()
|
||||||
|
for i in xrange(full_region.size()):
|
||||||
|
typed_diff[change_type].append(sublime.Region(position + i))
|
||||||
|
else:
|
||||||
|
point = self.view.text_point(line, 0)
|
||||||
|
region = self.view.full_line(point)
|
||||||
|
if change_type == '-':
|
||||||
|
region = sublime.Region(point, point + 5)
|
||||||
|
typed_diff[change_type].append(region)
|
||||||
|
|
||||||
|
for change in ['x', '+']:
|
||||||
|
self.view.add_regions("git.changes.{0}".format(change), typed_diff[change], 'git.changes.{0}'.format(change), 'dot', sublime.HIDDEN)
|
||||||
|
|
||||||
|
self.view.add_regions("git.changes.-", typed_diff['-'], 'git.changes.-', 'dot', sublime.DRAW_EMPTY_AS_OVERWRITE)
|
@@ -0,0 +1,169 @@
|
|||||||
|
import functools
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
from git import GitTextCommand, GitWindowCommand, plugin_file, view_contents, _make_text_safeish
|
||||||
|
import add
|
||||||
|
|
||||||
|
history = []
|
||||||
|
|
||||||
|
|
||||||
|
class GitQuickCommitCommand(GitTextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
self.get_window().show_input_panel("Message", "",
|
||||||
|
self.on_input, None, None)
|
||||||
|
|
||||||
|
def on_input(self, message):
|
||||||
|
if message.strip() == "":
|
||||||
|
self.panel("No commit message provided")
|
||||||
|
return
|
||||||
|
self.run_command(['git', 'add', self.get_file_name()],
|
||||||
|
functools.partial(self.add_done, message))
|
||||||
|
|
||||||
|
def add_done(self, message, result):
|
||||||
|
if result.strip():
|
||||||
|
sublime.error_message("Error adding file:\n" + result)
|
||||||
|
return
|
||||||
|
self.run_command(['git', 'commit', '-m', message])
|
||||||
|
|
||||||
|
|
||||||
|
# Commit is complicated. It'd be easy if I just wanted to let it run
|
||||||
|
# on OSX, and assume that subl was in the $PATH. However... I can't do
|
||||||
|
# that. Second choice was to set $GIT_EDITOR to sublime text for the call
|
||||||
|
# to commit, and let that Just Work. However, on Windows you can't pass
|
||||||
|
# -w to sublime, which means the editor won't wait, and so the commit will fail
|
||||||
|
# with an empty message.
|
||||||
|
# Thus this flow:
|
||||||
|
# 1. `status --porcelain --untracked-files=no` to know whether files need
|
||||||
|
# to be committed
|
||||||
|
# 2. `status` to get a template commit message (not the exact one git uses; I
|
||||||
|
# can't see a way to ask it to output that, which is not quite ideal)
|
||||||
|
# 3. Create a scratch buffer containing the template
|
||||||
|
# 4. When this buffer is closed, get its contents with an event handler and
|
||||||
|
# pass execution back to the original command. (I feel that the way this
|
||||||
|
# is done is a total hack. Unfortunately, I cannot see a better way right
|
||||||
|
# now.)
|
||||||
|
# 5. Strip lines beginning with # from the message, and save in a temporary
|
||||||
|
# file
|
||||||
|
# 6. `commit -F [tempfile]`
|
||||||
|
class GitCommitCommand(GitWindowCommand):
|
||||||
|
active_message = False
|
||||||
|
extra_options = ""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.lines = []
|
||||||
|
self.working_dir = self.get_working_dir()
|
||||||
|
self.run_command(
|
||||||
|
['git', 'status', '--untracked-files=no', '--porcelain'],
|
||||||
|
self.porcelain_status_done
|
||||||
|
)
|
||||||
|
|
||||||
|
def porcelain_status_done(self, result):
|
||||||
|
# todo: split out these status-parsing things... asdf
|
||||||
|
has_staged_files = False
|
||||||
|
result_lines = result.rstrip().split('\n')
|
||||||
|
for line in result_lines:
|
||||||
|
if line and not line[0].isspace():
|
||||||
|
has_staged_files = True
|
||||||
|
break
|
||||||
|
if not has_staged_files:
|
||||||
|
self.panel("Nothing to commit")
|
||||||
|
return
|
||||||
|
# Okay, get the template!
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
if s.get("verbose_commits"):
|
||||||
|
self.run_command(['git', 'diff', '--staged', '--no-color'], self.diff_done)
|
||||||
|
else:
|
||||||
|
self.run_command(['git', 'status'], self.diff_done)
|
||||||
|
|
||||||
|
def diff_done(self, result):
|
||||||
|
settings = sublime.load_settings("Git.sublime-settings")
|
||||||
|
historySize = settings.get('history_size')
|
||||||
|
|
||||||
|
def format(line):
|
||||||
|
return '# ' + line.replace("\n", " ")
|
||||||
|
|
||||||
|
if not len(self.lines):
|
||||||
|
self.lines = ["", ""]
|
||||||
|
|
||||||
|
self.lines.extend(map(format, history[:historySize]))
|
||||||
|
self.lines.extend([
|
||||||
|
"# --------------",
|
||||||
|
"# Please enter the commit message for your changes. Everything below",
|
||||||
|
"# this paragraph is ignored, and an empty message aborts the commit.",
|
||||||
|
"# Just close the window to accept your message.",
|
||||||
|
result.strip()
|
||||||
|
])
|
||||||
|
template = "\n".join(self.lines)
|
||||||
|
msg = self.window.new_file()
|
||||||
|
msg.set_scratch(True)
|
||||||
|
msg.set_name("COMMIT_EDITMSG")
|
||||||
|
self._output_to_view(msg, template, syntax=plugin_file("syntax/Git Commit Message.tmLanguage"))
|
||||||
|
msg.sel().clear()
|
||||||
|
msg.sel().add(sublime.Region(0, 0))
|
||||||
|
GitCommitCommand.active_message = self
|
||||||
|
|
||||||
|
def message_done(self, message):
|
||||||
|
# filter out the comments (git commit doesn't do this automatically)
|
||||||
|
settings = sublime.load_settings("Git.sublime-settings")
|
||||||
|
historySize = settings.get('history_size')
|
||||||
|
lines = [line for line in message.split("\n# --------------")[0].split("\n")
|
||||||
|
if not line.lstrip().startswith('#')]
|
||||||
|
message = '\n'.join(lines).strip()
|
||||||
|
|
||||||
|
if len(message) and historySize:
|
||||||
|
history.insert(0, message)
|
||||||
|
# write the temp file
|
||||||
|
message_file = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
message_file.write(_make_text_safeish(message, self.fallback_encoding, 'encode'))
|
||||||
|
message_file.close()
|
||||||
|
self.message_file = message_file
|
||||||
|
# and actually commit
|
||||||
|
with open(message_file.name, 'r') as fp:
|
||||||
|
self.run_command(['git', 'commit', '-F', '-', self.extra_options],
|
||||||
|
self.commit_done, working_dir=self.working_dir, stdin=fp.read())
|
||||||
|
|
||||||
|
def commit_done(self, result, **kwargs):
|
||||||
|
os.remove(self.message_file.name)
|
||||||
|
self.panel(result)
|
||||||
|
|
||||||
|
|
||||||
|
class GitCommitAmendCommand(GitCommitCommand):
|
||||||
|
extra_options = "--amend"
|
||||||
|
|
||||||
|
def diff_done(self, result):
|
||||||
|
self.after_show = result
|
||||||
|
self.run_command(['git', 'log', '-n', '1', '--format=format:%B'], self.amend_diff_done)
|
||||||
|
|
||||||
|
def amend_diff_done(self, result):
|
||||||
|
self.lines = result.split("\n")
|
||||||
|
super(GitCommitAmendCommand, self).diff_done(self.after_show)
|
||||||
|
|
||||||
|
|
||||||
|
class GitCommitMessageListener(sublime_plugin.EventListener):
|
||||||
|
def on_close(self, view):
|
||||||
|
if view.name() != "COMMIT_EDITMSG":
|
||||||
|
return
|
||||||
|
command = GitCommitCommand.active_message
|
||||||
|
if not command:
|
||||||
|
return
|
||||||
|
message = view_contents(view)
|
||||||
|
command.message_done(message)
|
||||||
|
|
||||||
|
|
||||||
|
class GitCommitHistoryCommand(sublime_plugin.TextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
self.edit = edit
|
||||||
|
self.view.window().show_quick_panel(history, self.panel_done, sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def panel_done(self, index):
|
||||||
|
if index > -1:
|
||||||
|
self.view.replace(self.edit, self.view.sel()[0], history[index] + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
class GitCommitSelectedHunk(add.GitAddSelectedHunkCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
self.run_command(['git', 'diff', '--no-color', self.get_file_name()], self.cull_diff)
|
||||||
|
self.get_window().run_command('git_commit')
|
@@ -0,0 +1,157 @@
|
|||||||
|
import sublime
|
||||||
|
import re
|
||||||
|
from git import git_root, GitTextCommand, GitWindowCommand
|
||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
|
def do_when(conditional, callback, *args, **kwargs):
|
||||||
|
if conditional():
|
||||||
|
return callback(*args, **kwargs)
|
||||||
|
sublime.set_timeout(functools.partial(do_when, conditional, callback, *args, **kwargs), 50)
|
||||||
|
|
||||||
|
|
||||||
|
def goto_xy(view, line, col):
|
||||||
|
view.run_command("goto_line", {"line": line})
|
||||||
|
for i in range(col):
|
||||||
|
view.run_command("move", {"by": "characters", "forward": True})
|
||||||
|
|
||||||
|
|
||||||
|
class GitDiff (object):
|
||||||
|
def run(self, edit=None):
|
||||||
|
self.run_command(['git', 'diff', '--no-color', '--', self.get_file_name()],
|
||||||
|
self.diff_done)
|
||||||
|
|
||||||
|
def diff_done(self, result):
|
||||||
|
if not result.strip():
|
||||||
|
self.panel("No output")
|
||||||
|
return
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
if s.get('diff_panel'):
|
||||||
|
view = self.panel(result)
|
||||||
|
else:
|
||||||
|
view = self.scratch(result, title="Git Diff")
|
||||||
|
|
||||||
|
lines_inserted = view.find_all(r'^\+[^+]{2} ')
|
||||||
|
lines_deleted = view.find_all(r'^-[^-]{2} ')
|
||||||
|
|
||||||
|
view.add_regions("inserted", lines_inserted, "markup.inserted.diff", "dot", sublime.HIDDEN)
|
||||||
|
view.add_regions("deleted", lines_deleted, "markup.deleted.diff", "dot", sublime.HIDDEN)
|
||||||
|
|
||||||
|
# Store the git root directory in the view so we can resolve relative paths
|
||||||
|
# when the user wants to navigate to the source file.
|
||||||
|
view.settings().set("git_root_dir", git_root(self.get_working_dir()))
|
||||||
|
|
||||||
|
|
||||||
|
class GitDiffCommit (object):
|
||||||
|
def run(self, edit=None):
|
||||||
|
self.run_command(['git', 'diff', '--cached', '--no-color'],
|
||||||
|
self.diff_done)
|
||||||
|
|
||||||
|
def diff_done(self, result):
|
||||||
|
if not result.strip():
|
||||||
|
self.panel("No output")
|
||||||
|
return
|
||||||
|
self.scratch(result, title="Git Diff")
|
||||||
|
|
||||||
|
|
||||||
|
class GitDiffCommand(GitDiff, GitTextCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitDiffAllCommand(GitDiff, GitWindowCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitDiffCommitCommand(GitDiffCommit, GitWindowCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitDiffTool(object):
|
||||||
|
def run(self, edit=None):
|
||||||
|
self.run_command(['git', 'difftool', '--', self.get_file_name()])
|
||||||
|
|
||||||
|
|
||||||
|
class GitDiffToolCommand(GitDiffTool, GitTextCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitDiffToolAll(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'difftool'])
|
||||||
|
|
||||||
|
|
||||||
|
class GitGotoDiff(sublime_plugin.TextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
v = self.view
|
||||||
|
view_scope_name = v.scope_name(v.sel()[0].a)
|
||||||
|
scope_markup_inserted = ("markup.inserted.diff" in view_scope_name)
|
||||||
|
scope_markup_deleted = ("markup.deleted.diff" in view_scope_name)
|
||||||
|
|
||||||
|
if not scope_markup_inserted and not scope_markup_deleted:
|
||||||
|
return
|
||||||
|
|
||||||
|
beg = v.sel()[0].a # Current position in selection
|
||||||
|
pt = v.line(beg).a # First position in the current diff line
|
||||||
|
self.column = beg - pt - 1 # The current column (-1 because the first char in diff file)
|
||||||
|
|
||||||
|
self.file_name = None
|
||||||
|
hunk_line = None
|
||||||
|
line_offset = 0
|
||||||
|
|
||||||
|
while pt > 0:
|
||||||
|
line = v.line(pt)
|
||||||
|
lineContent = v.substr(line)
|
||||||
|
if lineContent.startswith("@@"):
|
||||||
|
if not hunk_line:
|
||||||
|
hunk_line = lineContent
|
||||||
|
elif lineContent.startswith("+++ b/"):
|
||||||
|
self.file_name = v.substr(sublime.Region(line.a+6, line.b)).strip()
|
||||||
|
break
|
||||||
|
elif not hunk_line and not lineContent.startswith("-"):
|
||||||
|
line_offset = line_offset+1
|
||||||
|
|
||||||
|
pt = v.line(pt-1).a
|
||||||
|
|
||||||
|
hunk = re.match(r"^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*", hunk_line)
|
||||||
|
if not hunk:
|
||||||
|
sublime.status_message("No hunk info")
|
||||||
|
return
|
||||||
|
|
||||||
|
hunk_start_line = hunk.group(3)
|
||||||
|
self.goto_line = int(hunk_start_line) + line_offset - 1
|
||||||
|
|
||||||
|
git_root_dir = v.settings().get("git_root_dir")
|
||||||
|
|
||||||
|
# Sanity check and see if the file we're going to try to open even
|
||||||
|
# exists. If it does not, prompt the user for the correct base directory
|
||||||
|
# to use for their diff.
|
||||||
|
full_path_file_name = self.file_name
|
||||||
|
if git_root_dir:
|
||||||
|
full_path_file_name = os.path.join(git_root_dir, self.file_name)
|
||||||
|
else:
|
||||||
|
git_root_dir = ""
|
||||||
|
|
||||||
|
if not os.path.isfile(full_path_file_name):
|
||||||
|
caption = "Enter base directory for file '%s':" % self.file_name
|
||||||
|
v.window().show_input_panel(caption,
|
||||||
|
git_root_dir,
|
||||||
|
self.on_path_confirmed,
|
||||||
|
None,
|
||||||
|
None)
|
||||||
|
else:
|
||||||
|
self.on_path_confirmed(git_root_dir)
|
||||||
|
|
||||||
|
def on_path_confirmed(self, git_root_dir):
|
||||||
|
v = self.view
|
||||||
|
old_git_root_dir = v.settings().get("git_root_dir")
|
||||||
|
|
||||||
|
# If the user provided a new git_root_dir, save it in the view settings
|
||||||
|
# so they only have to fix it once
|
||||||
|
if old_git_root_dir != git_root_dir:
|
||||||
|
v.settings().set("git_root_dir", git_root_dir)
|
||||||
|
|
||||||
|
full_path_file_name = os.path.join(git_root_dir, self.file_name)
|
||||||
|
|
||||||
|
new_view = v.window().open_file(full_path_file_name)
|
||||||
|
do_when(lambda: not new_view.is_loading(),
|
||||||
|
lambda: goto_xy(new_view, self.goto_line, self.column))
|
@@ -0,0 +1,90 @@
|
|||||||
|
import sublime
|
||||||
|
from git import GitWindowCommand
|
||||||
|
|
||||||
|
|
||||||
|
class GitFlowCommand(GitWindowCommand):
|
||||||
|
def is_visible(self):
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
if s.get('flow'):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class GitFlowFeatureStartCommand(GitFlowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.get_window().show_input_panel('Enter Feature Name:', '', self.on_done, None, None)
|
||||||
|
|
||||||
|
def on_done(self, feature_name):
|
||||||
|
self.run_command(['git-flow', 'feature', 'start', feature_name])
|
||||||
|
|
||||||
|
|
||||||
|
class GitFlowFeatureFinishCommand(GitFlowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git-flow', 'feature'], self.feature_done)
|
||||||
|
|
||||||
|
def feature_done(self, result):
|
||||||
|
self.results = result.rstrip().split('\n')
|
||||||
|
self.quick_panel(self.results, self.panel_done,
|
||||||
|
sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
picked_feature = self.results[picked]
|
||||||
|
if picked_feature.startswith("*"):
|
||||||
|
picked_feature = picked_feature.strip("*")
|
||||||
|
picked_feature = picked_feature.strip()
|
||||||
|
self.run_command(['git-flow', 'feature', 'finish', picked_feature])
|
||||||
|
|
||||||
|
|
||||||
|
class GitFlowReleaseStartCommand(GitFlowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.get_window().show_input_panel('Enter Version Number:', '', self.on_done, None, None)
|
||||||
|
|
||||||
|
def on_done(self, release_name):
|
||||||
|
self.run_command(['git-flow', 'release', 'start', release_name])
|
||||||
|
|
||||||
|
|
||||||
|
class GitFlowReleaseFinishCommand(GitFlowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git-flow', 'release'], self.release_done)
|
||||||
|
|
||||||
|
def release_done(self, result):
|
||||||
|
self.results = result.rstrip().split('\n')
|
||||||
|
self.quick_panel(self.results, self.panel_done,
|
||||||
|
sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
picked_release = self.results[picked]
|
||||||
|
if picked_release.startswith("*"):
|
||||||
|
picked_release = picked_release.strip("*")
|
||||||
|
picked_release = picked_release.strip()
|
||||||
|
self.run_command(['git-flow', 'release', 'finish', picked_release])
|
||||||
|
|
||||||
|
|
||||||
|
class GitFlowHotfixStartCommand(GitFlowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.get_window().show_input_panel('Enter hotfix name:', '', self.on_done, None, None)
|
||||||
|
|
||||||
|
def on_done(self, hotfix_name):
|
||||||
|
self.run_command(['git-flow', 'hotfix', 'start', hotfix_name])
|
||||||
|
|
||||||
|
|
||||||
|
class GitFlowHotfixFinishCommand(GitFlowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git-flow', 'hotfix'], self.hotfix_done)
|
||||||
|
|
||||||
|
def hotfix_done(self, result):
|
||||||
|
self.results = result.rstrip().split('\n')
|
||||||
|
self.quick_panel(self.results, self.panel_done,
|
||||||
|
sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
picked_hotfix = self.results[picked]
|
||||||
|
if picked_hotfix.startswith("*"):
|
||||||
|
picked_hotfix = picked_hotfix.strip("*")
|
||||||
|
picked_hotfix = picked_hotfix.strip()
|
||||||
|
self.run_command(['git-flow', 'hotfix', 'finish', picked_hotfix])
|
@@ -0,0 +1,333 @@
|
|||||||
|
import os
|
||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
import functools
|
||||||
|
import os.path
|
||||||
|
import time
|
||||||
|
|
||||||
|
# when sublime loads a plugin it's cd'd into the plugin directory. Thus
|
||||||
|
# __file__ is useless for my purposes. What I want is "Packages/Git", but
|
||||||
|
# allowing for the possibility that someone has renamed the file.
|
||||||
|
# Fun discovery: Sublime on windows still requires posix path separators.
|
||||||
|
PLUGIN_DIRECTORY = os.getcwd().replace(os.path.normpath(os.path.join(os.getcwd(), '..', '..')) + os.path.sep, '').replace(os.path.sep, '/')
|
||||||
|
|
||||||
|
git_root_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def main_thread(callback, *args, **kwargs):
|
||||||
|
# sublime.set_timeout gets used to send things onto the main thread
|
||||||
|
# most sublime.[something] calls need to be on the main thread
|
||||||
|
sublime.set_timeout(functools.partial(callback, *args, **kwargs), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def open_url(url):
|
||||||
|
sublime.active_window().run_command('open_url', {"url": url})
|
||||||
|
|
||||||
|
|
||||||
|
def git_root(directory):
|
||||||
|
global git_root_cache
|
||||||
|
|
||||||
|
retval = False
|
||||||
|
leaf_dir = directory
|
||||||
|
|
||||||
|
if leaf_dir in git_root_cache and git_root_cache[leaf_dir]['expires'] > time.time():
|
||||||
|
return git_root_cache[leaf_dir]['retval']
|
||||||
|
|
||||||
|
while directory:
|
||||||
|
if os.path.exists(os.path.join(directory, '.git')):
|
||||||
|
retval = directory
|
||||||
|
break
|
||||||
|
parent = os.path.realpath(os.path.join(directory, os.path.pardir))
|
||||||
|
if parent == directory:
|
||||||
|
# /.. == /
|
||||||
|
retval = False
|
||||||
|
break
|
||||||
|
directory = parent
|
||||||
|
|
||||||
|
git_root_cache[leaf_dir] = {
|
||||||
|
'retval': retval,
|
||||||
|
'expires': time.time() + 5
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
# for readability code
|
||||||
|
def git_root_exist(directory):
|
||||||
|
return git_root(directory)
|
||||||
|
|
||||||
|
|
||||||
|
def view_contents(view):
|
||||||
|
region = sublime.Region(0, view.size())
|
||||||
|
return view.substr(region)
|
||||||
|
|
||||||
|
|
||||||
|
def plugin_file(name):
|
||||||
|
return os.path.join(PLUGIN_DIRECTORY, name)
|
||||||
|
|
||||||
|
|
||||||
|
def do_when(conditional, callback, *args, **kwargs):
|
||||||
|
if conditional():
|
||||||
|
return callback(*args, **kwargs)
|
||||||
|
sublime.set_timeout(functools.partial(do_when, conditional, callback, *args, **kwargs), 50)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_text_safeish(text, fallback_encoding, method='decode'):
|
||||||
|
# The unicode decode here is because sublime converts to unicode inside
|
||||||
|
# insert in such a way that unknown characters will cause errors, which is
|
||||||
|
# distinctly non-ideal... and there's no way to tell what's coming out of
|
||||||
|
# git in output. So...
|
||||||
|
try:
|
||||||
|
unitext = getattr(text, method)('utf-8')
|
||||||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||||
|
unitext = getattr(text, method)(fallback_encoding)
|
||||||
|
return unitext
|
||||||
|
|
||||||
|
|
||||||
|
class CommandThread(threading.Thread):
|
||||||
|
def __init__(self, command, on_done, working_dir="", fallback_encoding="", **kwargs):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.command = command
|
||||||
|
self.on_done = on_done
|
||||||
|
self.working_dir = working_dir
|
||||||
|
if "stdin" in kwargs:
|
||||||
|
self.stdin = kwargs["stdin"]
|
||||||
|
else:
|
||||||
|
self.stdin = None
|
||||||
|
if "stdout" in kwargs:
|
||||||
|
self.stdout = kwargs["stdout"]
|
||||||
|
else:
|
||||||
|
self.stdout = subprocess.PIPE
|
||||||
|
self.fallback_encoding = fallback_encoding
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
|
||||||
|
# Ignore directories that no longer exist
|
||||||
|
if os.path.isdir(self.working_dir):
|
||||||
|
|
||||||
|
# Per http://bugs.python.org/issue8557 shell=True is required to
|
||||||
|
# get $PATH on Windows. Yay portable code.
|
||||||
|
shell = os.name == 'nt'
|
||||||
|
if self.working_dir != "":
|
||||||
|
os.chdir(self.working_dir)
|
||||||
|
|
||||||
|
proc = subprocess.Popen(self.command,
|
||||||
|
stdout=self.stdout, stderr=subprocess.STDOUT,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
shell=shell, universal_newlines=True)
|
||||||
|
output = proc.communicate(self.stdin)[0]
|
||||||
|
if not output:
|
||||||
|
output = ''
|
||||||
|
# if sublime's python gets bumped to 2.7 we can just do:
|
||||||
|
# output = subprocess.check_output(self.command)
|
||||||
|
main_thread(self.on_done,
|
||||||
|
_make_text_safeish(output, self.fallback_encoding), **self.kwargs)
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError, e:
|
||||||
|
main_thread(self.on_done, e.returncode)
|
||||||
|
except OSError, e:
|
||||||
|
if e.errno == 2:
|
||||||
|
main_thread(sublime.error_message, "Git binary could not be found in PATH\n\nConsider using the git_command setting for the Git plugin\n\nPATH is: %s" % os.environ['PATH'])
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
# A base for all commands
|
||||||
|
class GitCommand(object):
|
||||||
|
may_change_files = False
|
||||||
|
|
||||||
|
def run_command(self, command, callback=None, show_status=True,
|
||||||
|
filter_empty_args=True, no_save=False, **kwargs):
|
||||||
|
if filter_empty_args:
|
||||||
|
command = [arg for arg in command if arg]
|
||||||
|
if 'working_dir' not in kwargs:
|
||||||
|
kwargs['working_dir'] = self.get_working_dir()
|
||||||
|
if 'fallback_encoding' not in kwargs and self.active_view() and self.active_view().settings().get('fallback_encoding'):
|
||||||
|
kwargs['fallback_encoding'] = self.active_view().settings().get('fallback_encoding').rpartition('(')[2].rpartition(')')[0]
|
||||||
|
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
if s.get('save_first') and self.active_view() and self.active_view().is_dirty() and not no_save:
|
||||||
|
self.active_view().run_command('save')
|
||||||
|
if command[0] == 'git' and s.get('git_command'):
|
||||||
|
command[0] = s.get('git_command')
|
||||||
|
if command[0] == 'git-flow' and s.get('git_flow_command'):
|
||||||
|
command[0] = s.get('git_flow_command')
|
||||||
|
if not callback:
|
||||||
|
callback = self.generic_done
|
||||||
|
|
||||||
|
thread = CommandThread(command, callback, **kwargs)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
if show_status:
|
||||||
|
message = kwargs.get('status_message', False) or ' '.join(command)
|
||||||
|
sublime.status_message(message)
|
||||||
|
|
||||||
|
def generic_done(self, result):
|
||||||
|
if self.may_change_files and self.active_view() and self.active_view().file_name():
|
||||||
|
if self.active_view().is_dirty():
|
||||||
|
result = "WARNING: Current view is dirty.\n\n"
|
||||||
|
else:
|
||||||
|
# just asking the current file to be re-opened doesn't do anything
|
||||||
|
print "reverting"
|
||||||
|
position = self.active_view().viewport_position()
|
||||||
|
self.active_view().run_command('revert')
|
||||||
|
do_when(lambda: not self.active_view().is_loading(), lambda: self.active_view().set_viewport_position(position, False))
|
||||||
|
# self.active_view().show(position)
|
||||||
|
|
||||||
|
view = self.active_view()
|
||||||
|
if view and view.settings().get('live_git_annotations'):
|
||||||
|
self.view.run_command('git_annotate')
|
||||||
|
|
||||||
|
if not result.strip():
|
||||||
|
return
|
||||||
|
self.panel(result)
|
||||||
|
|
||||||
|
def _output_to_view(self, output_file, output, clear=False,
|
||||||
|
syntax="Packages/Diff/Diff.tmLanguage", **kwargs):
|
||||||
|
output_file.set_syntax_file(syntax)
|
||||||
|
edit = output_file.begin_edit()
|
||||||
|
if clear:
|
||||||
|
region = sublime.Region(0, self.output_view.size())
|
||||||
|
output_file.erase(edit, region)
|
||||||
|
output_file.insert(edit, 0, output)
|
||||||
|
output_file.end_edit(edit)
|
||||||
|
|
||||||
|
def scratch(self, output, title=False, position=None, **kwargs):
|
||||||
|
scratch_file = self.get_window().new_file()
|
||||||
|
if title:
|
||||||
|
scratch_file.set_name(title)
|
||||||
|
scratch_file.set_scratch(True)
|
||||||
|
self._output_to_view(scratch_file, output, **kwargs)
|
||||||
|
scratch_file.set_read_only(True)
|
||||||
|
if position:
|
||||||
|
sublime.set_timeout(lambda: scratch_file.set_viewport_position(position), 0)
|
||||||
|
return scratch_file
|
||||||
|
|
||||||
|
def panel(self, output, **kwargs):
|
||||||
|
if not hasattr(self, 'output_view'):
|
||||||
|
self.output_view = self.get_window().get_output_panel("git")
|
||||||
|
self.output_view.set_read_only(False)
|
||||||
|
self._output_to_view(self.output_view, output, clear=True, **kwargs)
|
||||||
|
self.output_view.set_read_only(True)
|
||||||
|
self.get_window().run_command("show_panel", {"panel": "output.git"})
|
||||||
|
|
||||||
|
def quick_panel(self, *args, **kwargs):
|
||||||
|
self.get_window().show_quick_panel(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# A base for all git commands that work with the entire repository
|
||||||
|
class GitWindowCommand(GitCommand, sublime_plugin.WindowCommand):
|
||||||
|
def active_view(self):
|
||||||
|
return self.window.active_view()
|
||||||
|
|
||||||
|
def _active_file_name(self):
|
||||||
|
view = self.active_view()
|
||||||
|
if view and view.file_name() and len(view.file_name()) > 0:
|
||||||
|
return view.file_name()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fallback_encoding(self):
|
||||||
|
if self.active_view() and self.active_view().settings().get('fallback_encoding'):
|
||||||
|
return self.active_view().settings().get('fallback_encoding').rpartition('(')[2].rpartition(')')[0]
|
||||||
|
|
||||||
|
# If there's no active view or the active view is not a file on the
|
||||||
|
# filesystem (e.g. a search results view), we can infer the folder
|
||||||
|
# that the user intends Git commands to run against when there's only
|
||||||
|
# only one.
|
||||||
|
def is_enabled(self):
|
||||||
|
if self._active_file_name() or len(self.window.folders()) == 1:
|
||||||
|
return git_root(self.get_working_dir())
|
||||||
|
|
||||||
|
def get_file_name(self):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def get_relative_file_name(self):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# If there is a file in the active view use that file's directory to
|
||||||
|
# search for the Git root. Otherwise, use the only folder that is
|
||||||
|
# open.
|
||||||
|
def get_working_dir(self):
|
||||||
|
file_name = self._active_file_name()
|
||||||
|
if file_name:
|
||||||
|
return os.path.realpath(os.path.dirname(file_name))
|
||||||
|
else:
|
||||||
|
try: # handle case with no open folder
|
||||||
|
return self.window.folders()[0]
|
||||||
|
except IndexError:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def get_window(self):
|
||||||
|
return self.window
|
||||||
|
|
||||||
|
|
||||||
|
# A base for all git commands that work with the file in the active view
|
||||||
|
class GitTextCommand(GitCommand, sublime_plugin.TextCommand):
|
||||||
|
def active_view(self):
|
||||||
|
return self.view
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
# First, is this actually a file on the file system?
|
||||||
|
if self.view.file_name() and len(self.view.file_name()) > 0:
|
||||||
|
return git_root(self.get_working_dir())
|
||||||
|
|
||||||
|
def get_file_name(self):
|
||||||
|
return os.path.basename(self.view.file_name())
|
||||||
|
|
||||||
|
def get_relative_file_name(self):
|
||||||
|
working_dir = self.get_working_dir()
|
||||||
|
file_path = working_dir.replace(git_root(working_dir), '')[1:]
|
||||||
|
file_name = os.path.join(file_path, self.get_file_name())
|
||||||
|
return file_name.replace('\\', '/') # windows issues
|
||||||
|
|
||||||
|
def get_working_dir(self):
|
||||||
|
return os.path.realpath(os.path.dirname(self.view.file_name()))
|
||||||
|
|
||||||
|
def get_window(self):
|
||||||
|
# Fun discovery: if you switch tabs while a command is working,
|
||||||
|
# self.view.window() is None. (Admittedly this is a consequence
|
||||||
|
# of my deciding to do async command processing... but, hey,
|
||||||
|
# got to live with that now.)
|
||||||
|
# I did try tracking the window used at the start of the command
|
||||||
|
# and using it instead of view.window() later, but that results
|
||||||
|
# panels on a non-visible window, which is especially useless in
|
||||||
|
# the case of the quick panel.
|
||||||
|
# So, this is not necessarily ideal, but it does work.
|
||||||
|
return self.view.window() or sublime.active_window()
|
||||||
|
|
||||||
|
|
||||||
|
# A few miscellaneous commands
|
||||||
|
|
||||||
|
|
||||||
|
class GitCustomCommand(GitWindowCommand):
|
||||||
|
may_change_files = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.get_window().show_input_panel("Git command", "",
|
||||||
|
self.on_input, None, None)
|
||||||
|
|
||||||
|
def on_input(self, command):
|
||||||
|
command = str(command) # avoiding unicode
|
||||||
|
if command.strip() == "":
|
||||||
|
self.panel("No git command provided")
|
||||||
|
return
|
||||||
|
import shlex
|
||||||
|
command_splitted = ['git'] + shlex.split(command)
|
||||||
|
print command_splitted
|
||||||
|
self.run_command(command_splitted)
|
||||||
|
|
||||||
|
|
||||||
|
class GitGuiCommand(GitTextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
command = ['git', 'gui']
|
||||||
|
self.run_command(command)
|
||||||
|
|
||||||
|
|
||||||
|
class GitGitkCommand(GitTextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
command = ['gitk']
|
||||||
|
self.run_command(command)
|
@@ -0,0 +1,189 @@
|
|||||||
|
import functools
|
||||||
|
import re
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
from git import GitTextCommand, GitWindowCommand, plugin_file
|
||||||
|
|
||||||
|
|
||||||
|
class GitBlameCommand(GitTextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
# somewhat custom blame command:
|
||||||
|
# -w: ignore whitespace changes
|
||||||
|
# -M: retain blame when moving lines
|
||||||
|
# -C: retain blame when copying lines between files
|
||||||
|
command = ['git', 'blame', '-w', '-M', '-C']
|
||||||
|
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
selection = self.view.sel()[0] # todo: multi-select support?
|
||||||
|
if not selection.empty() or not s.get('blame_whole_file'):
|
||||||
|
# just the lines we have a selection on
|
||||||
|
begin_line, begin_column = self.view.rowcol(selection.begin())
|
||||||
|
end_line, end_column = self.view.rowcol(selection.end())
|
||||||
|
# blame will fail if last line is empty and is included in the selection
|
||||||
|
if end_line > begin_line and end_column == 0:
|
||||||
|
end_line -= 1
|
||||||
|
lines = str(begin_line + 1) + ',' + str(end_line + 1)
|
||||||
|
command.extend(('-L', lines))
|
||||||
|
callback = self.blame_done
|
||||||
|
else:
|
||||||
|
callback = functools.partial(self.blame_done,
|
||||||
|
position=self.view.viewport_position())
|
||||||
|
|
||||||
|
command.append(self.get_file_name())
|
||||||
|
self.run_command(command, callback)
|
||||||
|
|
||||||
|
def blame_done(self, result, position=None):
|
||||||
|
self.scratch(result, title="Git Blame", position=position,
|
||||||
|
syntax=plugin_file("syntax/Git Blame.tmLanguage"))
|
||||||
|
|
||||||
|
|
||||||
|
class GitLog(object):
|
||||||
|
def run(self, edit=None):
|
||||||
|
fn = self.get_file_name()
|
||||||
|
return self.run_log(fn != '', '--', fn)
|
||||||
|
|
||||||
|
def run_log(self, follow, *args):
|
||||||
|
# the ASCII bell (\a) is just a convenient character I'm pretty sure
|
||||||
|
# won't ever come up in the subject of the commit (and if it does then
|
||||||
|
# you positively deserve broken output...)
|
||||||
|
# 9000 is a pretty arbitrarily chosen limit; picked entirely because
|
||||||
|
# it's about the size of the largest repo I've tested this on... and
|
||||||
|
# there's a definite hiccup when it's loading that
|
||||||
|
command = ['git', 'log', '--pretty=%s\a%h %an <%aE>\a%ad (%ar)',
|
||||||
|
'--date=local', '--max-count=9000', '--follow' if follow else None]
|
||||||
|
command.extend(args)
|
||||||
|
self.run_command(
|
||||||
|
command,
|
||||||
|
self.log_done)
|
||||||
|
|
||||||
|
def log_done(self, result):
|
||||||
|
self.results = [r.split('\a', 2) for r in result.strip().split('\n')]
|
||||||
|
self.quick_panel(self.results, self.log_panel_done)
|
||||||
|
|
||||||
|
def log_panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
item = self.results[picked]
|
||||||
|
# the commit hash is the first thing on the second line
|
||||||
|
self.log_result(item[1].split(' ')[0])
|
||||||
|
|
||||||
|
def log_result(self, ref):
|
||||||
|
# I'm not certain I should have the file name here; it restricts the
|
||||||
|
# details to just the current file. Depends on what the user expects...
|
||||||
|
# which I'm not sure of.
|
||||||
|
self.run_command(
|
||||||
|
['git', 'log', '-p', '-1', ref, '--', self.get_file_name()],
|
||||||
|
self.details_done)
|
||||||
|
|
||||||
|
def details_done(self, result):
|
||||||
|
self.scratch(result, title="Git Commit Details", syntax=plugin_file("syntax/Git Commit Message.tmLanguage"))
|
||||||
|
|
||||||
|
|
||||||
|
class GitLogCommand(GitLog, GitTextCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitLogAllCommand(GitLog, GitWindowCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitShow(object):
|
||||||
|
def run(self, edit=None):
|
||||||
|
# GitLog Copy-Past
|
||||||
|
self.run_command(
|
||||||
|
['git', 'log', '--pretty=%s\a%h %an <%aE>\a%ad (%ar)',
|
||||||
|
'--date=local', '--max-count=9000', '--', self.get_file_name()],
|
||||||
|
self.show_done)
|
||||||
|
|
||||||
|
def show_done(self, result):
|
||||||
|
# GitLog Copy-Past
|
||||||
|
self.results = [r.split('\a', 2) for r in result.strip().split('\n')]
|
||||||
|
self.quick_panel(self.results, self.panel_done)
|
||||||
|
|
||||||
|
def panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
item = self.results[picked]
|
||||||
|
# the commit hash is the first thing on the second line
|
||||||
|
ref = item[1].split(' ')[0]
|
||||||
|
self.run_command(
|
||||||
|
['git', 'show', '%s:%s' % (ref, self.get_relative_file_name())],
|
||||||
|
self.details_done,
|
||||||
|
ref=ref)
|
||||||
|
|
||||||
|
def details_done(self, result, ref):
|
||||||
|
syntax = self.view.settings().get('syntax')
|
||||||
|
self.scratch(result, title="%s:%s" % (ref, self.get_file_name()), syntax=syntax)
|
||||||
|
|
||||||
|
|
||||||
|
class GitShowCommand(GitShow, GitTextCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitShowAllCommand(GitShow, GitWindowCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitGraph(object):
|
||||||
|
def run(self, edit=None):
|
||||||
|
filename = self.get_file_name()
|
||||||
|
self.run_command(
|
||||||
|
['git', 'log', '--graph', '--pretty=%h -%d (%cr) (%ci) <%an> %s', '--abbrev-commit', '--no-color', '--decorate', '--date=relative', '--follow' if filename else None, '--', filename],
|
||||||
|
self.log_done
|
||||||
|
)
|
||||||
|
|
||||||
|
def log_done(self, result):
|
||||||
|
self.scratch(result, title="Git Log Graph", syntax=plugin_file("syntax/Git Graph.tmLanguage"))
|
||||||
|
|
||||||
|
|
||||||
|
class GitGraphCommand(GitGraph, GitTextCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitGraphAllCommand(GitGraph, GitWindowCommand):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GitOpenFileCommand(GitLog, GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'branch', '-a', '--no-color'], self.branch_done)
|
||||||
|
|
||||||
|
def branch_done(self, result):
|
||||||
|
self.results = result.rstrip().split('\n')
|
||||||
|
self.quick_panel(self.results, self.branch_panel_done,
|
||||||
|
sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def branch_panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
self.branch = self.results[picked].split(' ')[-1]
|
||||||
|
self.run_log(False, self.branch)
|
||||||
|
|
||||||
|
def log_result(self, result_hash):
|
||||||
|
# the commit hash is the first thing on the second line
|
||||||
|
self.ref = result_hash
|
||||||
|
self.run_command(
|
||||||
|
['git', 'ls-tree', '-r', '--full-tree', self.ref],
|
||||||
|
self.ls_done)
|
||||||
|
|
||||||
|
def ls_done(self, result):
|
||||||
|
# Last two items are the ref and the file name
|
||||||
|
# p.s. has to be a list of lists; tuples cause errors later
|
||||||
|
self.results = [[match.group(2), match.group(1)] for match in re.finditer(r"\S+\s(\S+)\t(.+)", result)]
|
||||||
|
|
||||||
|
self.quick_panel(self.results, self.ls_panel_done)
|
||||||
|
|
||||||
|
def ls_panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
item = self.results[picked]
|
||||||
|
|
||||||
|
self.filename = item[0]
|
||||||
|
self.fileRef = item[1]
|
||||||
|
|
||||||
|
self.run_command(
|
||||||
|
['git', 'show', self.fileRef],
|
||||||
|
self.show_done)
|
||||||
|
|
||||||
|
def show_done(self, result):
|
||||||
|
self.scratch(result, title="%s:%s" % (self.fileRef, self.filename))
|
@@ -0,0 +1,159 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
from git import GitTextCommand, GitWindowCommand, git_root_exist
|
||||||
|
|
||||||
|
|
||||||
|
class GitInit(object):
|
||||||
|
def git_init(self, directory):
|
||||||
|
if os.path.exists(directory):
|
||||||
|
self.run_command(['git', 'init'], self.git_inited, working_dir=directory)
|
||||||
|
else:
|
||||||
|
sublime.status_message("Directory does not exist.")
|
||||||
|
|
||||||
|
def git_inited(self, result):
|
||||||
|
sublime.status_message(result)
|
||||||
|
|
||||||
|
|
||||||
|
class GitInitCommand(GitInit, GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.get_window().show_input_panel("Git directory", self.get_working_dir(), self.git_init, None, None)
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
if not git_root_exist(self.get_working_dir()):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class GitBranchCommand(GitWindowCommand):
|
||||||
|
may_change_files = True
|
||||||
|
command_to_run_after_branch = ['checkout']
|
||||||
|
extra_flags = []
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'branch', '--no-color'] + self.extra_flags, self.branch_done)
|
||||||
|
|
||||||
|
def branch_done(self, result):
|
||||||
|
self.results = result.rstrip().split('\n')
|
||||||
|
self.quick_panel(self.results, self.panel_done,
|
||||||
|
sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
picked_branch = self.results[picked]
|
||||||
|
if picked_branch.startswith("*"):
|
||||||
|
return
|
||||||
|
picked_branch = picked_branch.strip()
|
||||||
|
self.run_command(['git'] + self.command_to_run_after_branch + [picked_branch], self.update_status)
|
||||||
|
|
||||||
|
def update_status(self, result):
|
||||||
|
global branch
|
||||||
|
branch = ""
|
||||||
|
for view in self.window.views():
|
||||||
|
view.run_command("git_branch_status")
|
||||||
|
|
||||||
|
|
||||||
|
class GitMergeCommand(GitBranchCommand):
|
||||||
|
command_to_run_after_branch = ['merge']
|
||||||
|
extra_flags = ['--no-merge']
|
||||||
|
|
||||||
|
|
||||||
|
class GitDeleteBranchCommand(GitBranchCommand):
|
||||||
|
command_to_run_after_branch = ['branch', '-d']
|
||||||
|
|
||||||
|
|
||||||
|
class GitNewBranchCommand(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.get_window().show_input_panel("Branch name", "",
|
||||||
|
self.on_input, None, None)
|
||||||
|
|
||||||
|
def on_input(self, branchname):
|
||||||
|
if branchname.strip() == "":
|
||||||
|
self.panel("No branch name provided")
|
||||||
|
return
|
||||||
|
self.run_command(['git', 'checkout', '-b', branchname])
|
||||||
|
|
||||||
|
|
||||||
|
class GitNewTagCommand(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.get_window().show_input_panel("Tag name", "", self.on_input, None, None)
|
||||||
|
|
||||||
|
def on_input(self, tagname):
|
||||||
|
if not tagname.strip():
|
||||||
|
self.panel("No branch name provided")
|
||||||
|
return
|
||||||
|
self.run_command(['git', 'tag', tagname])
|
||||||
|
|
||||||
|
|
||||||
|
class GitShowTagsCommand(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'tag'], self.fetch_tag)
|
||||||
|
|
||||||
|
def fetch_tag(self, result):
|
||||||
|
self.results = result.rstrip().split('\n')
|
||||||
|
self.quick_panel(self.results, self.panel_done)
|
||||||
|
|
||||||
|
def panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
picked_tag = self.results[picked]
|
||||||
|
picked_tag = picked_tag.strip()
|
||||||
|
self.run_command(['git', 'show', picked_tag])
|
||||||
|
|
||||||
|
|
||||||
|
class GitPushTagsCommand(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'push', '--tags'])
|
||||||
|
|
||||||
|
|
||||||
|
class GitCheckoutCommand(GitTextCommand):
|
||||||
|
may_change_files = True
|
||||||
|
|
||||||
|
def run(self, edit):
|
||||||
|
self.run_command(['git', 'checkout', self.get_file_name()])
|
||||||
|
|
||||||
|
|
||||||
|
class GitFetchCommand(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'fetch'], callback=self.panel)
|
||||||
|
|
||||||
|
|
||||||
|
class GitPullCommand(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'pull'], callback=self.panel)
|
||||||
|
|
||||||
|
|
||||||
|
class GitPullCurrentBranchCommand(GitWindowCommand):
|
||||||
|
command_to_run_after_describe = 'pull'
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'describe', '--contains', '--all', 'HEAD'], callback=self.describe_done)
|
||||||
|
|
||||||
|
def describe_done(self, result):
|
||||||
|
self.current_branch = result.strip()
|
||||||
|
self.run_command(['git', 'remote'], callback=self.remote_done)
|
||||||
|
|
||||||
|
def remote_done(self, result):
|
||||||
|
self.remotes = result.rstrip().split('\n')
|
||||||
|
if len(self.remotes) == 1:
|
||||||
|
self.panel_done()
|
||||||
|
else:
|
||||||
|
self.quick_panel(self.remotes, self.panel_done, sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def panel_done(self, picked=0):
|
||||||
|
if picked < 0 or picked >= len(self.remotes):
|
||||||
|
return
|
||||||
|
self.picked_remote = self.remotes[picked]
|
||||||
|
self.picked_remote = self.picked_remote.strip()
|
||||||
|
self.run_command(['git', self.command_to_run_after_describe, self.picked_remote, self.current_branch])
|
||||||
|
|
||||||
|
|
||||||
|
class GitPushCommand(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'push'], callback=self.panel)
|
||||||
|
|
||||||
|
|
||||||
|
class GitPushCurrentBranchCommand(GitPullCurrentBranchCommand):
|
||||||
|
command_to_run_after_describe = 'push'
|
@@ -0,0 +1,47 @@
|
|||||||
|
from git import GitWindowCommand
|
||||||
|
|
||||||
|
|
||||||
|
class GitStashCommand(GitWindowCommand):
|
||||||
|
may_change_files = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'stash'])
|
||||||
|
|
||||||
|
|
||||||
|
class GitStashPopCommand(GitWindowCommand):
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'stash', 'pop'])
|
||||||
|
|
||||||
|
|
||||||
|
class GitStashApplyCommand(GitWindowCommand):
|
||||||
|
may_change_files = True
|
||||||
|
command_to_run_after_list = 'apply'
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'stash', 'list'], self.stash_list_done)
|
||||||
|
|
||||||
|
def stash_list_done(self, result):
|
||||||
|
# No stash list at all
|
||||||
|
if not result:
|
||||||
|
self.panel('No stash found')
|
||||||
|
return
|
||||||
|
|
||||||
|
self.results = result.rstrip().split('\n')
|
||||||
|
|
||||||
|
# If there is only one, apply it
|
||||||
|
if len(self.results) == 1:
|
||||||
|
self.stash_list_panel_done()
|
||||||
|
else:
|
||||||
|
self.quick_panel(self.results, self.stash_list_panel_done)
|
||||||
|
|
||||||
|
def stash_list_panel_done(self, picked=0):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
|
||||||
|
# get the stash ref (e.g. stash@{3})
|
||||||
|
self.stash = self.results[picked].split(':')[0]
|
||||||
|
self.run_command(['git', 'stash', self.command_to_run_after_list, self.stash])
|
||||||
|
|
||||||
|
|
||||||
|
class GitStashDropCommand(GitStashApplyCommand):
|
||||||
|
command_to_run_after_list = 'drop'
|
@@ -0,0 +1,63 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
from git import GitWindowCommand, git_root
|
||||||
|
|
||||||
|
|
||||||
|
class GitStatusCommand(GitWindowCommand):
|
||||||
|
force_open = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.run_command(['git', 'status', '--porcelain'], self.status_done)
|
||||||
|
|
||||||
|
def status_done(self, result):
|
||||||
|
self.results = filter(self.status_filter, result.rstrip().split('\n'))
|
||||||
|
if len(self.results):
|
||||||
|
self.show_status_list()
|
||||||
|
else:
|
||||||
|
sublime.status_message("Nothing to show")
|
||||||
|
|
||||||
|
def show_status_list(self):
|
||||||
|
self.quick_panel(self.results, self.panel_done,
|
||||||
|
sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def status_filter(self, item):
|
||||||
|
# for this class we don't actually care
|
||||||
|
if not re.match(r'^[ MADRCU?!]{1,2}\s+.*', item):
|
||||||
|
return False
|
||||||
|
return len(item) > 0
|
||||||
|
|
||||||
|
def panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
picked_file = self.results[picked]
|
||||||
|
# first 2 characters are status codes, the third is a space
|
||||||
|
picked_status = picked_file[:2]
|
||||||
|
picked_file = picked_file[3:]
|
||||||
|
self.panel_followup(picked_status, picked_file, picked)
|
||||||
|
|
||||||
|
def panel_followup(self, picked_status, picked_file, picked_index):
|
||||||
|
# split out solely so I can override it for laughs
|
||||||
|
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
root = git_root(self.get_working_dir())
|
||||||
|
if picked_status == '??' or s.get('status_opens_file') or self.force_open:
|
||||||
|
if(os.path.isfile(os.path.join(root, picked_file))):
|
||||||
|
self.window.open_file(os.path.join(root, picked_file))
|
||||||
|
else:
|
||||||
|
self.run_command(['git', 'diff', '--no-color', '--', picked_file.strip('"')],
|
||||||
|
self.diff_done, working_dir=root)
|
||||||
|
|
||||||
|
def diff_done(self, result):
|
||||||
|
if not result.strip():
|
||||||
|
return
|
||||||
|
self.scratch(result, title="Git Diff")
|
||||||
|
|
||||||
|
|
||||||
|
class GitOpenModifiedFilesCommand(GitStatusCommand):
|
||||||
|
force_open = True
|
||||||
|
|
||||||
|
def show_status_list(self):
|
||||||
|
for line_index in range(0, len(self.results)):
|
||||||
|
self.panel_done(line_index)
|
@@ -0,0 +1,58 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
from git import GitTextCommand
|
||||||
|
|
||||||
|
|
||||||
|
class GitBranchStatusListener(sublime_plugin.EventListener):
|
||||||
|
def on_activated(self, view):
|
||||||
|
view.run_command("git_branch_status")
|
||||||
|
|
||||||
|
def on_post_save(self, view):
|
||||||
|
view.run_command("git_branch_status")
|
||||||
|
|
||||||
|
|
||||||
|
class GitBranchStatusCommand(GitTextCommand):
|
||||||
|
def run(self, view):
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
if s.get("statusbar_branch"):
|
||||||
|
self.run_command(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], self.branch_done, show_status=False, no_save=True)
|
||||||
|
else:
|
||||||
|
self.view.set_status("git-branch", "")
|
||||||
|
if (s.get("statusbar_status")):
|
||||||
|
self.run_command(['git', 'status', '--porcelain'], self.status_done, show_status=False, no_save=True)
|
||||||
|
else:
|
||||||
|
self.view.set_status("git-status", "")
|
||||||
|
|
||||||
|
def branch_done(self, result):
|
||||||
|
self.view.set_status("git-branch", "git branch: " + result.strip())
|
||||||
|
|
||||||
|
def status_done(self, result):
|
||||||
|
lines = [line for line in result.splitlines() if re.match(r'^[ MADRCU?!]{1,2}\s+.*', line)]
|
||||||
|
index = [line[0] for line in lines if not line[0].isspace()]
|
||||||
|
working = [line[1] for line in lines if not line[1].isspace()]
|
||||||
|
self.view.set_status("git-status-index", "index: " + self.status_string(index))
|
||||||
|
self.view.set_status("git-status-working", "working: " + self.status_string(working))
|
||||||
|
|
||||||
|
def status_string(self, statuses):
|
||||||
|
s = sublime.load_settings("Git.sublime-settings")
|
||||||
|
symbols = s.get("statusbar_status_symbols")
|
||||||
|
if not statuses:
|
||||||
|
return symbols['clean']
|
||||||
|
status = []
|
||||||
|
if statuses.count('M'):
|
||||||
|
status.append("%d%s" % (statuses.count('M'), symbols['modified']))
|
||||||
|
if statuses.count('A'):
|
||||||
|
status.append("%d%s" % (statuses.count('A'), symbols['added']))
|
||||||
|
if statuses.count('D'):
|
||||||
|
status.append("%d%s" % (statuses.count('D'), symbols['deleted']))
|
||||||
|
if statuses.count('?'):
|
||||||
|
status.append("%d%s" % (statuses.count('?'), symbols['untracked']))
|
||||||
|
if statuses.count('U'):
|
||||||
|
status.append("%d%s" % (statuses.count('U'), symbols['conflicts']))
|
||||||
|
if statuses.count('R'):
|
||||||
|
status.append("%d%s" % (statuses.count('R'), symbols['renamed']))
|
||||||
|
if statuses.count('C'):
|
||||||
|
status.append("%d%s" % (statuses.count('C'), symbols['copied']))
|
||||||
|
return symbols['separator'].join(status)
|
@@ -0,0 +1,18 @@
|
|||||||
|
{ "name": "Git Blame",
|
||||||
|
"scopeName": "text.git-blame",
|
||||||
|
"fileTypes": ["git-blame"],
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"match": "^(\\^?[a-f0-9]+)\\s+([\\w\\-\\d\\.\\/]*)\\s*\\((.*?)\\s+(\\d{4}-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d [+-]\\d{4})\\s+(\\d+)\\)",
|
||||||
|
"name": "line.comment.git-blame",
|
||||||
|
"captures": {
|
||||||
|
"1": {"name": "string.sha.git-blame"},
|
||||||
|
"2": {"name": "string.path.git-blame"},
|
||||||
|
"3": {"name": "support.function.author.git-blame"},
|
||||||
|
"4": {"name": "constant.numeric.date.git-blame"},
|
||||||
|
"5": {"name": "variable.parameter.line-number.git-blame"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"uuid": "5d37add9-889e-4174-b232-4bd423b84c0a"
|
||||||
|
}
|
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>fileTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>git-blame</string>
|
||||||
|
</array>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Git Blame</string>
|
||||||
|
<key>patterns</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>captures</key>
|
||||||
|
<dict>
|
||||||
|
<key>1</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>string.sha.git-blame</string>
|
||||||
|
</dict>
|
||||||
|
<key>2</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>string.path.git-blame</string>
|
||||||
|
</dict>
|
||||||
|
<key>3</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>support.function.author.git-blame</string>
|
||||||
|
</dict>
|
||||||
|
<key>4</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>constant.numeric.date.git-blame</string>
|
||||||
|
</dict>
|
||||||
|
<key>5</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>variable.parameter.line-number.git-blame</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>match</key>
|
||||||
|
<string>^(\^?[a-f0-9]+)\s+([\w\-\d\.\/]*)\s*\((.*?)\s+(\d{4}-\d\d-\d\d( \d\d:\d\d:\d\d [+-]\d{4})?)\s+(\d+)\)</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>line.comment.git-blame</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>scopeName</key>
|
||||||
|
<string>text.git-blame</string>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>5d37add9-889e-4174-b232-4bd423b84c0a</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@@ -0,0 +1,21 @@
|
|||||||
|
{ "name": "Git Commit Message",
|
||||||
|
"scopeName": "text.git-commit",
|
||||||
|
"fileTypes": ["COMMIT_EDITMSG"],
|
||||||
|
"patterns": [
|
||||||
|
{ "name": "comment.line.number-sign.git-commit",
|
||||||
|
"match": "^\\s*(#).*$\n?",
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "punctuation.definition.comment.git-commit" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "name": "meta.diff.git-commit",
|
||||||
|
"comment": "diff at the end of the commit message when using commit -v, or viewing a log. End pattern is just something to be never matched so that the meta continues untill the end of the file.",
|
||||||
|
"begin": "diff\\ \\-\\-git",
|
||||||
|
"end": "(?=xxxxxx)123457",
|
||||||
|
"patterns": [
|
||||||
|
{ "include": "source.diff" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"uuid": "de3fb2fc-e564-4a31-9813-5ee26967c5c8"
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>fileTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>COMMIT_EDITMSG</string>
|
||||||
|
</array>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Git Commit Message</string>
|
||||||
|
<key>patterns</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>captures</key>
|
||||||
|
<dict>
|
||||||
|
<key>1</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>punctuation.definition.comment.git-commit</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>match</key>
|
||||||
|
<string>^\s*(#).*$
|
||||||
|
?</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>comment.line.number-sign.git-commit</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>begin</key>
|
||||||
|
<string>diff\ \-\-git</string>
|
||||||
|
<key>comment</key>
|
||||||
|
<string>diff at the end of the commit message when using commit -v, or viewing a log. End pattern is just something to be never matched so that the meta continues untill the end of the file.</string>
|
||||||
|
<key>end</key>
|
||||||
|
<string>(?=xxxxxx)123457</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>meta.diff.git-commit</string>
|
||||||
|
<key>patterns</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>include</key>
|
||||||
|
<string>source.diff</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>scopeName</key>
|
||||||
|
<string>text.git-commit</string>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>de3fb2fc-e564-4a31-9813-5ee26967c5c8</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@@ -0,0 +1,31 @@
|
|||||||
|
{ "name": "Git Graph",
|
||||||
|
"scopeName": "text.git-graph",
|
||||||
|
"fileTypes": ["git-graph"],
|
||||||
|
"patterns": [
|
||||||
|
{ "match": "^([| *\\\\]+)([0-9a-f]{4,40}) (.*?) (\\d{4}-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d [+-]\\d{4}) (?:\\(((?:[a-zA-Z0-9._\\-\\/]+(?:, )?)+)\\) )?",
|
||||||
|
"name": "log-entry.git-graph",
|
||||||
|
"captures": {
|
||||||
|
"1": {"name": "comment.git-graph" },
|
||||||
|
"2": {"name": "string.git-graph" },
|
||||||
|
"3": {"name": "support.function.git-graph" },
|
||||||
|
"4": {"name": "constant.numeric.git-graph" },
|
||||||
|
"5": {"name": "variable.parameter.git-graph" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "match": "^\\|[\\|_\\/\\\\ ]+\n?$",
|
||||||
|
"name": "comment.git-graph",
|
||||||
|
"comment": "lines with no commit details"
|
||||||
|
},
|
||||||
|
{ "match": "(?:[Ff]ix(?:e[ds])?|[Rr]esolve[ds]?|[Cc]lose[ds]?)?\\s*(?:#\\d+|\\[.*?\\])",
|
||||||
|
"name": "keyword.git-graph",
|
||||||
|
"comment": "issue numbers"
|
||||||
|
},
|
||||||
|
{ "match": "Merge branch '(.*?)' of .*?\n?$",
|
||||||
|
"name": "comment.git-graph",
|
||||||
|
"captures": {
|
||||||
|
"1": {"name": "variable.parameter.git-graph"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"uuid": "b900521e-af64-471b-aec8-1ecf88aab595"
|
||||||
|
}
|
@@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>fileTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>git-graph</string>
|
||||||
|
</array>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Git Graph</string>
|
||||||
|
<key>patterns</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>captures</key>
|
||||||
|
<dict>
|
||||||
|
<key>1</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>comment.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
<key>2</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>string.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
<key>3</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>support.function.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
<key>4</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>constant.numeric.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
<key>5</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>variable.parameter.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
<key>6</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>keyword.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>match</key>
|
||||||
|
<string>^([| *\\]+)([0-9a-f]{4,40}) -( \(.*?\))? (.*) (\(.*) (<.*>) .*</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>log-entry.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>comment</key>
|
||||||
|
<string>lines with no commit details</string>
|
||||||
|
<key>match</key>
|
||||||
|
<string>^\|[\|_\/\\ ]+
|
||||||
|
?$</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>comment.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>comment</key>
|
||||||
|
<string>issue numbers</string>
|
||||||
|
<key>match</key>
|
||||||
|
<string>(?:[Ff]ix(?:e[ds])?|[Rr]esolve[ds]?|[Cc]lose[ds]?)?\s*(?:#\d+|\[.*?\])</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>keyword.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>captures</key>
|
||||||
|
<dict>
|
||||||
|
<key>1</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>variable.parameter.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>match</key>
|
||||||
|
<string>Merge branch '(.*?)' of .*?
|
||||||
|
?$</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>comment.git-graph</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>scopeName</key>
|
||||||
|
<string>text.git-graph</string>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>b900521e-af64-471b-aec8-1ecf88aab595</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
1
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/GitHubinator/.gitignore
vendored
Normal file
1
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/GitHubinator/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{ "command": "githubinator", "caption": "GitHubinator", "args": { "permalink": false } },
|
||||||
|
{ "command": "githubinator", "caption": "GitHubinator Permalink", "args": { "permalink": true } },
|
||||||
|
{ "command": "githubinator", "caption": "GitHubinator Blame", "args": { "permalink": false, "mode": "blame" } },
|
||||||
|
{ "command": "githubinator", "caption": "GitHubinator Blame Permalink", "args": { "permalink": true, "mode": "blame" } }
|
||||||
|
]
|
@@ -0,0 +1,4 @@
|
|||||||
|
[
|
||||||
|
{ "keys": ["super+\\"], "command": "githubinator", "args" : { "permalink": false }},
|
||||||
|
{ "keys": ["shift+super+\\"], "command": "githubinator", "args" : { "permalink": true }}
|
||||||
|
]
|
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "GitHubinator",
|
||||||
|
"command": "githubinator",
|
||||||
|
"args": { "permalink": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "GitHubinator Permalink",
|
||||||
|
"command": "githubinator",
|
||||||
|
"args": { "permalink": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "GitHubinator Blame",
|
||||||
|
"command": "githubinator",
|
||||||
|
"args": { "permalink": false, "mode": "blame" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "GitHubinator Blame Permalink",
|
||||||
|
"command": "githubinator",
|
||||||
|
"args": { "permalink": true, "mode": "blame" }
|
||||||
|
}
|
||||||
|
]
|
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"default_remote": "origin"
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
# GitHubinator*
|
||||||
|
|
||||||
|
*_With regards to [Dr. Heinz Doofenshmirtz](http://en.wikipedia.org/wiki/Dr._Heinz_Doofenshmirtz)_
|
||||||
|
|
||||||
|
This will allow you to select text in a Sublime Text 2 file, and see the highlighted lines on GitHub's remote repo, if one exists.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
If you use [Package Control](http://wbond.net/sublime_packages/package_control), just install it from there. If not:
|
||||||
|
|
||||||
|
Clone this repo to your Sublime Text 2 Packages folder:
|
||||||
|
|
||||||
|
cd ~/"Library/Application Support/Sublime Text 2/Packages/"
|
||||||
|
git clone https://github.com/ehamiter/ST2-GitHubinator.git
|
||||||
|
|
||||||
|
The plugin should be picked up automatically. If not, restart Sublime Text.
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The defaults should work for most setups, but if you have a different remote name, you can configure it in the `Githubinator.sublime-settings` file:
|
||||||
|
|
||||||
|
{
|
||||||
|
"default_remote": "origin"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Select some text.
|
||||||
|
Activate the context menu and select "GitHubinator" or by keypress (⌘\\ by default, configurable in .sublime-keymap file).
|
@@ -0,0 +1,76 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class GithubinatorCommand(sublime_plugin.TextCommand):
|
||||||
|
'''This will allow you to highlight your code, activate the plugin, then see the
|
||||||
|
highlighted results on GitHub.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
s = sublime.load_settings("Githubinator.sublime-settings")
|
||||||
|
global DEFAULT_GIT_REMOTE; DEFAULT_GIT_REMOTE = s.get("default_remote")
|
||||||
|
|
||||||
|
def run(self, edit, permalink = False, mode = 'blob'):
|
||||||
|
self.load_config()
|
||||||
|
if not self.view.file_name():
|
||||||
|
return
|
||||||
|
|
||||||
|
full_name = os.path.realpath(self.view.file_name())
|
||||||
|
folder_name, file_name = os.path.split(full_name)
|
||||||
|
|
||||||
|
git_path = self.recurse_dir(folder_name, '.git')
|
||||||
|
if not git_path:
|
||||||
|
sublime.status_message('Could not find .git directory.')
|
||||||
|
print('Could not find .git directory.')
|
||||||
|
return
|
||||||
|
|
||||||
|
git_config_path = os.path.join(git_path, '.git', 'config')
|
||||||
|
new_git_path = folder_name[len(git_path):]
|
||||||
|
|
||||||
|
with open(git_config_path, "r") as git_config_file:
|
||||||
|
config = git_config_file.read()
|
||||||
|
|
||||||
|
sel = self.view.sel()[0]
|
||||||
|
begin_line = self.view.rowcol(sel.begin())[0] + 1
|
||||||
|
end_line = self.view.rowcol(sel.end())[0] + 1
|
||||||
|
if begin_line == end_line:
|
||||||
|
lines = begin_line
|
||||||
|
else:
|
||||||
|
lines = '%s-%s' % (begin_line, end_line)
|
||||||
|
|
||||||
|
for remote in [DEFAULT_GIT_REMOTE]:
|
||||||
|
regex = r'.*\s.*(?:https://github\.com/|github\.com:|git://github\.com/)(.*)/(.*?)(?:\.git)?\r?\n'
|
||||||
|
result = re.search(remote + regex, config)
|
||||||
|
if not result:
|
||||||
|
continue
|
||||||
|
matches = result.groups()
|
||||||
|
|
||||||
|
ref_path = open(os.path.join(git_path, '.git', 'HEAD'), "r").read().replace('ref: ', '')[:-1]
|
||||||
|
branch = ref_path.replace('refs/heads/','')
|
||||||
|
sha = open(os.path.join(git_path, '.git', ref_path), "r").read()[:-1]
|
||||||
|
target = sha if permalink else branch
|
||||||
|
|
||||||
|
full_link = 'https://github.com/%s/%s/%s/%s%s/%s#L%s' % \
|
||||||
|
(matches[0], matches[1], mode, target, new_git_path, file_name, lines)
|
||||||
|
sublime.set_clipboard(full_link)
|
||||||
|
sublime.status_message('Copied %s to clipboard.' % full_link)
|
||||||
|
print('Copied %s to clipboard.' % full_link)
|
||||||
|
self.view.window().run_command('open_url', {"url": full_link})
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def recurse_dir(self, path, folder):
|
||||||
|
items = os.listdir(path)
|
||||||
|
if folder in items and os.path.isdir(os.path.join(path, folder)):
|
||||||
|
return path
|
||||||
|
dirname = os.path.dirname(path)
|
||||||
|
if dirname == path:
|
||||||
|
return None
|
||||||
|
return self.recurse_dir(dirname, folder)
|
||||||
|
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
return self.view.file_name() and len(self.view.file_name()) > 0
|
@@ -0,0 +1 @@
|
|||||||
|
{"url": "https://github.com/ehamiter/ST2-GitHubinator", "version": "2013.03.02.08.48.58", "description": "Sublime Text 2 plugin that shows selected ST2 text on GitHub"}
|
3
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/Modific/.gitignore
vendored
Normal file
3
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/Modific/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.pyc
|
||||||
|
images
|
||||||
|
!.gitignore
|
@@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{ "keys": ["ctrl+alt+c"], "command": "show_original_part" },
|
||||||
|
{ "keys": ["ctrl+alt+r"], "command": "replace_modified_part" },
|
||||||
|
{ "keys": ["ctrl+alt+d"], "command": "show_diff" },
|
||||||
|
{ "keys": ["ctrl+alt+u"], "command": "uncommitted_files" },
|
||||||
|
|
||||||
|
{ "keys": ["ctrl+shift+pageup"], "command": "jump_between_changes", "args": {"direction": "prev"} },
|
||||||
|
{ "keys": ["ctrl+shift+pagedown"], "command": "jump_between_changes", "args": {"direction": "next"} }
|
||||||
|
]
|
@@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{ "keys": ["ctrl+super+c"], "command": "show_original_part" },
|
||||||
|
{ "keys": ["ctrl+super+r"], "command": "replace_modified_part" },
|
||||||
|
{ "keys": ["ctrl+alt+d"], "command": "show_diff" },
|
||||||
|
{ "keys": ["ctrl+super+u"], "command": "uncommitted_files" },
|
||||||
|
|
||||||
|
{ "keys": ["ctrl+shift+pageup"], "command": "jump_between_changes", "args": {"direction": "prev"} },
|
||||||
|
{ "keys": ["ctrl+shift+pagedown"], "command": "jump_between_changes", "args": {"direction": "next"} }
|
||||||
|
]
|
@@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{ "keys": ["ctrl+alt+c"], "command": "show_original_part" },
|
||||||
|
{ "keys": ["ctrl+alt+r"], "command": "replace_modified_part" },
|
||||||
|
{ "keys": ["ctrl+alt+d"], "command": "show_diff" },
|
||||||
|
{ "keys": ["ctrl+alt+u"], "command": "uncommitted_files" },
|
||||||
|
|
||||||
|
{ "keys": ["ctrl+shift+pageup"], "command": "jump_between_changes", "args": {"direction": "prev"} },
|
||||||
|
{ "keys": ["ctrl+shift+pagedown"], "command": "jump_between_changes", "args": {"direction": "next"} }
|
||||||
|
]
|
@@ -0,0 +1,90 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Preferences",
|
||||||
|
"mnemonic": "n",
|
||||||
|
"id": "preferences",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Package Settings",
|
||||||
|
"mnemonic": "P",
|
||||||
|
"id": "package-settings",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Modific",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {"file": "${packages}/Modific/README.md"},
|
||||||
|
"caption": "README"
|
||||||
|
},
|
||||||
|
{ "caption": "-" },
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {"file": "${packages}/Modific/Modific.sublime-settings"},
|
||||||
|
"caption": "Settings – Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {"file": "${packages}/User/Modific.sublime-settings"},
|
||||||
|
"caption": "Settings – User"
|
||||||
|
},
|
||||||
|
{ "caption": "-" },
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {
|
||||||
|
"file": "${packages}/Modific/Default (OSX).sublime-keymap",
|
||||||
|
"platform": "OSX"
|
||||||
|
},
|
||||||
|
"caption": "Key Bindings – Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {
|
||||||
|
"file": "${packages}/Modific/Default (Linux).sublime-keymap",
|
||||||
|
"platform": "Linux"
|
||||||
|
},
|
||||||
|
"caption": "Key Bindings – Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {
|
||||||
|
"file": "${packages}/Modific/Default (Windows).sublime-keymap",
|
||||||
|
"platform": "Windows"
|
||||||
|
},
|
||||||
|
"caption": "Key Bindings – Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {
|
||||||
|
"file": "${packages}/User/Default (OSX).sublime-keymap",
|
||||||
|
"platform": "OSX"
|
||||||
|
},
|
||||||
|
"caption": "Key Bindings – User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {
|
||||||
|
"file": "${packages}/User/Default (Linux).sublime-keymap",
|
||||||
|
"platform": "Linux"
|
||||||
|
},
|
||||||
|
"caption": "Key Bindings – User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {
|
||||||
|
"file": "${packages}/User/Default (Windows).sublime-keymap",
|
||||||
|
"platform": "Windows"
|
||||||
|
},
|
||||||
|
"caption": "Key Bindings – User"
|
||||||
|
},
|
||||||
|
{ "caption": "-" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@@ -0,0 +1,618 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
import functools
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
IS_ST3 = sublime.version().startswith('3')
|
||||||
|
|
||||||
|
def get_settings():
|
||||||
|
return sublime.load_settings("Modific.sublime-settings")
|
||||||
|
|
||||||
|
|
||||||
|
def get_vcs_settings():
|
||||||
|
return get_settings().get('vcs', [
|
||||||
|
["git", "git"],
|
||||||
|
["svn", "svn"],
|
||||||
|
["bzr", "bzr"],
|
||||||
|
["hg", "hg"]
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def vcs_root(directory):
|
||||||
|
"""
|
||||||
|
Determines root directory for VCS
|
||||||
|
"""
|
||||||
|
|
||||||
|
vcs_check = [(lambda vcs: lambda dir: os.path.exists(os.path.join(dir, '.' + vcs))
|
||||||
|
and {'root': dir, 'name': vcs})(vcs)
|
||||||
|
for vcs, _ in get_vcs_settings()]
|
||||||
|
|
||||||
|
while directory:
|
||||||
|
available = list(filter(lambda x: x, [check(directory) for check in vcs_check]))
|
||||||
|
if available:
|
||||||
|
return directory, available[0]
|
||||||
|
|
||||||
|
parent = os.path.realpath(os.path.join(directory, os.path.pardir))
|
||||||
|
if parent == directory:
|
||||||
|
# /.. == /
|
||||||
|
return None, None
|
||||||
|
directory = parent
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def get_vcs(directory):
|
||||||
|
"""
|
||||||
|
Determines, which of VCS systems we should use for given folder.
|
||||||
|
Currently, uses priority of definitions in get_vcs_settings()
|
||||||
|
"""
|
||||||
|
root_dir, vcs = vcs_root(directory)
|
||||||
|
return vcs
|
||||||
|
|
||||||
|
|
||||||
|
def main_thread(callback, *args, **kwargs):
|
||||||
|
# sublime.set_timeout gets used to send things onto the main thread
|
||||||
|
# most sublime.[something] calls need to be on the main thread
|
||||||
|
sublime.set_timeout(functools.partial(callback, *args, **kwargs), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_text_safeish(text, fallback_encoding, method='decode'):
|
||||||
|
# The unicode decode here is because sublime converts to unicode inside
|
||||||
|
# insert in such a way that unknown characters will cause errors, which is
|
||||||
|
# distinctly non-ideal... and there's no way to tell what's coming out of
|
||||||
|
# git in output. So...
|
||||||
|
try:
|
||||||
|
unitext = getattr(text, method)('utf-8')
|
||||||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||||
|
unitext = getattr(text, method)(fallback_encoding)
|
||||||
|
except AttributeError:
|
||||||
|
# strongly implies we're already unicode, but just in case let's cast
|
||||||
|
# to string
|
||||||
|
unitext = str(text)
|
||||||
|
return unitext
|
||||||
|
|
||||||
|
|
||||||
|
def do_when(conditional, callback, *args, **kwargs):
|
||||||
|
if conditional():
|
||||||
|
return callback(*args, **kwargs)
|
||||||
|
sublime.set_timeout(functools.partial(do_when, conditional, callback, *args, **kwargs), 50)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandThread(threading.Thread):
|
||||||
|
def __init__(self, command, on_done, working_dir="", fallback_encoding="", console_encoding="", **kwargs):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.command = command
|
||||||
|
self.on_done = on_done
|
||||||
|
self.working_dir = working_dir
|
||||||
|
if 'stdin' in kwargs:
|
||||||
|
self.stdin = kwargs['stdin'].encode()
|
||||||
|
else:
|
||||||
|
self.stdin = None
|
||||||
|
self.stdout = kwargs.get('stdout', subprocess.PIPE)
|
||||||
|
self.console_encoding = console_encoding
|
||||||
|
self.fallback_encoding = fallback_encoding
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
# Per http://bugs.python.org/issue8557 shell=True is required to
|
||||||
|
# get $PATH on Windows. Yay portable code.
|
||||||
|
shell = os.name == 'nt'
|
||||||
|
if self.working_dir != "":
|
||||||
|
os.chdir(self.working_dir)
|
||||||
|
|
||||||
|
if self.console_encoding:
|
||||||
|
self.command = [s.encode(self.console_encoding) for s in self.command]
|
||||||
|
|
||||||
|
proc = subprocess.Popen(self.command,
|
||||||
|
stdout=self.stdout, stderr=subprocess.STDOUT,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
shell=shell, universal_newlines=True)
|
||||||
|
output = proc.communicate(self.stdin)[0]
|
||||||
|
if not output:
|
||||||
|
output = ''
|
||||||
|
# if sublime's python gets bumped to 2.7 we can just do:
|
||||||
|
# output = subprocess.check_output(self.command)
|
||||||
|
main_thread(self.on_done,
|
||||||
|
_make_text_safeish(output, self.fallback_encoding), **self.kwargs)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
main_thread(self.on_done, e.returncode)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 2:
|
||||||
|
main_thread(sublime.error_message,
|
||||||
|
"'%s' binary could not be found in PATH\n\nConsider using `vcs` property to specify PATH\n\nPATH is: %s" % (self.command[0], os.environ['PATH']))
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
class EditViewCommand(sublime_plugin.TextCommand):
|
||||||
|
|
||||||
|
def run(self, edit, command=None, output='', begin=0, region=None):
|
||||||
|
"""
|
||||||
|
For some reason Sublime's view.run_command() doesn't allow to pass tuples,
|
||||||
|
therefore region must be a list
|
||||||
|
"""
|
||||||
|
region = sublime.Region(int(region[0]), int(region[1])) if region else None
|
||||||
|
if command == 'insert':
|
||||||
|
self.view.insert(edit, int(begin), output)
|
||||||
|
elif command == 'replace':
|
||||||
|
self.view.replace(edit, region, output)
|
||||||
|
elif command == 'erase':
|
||||||
|
self.view.erase(edit, region)
|
||||||
|
else:
|
||||||
|
print('Invalid command: ', command)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class VcsCommand(object):
|
||||||
|
may_change_files = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.settings = get_settings()
|
||||||
|
super(VcsCommand, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def run_command(self, command, callback=None, show_status=True,
|
||||||
|
filter_empty_args=True, **kwargs):
|
||||||
|
if filter_empty_args:
|
||||||
|
command = [arg for arg in command if arg]
|
||||||
|
if 'working_dir' not in kwargs:
|
||||||
|
kwargs['working_dir'] = self.get_working_dir()
|
||||||
|
if 'fallback_encoding' not in kwargs and self.active_view() and self.active_view().settings().get('fallback_encoding'):
|
||||||
|
kwargs['fallback_encoding'] = self.active_view().settings().get('fallback_encoding').rpartition('(')[2].rpartition(')')[0]
|
||||||
|
kwargs['console_encoding'] = self.settings.get('console_encoding')
|
||||||
|
|
||||||
|
autosave = self.settings.get('autosave', True)
|
||||||
|
if self.active_view() and self.active_view().is_dirty() and autosave:
|
||||||
|
self.active_view().run_command('save')
|
||||||
|
if not callback:
|
||||||
|
callback = self.generic_done
|
||||||
|
|
||||||
|
thread = CommandThread(command, callback, **kwargs)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
if show_status:
|
||||||
|
message = kwargs.get('status_message', False) or ' '.join(command)
|
||||||
|
sublime.status_message(message)
|
||||||
|
|
||||||
|
def generic_done(self, result):
|
||||||
|
if self.may_change_files and self.active_view() and self.active_view().file_name():
|
||||||
|
if self.active_view().is_dirty():
|
||||||
|
result = "WARNING: Current view is dirty.\n\n"
|
||||||
|
else:
|
||||||
|
# just asking the current file to be re-opened doesn't do anything
|
||||||
|
print("reverting")
|
||||||
|
position = self.active_view().viewport_position()
|
||||||
|
self.active_view().run_command('revert')
|
||||||
|
do_when(lambda: not self.active_view().is_loading(), lambda: self.active_view().set_viewport_position(position, False))
|
||||||
|
|
||||||
|
if not result.strip():
|
||||||
|
return
|
||||||
|
self.panel(result)
|
||||||
|
|
||||||
|
def _output_to_view(self, output_file, output, clear=False,
|
||||||
|
syntax="Packages/Diff/Diff.tmLanguage"):
|
||||||
|
output_file.set_syntax_file(syntax)
|
||||||
|
if clear:
|
||||||
|
output_file.run_command('edit_view', dict(command='replace', region=[0, self.output_view.size()], output=output))
|
||||||
|
else:
|
||||||
|
output_file.run_command('edit_view', dict(command='insert', output=output))
|
||||||
|
|
||||||
|
def scratch(self, output, title=False, position=None, **kwargs):
|
||||||
|
scratch_file = self.get_window().new_file()
|
||||||
|
if title:
|
||||||
|
scratch_file.set_name(title)
|
||||||
|
scratch_file.set_scratch(True)
|
||||||
|
self._output_to_view(scratch_file, output, **kwargs)
|
||||||
|
scratch_file.set_read_only(True)
|
||||||
|
if position:
|
||||||
|
sublime.set_timeout(lambda: scratch_file.set_viewport_position(position), 0)
|
||||||
|
return scratch_file
|
||||||
|
|
||||||
|
def panel(self, output, **kwargs):
|
||||||
|
if not hasattr(self, 'output_view'):
|
||||||
|
self.output_view = self.get_window().get_output_panel("vcs")
|
||||||
|
self.output_view.set_read_only(False)
|
||||||
|
self._output_to_view(self.output_view, output, clear=True, **kwargs)
|
||||||
|
self.output_view.set_read_only(True)
|
||||||
|
self.get_window().run_command("show_panel", {"panel": "output.vcs"})
|
||||||
|
|
||||||
|
def _active_file_name(self):
|
||||||
|
view = self.active_view()
|
||||||
|
if view and view.file_name() and len(view.file_name()) > 0:
|
||||||
|
return view.file_name()
|
||||||
|
|
||||||
|
def active_view(self):
|
||||||
|
return self.view
|
||||||
|
|
||||||
|
def get_window(self):
|
||||||
|
if (hasattr(self, 'view') and hasattr(self.view, 'window')):
|
||||||
|
return self.view.window()
|
||||||
|
else:
|
||||||
|
return sublime.active_window()
|
||||||
|
|
||||||
|
def get_working_dir(self):
|
||||||
|
return os.path.dirname(self._active_file_name())
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
file_name = self._active_file_name()
|
||||||
|
if file_name and os.path.exists(file_name):
|
||||||
|
return bool(get_vcs(self.get_working_dir()))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_user_command(self, vcs_name):
|
||||||
|
return dict(get_vcs_settings()).get(vcs_name, False)
|
||||||
|
|
||||||
|
|
||||||
|
class DiffCommand(VcsCommand):
|
||||||
|
""" Here you can define diff commands for your VCS
|
||||||
|
method name pattern: %(vcs_name)s_diff_command
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, edit):
|
||||||
|
vcs = get_vcs(self.get_working_dir())
|
||||||
|
filepath = self.view.file_name()
|
||||||
|
filename = os.path.basename(filepath)
|
||||||
|
max_file_size = self.settings.get('max_file_size', 1024) * 1024
|
||||||
|
if not os.path.exists(filepath) or os.path.getsize(filepath) > max_file_size:
|
||||||
|
# skip large files
|
||||||
|
return
|
||||||
|
get_command = getattr(self, '{0}_diff_command'.format(vcs['name']), None)
|
||||||
|
if get_command:
|
||||||
|
self.run_command(get_command(filename), self.diff_done)
|
||||||
|
|
||||||
|
def diff_done(self, result):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def git_diff_command(self, file_name):
|
||||||
|
return [self.get_user_command('git') or 'git', 'diff', '--no-color', '--no-ext-diff', '--', file_name]
|
||||||
|
|
||||||
|
def svn_diff_command(self, file_name):
|
||||||
|
params = [self.get_user_command('svn') or 'svn', 'diff']
|
||||||
|
if self.settings.get('svn_use_internal_diff', True):
|
||||||
|
params.append('--internal-diff')
|
||||||
|
if file_name.find('@') != -1:
|
||||||
|
file_name += '@'
|
||||||
|
params.extend(['--revision', 'HEAD'])
|
||||||
|
params.extend([file_name])
|
||||||
|
return params
|
||||||
|
|
||||||
|
def bzr_diff_command(self, file_name):
|
||||||
|
return [self.get_user_command('bzr') or 'bzr', 'diff', file_name]
|
||||||
|
|
||||||
|
def hg_diff_command(self, file_name):
|
||||||
|
return [self.get_user_command('hg') or 'hg', 'diff', file_name]
|
||||||
|
|
||||||
|
|
||||||
|
class ShowDiffCommand(DiffCommand, sublime_plugin.TextCommand):
|
||||||
|
def diff_done(self, result):
|
||||||
|
if not result.strip():
|
||||||
|
return
|
||||||
|
|
||||||
|
file_name = re.findall(r'([^\\\/]+)$', self.view.file_name())
|
||||||
|
self.scratch(result, title="Diff - " + file_name[0])
|
||||||
|
|
||||||
|
|
||||||
|
class DiffParser(object):
|
||||||
|
instance = None
|
||||||
|
|
||||||
|
def __init__(self, diff):
|
||||||
|
self.diff = diff
|
||||||
|
self.chunks = None
|
||||||
|
self.__class__.instance = self
|
||||||
|
|
||||||
|
def _append_to_chunks(self, start, lines):
|
||||||
|
self.chunks.append({
|
||||||
|
"start": start,
|
||||||
|
"end": start + len(lines),
|
||||||
|
"lines": lines
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_chunks(self):
|
||||||
|
if self.chunks is None:
|
||||||
|
self.chunks = []
|
||||||
|
diff = self.diff.strip()
|
||||||
|
if diff:
|
||||||
|
re_header = re.compile(r'^@@[0-9\-, ]+\+(\d+)', re.S)
|
||||||
|
current = None
|
||||||
|
lines = []
|
||||||
|
for line in diff.splitlines():
|
||||||
|
# ignore lines with '\' at the beginning
|
||||||
|
if line.startswith('\\'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
matches = re.findall(re_header, line)
|
||||||
|
if matches:
|
||||||
|
if current is not None:
|
||||||
|
self._append_to_chunks(current, lines)
|
||||||
|
current = int(matches[0])
|
||||||
|
lines = []
|
||||||
|
elif current:
|
||||||
|
lines.append(line)
|
||||||
|
if current is not None and lines:
|
||||||
|
self._append_to_chunks(current, lines)
|
||||||
|
|
||||||
|
return self.chunks
|
||||||
|
|
||||||
|
def get_lines_to_hl(self):
|
||||||
|
inserted = []
|
||||||
|
changed = []
|
||||||
|
deleted = []
|
||||||
|
|
||||||
|
for chunk in self.get_chunks():
|
||||||
|
current = chunk['start']
|
||||||
|
deleted_line = None
|
||||||
|
for line in chunk['lines']:
|
||||||
|
if line.startswith('-'):
|
||||||
|
if (not deleted_line or deleted_line not in deleted):
|
||||||
|
deleted.append(current)
|
||||||
|
deleted_line = current
|
||||||
|
elif line.startswith('+'):
|
||||||
|
if deleted_line:
|
||||||
|
deleted.pop()
|
||||||
|
deleted_line = None
|
||||||
|
changed.append(current)
|
||||||
|
elif current - 1 in changed:
|
||||||
|
changed.append(current)
|
||||||
|
else:
|
||||||
|
inserted.append(current)
|
||||||
|
current += 1
|
||||||
|
else:
|
||||||
|
deleted_line = None
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
return inserted, changed, deleted
|
||||||
|
|
||||||
|
def get_original_part(self, line_num):
|
||||||
|
""" returns a chunk of code that relates to the given line
|
||||||
|
and was there before modifications
|
||||||
|
|
||||||
|
return (lines list, start_line int, replace_lines int)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# for each chunk from diff:
|
||||||
|
for chunk in self.get_chunks():
|
||||||
|
# if line_num is within that chunk
|
||||||
|
if chunk['start'] <= line_num <= chunk['end']:
|
||||||
|
ret_lines = []
|
||||||
|
current = chunk['start'] # line number that corresponds to current version of file
|
||||||
|
first = None # number of the first line to change
|
||||||
|
replace_lines = 0 # number of lines to change
|
||||||
|
return_this_lines = False # flag shows whether we can return accumulated lines
|
||||||
|
for line in chunk['lines']:
|
||||||
|
if line.startswith('-') or line.startswith('+'):
|
||||||
|
first = first or current
|
||||||
|
if current == line_num:
|
||||||
|
return_this_lines = True
|
||||||
|
if line.startswith('-'):
|
||||||
|
# if line starts with '-' we have previous version
|
||||||
|
ret_lines.append(line[1:])
|
||||||
|
else:
|
||||||
|
# if line starts with '+' we only increment numbers
|
||||||
|
replace_lines += 1
|
||||||
|
current += 1
|
||||||
|
elif return_this_lines:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# gap between modifications
|
||||||
|
# reset our variables
|
||||||
|
current += 1
|
||||||
|
first = current
|
||||||
|
replace_lines = 0
|
||||||
|
ret_lines = []
|
||||||
|
if return_this_lines:
|
||||||
|
return ret_lines, first, replace_lines
|
||||||
|
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
|
||||||
|
class HlChangesCommand(DiffCommand, sublime_plugin.TextCommand):
|
||||||
|
def hl_lines(self, lines, hl_key):
|
||||||
|
if (not len(lines)):
|
||||||
|
self.view.erase_regions(hl_key)
|
||||||
|
return
|
||||||
|
|
||||||
|
icon = self.settings.get('region_icon') or 'modific'
|
||||||
|
if icon == 'modific':
|
||||||
|
if IS_ST3:
|
||||||
|
icon = 'Packages/Modific/icons/' + hl_key + '.png'
|
||||||
|
else:
|
||||||
|
icon = '../Modific/icons/' + hl_key
|
||||||
|
points = [self.view.text_point(l - 1, 0) for l in lines]
|
||||||
|
regions = [sublime.Region(p, p) for p in points]
|
||||||
|
self.view.add_regions(hl_key, regions, "markup.%s.diff" % hl_key,
|
||||||
|
icon, sublime.HIDDEN | sublime.DRAW_EMPTY)
|
||||||
|
|
||||||
|
def diff_done(self, diff):
|
||||||
|
if diff and '@@' not in diff:
|
||||||
|
# probably this is an error message
|
||||||
|
# if print raise UnicodeEncodeError, try to encode string to utf-8 (issue #35)
|
||||||
|
try:
|
||||||
|
print(diff)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
print(diff.encode('utf-8'))
|
||||||
|
|
||||||
|
diff_parser = DiffParser(diff)
|
||||||
|
(inserted, changed, deleted) = diff_parser.get_lines_to_hl()
|
||||||
|
|
||||||
|
if self.settings.get('debug'):
|
||||||
|
print(inserted, changed, deleted)
|
||||||
|
self.hl_lines(inserted, 'inserted')
|
||||||
|
self.hl_lines(deleted, 'deleted')
|
||||||
|
self.hl_lines(changed, 'changed')
|
||||||
|
|
||||||
|
|
||||||
|
class ShowOriginalPartCommand(DiffCommand, sublime_plugin.TextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
diff_parser = DiffParser.instance
|
||||||
|
if not diff_parser:
|
||||||
|
return
|
||||||
|
|
||||||
|
(row, col) = self.view.rowcol(self.view.sel()[0].begin())
|
||||||
|
(lines, start, replace_lines) = diff_parser.get_original_part(row + 1)
|
||||||
|
if lines is not None:
|
||||||
|
self.panel(os.linesep.join(lines))
|
||||||
|
|
||||||
|
|
||||||
|
class ReplaceModifiedPartCommand(DiffCommand, sublime_plugin.TextCommand):
|
||||||
|
def run(self, edit):
|
||||||
|
self.view.run_command('save')
|
||||||
|
|
||||||
|
diff_parser = DiffParser.instance
|
||||||
|
if not diff_parser:
|
||||||
|
return
|
||||||
|
|
||||||
|
(row, col) = self.view.rowcol(self.view.sel()[0].begin())
|
||||||
|
(lines, current, replace_lines) = diff_parser.get_original_part(row + 1)
|
||||||
|
if self.settings.get('debug'):
|
||||||
|
print('replace', (lines, current, replace_lines))
|
||||||
|
if lines is not None:
|
||||||
|
begin = self.view.text_point(current - 1, 0)
|
||||||
|
content = os.linesep.join(lines)
|
||||||
|
if replace_lines:
|
||||||
|
end = self.view.line(self.view.text_point(replace_lines + current - 2, 0)).end()
|
||||||
|
region = sublime.Region(begin, end)
|
||||||
|
if lines:
|
||||||
|
self.view.run_command('edit_view', dict(command='replace', region=[region.begin(), region.end()], output=content))
|
||||||
|
else:
|
||||||
|
region = self.view.full_line(region)
|
||||||
|
self.view.run_command('edit_view', dict(command='erase', region=[region.begin(), region.end()]))
|
||||||
|
else:
|
||||||
|
self.view.run_command('edit_view', dict(command='insert', begin=begin, output=content + os.linesep))
|
||||||
|
self.view.run_command('save')
|
||||||
|
|
||||||
|
|
||||||
|
class HlChangesBackground(sublime_plugin.EventListener):
|
||||||
|
def on_load(self, view):
|
||||||
|
view.run_command('hl_changes')
|
||||||
|
|
||||||
|
def on_activated(self, view):
|
||||||
|
view.run_command('hl_changes')
|
||||||
|
|
||||||
|
def on_post_save(self, view):
|
||||||
|
view.run_command('hl_changes')
|
||||||
|
|
||||||
|
|
||||||
|
class JumpBetweenChangesCommand(DiffCommand, sublime_plugin.TextCommand):
|
||||||
|
def run(self, edit, direction='next'):
|
||||||
|
lines = self._get_lines()
|
||||||
|
if not lines:
|
||||||
|
return
|
||||||
|
|
||||||
|
if direction == 'prev':
|
||||||
|
lines.reverse()
|
||||||
|
|
||||||
|
(current_line, col) = self.view.rowcol(self.view.sel()[0].begin())
|
||||||
|
current_line += 1
|
||||||
|
jump_to = None
|
||||||
|
for line in lines:
|
||||||
|
if direction == 'next' and current_line < line:
|
||||||
|
jump_to = line
|
||||||
|
break
|
||||||
|
if direction == 'prev' and current_line > line:
|
||||||
|
jump_to = line
|
||||||
|
break
|
||||||
|
|
||||||
|
if not jump_to:
|
||||||
|
jump_to = lines[0]
|
||||||
|
|
||||||
|
self.goto_line(edit, jump_to)
|
||||||
|
|
||||||
|
def goto_line(self, edit, line):
|
||||||
|
# Convert from 1 based to a 0 based line number
|
||||||
|
line = int(line) - 1
|
||||||
|
|
||||||
|
# Negative line numbers count from the end of the buffer
|
||||||
|
if line < 0:
|
||||||
|
lines, _ = self.view.rowcol(self.view.size())
|
||||||
|
line = lines + line + 1
|
||||||
|
|
||||||
|
pt = self.view.text_point(line, 0)
|
||||||
|
|
||||||
|
self.view.sel().clear()
|
||||||
|
self.view.sel().add(sublime.Region(pt))
|
||||||
|
|
||||||
|
self.view.show(pt)
|
||||||
|
|
||||||
|
def _get_lines(self):
|
||||||
|
diff_parser = DiffParser.instance
|
||||||
|
if not diff_parser:
|
||||||
|
return
|
||||||
|
|
||||||
|
(inserted, changed, deleted) = diff_parser.get_lines_to_hl()
|
||||||
|
lines = list(set(inserted + changed + deleted))
|
||||||
|
lines.sort()
|
||||||
|
|
||||||
|
prev = None
|
||||||
|
ret_lines = []
|
||||||
|
for line in lines:
|
||||||
|
if prev != line - 1:
|
||||||
|
ret_lines.append(line)
|
||||||
|
prev = line
|
||||||
|
|
||||||
|
return ret_lines
|
||||||
|
|
||||||
|
|
||||||
|
class UncommittedFilesCommand(VcsCommand, sublime_plugin.WindowCommand):
|
||||||
|
def active_view(self):
|
||||||
|
return self.window.active_view()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.root, self.vcs = vcs_root(self.get_working_dir())
|
||||||
|
status_command = getattr(self, '{0}_status_command'.format(self.vcs['name']), None)
|
||||||
|
if status_command:
|
||||||
|
self.run_command(status_command(), self.status_done, working_dir=self.root)
|
||||||
|
|
||||||
|
def git_status_command(self):
|
||||||
|
return [self.get_user_command('git') or 'git', 'status', '--porcelain']
|
||||||
|
|
||||||
|
def svn_status_command(self):
|
||||||
|
return [self.get_user_command('svn') or 'svn', 'status', '--quiet']
|
||||||
|
|
||||||
|
def bzr_status_command(self):
|
||||||
|
return [self.get_user_command('bzr') or 'bzr', 'status', '-S', '--no-pending', '-V']
|
||||||
|
|
||||||
|
def hg_status_command(self):
|
||||||
|
return [self.get_user_command('hg') or 'hg', 'status']
|
||||||
|
|
||||||
|
def git_status_file(self, file_name):
|
||||||
|
# first 2 characters are status codes, the third is a space
|
||||||
|
return file_name[3:]
|
||||||
|
|
||||||
|
def svn_status_file(self, file_name):
|
||||||
|
return file_name[8:]
|
||||||
|
|
||||||
|
def bzr_status_file(self, file_name):
|
||||||
|
return file_name[4:]
|
||||||
|
|
||||||
|
def hg_status_file(self, file_name):
|
||||||
|
return file_name[2:]
|
||||||
|
|
||||||
|
def status_done(self, result):
|
||||||
|
self.results = list(filter(lambda x: len(x) > 0 and not x.lstrip().startswith('>'),
|
||||||
|
result.rstrip().split('\n')))
|
||||||
|
if len(self.results):
|
||||||
|
self.show_status_list()
|
||||||
|
else:
|
||||||
|
sublime.status_message("Nothing to show")
|
||||||
|
|
||||||
|
def show_status_list(self):
|
||||||
|
self.get_window().show_quick_panel(self.results, self.panel_done,
|
||||||
|
sublime.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
def panel_done(self, picked):
|
||||||
|
if 0 > picked < len(self.results):
|
||||||
|
return
|
||||||
|
picked_file = self.results[picked]
|
||||||
|
get_file = getattr(self, '{0}_status_file'.format(self.vcs['name']), None)
|
||||||
|
if (get_file):
|
||||||
|
self.open_file(get_file(picked_file))
|
||||||
|
|
||||||
|
def open_file(self, picked_file):
|
||||||
|
if os.path.isfile(os.path.join(self.root, picked_file)):
|
||||||
|
self.window.open_file(os.path.join(self.root, picked_file))
|
||||||
|
else:
|
||||||
|
sublime.status_message("File doesn't exist")
|
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Modific: Show diff",
|
||||||
|
"command": "show_diff"
|
||||||
|
}
|
||||||
|
]
|
@@ -0,0 +1,33 @@
|
|||||||
|
// Modific default settings
|
||||||
|
{
|
||||||
|
// Name of a region icon
|
||||||
|
// Valid icon names are: modific, dot, circle, bookmark and cross
|
||||||
|
// WARNING: if you set value different than 'modific',
|
||||||
|
// you may experience issues with UI of Sublime.
|
||||||
|
// See https://github.com/gornostal/Modific/issues/9
|
||||||
|
"region_icon": "modific",
|
||||||
|
|
||||||
|
// You can use your commands instead of plain "git" or "svn"
|
||||||
|
// e.g. "/usr/bin/git" or "C:\bin\git.exe"
|
||||||
|
"vcs": [
|
||||||
|
["git", "git"],
|
||||||
|
["svn", "svn"],
|
||||||
|
["bzr", "bzr"],
|
||||||
|
["hg" , "hg"]
|
||||||
|
],
|
||||||
|
|
||||||
|
//if you have some weird OS, that has non-unicode console
|
||||||
|
//place its console encoding here
|
||||||
|
"console_encoding" : "",
|
||||||
|
|
||||||
|
// if true, plugin prints some debug information to the console window
|
||||||
|
"debug": false,
|
||||||
|
|
||||||
|
// set to false to disable automatic saving
|
||||||
|
"autosave": true,
|
||||||
|
|
||||||
|
"svn_use_internal_diff": true,
|
||||||
|
|
||||||
|
// File size limit (in KB) for drawing icons on the gutter
|
||||||
|
"max_file_size": 1024
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
Modific
|
||||||
|
=========
|
||||||
|
|
||||||
|
Modific is a ST2(3) plugin for highlighting lines changed since the last commit (you know what I mean if you used Netbeans).
|
||||||
|
|
||||||
|
For now it supports **Git**, **SVN**, **Bazaar** and **Mercurial**.
|
||||||
|
|
||||||
|
|
||||||
|
Install
|
||||||
|
-------
|
||||||
|
|
||||||
|
The easiest way to install is through **[Package Control](http://wbond.net/sublime\_packages/package\_control)**.
|
||||||
|
|
||||||
|
Once you install Package Control, restart ST3 and bring up the Command Palette (`Ctrl+Shift+P` on Linux/Windows, `Cmd+Shift+P` on OS X). Select "Package Control: Install Package", wait while Package Control fetches the latest package list, then select *Modific* when the list appears. The advantage of using this method is that Package Control will automatically keep *Modific* up to date with the latest version.
|
||||||
|
|
||||||
|
Or you can **download** the latest source from [GitHub](https://github.com/gornostal/Modific/zipball/master) and copy the *Modific* folder to your Sublime Text "Packages" directory.
|
||||||
|
|
||||||
|
Or **clone** the repository to your Sublime Text "Packages" directory:
|
||||||
|
|
||||||
|
git clone git://github.com/gornostal/Modific.git
|
||||||
|
|
||||||
|
|
||||||
|
The "Packages" directory is located at:
|
||||||
|
|
||||||
|
* OS X:
|
||||||
|
|
||||||
|
~/Library/Application Support/Sublime Text 2/Packages/
|
||||||
|
|
||||||
|
* Linux:
|
||||||
|
|
||||||
|
~/.config/sublime-text-2/Packages/
|
||||||
|
|
||||||
|
* Windows:
|
||||||
|
|
||||||
|
%APPDATA%/Sublime Text 2/Packages/
|
||||||
|
|
||||||
|
Please, make sure your VCS binaries is in the PATH (**especially if you are on Windows**).
|
||||||
|
|
||||||
|
To do that on Windows, open `Controll Panel -> System -> Advanced system settings -> Environment variables -> System Variables`, find PATH, click "Edit" and append `;C:\path\to\VCS\binaries` for every VCS you will use (or make sure it's already there).
|
||||||
|
|
||||||
|
Features / Usage
|
||||||
|
----------------
|
||||||
|
|
||||||
|
**Highlight changes** *(automatically: on save or when window gets focus)*
|
||||||
|
[](http://i.imgur.com/FgpyR.jpg)
|
||||||
|
|
||||||
|
**Show diff** `Ctrl+Alt+D` on Linux/Windows and OS X
|
||||||
|
[](http://i.imgur.com/csCw7.jpg)
|
||||||
|
|
||||||
|
**Preview of the commited code for current line** `Ctrl+Alt+C` on Linux/Windows, `Ctlr+Super+C` on OS X
|
||||||
|
[](http://i.imgur.com/siVOX.jpg)
|
||||||
|
|
||||||
|
**Revert modification** `Ctrl+Alt+R` on Linux/Windows, `Ctlr+Super+R` on OS X
|
||||||
|
|
||||||
|
This command reverts modifications if your cursor stays on modified line (or if on group of lines, then whole group will be reverted)
|
||||||
|
|
||||||
|
**View uncommitted files in a quick panel** `Ctrl+Alt+U` on Linux/Windows, `Ctlr+Super+U` on OS X
|
||||||
|
[](http://i.imgur.com/sldHN.jpg)
|
||||||
|
|
||||||
|
**Go through changed lines** `Ctrl+Shift+Page Up(Down)`
|
||||||
|
|
||||||
|
For those who expected to see a clone of Netbeans feature - unfortunately, with existing Sublime Text API that is impossible :(
|
||||||
|
|
||||||
|
[Discussion on the forum](http://www.sublimetext.com/forum/viewtopic.php?f=5&t=7468)
|
||||||
|
|
||||||
|
Configuring
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Open `Prefrences -> Package Settings -> Modific -> Settings - Default` and look for available settings.
|
||||||
|
|
||||||
|
If you want to change something, don't do it in this file. Open `Preferences -> Package Settings -> Modific -> Settings - User` and put there your configuration.
|
||||||
|
|
||||||
|
You can configure is a type of icon (dot, circle or bookmark) and path for your VCS binaries (or leave them as is, if you have them in your PATH). It's also possible to set priority for VCS used (when you have more than one simultaneously) by reordering their definitions.
|
||||||
|
|
||||||
|
If some sacred punishment has been bestowed upon you, and you have no other choice but to use OS, where console has non-UTF8 encoding, you can set console_encoding parameter to the name of your beloved encoding. This parameter is specifically designed for Windows XP users, who have their git repositories in folders with cyrillic path. Since russian XP uses CP1251 as default encoding (including console), VCS diff commands will be encoded appropriately, when using this parameter.
|
||||||
|
|
||||||
|
If you use different than the default theme, you can customize colors of bullets on the gutter by adding [this](https://gist.github.com/3692073) chunk of code to your theme.
|
||||||
|
|
||||||
|
Thanks to
|
||||||
|
---------
|
||||||
|
|
||||||
|
@beefsack for purchasing a license
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
Released under the [WTFPLv2](http://sam.zoy.org/wtfpl/COPYING).
|
Binary file not shown.
After Width: | Height: | Size: 96 B |
Binary file not shown.
After Width: | Height: | Size: 95 B |
Binary file not shown.
After Width: | Height: | Size: 95 B |
@@ -0,0 +1 @@
|
|||||||
|
{"url": "https://github.com/gornostal/Modific", "version": "2013.03.01.06.02.08", "description": "Highlight lines changed since the last commit (supports Git, SVN, Bazaar and Mercurial) / ST2(3) plugin"}
|
4
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/SideBarGit/.gitignore
vendored
Normal file
4
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/SideBarGit/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*.cache
|
||||||
|
*.sublime-project
|
||||||
|
package-metadata.json
|
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "File: Refresh",
|
||||||
|
"command": "side_bar_git_refresh_tab_contents_by_running_command_again"
|
||||||
|
}
|
||||||
|
]
|
@@ -0,0 +1,177 @@
|
|||||||
|
[
|
||||||
|
{ "caption": "-" , "id":"side-bar-end-separator"},
|
||||||
|
{
|
||||||
|
"caption": "Git ", "id":"side-bar-git",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Add & Commit…", "command": "side_bar_git_add_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "Add & Commit & Push…", "command": "side_bar_git_add_commit_push", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Commit…", "command": "side_bar_git_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "Commit Undo", "command": "side_bar_git_commit_undo", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Commit Amend", "command": "side_bar_git_commit_amend", "args": {"paths": []} },
|
||||||
|
{ "caption": "Commit All…", "command": "side_bar_git_commit_all", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Status", "command": "side_bar_git_status", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Diff", "id":"side-bar-git-diff",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "all changes since the last commit", "command": "side_bar_git_diff_all_changes_since_last_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "all changes since the last commit ( ignore whitespace )", "command": "side_bar_git_diff_all_changes_since_last_commit_ignore_white_space", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "changes that have not been staged", "command": "side_bar_git_diff_changes_not_staged", "args": {"paths": []} },
|
||||||
|
{ "caption": "changes that are staged but not committed", "command": "side_bar_git_diff_changes_staged_not_commited", "args": {"paths": []} },
|
||||||
|
{ "caption": "between the index and last commit", "command": "side_bar_git_diff_between_index_and_last_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between remote and last local commit (origin/master..)", "command": "side_bar_git_diff_between_remote_and_last_local_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "between last local commit and remote (..origin/master)", "command": "side_bar_git_diff_between_last_local_commit_and_remote", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between the two latest tags", "command": "xxxx", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Difftool", "id":"side-bar-git-difftool",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "all changes since the last commit", "command": "side_bar_git_difftool_all_changes_since_last_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "changes that have not been staged", "command": "side_bar_git_difftool_changes_not_staged", "args": {"paths": []} },
|
||||||
|
{ "caption": "changes that are staged but not committed", "command": "side_bar_git_difftool_changes_staged_not_commited", "args": {"paths": []} },
|
||||||
|
{ "caption": "between the index and last commit", "command": "side_bar_git_difftool_between_index_and_last_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between remote and last local commit (origin/master..)", "command": "side_bar_git_difftool_between_remote_and_last_local_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "between last local commit and remote (..origin/master)", "command": "side_bar_git_difftool_between_last_local_commit_and_remote", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between the two latest tags", "command": "xxxx", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Log", "id":"side-bar-git-log",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "short summary of changes last 30", "command": "side_bar_git_log_stat_short_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "short summary of changes full", "command": "side_bar_git_log_stat_short_full", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "summary of changes last 30", "command": "side_bar_git_log_stat_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "summary of changes full", "command": "side_bar_git_log_stat_full", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "list of changes last 50", "command": "side_bar_git_log_stat_list_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "list of changes last 50 with commit", "command": "side_bar_git_log_stat_list_commit_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "diffs of changes last 30", "command": "side_bar_git_log_extended_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "diffs of changes full", "command": "side_bar_git_log_extended_full", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "summary of changes since latest tag", "command": "xxxx", "args": {"paths": []} },
|
||||||
|
{ "caption": "summary of changes since latest push", "command": "side_bar_git_log_since_latest_push", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between the two latest tags", "command": "xxxx", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "Reflog", "command": "side_bar_git_reflog", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "Blame", "command": "side_bar_git_blame", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Tags", "id":"side-bar-git-tags",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Auto-Tag", "command": "xxxx", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "Add…", "command": "xxxx", "args": {"paths": []} },
|
||||||
|
{ "caption": "Remove…", "command": "xxxx", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "List", "command": "xxxx", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Revert", "id":"side-bar-git-revert",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "discard changes to tracked", "command": "side_bar_git_revert_tracked", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "discard changes to tracked, clean untracked", "command": "side_bar_git_revert_tracked_clean_untracked", "args": {"paths": []} },
|
||||||
|
{ "caption": "discard changes to tracked, clean untracked, unstage", "command": "side_bar_git_revert_tracked_clean_untracked_unstage", "args": {"paths": []} },
|
||||||
|
{ "caption": "discard changes to tracked, unstage, clean untracked", "command": "side_bar_git_revert_tracked_unstage_clean_untracked", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "unstage", "command": "side_bar_git_revert_unstage", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{
|
||||||
|
"caption": "Branch",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "new from current and switch to…", "command": "side_bar_git_branch_new_from_current", "args": {"paths": []} },
|
||||||
|
{ "caption": "new from master and switch to…", "command": "side_bar_git_branch_new_from_master", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "new from cleaned current and switch to…", "command": "side_bar_git_branch_new_from_clean_current", "args": {"paths": []} },
|
||||||
|
{ "caption": "new from cleaned master and switch to…", "command": "side_bar_git_branch_new_from_clean_master", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "switch to master", "command": "side_bar_git_branch_switch_to_master", "args": {"paths": []} },
|
||||||
|
{ "caption": "switch to…", "command": "side_bar_git_branch_switch_to", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "rebase current into master", "command": "side_bar_git_rebase_current_into_master", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "merge changes to current from…", "command": "side_bar_git_merge_to_current_from", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "delete…", "command": "side_bar_git_branch_delete", "args": {"paths": []} },
|
||||||
|
{ "caption": "delete even if unmerged (force deletion)…","command": "side_bar_git_branch_delete_force", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "Checkout to…", "command": "side_bar_git_checkout_to", "args": {"paths": []} },
|
||||||
|
{ "caption": "Checkout repository to…", "command": "side_bar_git_checkout_repository_to", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Push, Pull, Fetch", "id":"side-bar-git-pull-push-fetch",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Push", "command": "side_bar_git_push", "args": {"paths": []} },
|
||||||
|
{ "caption": "Push All Branches", "command": "side_bar_git_push_all_branches", "args": {"paths": []} },
|
||||||
|
{ "caption": "Push, Push Tags", "command": "side_bar_git_push_and_push_tags", "args": {"paths": []} },
|
||||||
|
{ "caption": "Push Tags", "command": "side_bar_git_push_tags", "args": {"paths": []} },
|
||||||
|
{ "caption": "Push with options…", "command": "side_bar_git_push_with_options", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Pull", "command": "side_bar_git_pull", "args": {"paths": []} },
|
||||||
|
{ "caption": "Pull with options…", "command": "side_bar_git_pull_with_options", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Fetch", "command": "side_bar_git_fetch", "args": {"paths": []} },
|
||||||
|
{ "caption": "Fetch with options…", "command": "side_bar_git_fetch_with_options", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "Remote add…", "command": "side_bar_git_remote_add", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Clone…", "command": "side_bar_git_clone", "args": {"paths": []} },
|
||||||
|
{ "caption": "Init", "command": "side_bar_git_init", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Add", "command": "side_bar_git_add", "args": {"paths": []} },
|
||||||
|
{ "caption": "Remove", "command": "side_bar_git_remove", "args": {"paths": []} },
|
||||||
|
{ "caption": "Remove Keep Local", "command": "side_bar_git_remove_keep_local", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Add to Git Ignore", "command": "side_bar_git_ignore_add", "args": {"paths": []} },
|
||||||
|
{ "caption": "Open Git Ignore", "command": "side_bar_git_ignore_open", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Git GUI", "command": "side_bar_git_gui", "args": {"paths": []} },
|
||||||
|
{ "caption": "Gitk", "command": "side_bar_git_gitk", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "Liberal Git Command", "command": "side_bar_git_liberal", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "-", "id": "side-bar-end-separator" }
|
||||||
|
]
|
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
{ "keys": ["f5"], "command": "side_bar_git_refresh_tab_contents_by_running_command_again" }
|
||||||
|
]
|
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
{ "keys": ["f5"], "command": "side_bar_git_refresh_tab_contents_by_running_command_again" }
|
||||||
|
]
|
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
{ "keys": ["f5"], "command": "side_bar_git_refresh_tab_contents_by_running_command_again" }
|
||||||
|
]
|
@@ -0,0 +1,39 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Preferences",
|
||||||
|
"mnemonic": "n",
|
||||||
|
"id": "preferences",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Package Settings",
|
||||||
|
"mnemonic": "P",
|
||||||
|
"id": "package-settings",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Side Bar Git",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "open_file", "args":
|
||||||
|
{
|
||||||
|
"file": "${packages}/SideBarGit/SideBarGit.sublime-settings"
|
||||||
|
},
|
||||||
|
"caption": "Settings – Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file", "args":
|
||||||
|
{
|
||||||
|
"file": "${packages}/User/SideBarGit.sublime-settings"
|
||||||
|
},
|
||||||
|
"caption": "Settings – User"
|
||||||
|
},
|
||||||
|
{ "caption": "-" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@@ -0,0 +1,177 @@
|
|||||||
|
[
|
||||||
|
{ "caption": "-" , "id":"side-bar-end-separator"},
|
||||||
|
{
|
||||||
|
"caption": "Git ", "id":"side-bar-git",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Add & Commit…", "command": "side_bar_git_add_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "Add & Commit & Push…", "command": "side_bar_git_add_commit_push", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Commit…", "command": "side_bar_git_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "Commit Undo", "command": "side_bar_git_commit_undo", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Commit Amend", "command": "side_bar_git_commit_amend", "args": {"paths": []} },
|
||||||
|
{ "caption": "Commit All…", "command": "side_bar_git_commit_all", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Status", "command": "side_bar_git_status", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Diff", "id":"side-bar-git-diff",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "all changes since the last commit", "command": "side_bar_git_diff_all_changes_since_last_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "all changes since the last commit ( ignore whitespace )", "command": "side_bar_git_diff_all_changes_since_last_commit_ignore_white_space", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "changes that have not been staged", "command": "side_bar_git_diff_changes_not_staged", "args": {"paths": []} },
|
||||||
|
{ "caption": "changes that are staged but not committed", "command": "side_bar_git_diff_changes_staged_not_commited", "args": {"paths": []} },
|
||||||
|
{ "caption": "between the index and last commit", "command": "side_bar_git_diff_between_index_and_last_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between remote and last local commit (origin/master..)", "command": "side_bar_git_diff_between_remote_and_last_local_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "between last local commit and remote (..origin/master)", "command": "side_bar_git_diff_between_last_local_commit_and_remote", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between the two latest tags", "command": "xxxx", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Difftool", "id":"side-bar-git-difftool",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "all changes since the last commit", "command": "side_bar_git_difftool_all_changes_since_last_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "changes that have not been staged", "command": "side_bar_git_difftool_changes_not_staged", "args": {"paths": []} },
|
||||||
|
{ "caption": "changes that are staged but not committed", "command": "side_bar_git_difftool_changes_staged_not_commited", "args": {"paths": []} },
|
||||||
|
{ "caption": "between the index and last commit", "command": "side_bar_git_difftool_between_index_and_last_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between remote and last local commit (origin/master..)", "command": "side_bar_git_difftool_between_remote_and_last_local_commit", "args": {"paths": []} },
|
||||||
|
{ "caption": "between last local commit and remote (..origin/master)", "command": "side_bar_git_difftool_between_last_local_commit_and_remote", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between the two latest tags", "command": "xxxx", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Log", "id":"side-bar-git-log",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "short summary of changes last 30", "command": "side_bar_git_log_stat_short_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "short summary of changes full", "command": "side_bar_git_log_stat_short_full", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "summary of changes last 30", "command": "side_bar_git_log_stat_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "summary of changes full", "command": "side_bar_git_log_stat_full", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "list of changes last 50", "command": "side_bar_git_log_stat_list_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "list of changes last 50 with commit", "command": "side_bar_git_log_stat_list_commit_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "diffs of changes last 30", "command": "side_bar_git_log_extended_latest", "args": {"paths": []} },
|
||||||
|
{ "caption": "diffs of changes full", "command": "side_bar_git_log_extended_full", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "summary of changes since latest tag", "command": "xxxx", "args": {"paths": []} },
|
||||||
|
{ "caption": "summary of changes since latest push", "command": "side_bar_git_log_since_latest_push", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "between the two latest tags", "command": "xxxx", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "Reflog", "command": "side_bar_git_reflog", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "Blame", "command": "side_bar_git_blame", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Tags", "id":"side-bar-git-tags",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Auto-Tag", "command": "xxxx", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "Add…", "command": "xxxx", "args": {"paths": []} },
|
||||||
|
{ "caption": "Remove…", "command": "xxxx", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "List", "command": "xxxx", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Revert", "id":"side-bar-git-revert",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "discard changes to tracked", "command": "side_bar_git_revert_tracked", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "discard changes to tracked, clean untracked", "command": "side_bar_git_revert_tracked_clean_untracked", "args": {"paths": []} },
|
||||||
|
{ "caption": "discard changes to tracked, clean untracked, unstage", "command": "side_bar_git_revert_tracked_clean_untracked_unstage", "args": {"paths": []} },
|
||||||
|
{ "caption": "discard changes to tracked, unstage, clean untracked", "command": "side_bar_git_revert_tracked_unstage_clean_untracked", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "unstage", "command": "side_bar_git_revert_unstage", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{
|
||||||
|
"caption": "Branch",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "new from current and switch to…", "command": "side_bar_git_branch_new_from_current", "args": {"paths": []} },
|
||||||
|
{ "caption": "new from master and switch to…", "command": "side_bar_git_branch_new_from_master", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "new from cleaned current and switch to…", "command": "side_bar_git_branch_new_from_clean_current", "args": {"paths": []} },
|
||||||
|
{ "caption": "new from cleaned master and switch to…", "command": "side_bar_git_branch_new_from_clean_master", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "switch to master", "command": "side_bar_git_branch_switch_to_master", "args": {"paths": []} },
|
||||||
|
{ "caption": "switch to…", "command": "side_bar_git_branch_switch_to", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "rebase current into master", "command": "side_bar_git_rebase_current_into_master", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "merge changes to current from…", "command": "side_bar_git_merge_to_current_from", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "delete…", "command": "side_bar_git_branch_delete", "args": {"paths": []} },
|
||||||
|
{ "caption": "delete even if unmerged (force deletion)…","command": "side_bar_git_branch_delete_force", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "Checkout to…", "command": "side_bar_git_checkout_to", "args": {"paths": []} },
|
||||||
|
{ "caption": "Checkout repository to…", "command": "side_bar_git_checkout_repository_to", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{
|
||||||
|
"caption": "Push, Pull, Fetch", "id":"side-bar-git-pull-push-fetch",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{ "caption": "Push", "command": "side_bar_git_push", "args": {"paths": []} },
|
||||||
|
{ "caption": "Push All Branches", "command": "side_bar_git_push_all_branches", "args": {"paths": []} },
|
||||||
|
{ "caption": "Push, Push Tags", "command": "side_bar_git_push_and_push_tags", "args": {"paths": []} },
|
||||||
|
{ "caption": "Push Tags", "command": "side_bar_git_push_tags", "args": {"paths": []} },
|
||||||
|
{ "caption": "Push with options…", "command": "side_bar_git_push_with_options", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Pull", "command": "side_bar_git_pull", "args": {"paths": []} },
|
||||||
|
{ "caption": "Pull with options…", "command": "side_bar_git_pull_with_options", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Fetch", "command": "side_bar_git_fetch", "args": {"paths": []} },
|
||||||
|
{ "caption": "Fetch with options…", "command": "side_bar_git_fetch_with_options", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "Remote add…", "command": "side_bar_git_remote_add", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Clone…", "command": "side_bar_git_clone", "args": {"paths": []} },
|
||||||
|
{ "caption": "Init", "command": "side_bar_git_init", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Add", "command": "side_bar_git_add", "args": {"paths": []} },
|
||||||
|
{ "caption": "Remove", "command": "side_bar_git_remove", "args": {"paths": []} },
|
||||||
|
{ "caption": "Remove Keep Local", "command": "side_bar_git_remove_keep_local", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Add to Git Ignore", "command": "side_bar_git_ignore_add", "args": {"paths": []} },
|
||||||
|
{ "caption": "Open Git Ignore", "command": "side_bar_git_ignore_open", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
|
||||||
|
{ "caption": "Git GUI", "command": "side_bar_git_gui", "args": {"paths": []} },
|
||||||
|
{ "caption": "Gitk", "command": "side_bar_git_gitk", "args": {"paths": []} },
|
||||||
|
{ "caption": "-"},
|
||||||
|
{ "caption": "Liberal Git Command", "command": "side_bar_git_liberal", "args": {"paths": []} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "caption": "-", "id": "side-bar-end-separator" }
|
||||||
|
]
|
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"statusbar_branch" : true,
|
||||||
|
"path_to_git_unixes":"" //example: /usr/local/git/bin/git
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
|||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
import functools
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
import codecs
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from SideBarGit import SideBarGit
|
||||||
|
from SideBarItem import SideBarItem
|
||||||
|
from Utils import Object
|
||||||
|
|
||||||
|
Object.running = False
|
||||||
|
Object.timing = time.time()
|
||||||
|
|
||||||
|
class SideBarGitGutterDiff(sublime_plugin.EventListener):
|
||||||
|
|
||||||
|
def on_load(self, view):
|
||||||
|
self.run(view)
|
||||||
|
|
||||||
|
def on_modified(self, view):
|
||||||
|
now = time.time()
|
||||||
|
if now - Object.timing > 0.1:
|
||||||
|
Object.timing = now
|
||||||
|
self.run(view)
|
||||||
|
else:
|
||||||
|
Object.timing = now
|
||||||
|
|
||||||
|
def on_post_save(self, view):
|
||||||
|
self.run(view)
|
||||||
|
|
||||||
|
def run(self, view):
|
||||||
|
if Object.running == False and view.file_name() != None and view.file_name() != '':
|
||||||
|
Object.running = True
|
||||||
|
# cache file repository ( if any )
|
||||||
|
if not view.settings().has('SideBarGitGutterRepository'):
|
||||||
|
item = SideBarItem(view.file_name(), False)
|
||||||
|
_item = SideBarItem(view.file_name(), False)
|
||||||
|
repos = SideBarGit().getSelectedRepos([_item])
|
||||||
|
if len(repos) > 0:
|
||||||
|
view.settings().set('SideBarGitGutterRepository', repos[0].repository.path())
|
||||||
|
view.settings().set('SideBarGitGutterCWD', repos[0].repository.path())
|
||||||
|
view.settings().set('SideBarGitGutterPath', item.forCwdSystemPathRelativeFrom(repos[0].repository.path()))
|
||||||
|
else:
|
||||||
|
view.settings().set('SideBarGitGutterRepository', '')
|
||||||
|
|
||||||
|
# if in a repo check for modifications
|
||||||
|
repo = view.settings().get('SideBarGitGutterRepository')
|
||||||
|
if repo != '':
|
||||||
|
SideBarGitGutterDiffThread(
|
||||||
|
view,
|
||||||
|
repo,
|
||||||
|
view.settings().get('SideBarGitGutterCWD'),
|
||||||
|
view.settings().get('SideBarGitGutterPath'),
|
||||||
|
view.substr(sublime.Region(0, view.size()))
|
||||||
|
).start()
|
||||||
|
else:
|
||||||
|
Object.running = False
|
||||||
|
|
||||||
|
class SideBarGitGutterDiffThread(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, view, repo, cwd, path, content):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.view = view
|
||||||
|
self.repo = repo
|
||||||
|
self.cwd = cwd
|
||||||
|
self.path = path
|
||||||
|
self.content = content
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
tmp = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
codecs.open(tmp.name, 'w+', 'utf-8').write(self.content)
|
||||||
|
|
||||||
|
comand = ['git', 'diff', '-p', '--unified=0', '--no-color', '--ignore-all-space', '--ignore-space-at-eol', '--ignore-space-change', 'HEAD:'+self.path, tmp.name]
|
||||||
|
|
||||||
|
process = subprocess.Popen(
|
||||||
|
comand,
|
||||||
|
cwd=self.cwd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
shell=sys.platform == 'win32',
|
||||||
|
universal_newlines=True)
|
||||||
|
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
if stdout != '' and stdout.find('fatal:') != 0:
|
||||||
|
|
||||||
|
hunk = re.finditer('\n@@ -([0-9]+),?([0-9]*) \+([0-9]+),?([0-9]*) @@', stdout)
|
||||||
|
|
||||||
|
additions = []
|
||||||
|
deletions = []
|
||||||
|
changes = []
|
||||||
|
|
||||||
|
for change in hunk:
|
||||||
|
g = []
|
||||||
|
for group in change.groups():
|
||||||
|
if group == '':
|
||||||
|
g.append(1)
|
||||||
|
else:
|
||||||
|
g.append(int(group))
|
||||||
|
deleted = g[1]
|
||||||
|
added = g[3]
|
||||||
|
|
||||||
|
if deleted == added and added == 1:
|
||||||
|
changes.append([g[2]-1, g[2]]);
|
||||||
|
else:
|
||||||
|
if deleted > 0:
|
||||||
|
if deleted == 1:
|
||||||
|
if added > deleted:
|
||||||
|
deletions.append([g[2], g[2]+deleted-added]);
|
||||||
|
else:
|
||||||
|
deletions.append([g[2], g[2]+1]);
|
||||||
|
else:
|
||||||
|
deletions.append([g[2]-1, g[2]+deleted-1])
|
||||||
|
if added > 0:
|
||||||
|
if added == 1:
|
||||||
|
additions.append([g[2], g[2]+added]);
|
||||||
|
else:
|
||||||
|
additions.append([g[2]-1, g[2]+added-1])
|
||||||
|
tmp.close();
|
||||||
|
os.remove(tmp.name)
|
||||||
|
sublime.set_timeout(functools.partial(self.add_regions, additions, deletions, changes), 0)
|
||||||
|
else:
|
||||||
|
tmp.close();
|
||||||
|
os.remove(tmp.name)
|
||||||
|
sublime.set_timeout(functools.partial(self.erase_regions), 0)
|
||||||
|
if stdout.find('fatal:'):
|
||||||
|
sublime.set_timeout(functools.partial(self.mark_not_in_a_repository), 0)
|
||||||
|
Object.running = False
|
||||||
|
|
||||||
|
def add_regions(self, additions, deletions, changes):
|
||||||
|
|
||||||
|
self.erase_regions()
|
||||||
|
|
||||||
|
rs = []
|
||||||
|
for r in additions:
|
||||||
|
while r[0] != r[1]:
|
||||||
|
rs.append(sublime.Region(self.view.text_point(r[0], 0)))
|
||||||
|
r[0] = r[0]+1
|
||||||
|
if len(rs):
|
||||||
|
self.view.add_regions("git.diff.additions", rs, "number", "dot", sublime.HIDDEN)
|
||||||
|
|
||||||
|
rs = []
|
||||||
|
for r in deletions:
|
||||||
|
while r[0] != r[1]:
|
||||||
|
rs.append(sublime.Region(self.view.text_point(r[0], 0)))
|
||||||
|
r[0] = r[0]+1
|
||||||
|
if len(rs):
|
||||||
|
self.view.add_regions("git.diff.deletions", rs, "entity.name.class", "dot", sublime.HIDDEN)
|
||||||
|
|
||||||
|
rs = []
|
||||||
|
for r in changes:
|
||||||
|
while r[0] != r[1]:
|
||||||
|
rs.append(sublime.Region(self.view.text_point(r[0], 0)))
|
||||||
|
r[0] = r[0]+1
|
||||||
|
if len(rs):
|
||||||
|
self.view.add_regions("git.diff.changes", rs, "string", "dot", sublime.HIDDEN)
|
||||||
|
|
||||||
|
|
||||||
|
def erase_regions(self):
|
||||||
|
self.view.erase_regions("git.diff.additions")
|
||||||
|
self.view.erase_regions("git.diff.deletions")
|
||||||
|
self.view.erase_regions("git.diff.changes")
|
||||||
|
|
||||||
|
def mark_not_in_a_repository(self):
|
||||||
|
self.view.settings().set('SideBarGitGutterRepository', '')
|
@@ -0,0 +1,43 @@
|
|||||||
|
import sublime, sublime_plugin
|
||||||
|
from sidebar.SideBarGit import SideBarGit
|
||||||
|
from sidebar.SideBarSelection import SideBarSelection
|
||||||
|
import threading
|
||||||
|
|
||||||
|
class Object():
|
||||||
|
pass
|
||||||
|
|
||||||
|
s = sublime.load_settings('SideBarGit.sublime-settings')
|
||||||
|
|
||||||
|
class StatusBarBranch(sublime_plugin.EventListener):
|
||||||
|
|
||||||
|
def on_load(self, v):
|
||||||
|
if s.get('statusbar_branch') and v.file_name():
|
||||||
|
StatusBarBranchGet(v.file_name(), v).start()
|
||||||
|
|
||||||
|
def on_activated(self, v):
|
||||||
|
if s.get('statusbar_branch') and v.file_name():
|
||||||
|
StatusBarBranchGet(v.file_name(), v).start()
|
||||||
|
|
||||||
|
class StatusBarBranchGet(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, file_name, v):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.file_name = file_name
|
||||||
|
self.v = v
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
for repo in SideBarGit().getSelectedRepos(SideBarSelection([self.file_name]).getSelectedItems()):
|
||||||
|
object = Object()
|
||||||
|
object.item = repo.repository
|
||||||
|
object.command = ['git', 'branch']
|
||||||
|
object.silent = True
|
||||||
|
SideBarGit().run(object)
|
||||||
|
sublime.set_timeout(lambda:self.on_done(SideBarGit.last_stdout.decode('utf-8')), 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_done(self, branches):
|
||||||
|
branches = branches.split('\n')
|
||||||
|
for branch in branches:
|
||||||
|
if branch.startswith("*"):
|
||||||
|
self.v.set_status('statusbar_sidebargit_branch', branch)
|
||||||
|
return
|
@@ -0,0 +1,19 @@
|
|||||||
|
"None are so hopelessly enslaved as those who falsely believe they are free."
|
||||||
|
Johann Wolfgang von Goethe
|
||||||
|
|
||||||
|
Copyright (C) 2012 Tito Bouzout <tito.bouzout@gmail.com>
|
||||||
|
|
||||||
|
This license apply to all the files inside this program unless noted
|
||||||
|
different for some files or portions of code inside these files.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation. http://www.gnu.org/licenses/gpl.html
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see http://www.gnu.org/licenses/gpl.html
|
@@ -0,0 +1,182 @@
|
|||||||
|
Description
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Provides git commands on Side Bar of Files and Folders for Sublime Text 2. For sublime text see: http://www.sublimetext.com/
|
||||||
|
|
||||||
|
It should work with files names in any language.
|
||||||
|
|
||||||
|
This plugin is a port of the "Komodin" extension for komodo edit. See: https://github.com/titoBouzout/komodo-komodin-git
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Right click on "multiple/single" "files/folders" of the "tree" sidebar to apply commands on selected files which maybe are from different repositories.
|
||||||
|
|
||||||
|
* To apply commands to focused document use the document context menu.
|
||||||
|
|
||||||
|
* Tip: If a command sends output to a tab ( example a diff ), pressing F5 on that tab: will execute the command again and refresh the tab with the new contents ( example the new computed diff )
|
||||||
|
|
||||||
|
Provides the following commands
|
||||||
|
------------------
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
Add & Commit
|
||||||
|
o git add -- "/selected/paths/files/or/and/folders"
|
||||||
|
o git commit -m "promptMessage" -- "/selected/paths/files/or/and/folders"
|
||||||
|
Add & Commit & Push
|
||||||
|
o git add -- "/selected/paths/files/or/and/folders"
|
||||||
|
o git commit -m "promptMessage" -- "/selected/paths/files/or/and/folders"
|
||||||
|
o git push
|
||||||
|
|
||||||
|
Commit
|
||||||
|
o git commit -m "promptMessage" -- "/selected/paths/files/or/and/folders"
|
||||||
|
Commit Undo
|
||||||
|
o git reset --soft HEAD~1
|
||||||
|
|
||||||
|
Commit Amend
|
||||||
|
o git commit --amend -C HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
Commit All
|
||||||
|
o git commit -a -m "promptMessage"
|
||||||
|
|
||||||
|
Status
|
||||||
|
o git status --untracked-files=all -- "/selected/paths/files/or/and/folders"
|
||||||
|
|
||||||
|
Diff
|
||||||
|
|
||||||
|
all changes since the last commit
|
||||||
|
o git diff HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
changes that have not been staged
|
||||||
|
o git diff -- "/selected/paths/files/or/and/folders"
|
||||||
|
changes that are staged but not committed
|
||||||
|
o git diff --staged -- "/selected/paths/files/or/and/folders"
|
||||||
|
between the index and last commit
|
||||||
|
o git diff --cached -- "/selected/paths/files/or/and/folders"
|
||||||
|
between remote and last local commit (origin/master..)
|
||||||
|
o git diff origin/master.. -- "/selected/paths/files/or/and/folders"
|
||||||
|
between last local commit and remote (..origin/master)
|
||||||
|
o git diff ..origin/master -- "/selected/paths/files/or/and/folders"
|
||||||
|
between the two latest tags
|
||||||
|
o git diff "previousTag".."lastTag" -- "/selected/paths/files/or/and/folders"
|
||||||
|
|
||||||
|
Log stat last 30
|
||||||
|
o git log -n 30 --stat --graph -- "/selected/paths/files/or/and/folders"
|
||||||
|
|
||||||
|
Log stat full
|
||||||
|
o git log --stat --graph -- "/selected/paths/files/or/and/folders"
|
||||||
|
Log extended last 30
|
||||||
|
o git log -n 30 -p -- "/selected/paths/files/or/and/folders"
|
||||||
|
Log extended full
|
||||||
|
o git log -p -- "/selected/paths/files/or/and/folders"
|
||||||
|
Log since last tag
|
||||||
|
o git log "lastTag"... --stat --graph -- "/selected/paths/files/or/and/folders"
|
||||||
|
Log since last push
|
||||||
|
o git log origin/master... --stat --graph -- "/selected/paths/files/or/and/folders"
|
||||||
|
Log between the two latest tags
|
||||||
|
o git log "prevToLastTag".."lastTag" --stat --graph -- "/selected/paths/files/or/and/folders"
|
||||||
|
|
||||||
|
Blame
|
||||||
|
o git blame -- "/selected/paths/files/NOT/folders"
|
||||||
|
|
||||||
|
Auto-Tag
|
||||||
|
o git tag "YYMMDD.Version"
|
||||||
|
|
||||||
|
Tag Add
|
||||||
|
o git tag "promptMessage"
|
||||||
|
Tag Remove
|
||||||
|
o git tag -d "promptMessage"
|
||||||
|
Tag List
|
||||||
|
o git tag -l
|
||||||
|
|
||||||
|
Revert Discard changes to tracked
|
||||||
|
o git checkout HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
|
||||||
|
Revert Discard changes to tracked, clean untracked
|
||||||
|
o git checkout HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
o git clean -f -d -- "/selected/paths/files/or/and/folders"
|
||||||
|
Revert Discard changes to tracked, clean untracked, unstage
|
||||||
|
o git checkout HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
o git clean -f -d -- "/selected/paths/files/or/and/folders"
|
||||||
|
o git reset HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
Revert Discard changes to tracked, unstage, clean untracked
|
||||||
|
o git checkout HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
o git reset HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
o git clean -f -d -- "/selected/paths/files/or/and/folders"
|
||||||
|
Revert Unstage
|
||||||
|
o git reset HEAD -- "/selected/paths/files/or/and/folders"
|
||||||
|
|
||||||
|
Checkout to
|
||||||
|
o git checkout promptMessage -- "/selected/paths/files/or/and/folders"
|
||||||
|
Checkout repo to
|
||||||
|
o cd repoPath
|
||||||
|
o git checkout promptMessage
|
||||||
|
|
||||||
|
Push
|
||||||
|
o git push
|
||||||
|
|
||||||
|
Push, Push Tags
|
||||||
|
o git push && git push --tags
|
||||||
|
Push Tags
|
||||||
|
o git push --tags
|
||||||
|
Push with options…
|
||||||
|
o promptMessage
|
||||||
|
|
||||||
|
Pull
|
||||||
|
o git pull
|
||||||
|
Pull with options…
|
||||||
|
o promptMessage
|
||||||
|
|
||||||
|
Fetch
|
||||||
|
o git fetch
|
||||||
|
Fetch with options…
|
||||||
|
o promptMessage
|
||||||
|
|
||||||
|
Remote add
|
||||||
|
o git remote add promptMessage
|
||||||
|
Configure default remote
|
||||||
|
o git config branch.promptBranch.remote promptRemoteName
|
||||||
|
|
||||||
|
Clone
|
||||||
|
o git clone promptMessage
|
||||||
|
Init
|
||||||
|
o git init
|
||||||
|
|
||||||
|
Add
|
||||||
|
o git add -- "/selected/paths/files/or/and/folders"
|
||||||
|
Remove
|
||||||
|
o git rm -r -f -- "/selected/paths/files/or/and/folders"
|
||||||
|
Remove Keep Local
|
||||||
|
o git rm -r --cached -- "/selected/paths/files/or/and/folders"
|
||||||
|
|
||||||
|
Open Git Ignore
|
||||||
|
Add to Git Ignore
|
||||||
|
|
||||||
|
Git GUI
|
||||||
|
Gitk
|
||||||
|
Liberal Git Command
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Install this repository via "Package Control" http://wbond.net/sublime_packages/package_control
|
||||||
|
|
||||||
|
Todo
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Tag commands not yet ported
|
||||||
|
|
||||||
|
Source-code
|
||||||
|
------------------
|
||||||
|
|
||||||
|
https://github.com/SublimeText/SideBarGit
|
||||||
|
|
||||||
|
Forum Thread
|
||||||
|
------------------
|
||||||
|
|
||||||
|
http://www.sublimetext.com/forum/viewtopic.php?f=5&t=3405
|
||||||
|
|
||||||
|
Contribute
|
||||||
|
------------------
|
||||||
|
|
||||||
|
[Consider make a contribution](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=extensiondevelopment%40gmail%2ecom&lc=UY&item_name=Tito&item_number=sublime%2dtext%2dside%2dbar%2dplugin¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted )
|
@@ -0,0 +1,317 @@
|
|||||||
|
# coding=utf8
|
||||||
|
import sublime
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from SideBarItem import SideBarItem
|
||||||
|
|
||||||
|
class Object():
|
||||||
|
pass
|
||||||
|
|
||||||
|
s = sublime.load_settings('SideBarGit.sublime-settings')
|
||||||
|
path_to_git_unixes = s.get('path_to_git_unixes');
|
||||||
|
|
||||||
|
class SideBarGit:
|
||||||
|
|
||||||
|
last_stdout = ''
|
||||||
|
|
||||||
|
def run(
|
||||||
|
self,
|
||||||
|
object,
|
||||||
|
modal = False,
|
||||||
|
background = False,
|
||||||
|
|
||||||
|
refresh_funct_view = False,
|
||||||
|
refresh_funct_command = False,
|
||||||
|
refresh_funct_item = False,
|
||||||
|
refresh_funct_to_status_bar = False,
|
||||||
|
refresh_funct_title = False,
|
||||||
|
refresh_funct_no_results = False,
|
||||||
|
refresh_funct_syntax_file = False
|
||||||
|
):
|
||||||
|
|
||||||
|
if not refresh_funct_view:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
object = Object()
|
||||||
|
object.command = refresh_funct_command
|
||||||
|
object.item = SideBarItem(refresh_funct_item, os.path.isdir(refresh_funct_item))
|
||||||
|
object.to_status_bar = refresh_funct_to_status_bar
|
||||||
|
object.title = refresh_funct_title
|
||||||
|
object.no_results = refresh_funct_no_results
|
||||||
|
object.syntax_file = refresh_funct_syntax_file
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
if debug:
|
||||||
|
print '----------------------------------------------------------'
|
||||||
|
print 'GIT:'
|
||||||
|
print object.command
|
||||||
|
print 'CWD:'
|
||||||
|
print object.item.forCwdSystemPath()
|
||||||
|
print 'PATH:'
|
||||||
|
print object.item.forCwdSystemName()
|
||||||
|
|
||||||
|
failed = False
|
||||||
|
|
||||||
|
if sublime.platform() == 'windows':
|
||||||
|
object.command = map(self.escapeCMDWindows, object.command)
|
||||||
|
|
||||||
|
if sublime.platform() is not 'windows' and object.command[0] == 'git':
|
||||||
|
if path_to_git_unixes != '':
|
||||||
|
object.command[0] = s.get('path_to_git_unixes')
|
||||||
|
elif os.path.exists('/usr/local/git/bin'):
|
||||||
|
object.command[0] = '/usr/local/git/bin/git'
|
||||||
|
|
||||||
|
cwd = object.item.forCwdSystemPath()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if sublime.platform() == 'windows':
|
||||||
|
|
||||||
|
process = subprocess.Popen(
|
||||||
|
#" ".join(object.command),
|
||||||
|
object.command,
|
||||||
|
cwd=cwd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
shell=True,
|
||||||
|
universal_newlines=True)
|
||||||
|
else:
|
||||||
|
process = subprocess.Popen(
|
||||||
|
object.command,
|
||||||
|
cwd=cwd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
shell=False,
|
||||||
|
universal_newlines=True)
|
||||||
|
|
||||||
|
if background:
|
||||||
|
if debug:
|
||||||
|
print 'SUCCESS'
|
||||||
|
print '----------------------------------------------------------'
|
||||||
|
return True
|
||||||
|
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
SideBarGit.last_stdout = str(stdout).rstrip()
|
||||||
|
self.last_stdout = str(stdout).rstrip()
|
||||||
|
|
||||||
|
stdout = stdout.strip()
|
||||||
|
|
||||||
|
if stdout.find('fatal:') == 0 or stdout.find('error:') == 0 or stdout.find('Permission denied') == 0 or stderr:
|
||||||
|
print 'FAILED'
|
||||||
|
failed = True
|
||||||
|
else:
|
||||||
|
if debug:
|
||||||
|
print 'SUCCESS'
|
||||||
|
if stdout:
|
||||||
|
if debug:
|
||||||
|
print 'STDOUT'
|
||||||
|
print stdout
|
||||||
|
if stderr:
|
||||||
|
print 'STDERR'
|
||||||
|
print stderr
|
||||||
|
except OSError as (errno, strerror):
|
||||||
|
print 'FAILED'
|
||||||
|
failed = True
|
||||||
|
print errno
|
||||||
|
print strerror
|
||||||
|
SideBarGit.last_stdout = ''
|
||||||
|
self.last_stdout = ''
|
||||||
|
except IOError as (errno, strerror):
|
||||||
|
print 'FAILED'
|
||||||
|
failed = True
|
||||||
|
print errno
|
||||||
|
print strerror
|
||||||
|
SideBarGit.last_stdout = ''
|
||||||
|
self.last_stdout = ''
|
||||||
|
if debug:
|
||||||
|
print '----------------------------------------------------------'
|
||||||
|
|
||||||
|
try:
|
||||||
|
object.to_status_bar
|
||||||
|
except:
|
||||||
|
object.to_status_bar = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
object.silent
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
try:
|
||||||
|
strerror
|
||||||
|
if errno == 2:
|
||||||
|
self.alert(strerror+'\nPossible error:\n'+object.command[0]+' not found on $PATH')
|
||||||
|
else:
|
||||||
|
self.alert(strerror)
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
if not stdout and not stderr:
|
||||||
|
return False
|
||||||
|
if stdout.find('Permission denied') == 0 or stdout.find('fatal: The remote end hung up unexpectedly') == 0:
|
||||||
|
self.alert((stdout or '')+'\n'+(stderr or '')+'\nPossible error:\nssh keys not in .ssh/ directory or keys not opened')
|
||||||
|
else:
|
||||||
|
self.alert((stdout or '')+'\n'+(stderr or ''))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if stdout != '' and refresh_funct_view == False and (object.to_status_bar or " ".join(object.command).find('git push') == 0 or stdout.find('nothing to commit') == 0):
|
||||||
|
self.status(stdout)
|
||||||
|
else:
|
||||||
|
if stdout == '' and refresh_funct_view == False:
|
||||||
|
try:
|
||||||
|
self.status(object.no_results)
|
||||||
|
except:
|
||||||
|
self.status('No output to show')
|
||||||
|
return True
|
||||||
|
if stdout == '' and refresh_funct_view != False:
|
||||||
|
try:
|
||||||
|
stdout = object.no_results
|
||||||
|
except:
|
||||||
|
stdout = 'No output to show'
|
||||||
|
if stdout == '':
|
||||||
|
return True
|
||||||
|
|
||||||
|
if refresh_funct_view == False:
|
||||||
|
view = sublime.active_window().new_file()
|
||||||
|
else:
|
||||||
|
view = refresh_funct_view
|
||||||
|
try:
|
||||||
|
view.set_name(object.title.decode('utf-8'))
|
||||||
|
except:
|
||||||
|
view.set_name('No Title')
|
||||||
|
try:
|
||||||
|
if object.syntax_file != False:
|
||||||
|
view.set_syntax_file(object.syntax_file)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
object.word_wrap
|
||||||
|
view.settings().set('word_wrap', False)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
view.settings().set('fallback_encoding', 'UTF-8')
|
||||||
|
view.settings().set('encoding', 'UTF-8')
|
||||||
|
view.settings().set('default_dir', object.item.dirname())
|
||||||
|
view.set_scratch(True)
|
||||||
|
|
||||||
|
if refresh_funct_view == False:
|
||||||
|
view.settings().set('SideBarGitIsASideBarGitTab', True)
|
||||||
|
view.settings().set('SideBarGitCommand', object.command)
|
||||||
|
view.settings().set('SideBarGitModal', modal)
|
||||||
|
view.settings().set('SideBarGitBackground', background)
|
||||||
|
view.settings().set('SideBarGitItem', object.item.path())
|
||||||
|
try:
|
||||||
|
view.settings().set('SideBarGitToStatusBar', object.to_status_bar)
|
||||||
|
except:
|
||||||
|
view.settings().set('SideBarGitToStatusBar', False)
|
||||||
|
try:
|
||||||
|
view.settings().set('SideBarGitTitle', object.title)
|
||||||
|
except:
|
||||||
|
view.settings().set('SideBarGitTitle', 'No Title')
|
||||||
|
try:
|
||||||
|
view.settings().set('SideBarGitNoResults', object.no_results)
|
||||||
|
except:
|
||||||
|
view.settings().set('SideBarGitNoResults', 'No output to show')
|
||||||
|
try:
|
||||||
|
view.settings().set('SideBarGitSyntaxFile', object.syntax_file)
|
||||||
|
except:
|
||||||
|
view.settings().set('SideBarGitSyntaxFile', False)
|
||||||
|
|
||||||
|
content = "[SideBarGit@SublimeText "
|
||||||
|
content += object.item.name().decode('utf-8')
|
||||||
|
content += "/] "
|
||||||
|
content += (" ".join(object.command)).decode('utf-8')
|
||||||
|
content += "\n\n"
|
||||||
|
content += "# Improve this command, the output or the tab title by posting here:"
|
||||||
|
content += "\n"
|
||||||
|
content += "# http://www.sublimetext.com/forum/viewtopic.php?f=5&t=3405"
|
||||||
|
content += "\n"
|
||||||
|
content += "# Tip: F5 will run the command again and refresh the contents of this tab"
|
||||||
|
content += "\n\n"
|
||||||
|
try:
|
||||||
|
content += stdout
|
||||||
|
except:
|
||||||
|
content += unicode(stdout, 'UTF-8', errors='ignore')
|
||||||
|
|
||||||
|
edit = view.begin_edit()
|
||||||
|
view.replace(edit, sublime.Region(0, view.size()), content);
|
||||||
|
view.sel().clear()
|
||||||
|
view.sel().add(sublime.Region(0))
|
||||||
|
view.end_edit(edit)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def confirm(self, message, function, arg1):
|
||||||
|
if int(sublime.version()) >= 2186:
|
||||||
|
if sublime.ok_cancel_dialog(u'Side Bar Git : '+message):
|
||||||
|
function(arg1, True)
|
||||||
|
else:
|
||||||
|
import functools
|
||||||
|
sublime.active_window().run_command('hide_panel');
|
||||||
|
sublime.active_window().show_input_panel("Confirmation Required:", message.decode('utf-8'), functools.partial(function, arg1, True), None, None)
|
||||||
|
|
||||||
|
def prompt(self, message, default, function, arg1):
|
||||||
|
import functools
|
||||||
|
sublime.active_window().run_command('hide_panel');
|
||||||
|
sublime.active_window().show_input_panel(message.decode('utf-8'), default.decode('utf-8'), functools.partial(function, arg1, True), None, None)
|
||||||
|
|
||||||
|
def alert(self, message):
|
||||||
|
try:
|
||||||
|
sublime.error_message('Git : '+(message.decode('utf-8')))
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
sublime.error_message('Git : '+message)
|
||||||
|
except:
|
||||||
|
print message
|
||||||
|
|
||||||
|
def status(self, message):
|
||||||
|
message = message[:200] + (message[200:] and '…')
|
||||||
|
message = message.replace('\n', ' ')
|
||||||
|
try:
|
||||||
|
v = sublime.active_window().active_view()
|
||||||
|
v.set_status('SideBarGit', 'Git : '+(message.decode('utf-8')))
|
||||||
|
sublime.set_timeout(lambda: SideBarGit().statusRemove(v), 16000)
|
||||||
|
except:#there is no tabs opened
|
||||||
|
sublime.status_message('Git : '+(message.decode('utf-8')))
|
||||||
|
|
||||||
|
def statusRemove(self, v):
|
||||||
|
try:
|
||||||
|
v.erase_status('SideBarGit')
|
||||||
|
except:#this view is not there
|
||||||
|
pass
|
||||||
|
|
||||||
|
def quickPanel(self, function, extra, data):
|
||||||
|
import functools
|
||||||
|
window = sublime.active_window()
|
||||||
|
# window.show_input_panel("BUG!", '', '', None, None)
|
||||||
|
# window.run_command('hide_panel');
|
||||||
|
data = [item[:70] for item in data]
|
||||||
|
window.show_quick_panel(data, functools.partial(self.quickPanelDone, function, extra, data))
|
||||||
|
|
||||||
|
def quickPanelDone(self, function, extra, data, result):
|
||||||
|
if result != -1:
|
||||||
|
function(extra, data, result)
|
||||||
|
|
||||||
|
def getSelectedRepos(self, items):
|
||||||
|
repos = []
|
||||||
|
reposTemp = []
|
||||||
|
for item in items:
|
||||||
|
original = item.path()
|
||||||
|
while not os.path.exists(item.join('.git')):
|
||||||
|
if item.dirname() == item.path():
|
||||||
|
break;
|
||||||
|
item.path(item.dirname())
|
||||||
|
|
||||||
|
if os.path.exists(item.join('.git')):
|
||||||
|
try:
|
||||||
|
index = reposTemp.index(item.path())
|
||||||
|
except ValueError:
|
||||||
|
reposTemp.append(item.path())
|
||||||
|
index = reposTemp.index(item.path())
|
||||||
|
repos.append(Object())
|
||||||
|
repos[index].repository = item
|
||||||
|
repos[index].items = []
|
||||||
|
repos[index].items.append(SideBarItem(original, os.path.isdir(original)))
|
||||||
|
return repos
|
||||||
|
|
||||||
|
def escapeCMDWindows(self, string):
|
||||||
|
return string.replace('^', '^^')
|
@@ -0,0 +1,480 @@
|
|||||||
|
# coding=utf8
|
||||||
|
import sublime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from SideBarProject import SideBarProject
|
||||||
|
|
||||||
|
try:
|
||||||
|
import desktop
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Object():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def expand_vars(path):
|
||||||
|
for k, v in os.environ.iteritems():
|
||||||
|
try:
|
||||||
|
# dirty hack, this should be autofixed in python3
|
||||||
|
k = unicode(k.encode('utf8'))
|
||||||
|
v = unicode(v.encode('utf8'))
|
||||||
|
path = path.replace(u'%'+k+'%', v).replace(u'%'+k.lower()+'%', v)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return path
|
||||||
|
|
||||||
|
class SideBarItem:
|
||||||
|
|
||||||
|
def __init__(self, path, is_directory):
|
||||||
|
self._path = path
|
||||||
|
self._is_directory = is_directory
|
||||||
|
|
||||||
|
def path(self, path = ''):
|
||||||
|
if path == '':
|
||||||
|
return self._path
|
||||||
|
else:
|
||||||
|
self._path = path
|
||||||
|
self._is_directory = os.path.isdir(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def pathSystem(self):
|
||||||
|
import sys
|
||||||
|
return self.path().encode(sys.getfilesystemencoding())
|
||||||
|
|
||||||
|
def pathWithoutProject(self):
|
||||||
|
path = self.path()
|
||||||
|
for directory in SideBarProject().getDirectories():
|
||||||
|
path = path.replace(directory, '', 1)
|
||||||
|
return path.replace('\\', '/')
|
||||||
|
|
||||||
|
def pathProject(self):
|
||||||
|
path = self.path()
|
||||||
|
for directory in SideBarProject().getDirectories():
|
||||||
|
path2 = path.replace(directory, '', 1)
|
||||||
|
if path2 != path:
|
||||||
|
return directory
|
||||||
|
return False
|
||||||
|
|
||||||
|
def projectURL(self, type):
|
||||||
|
filename = os.path.normpath(os.path.join(sublime.packages_path(), '..', 'Settings', 'SideBarEnhancements.json'))
|
||||||
|
if os.path.lexists(filename):
|
||||||
|
#try:
|
||||||
|
import json
|
||||||
|
data = file(filename, 'r').read()
|
||||||
|
data = data.replace('\t', ' ').replace('\\', '/').replace('\\', '/').replace('//', '/').replace('//', '/').replace('http:/', 'http://').replace('https:/', 'https://')
|
||||||
|
data = json.loads(data, strict=False)
|
||||||
|
|
||||||
|
for path in data.keys():
|
||||||
|
path2 = expand_vars(path)
|
||||||
|
print '-------------------------------------------------------'
|
||||||
|
print 'searching:'
|
||||||
|
path2 = path2.replace('\\', '/').replace('\\', '/').replace('//', '/').replace('//', '/')
|
||||||
|
print path2
|
||||||
|
print 'in:'
|
||||||
|
path3 = self.path().replace('\\', '/').replace('\\', '/').replace('//', '/').replace('//', '/')
|
||||||
|
print path3
|
||||||
|
print '-------------------------------------------------------'
|
||||||
|
path4 = re.sub(re.compile("^"+re.escape(path2), re.IGNORECASE), '', path3);
|
||||||
|
print path4
|
||||||
|
if path4 != path3:
|
||||||
|
url = data[path][type]
|
||||||
|
if url:
|
||||||
|
if url[-1:] != '/':
|
||||||
|
url = url+'/'
|
||||||
|
import urllib
|
||||||
|
return url+(re.sub("^/", '', urllib.quote(path4.encode('utf-8'))));
|
||||||
|
|
||||||
|
#except:
|
||||||
|
# return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isUnderCurrentProject(self):
|
||||||
|
path = self.path()
|
||||||
|
path2 = self.path()
|
||||||
|
for directory in SideBarProject().getDirectories():
|
||||||
|
path2 = path2.replace(directory, '', 1)
|
||||||
|
return path != path2
|
||||||
|
|
||||||
|
def pathRelativeFromProject(self):
|
||||||
|
return re.sub('^/+', '', self.pathWithoutProject())
|
||||||
|
|
||||||
|
def pathRelativeFromProjectEncoded(self):
|
||||||
|
import urllib
|
||||||
|
return urllib.quote(self.pathRelativeFromProject().encode('utf-8'))
|
||||||
|
|
||||||
|
def pathRelativeFromView(self):
|
||||||
|
return os.path.relpath(self.path(), os.path.dirname(sublime.active_window().active_view().file_name())).replace('\\', '/')
|
||||||
|
|
||||||
|
def pathRelativeFromViewEncoded(self):
|
||||||
|
import urllib
|
||||||
|
return urllib.quote(os.path.relpath(self.path(), os.path.dirname(sublime.active_window().active_view().file_name())).replace('\\', '/').encode('utf-8'))
|
||||||
|
|
||||||
|
def pathAbsoluteFromProject(self):
|
||||||
|
return self.pathWithoutProject()
|
||||||
|
|
||||||
|
def pathAbsoluteFromProjectEncoded(self):
|
||||||
|
import urllib
|
||||||
|
return urllib.quote(self.pathAbsoluteFromProject().encode('utf-8'))
|
||||||
|
|
||||||
|
def uri(self):
|
||||||
|
import urllib
|
||||||
|
return 'file:'+urllib.pathname2url(self.path().encode('utf-8'));
|
||||||
|
|
||||||
|
def join(self, name):
|
||||||
|
return os.path.join(self.path(), name)
|
||||||
|
|
||||||
|
def dirname(self):
|
||||||
|
branch, leaf = os.path.split(self.path())
|
||||||
|
return branch;
|
||||||
|
|
||||||
|
def forCwdSystemPath(self):
|
||||||
|
if self.isDirectory():
|
||||||
|
return self.pathSystem()
|
||||||
|
else:
|
||||||
|
return self.dirnameSystem()
|
||||||
|
|
||||||
|
def forCwdSystemName(self):
|
||||||
|
if self.isDirectory():
|
||||||
|
return '.'
|
||||||
|
else:
|
||||||
|
path = self.pathSystem()
|
||||||
|
branch = self.dirnameSystem()
|
||||||
|
leaf = path.replace(branch, '', 1).replace('\\', '').replace('/', '')
|
||||||
|
return leaf
|
||||||
|
|
||||||
|
def forCwdSystemPathRelativeFrom(self, relativeFrom):
|
||||||
|
relative = SideBarItem(relativeFrom, os.path.isdir(relativeFrom))
|
||||||
|
path = self.pathSystem().replace(relative.pathSystem(), '', 1).replace('\\', '/')
|
||||||
|
if path == '':
|
||||||
|
return '.'
|
||||||
|
else:
|
||||||
|
return re.sub('^/+', '', path)
|
||||||
|
|
||||||
|
def forCwdSystemPathRelativeFromRecursive(self, relativeFrom):
|
||||||
|
relative = SideBarItem(relativeFrom, os.path.isdir(relativeFrom))
|
||||||
|
path = self.pathSystem().replace(relative.pathSystem(), '', 1).replace('\\', '/')
|
||||||
|
if path == '':
|
||||||
|
return '.'
|
||||||
|
else:
|
||||||
|
if self.isDirectory():
|
||||||
|
return re.sub('^/+', '', path)+'/'
|
||||||
|
else:
|
||||||
|
return re.sub('^/+', '', path)
|
||||||
|
|
||||||
|
def dirnameSystem(self):
|
||||||
|
import sys
|
||||||
|
return self.dirname().encode(sys.getfilesystemencoding())
|
||||||
|
|
||||||
|
def dirnameCreate(self):
|
||||||
|
try:
|
||||||
|
os.makedirs(self.dirname())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
branch, leaf = os.path.split(self.path())
|
||||||
|
return leaf;
|
||||||
|
|
||||||
|
def nameSystem(self):
|
||||||
|
import sys
|
||||||
|
return self.name().encode(sys.getfilesystemencoding())
|
||||||
|
|
||||||
|
def nameEncoded(self):
|
||||||
|
import urllib
|
||||||
|
return urllib.quote(self.name().encode('utf-8'));
|
||||||
|
|
||||||
|
def namePretty(self):
|
||||||
|
return self.name().replace(self.extension(), '').replace('-', ' ').replace('_', ' ').strip();
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
if sublime.platform() == 'osx':
|
||||||
|
import subprocess
|
||||||
|
subprocess.Popen(['open', '-a', self.nameSystem()], cwd=self.dirnameSystem())
|
||||||
|
elif sublime.platform() == 'windows':
|
||||||
|
import subprocess
|
||||||
|
subprocess.Popen([self.nameSystem()], cwd=self.dirnameSystem(), shell=True)
|
||||||
|
else:
|
||||||
|
desktop.open(self.path())
|
||||||
|
|
||||||
|
def edit(self):
|
||||||
|
return sublime.active_window().open_file(self.path())
|
||||||
|
|
||||||
|
def isDirectory(self):
|
||||||
|
return self._is_directory
|
||||||
|
|
||||||
|
def isFile(self):
|
||||||
|
return self.isDirectory() == False
|
||||||
|
|
||||||
|
def contentUTF8(self):
|
||||||
|
import codecs
|
||||||
|
return codecs.open(self.path(), 'r', 'utf-8').read()
|
||||||
|
|
||||||
|
def contentBinary(self):
|
||||||
|
return file(self.path(), "rb").read()
|
||||||
|
|
||||||
|
def contentBase64(self):
|
||||||
|
return 'data:'+self.mime()+';base64,'+(file(self.path(), "rb").read().encode("base64").replace('\n', ''))
|
||||||
|
|
||||||
|
def reveal(self):
|
||||||
|
sublime.active_window().run_command("open_dir", {"dir": self.dirname(), "file": self.name()} )
|
||||||
|
|
||||||
|
def write(self, content):
|
||||||
|
file(self.path(), 'w+').write(content)
|
||||||
|
|
||||||
|
def mime(self):
|
||||||
|
import mimetypes
|
||||||
|
return mimetypes.guess_type(self.path())[0] or 'application/octet-stream'
|
||||||
|
|
||||||
|
def extension(self):
|
||||||
|
return os.path.splitext('name'+self.name())[1].lower()
|
||||||
|
|
||||||
|
def exists(self):
|
||||||
|
return os.path.isdir(self.path()) or os.path.isfile(self.path())
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
if self.isDirectory():
|
||||||
|
self.dirnameCreate()
|
||||||
|
os.makedirs(self.path())
|
||||||
|
else:
|
||||||
|
self.dirnameCreate()
|
||||||
|
self.write('')
|
||||||
|
|
||||||
|
def copy(self, location, replace = False):
|
||||||
|
location = SideBarItem(location, os.path.isdir(location));
|
||||||
|
if location.exists() and replace == False:
|
||||||
|
return False
|
||||||
|
elif location.exists() and location.isFile():
|
||||||
|
os.remove(location.path())
|
||||||
|
|
||||||
|
location.dirnameCreate();
|
||||||
|
if self.isDirectory():
|
||||||
|
if location.exists():
|
||||||
|
self.copy_recursive(self.path(), location.path())
|
||||||
|
else:
|
||||||
|
shutil.copytree(self.path(), location.path())
|
||||||
|
else:
|
||||||
|
shutil.copy2(self.path(), location.path())
|
||||||
|
return True
|
||||||
|
|
||||||
|
def copy_recursive(self, _from, _to):
|
||||||
|
|
||||||
|
if os.path.isfile(_from) or os.path.islink(_from):
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(_to));
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if os.path.exists(_to):
|
||||||
|
os.remove(_to)
|
||||||
|
shutil.copy2(_from, _to)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.makedirs(_to);
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
for content in os.listdir(_from):
|
||||||
|
__from = os.path.join(_from, content)
|
||||||
|
__to = os.path.join(_to, content)
|
||||||
|
self.copy_recursive(__from, __to)
|
||||||
|
|
||||||
|
def move(self, location, replace = False):
|
||||||
|
location = SideBarItem(location, os.path.isdir(location));
|
||||||
|
if location.exists() and replace == False:
|
||||||
|
if self.path().lower() == location.path().lower():
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif location.exists() and location.isFile():
|
||||||
|
os.remove(location.path())
|
||||||
|
|
||||||
|
if self.path().lower() == location.path().lower():
|
||||||
|
location.dirnameCreate();
|
||||||
|
os.rename(self.path(), location.path()+'.sublime-temp')
|
||||||
|
os.rename(location.path()+'.sublime-temp', location.path())
|
||||||
|
self._move_moveViews(self.path(), location.path())
|
||||||
|
else:
|
||||||
|
location.dirnameCreate();
|
||||||
|
if location.exists():
|
||||||
|
self.move_recursive(self.path(), location.path())
|
||||||
|
else:
|
||||||
|
os.rename(self.path(), location.path())
|
||||||
|
self._move_moveViews(self.path(), location.path())
|
||||||
|
return True
|
||||||
|
|
||||||
|
def move_recursive(self, _from, _to):
|
||||||
|
if os.path.isfile(_from) or os.path.islink(_from):
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(_to));
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if os.path.exists(_to):
|
||||||
|
os.remove(_to)
|
||||||
|
os.rename(_from, _to)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.makedirs(_to);
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
for content in os.listdir(_from):
|
||||||
|
__from = os.path.join(_from, content)
|
||||||
|
__to = os.path.join(_to, content)
|
||||||
|
self.move_recursive(__from, __to)
|
||||||
|
os.rmdir(_from)
|
||||||
|
|
||||||
|
def _move_moveViews(self, old, location):
|
||||||
|
for window in sublime.windows():
|
||||||
|
active_view = window.active_view()
|
||||||
|
views = []
|
||||||
|
for view in window.views():
|
||||||
|
if view.file_name():
|
||||||
|
views.append(view)
|
||||||
|
views.reverse();
|
||||||
|
for view in views:
|
||||||
|
if old == view.file_name():
|
||||||
|
active_view = self._move_moveView(window, view, location, active_view)
|
||||||
|
elif view.file_name().find(old+'\\') == 0:
|
||||||
|
active_view = self._move_moveView(window, view, view.file_name().replace(old+'\\', location+'\\', 1), active_view)
|
||||||
|
elif view.file_name().find(old+'/') == 0:
|
||||||
|
active_view = self._move_moveView(window, view, view.file_name().replace(old+'/', location+'/', 1), active_view)
|
||||||
|
|
||||||
|
def _move_moveView(self, window, view, location, active_view):
|
||||||
|
if active_view == view:
|
||||||
|
is_active_view = True
|
||||||
|
else:
|
||||||
|
is_active_view = False
|
||||||
|
|
||||||
|
options = Object()
|
||||||
|
|
||||||
|
options.scroll = view.viewport_position()
|
||||||
|
|
||||||
|
options.selections = [[item.a, item.b] for item in view.sel()]
|
||||||
|
|
||||||
|
options.marks = [[item.a, item.b] for item in view.get_regions("mark")]
|
||||||
|
|
||||||
|
options.bookmarks = [[item.a, item.b] for item in view.get_regions("bookmarks")]
|
||||||
|
|
||||||
|
if int(sublime.version()) >= 2167:
|
||||||
|
options.folds = [[item.a, item.b] for item in view.folded_regions()]
|
||||||
|
else:
|
||||||
|
options.folds = [[item.a, item.b] for item in view.unfold(sublime.Region(0, view.size()))]
|
||||||
|
|
||||||
|
options.syntax = view.settings().get('syntax')
|
||||||
|
|
||||||
|
try:
|
||||||
|
_window = window or view.window() or sublime.active_window()
|
||||||
|
options.position = _window.get_view_index(view)
|
||||||
|
except:
|
||||||
|
options.position = False
|
||||||
|
|
||||||
|
window.focus_view(view)
|
||||||
|
if view.is_dirty():
|
||||||
|
options.content = view.substr(sublime.Region(0, view.size()))
|
||||||
|
view.window().run_command('revert')
|
||||||
|
else:
|
||||||
|
options.content = False
|
||||||
|
|
||||||
|
_view = view
|
||||||
|
view = window.open_file(location)
|
||||||
|
window.focus_view(_view)
|
||||||
|
window.run_command('close')
|
||||||
|
|
||||||
|
sublime.set_timeout(lambda: self._move_restoreView(view, options, window), 200)
|
||||||
|
|
||||||
|
if is_active_view:
|
||||||
|
window.focus_view(view)
|
||||||
|
return view
|
||||||
|
else:
|
||||||
|
window.focus_view(active_view)
|
||||||
|
return active_view
|
||||||
|
|
||||||
|
def _move_restoreView(self, view, options, window):
|
||||||
|
if view.is_loading():
|
||||||
|
sublime.set_timeout(lambda: self._move_restoreView(view, options, window), 100)
|
||||||
|
else:
|
||||||
|
if options.content != False:
|
||||||
|
edit = view.begin_edit()
|
||||||
|
view.replace(edit, sublime.Region(0, view.size()), options.content);
|
||||||
|
view.sel().clear()
|
||||||
|
view.sel().add(sublime.Region(0))
|
||||||
|
view.end_edit(edit)
|
||||||
|
|
||||||
|
if options.position != False:
|
||||||
|
try:
|
||||||
|
_window = window or view.window() or sublime.active_window()
|
||||||
|
group, index = options.position
|
||||||
|
_window.set_view_index(view, group, index)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if options.syntax:
|
||||||
|
view.settings().set('syntax', options.syntax);
|
||||||
|
|
||||||
|
for r in options.folds:
|
||||||
|
view.fold(sublime.Region(r[0], r[1]))
|
||||||
|
|
||||||
|
view.sel().clear()
|
||||||
|
for r in options.selections:
|
||||||
|
view.sel().add(sublime.Region(r[0], r[1]))
|
||||||
|
|
||||||
|
rs = []
|
||||||
|
for r in options.marks:
|
||||||
|
rs.append(sublime.Region(r[0], r[1]))
|
||||||
|
if len(rs):
|
||||||
|
view.add_regions("mark", rs, "mark", "dot", sublime.HIDDEN | sublime.PERSISTENT)
|
||||||
|
|
||||||
|
rs = []
|
||||||
|
for r in options.bookmarks:
|
||||||
|
rs.append(sublime.Region(r[0], r[1]))
|
||||||
|
if len(rs):
|
||||||
|
view.add_regions("bookmarks", rs, "bookmarks", "bookmark", sublime.HIDDEN | sublime.PERSISTENT)
|
||||||
|
|
||||||
|
view.set_viewport_position(options.scroll, False)
|
||||||
|
|
||||||
|
def close_associated_buffers(self):
|
||||||
|
path = self.path()
|
||||||
|
closed_items = []
|
||||||
|
for window in sublime.windows():
|
||||||
|
active_view = window.active_view()
|
||||||
|
views = []
|
||||||
|
for view in window.views():
|
||||||
|
if view.file_name():
|
||||||
|
views.append(view)
|
||||||
|
views.reverse();
|
||||||
|
for view in views:
|
||||||
|
if path == view.file_name():
|
||||||
|
if view.window():
|
||||||
|
closed_items.append([view.file_name(), view.window(), view.window().get_view_index(view)])
|
||||||
|
if len(window.views()) == 1:
|
||||||
|
window.new_file()
|
||||||
|
window.focus_view(view)
|
||||||
|
window.run_command('revert')
|
||||||
|
window.run_command('close')
|
||||||
|
elif view.file_name().find(path+'\\') == 0:
|
||||||
|
if view.window():
|
||||||
|
closed_items.append([view.file_name(), view.window(), view.window().get_view_index(view)])
|
||||||
|
if len(window.views()) == 1:
|
||||||
|
window.new_file()
|
||||||
|
window.focus_view(view)
|
||||||
|
window.run_command('revert')
|
||||||
|
window.run_command('close')
|
||||||
|
elif view.file_name().find(path+'/') == 0:
|
||||||
|
if view.window():
|
||||||
|
closed_items.append([view.file_name(), view.window(), view.window().get_view_index(view)])
|
||||||
|
if len(window.views()) == 1:
|
||||||
|
window.new_file()
|
||||||
|
window.focus_view(view)
|
||||||
|
window.run_command('revert')
|
||||||
|
window.run_command('close')
|
||||||
|
|
||||||
|
# try to repaint
|
||||||
|
try:
|
||||||
|
window.focus_view(active_view)
|
||||||
|
window.focus_view(window.active_view())
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
window.focus_view(window.active_view())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return closed_items
|
@@ -0,0 +1,119 @@
|
|||||||
|
import sublime
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
class SideBarProject:
|
||||||
|
|
||||||
|
def getDirectories(self):
|
||||||
|
return sublime.active_window().folders()
|
||||||
|
|
||||||
|
def hasOpenedProject(self):
|
||||||
|
return self.getProjectFile() != None
|
||||||
|
|
||||||
|
def getDirectoryFromPath(self, path):
|
||||||
|
for directory in self.getDirectories():
|
||||||
|
maybe_path = path.replace(directory, '', 1)
|
||||||
|
if maybe_path != path:
|
||||||
|
return directory
|
||||||
|
|
||||||
|
def getProjectFile(self):
|
||||||
|
if not self.getDirectories():
|
||||||
|
return None
|
||||||
|
import json
|
||||||
|
data = file(os.path.normpath(os.path.join(sublime.packages_path(), '..', 'Settings', 'Session.sublime_session')), 'r').read()
|
||||||
|
data = data.replace('\t', ' ')
|
||||||
|
data = json.loads(data, strict=False)
|
||||||
|
projects = data['workspaces']['recent_workspaces']
|
||||||
|
|
||||||
|
if os.path.lexists(os.path.join(sublime.packages_path(), '..', 'Settings', 'Auto Save Session.sublime_session')):
|
||||||
|
data = file(os.path.normpath(os.path.join(sublime.packages_path(), '..', 'Settings', 'Auto Save Session.sublime_session')), 'r').read()
|
||||||
|
data = data.replace('\t', ' ')
|
||||||
|
data = json.loads(data, strict=False)
|
||||||
|
if 'workspaces' in data and 'recent_workspaces' in data['workspaces'] and data['workspaces']['recent_workspaces']:
|
||||||
|
projects += data['workspaces']['recent_workspaces']
|
||||||
|
projects = list(set(projects))
|
||||||
|
for project_file in projects:
|
||||||
|
project_file = re.sub(r'^/([^/])/', '\\1:/', project_file);
|
||||||
|
project_json = json.loads(file(project_file, 'r').read(), strict=False)
|
||||||
|
if 'folders' in project_json:
|
||||||
|
folders = project_json['folders']
|
||||||
|
found_all = True
|
||||||
|
for directory in self.getDirectories():
|
||||||
|
found = False
|
||||||
|
for folder in folders:
|
||||||
|
folder_path = re.sub(r'^/([^/])/', '\\1:/', folder['path']);
|
||||||
|
if folder_path == directory.replace('\\', '/'):
|
||||||
|
found = True
|
||||||
|
break;
|
||||||
|
if found == False:
|
||||||
|
found_all = False
|
||||||
|
break;
|
||||||
|
if found_all:
|
||||||
|
return project_file
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getProjectJson(self):
|
||||||
|
if not self.hasOpenedProject():
|
||||||
|
return None
|
||||||
|
import json
|
||||||
|
return json.loads(file(self.getProjectFile(), 'r').read(), strict=False)
|
||||||
|
|
||||||
|
def excludeDirectory(self, path):
|
||||||
|
import json
|
||||||
|
project_file = self.getProjectFile();
|
||||||
|
project = self.getProjectJson()
|
||||||
|
|
||||||
|
path = re.sub(r'^([^/])\:/', '/\\1/', path.replace('\\', '/'))
|
||||||
|
|
||||||
|
for folder in project['folders']:
|
||||||
|
if path.find(folder['path']) == 0:
|
||||||
|
try:
|
||||||
|
folder['folder_exclude_patterns'].append(re.sub(r'/+$', '', path.replace(folder['path']+'/', '', 1)))
|
||||||
|
except:
|
||||||
|
folder['folder_exclude_patterns'] = [re.sub(r'/+$', '', path.replace(folder['path']+'/', '', 1))]
|
||||||
|
file(project_file, 'w+').write(json.dumps(project, indent=1))
|
||||||
|
return
|
||||||
|
|
||||||
|
def excludeFile(self, path):
|
||||||
|
import json
|
||||||
|
project_file = self.getProjectFile();
|
||||||
|
project = self.getProjectJson()
|
||||||
|
|
||||||
|
path = re.sub(r'^([^/])\:/', '/\\1/', path.replace('\\', '/'))
|
||||||
|
|
||||||
|
for folder in project['folders']:
|
||||||
|
if path.find(folder['path']) == 0:
|
||||||
|
try:
|
||||||
|
folder['file_exclude_patterns'].append(path.replace(folder['path']+'/', '', 1))
|
||||||
|
except:
|
||||||
|
folder['file_exclude_patterns'] = [path.replace(folder['path']+'/', '', 1)]
|
||||||
|
file(project_file, 'w+').write(json.dumps(project, indent=1))
|
||||||
|
return
|
||||||
|
|
||||||
|
def rootAdd(self, path):
|
||||||
|
import json
|
||||||
|
project_file = self.getProjectFile();
|
||||||
|
project = self.getProjectJson()
|
||||||
|
|
||||||
|
path = re.sub(r'^([^/])\:/', '/\\1/', path.replace('\\', '/'))
|
||||||
|
project['folders'].append({'path':path});
|
||||||
|
|
||||||
|
file(project_file, 'w+').write(json.dumps(project, indent=1))
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
try:
|
||||||
|
sublime.set_timeout(lambda:sublime.active_window().run_command('refresh_folder_list'), 200);
|
||||||
|
sublime.set_timeout(lambda:sublime.active_window().run_command('refresh_folder_list'), 600);
|
||||||
|
sublime.set_timeout(lambda:sublime.active_window().run_command('refresh_folder_list'), 1300);
|
||||||
|
sublime.set_timeout(lambda:sublime.active_window().run_command('refresh_folder_list'), 2300);
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getPreference(self, name):
|
||||||
|
if not self.hasOpenedProject():
|
||||||
|
return None
|
||||||
|
project = self.getProjectJson()
|
||||||
|
try:
|
||||||
|
return project[name]
|
||||||
|
except:
|
||||||
|
return None
|
@@ -0,0 +1,186 @@
|
|||||||
|
# coding=utf8
|
||||||
|
import sublime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from SideBarProject import SideBarProject
|
||||||
|
from SideBarItem import SideBarItem
|
||||||
|
|
||||||
|
class SideBarSelection:
|
||||||
|
|
||||||
|
def __init__(self, paths = []):
|
||||||
|
|
||||||
|
if len(paths) < 1:
|
||||||
|
try:
|
||||||
|
path = sublime.active_window().active_view().file_name()
|
||||||
|
if self.isNone(path):
|
||||||
|
paths = []
|
||||||
|
else:
|
||||||
|
paths = [path]
|
||||||
|
except:
|
||||||
|
paths = []
|
||||||
|
self._paths = paths
|
||||||
|
self._paths.sort()
|
||||||
|
self._obtained_selection_information_basic = False
|
||||||
|
self._obtained_selection_information_extended = False
|
||||||
|
|
||||||
|
def len(self):
|
||||||
|
return len(self._paths)
|
||||||
|
|
||||||
|
def hasDirectories(self):
|
||||||
|
self._obtainSelectionInformationBasic()
|
||||||
|
return self._has_directories
|
||||||
|
|
||||||
|
def hasFiles(self):
|
||||||
|
self._obtainSelectionInformationBasic()
|
||||||
|
return self._has_files
|
||||||
|
|
||||||
|
def hasOnlyDirectories(self):
|
||||||
|
self._obtainSelectionInformationBasic()
|
||||||
|
return self._only_directories
|
||||||
|
|
||||||
|
def hasOnlyFiles(self):
|
||||||
|
self._obtainSelectionInformationBasic()
|
||||||
|
return self._only_files
|
||||||
|
|
||||||
|
def hasProjectDirectories(self):
|
||||||
|
if self.hasDirectories():
|
||||||
|
project_directories = SideBarProject().getDirectories()
|
||||||
|
for item in self.getSelectedDirectories():
|
||||||
|
if item.path() in project_directories:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def hasItemsUnderProject(self):
|
||||||
|
for item in self.getSelectedItems():
|
||||||
|
if item.isUnderCurrentProject():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def hasImages(self):
|
||||||
|
return self.hasFilesWithExtension('gif|jpg|jpeg|png')
|
||||||
|
|
||||||
|
def hasFilesWithExtension(self, extensions):
|
||||||
|
extensions = re.compile('('+extensions+')$', re.I);
|
||||||
|
for item in self.getSelectedFiles():
|
||||||
|
if extensions.search(item.path()):
|
||||||
|
return True;
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getSelectedItems(self):
|
||||||
|
self._obtainSelectionInformationExtended()
|
||||||
|
return self._files + self._directories;
|
||||||
|
|
||||||
|
def getSelectedItemsWithoutChildItems(self):
|
||||||
|
self._obtainSelectionInformationExtended()
|
||||||
|
items = []
|
||||||
|
for item in self._items_without_containing_child_items:
|
||||||
|
items.append(SideBarItem(item, os.path.isdir(item)))
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getSelectedDirectories(self):
|
||||||
|
self._obtainSelectionInformationExtended()
|
||||||
|
return self._directories;
|
||||||
|
|
||||||
|
def getSelectedFiles(self):
|
||||||
|
self._obtainSelectionInformationExtended()
|
||||||
|
return self._files;
|
||||||
|
|
||||||
|
def getSelectedDirectoriesOrDirnames(self):
|
||||||
|
self._obtainSelectionInformationExtended()
|
||||||
|
return self._directories_or_dirnames;
|
||||||
|
|
||||||
|
def getSelectedImages(self):
|
||||||
|
return self.getSelectedFilesWithExtension('gif|jpg|jpeg|png')
|
||||||
|
|
||||||
|
def getSelectedFilesWithExtension(self, extensions):
|
||||||
|
items = []
|
||||||
|
extensions = re.compile('('+extensions+')$', re.I);
|
||||||
|
for item in self.getSelectedFiles():
|
||||||
|
if extensions.search(item.path()):
|
||||||
|
items.append(item)
|
||||||
|
return items
|
||||||
|
|
||||||
|
def _obtainSelectionInformationBasic(self):
|
||||||
|
if not self._obtained_selection_information_basic:
|
||||||
|
self._obtained_selection_information_basic = True
|
||||||
|
|
||||||
|
self._has_directories = False
|
||||||
|
self._has_files = False
|
||||||
|
self._only_directories = False
|
||||||
|
self._only_files = False
|
||||||
|
|
||||||
|
for path in self._paths:
|
||||||
|
if self._has_directories == False and os.path.isdir(path):
|
||||||
|
self._has_directories = True
|
||||||
|
if self._has_files == False and os.path.isdir(path) == False:
|
||||||
|
self._has_files = True
|
||||||
|
if self._has_files and self._has_directories:
|
||||||
|
break
|
||||||
|
|
||||||
|
if self._has_files and self._has_directories:
|
||||||
|
self._only_directories = False
|
||||||
|
self._only_files = False
|
||||||
|
elif self._has_files:
|
||||||
|
self._only_files = True
|
||||||
|
elif self._has_directories:
|
||||||
|
self._only_directories = True
|
||||||
|
|
||||||
|
def _obtainSelectionInformationExtended(self):
|
||||||
|
if not self._obtained_selection_information_extended:
|
||||||
|
self._obtained_selection_information_extended = True
|
||||||
|
|
||||||
|
self._directories = []
|
||||||
|
self._files = []
|
||||||
|
self._directories_or_dirnames = []
|
||||||
|
self._items_without_containing_child_items = []
|
||||||
|
|
||||||
|
_directories = []
|
||||||
|
_files = []
|
||||||
|
_directories_or_dirnames = []
|
||||||
|
_items_without_containing_child_items = []
|
||||||
|
|
||||||
|
for path in self._paths:
|
||||||
|
if os.path.isdir(path):
|
||||||
|
item = SideBarItem(path, True)
|
||||||
|
if item.path() not in _directories:
|
||||||
|
_directories.append(item.path())
|
||||||
|
self._directories.append(item)
|
||||||
|
if item.path() not in _directories_or_dirnames:
|
||||||
|
_directories_or_dirnames.append(item.path())
|
||||||
|
self._directories_or_dirnames.append(item)
|
||||||
|
_items_without_containing_child_items = self._itemsWithoutContainingChildItems(_items_without_containing_child_items, item.path())
|
||||||
|
else:
|
||||||
|
item = SideBarItem(path, False)
|
||||||
|
if item.path() not in _files:
|
||||||
|
_files.append(item.path())
|
||||||
|
self._files.append(item)
|
||||||
|
_items_without_containing_child_items = self._itemsWithoutContainingChildItems(_items_without_containing_child_items, item.path())
|
||||||
|
item = SideBarItem(os.path.dirname(path), True)
|
||||||
|
if item.path() not in _directories_or_dirnames:
|
||||||
|
_directories_or_dirnames.append(item.path())
|
||||||
|
self._directories_or_dirnames.append(item)
|
||||||
|
|
||||||
|
self._items_without_containing_child_items = _items_without_containing_child_items
|
||||||
|
|
||||||
|
def _itemsWithoutContainingChildItems(self, items, item):
|
||||||
|
new_list = []
|
||||||
|
add = True
|
||||||
|
for i in items:
|
||||||
|
if i.find(item+'\\') == 0 or i.find(item+'/') == 0:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
new_list.append(i)
|
||||||
|
if (item+'\\').find(i+'\\') == 0 or (item+'/').find(i+'/') == 0:
|
||||||
|
add = False
|
||||||
|
if add:
|
||||||
|
new_list.append(item)
|
||||||
|
return new_list
|
||||||
|
|
||||||
|
def isNone(self, path):
|
||||||
|
if path == None or path == '' or path == '.' or path == '..' or path == './' or path == '/' or path == '//' or path == '\\' or path == '\\\\' or path == '\\\\\\\\':
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
@@ -0,0 +1,34 @@
|
|||||||
|
[
|
||||||
|
// Git Chords - https://github.com/kemayo/sublime-text-2-git
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+a"], "command": "git_add_choice" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+b"], "command": "git_branch" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+c"], "command": "git_commit" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+d"], "command": "git_diff" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+f"], "command": "git_fetch" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+g"], "command": "git_graph" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+h"], "command": "git_commit_history" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+l"], "command": "git_log" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+m"], "command": "git_merge" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+n"], "command": "git_new_branch" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+p"], "command": "git_pull_current_branch" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+q"], "command": "git_quick_commit" },
|
||||||
|
// dangerous
|
||||||
|
// { "keys": ["ctrl+shift+g", "ctrl+shift+r"], "command": "git_reset_hard_head" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+s"], "command": "git_status" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+t"], "command": "git_new_tag" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+z"], "command": "git_commit_amend" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+up"], "command": "git_push" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+down"], "command": "git_pull" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+right"], "command": "git_stash" },
|
||||||
|
{ "keys": ["ctrl+shift+g", "ctrl+shift+left"], "command": "git_stash_pop" },
|
||||||
|
|
||||||
|
//Modific - https://github.com/gornostal/Modific
|
||||||
|
{ "keys": ["ctrl+alt+c"], "command": "show_original_part" },
|
||||||
|
//using this binding with https://github.com/braindamageinc/SublimeHttpRequester
|
||||||
|
//{ "keys": ["ctrl+alt+r"], "command": "replace_modified_part" },
|
||||||
|
//Git plugin does this already
|
||||||
|
//{ "keys": ["ctrl+alt+d"], "command": "show_diff" },
|
||||||
|
{ "keys": ["ctrl+alt+u"], "command": "uncommitted_files" }
|
||||||
|
//{ "keys": ["ctrl+shift+pageup"], "command": "jump_between_changes", "args": {"direction": "prev"} },
|
||||||
|
//{ "keys": ["ctrl+shift+pagedown"], "command": "jump_between_changes", "args": {"direction": "next"} }
|
||||||
|
]
|
@@ -0,0 +1,27 @@
|
|||||||
|
SublimeKeyMap.Git
|
||||||
|
=================
|
||||||
|
|
||||||
|
A simple repository used to host / share my customized Sublime Text 2 key bindings for Git plugins
|
||||||
|
|
||||||
|
Designed to be incorporated into `Package Control.sublime-settings` like:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"installed_packages":
|
||||||
|
[
|
||||||
|
"EditorConfig",
|
||||||
|
"Git",
|
||||||
|
"GitHubinator",
|
||||||
|
"Modific",
|
||||||
|
"SideBarGit",
|
||||||
|
"sublime-github"
|
||||||
|
],
|
||||||
|
"package_name_map": {
|
||||||
|
"SublimeKeyMap.Git": "ZZZ.EthanBrown.SublimeKeyMap.Git"
|
||||||
|
},
|
||||||
|
"repositories":
|
||||||
|
[
|
||||||
|
"https://github.com/Iristyle/SublimeKeyMap.Git"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
@@ -0,0 +1 @@
|
|||||||
|
{"url": "https://github.com/Iristyle/SublimeKeyMap.Git", "version": "2013.03.17.19.57.57", "description": "A simple repository used to host / share my customized Sublime Text 2 key bindings for Git plugins"}
|
1
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/sublime-github/.gitignore
vendored
Normal file
1
EthanBrown.SublimeText2.GitPackages/tools/PackageCache/sublime-github/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{ "keys": ["super+g", "super+n"], "command": "public_gist_from_selection" },
|
||||||
|
{ "keys": ["super+g", "super+p","super+n"], "command": "private_gist_from_selection" },
|
||||||
|
{ "keys": ["super+g", "super+o"], "command": "open_gist_in_editor" },
|
||||||
|
{ "keys": ["super+g", "super+c"], "command": "open_gist_url" }
|
||||||
|
]
|
@@ -0,0 +1,37 @@
|
|||||||
|
/* Sublime GitHub default settings */
|
||||||
|
{
|
||||||
|
"accounts": {
|
||||||
|
"GitHub": {
|
||||||
|
"base_uri": "https://api.github.com",
|
||||||
|
"github_token": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// You can add support for a private GitHub installation by adding another entry
|
||||||
|
// to the accounts entry in the User settings file (Preferences ->
|
||||||
|
// Package Settings -> GitHub -> Settings - User). You can then switch between
|
||||||
|
// accounts via the GitHub: Switch Accounts command
|
||||||
|
//
|
||||||
|
// "YourCo": {
|
||||||
|
// "base_uri": "https://github.yourco.com/api/v3",
|
||||||
|
// "github_token": ""
|
||||||
|
// }
|
||||||
|
|
||||||
|
// The format of the each line in the list of gists.
|
||||||
|
// The value is either a Python format string, or a list of Python format
|
||||||
|
// strings. In the latter case, each element of the list will be a separate
|
||||||
|
// line in the select list.
|
||||||
|
// Valid parameters:
|
||||||
|
// filename - filename of [first file in] gist
|
||||||
|
// description - description of gist
|
||||||
|
// index - 1-based index of gist in the list
|
||||||
|
//
|
||||||
|
// Some things to try:
|
||||||
|
// "gist_list_format": "%(index)d. %(filename)s: %(description)s",
|
||||||
|
// "gist_list_format": ["%(filename)s", "%(description)s"],
|
||||||
|
"gist_list_format": "%(filename)s: %(description)s",
|
||||||
|
// If true, display the list of gists in a monospace font
|
||||||
|
"gist_list_monospace": false,
|
||||||
|
// output debug info to the console
|
||||||
|
"debug": false
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
[
|
||||||
|
{ "caption": "GitHub: Private Gist from Selection", "command": "private_gist_from_selection" },
|
||||||
|
{ "caption": "GitHub: Public Gist from Selection", "command": "public_gist_from_selection" },
|
||||||
|
{ "caption": "GitHub: Copy Gist to Clipboard", "command": "open_gist" },
|
||||||
|
{ "caption": "GitHub: Copy Starred Gist to Clipboard", "command": "open_starred_gist" },
|
||||||
|
{ "caption": "GitHub: Open Gist in Editor", "command": "open_gist_in_editor" },
|
||||||
|
{ "caption": "GitHub: Open Starred Gist in Editor", "command": "open_starred_gist_in_editor" },
|
||||||
|
{ "caption": "GitHub: Copy Gist URL to Clipboard", "command": "open_gist_url" },
|
||||||
|
{ "caption": "GitHub: Open Gist in Browser", "command": "open_gist_in_browser" },
|
||||||
|
{ "caption": "GitHub: Open Starred Gist in Browser", "command": "open_starred_gist_in_browser" },
|
||||||
|
{ "caption": "GitHub: Update Gist", "command": "update_gist" },
|
||||||
|
{ "caption": "GitHub: Switch Accounts", "command": "switch_accounts" },
|
||||||
|
{ "caption": "GitHub: Copy Remote URL to Clipboard", "command": "copy_remote_url" },
|
||||||
|
{ "caption": "GitHub: Open Remote URL in Browser", "command": "open_remote_url" }
|
||||||
|
]
|
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2011 Brad Greenlee
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,34 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Preferences",
|
||||||
|
"mnemonic": "n",
|
||||||
|
"id": "preferences",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Package Settings",
|
||||||
|
"mnemonic": "P",
|
||||||
|
"id": "package-settings",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "GitHub",
|
||||||
|
"children":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {"file": "${packages}/sublime-github/GitHub.sublime-settings"},
|
||||||
|
"caption": "Settings – Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "open_file",
|
||||||
|
"args": {"file": "${packages}/User/GitHub.sublime-settings"},
|
||||||
|
"caption": "Settings – User"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@@ -0,0 +1,18 @@
|
|||||||
|
Sublime GitHub includes some external libraries to simplify installation.
|
||||||
|
|
||||||
|
Requests License
|
||||||
|
================
|
||||||
|
|
||||||
|
Copyright (c) 2012 Kenneth Reitz.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
@@ -0,0 +1,169 @@
|
|||||||
|
# Sublime GitHub
|
||||||
|
|
||||||
|
This is a plugin for the [Sublime Text 2](http://www.sublimetext.com/) text
|
||||||
|
editor that allows you to create and browse your [GitHub Gists](http://gist.github.com).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
**The easiest way to install is via the** [**Sublime Package Control**](http://wbond.net/sublime_packages/package_control) **plugin.**
|
||||||
|
Just open "Package Control: Install Package" in your Command Palette and search for
|
||||||
|
"sublime-github" (or, if you already have it installed, select "Package Control: Upgrade Package"
|
||||||
|
to upgrade).
|
||||||
|
|
||||||
|
To install it manually in a shell/Terminal (on OS X, Linux or Cygwin), via git:
|
||||||
|
|
||||||
|
cd ~/"Library/Application Support/Sublime Text 2/Packages/" # location on OS X; will be different on Linux & Windows
|
||||||
|
git clone https://github.com/bgreenlee/sublime-github.git
|
||||||
|
|
||||||
|
or, if you don't have git installed:
|
||||||
|
|
||||||
|
cd ~/"Library/Application Support/Sublime Text 2/Packages/"
|
||||||
|
rm -rf bgreenlee-sublime-github* # remove any old versions
|
||||||
|
curl -L https://github.com/bgreenlee/sublime-github/tarball/master | tar xf -
|
||||||
|
|
||||||
|
The plugin should be picked up automatically. If not, restart Sublime Text.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The first time you run one of the commands, it will ask you for your GitHub
|
||||||
|
username and password in order to create a GitHub API access token, which gets saved
|
||||||
|
in the Sublime GitHub user settings file. Your username and password are not
|
||||||
|
stored anywhere, but if you would rather generate the access token yourself, see
|
||||||
|
the "Generating Your Own Access Token" section below.
|
||||||
|
|
||||||
|
The following commands are available in the Command Palette:
|
||||||
|
|
||||||
|
* **GitHub: Private Gist from Selection**
|
||||||
|
|
||||||
|
Create a private gist from the currently selected text (or, if nothing is selected,
|
||||||
|
the contents of the active editor.
|
||||||
|
|
||||||
|
* **GitHub: Public Gist from Selection**
|
||||||
|
|
||||||
|
Create a public gist from the currently selected text (or, if nothing is selected,
|
||||||
|
the contents of the active editor.
|
||||||
|
|
||||||
|
* **GitHub: Copy Gist to Clipboard**
|
||||||
|
|
||||||
|
Displays a quick select panel listing all of your gists, and selecting one will
|
||||||
|
copy the contents of that gist to your clipboard.
|
||||||
|
|
||||||
|
* **GitHub: Copy Starred Gist to Clipboard**
|
||||||
|
|
||||||
|
Displays a quick select panel listing only your starred gists, and selecting one will
|
||||||
|
copy the contents of that gist to your clipboard.
|
||||||
|
|
||||||
|
* **GitHub: Open Gist in Editor**
|
||||||
|
|
||||||
|
Displays a quick select panel listing all of your gists, and selecting one will
|
||||||
|
open a new editor tab with the contents of that gist.
|
||||||
|
|
||||||
|
* **GitHub: Open Starred Gist in Editor**
|
||||||
|
|
||||||
|
Displays a quick select panel listing only your starred gists, and selecting one will
|
||||||
|
open a new editor tab with the contents of that gist.
|
||||||
|
|
||||||
|
* **GitHub: Open Gist in Browser**
|
||||||
|
|
||||||
|
Displays a quick select panel listing all of your gists, and selecting one will
|
||||||
|
open that gist in your default web browser.
|
||||||
|
|
||||||
|
* **GitHub: Open Starred Gist in Browser**
|
||||||
|
|
||||||
|
Displays a quick select panel listing only your starred gists, and selecting one will
|
||||||
|
open that gist in your default web browser.
|
||||||
|
|
||||||
|
* **GitHub: Update Gist**
|
||||||
|
|
||||||
|
Update the gist open in the current editor.
|
||||||
|
|
||||||
|
* **GitHub: Switch Accounts**
|
||||||
|
|
||||||
|
Switch to another GitHub account (see Adding Additional Accounts below)
|
||||||
|
|
||||||
|
* **GitHub: Open Remote URL in Browser**
|
||||||
|
|
||||||
|
Open the current file's location in the repository in the browser. *Note:* Requires
|
||||||
|
the Git plugin, available through the Package Manager. After installing, restart
|
||||||
|
Sublime Text.
|
||||||
|
|
||||||
|
* **GitHub: Copy Remote URL to Clipboard**
|
||||||
|
|
||||||
|
Put the url of the current file's location in the repository into the clipboard.
|
||||||
|
*Note:* Requires the Git plugin, available through the Package Manager. After
|
||||||
|
installing, restart Sublime Text.
|
||||||
|
|
||||||
|
## Adding Additional Accounts
|
||||||
|
|
||||||
|
If have multiple GitHub accounts, or have a private GitHub installation, you can add the other
|
||||||
|
accounts and switch between them whenever you like.
|
||||||
|
|
||||||
|
Go to the GitHub user settings file (Preferences -> Package Settings -> GitHub -> Settings - User),
|
||||||
|
and add another entry to the `accounts` dictionary. If it is another GitHub account, copy the
|
||||||
|
`base_uri` for the default GitHub entry (if you don't see it, you can get it from Preferences ->
|
||||||
|
Package Settings -> GitHub -> Settings - Default, or in the example below), and just give the
|
||||||
|
account a different name. If you're adding a private GitHub installation, the `base_uri` will be
|
||||||
|
whatever the base url is for your private GitHub, plus "/api/v3". For example:
|
||||||
|
|
||||||
|
"accounts":
|
||||||
|
{
|
||||||
|
"GitHub":
|
||||||
|
{
|
||||||
|
"base_uri": "https://api.github.com",
|
||||||
|
"github_token": "..."
|
||||||
|
},
|
||||||
|
"YourCo":
|
||||||
|
{
|
||||||
|
"base_uri": "https://github.yourco.com/api/v3",
|
||||||
|
"github_token": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Don't worry about setting the `github_token`--that will be set for you automatically, after you
|
||||||
|
switch accounts (Shift-Cmd-P, "GitHub: Switch Accounts").
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
* Linux requires the [curl](http://curl.haxx.se/) binary to be installed on your system (in one of:
|
||||||
|
`/usr/local/sbin`, `/usr/local/bin`, `/usr/sbin`, `/usr/bin`, `/sbin`, or `/bin`).
|
||||||
|
|
||||||
|
* Depending on the number of gists you have, there can be a considerable delay the first time
|
||||||
|
your list of gists is fetched. Subsequent requests will be cached and should be a bit faster
|
||||||
|
(although the GitHub API's ETags are currently not correct; once that fix that, it should speed
|
||||||
|
things up). In the meantime, if there are gists that you open frequently, open them on GitHub and
|
||||||
|
"Star" them, then access them via the Open/Copy Starred Gist commands.
|
||||||
|
|
||||||
|
* Setting the file type for syntax highlighting when opening a gist in the editor does not work
|
||||||
|
in Linux. I could get it to work with significant effort, so if you desperately want it, open
|
||||||
|
an issue.
|
||||||
|
|
||||||
|
## Generating Your Own Access Token
|
||||||
|
|
||||||
|
If you feel uncomfortable giving your GitHub username and password to the
|
||||||
|
plugin, you can generate a GitHub API access token yourself. Just open up
|
||||||
|
a Terminal window/shell (on OS X, Linux or Cygwin), and run:
|
||||||
|
|
||||||
|
curl -u username -d '{"scopes":["gist"]}' https://api.github.com/authorizations
|
||||||
|
|
||||||
|
where `username` is your GitHub username. You'll be prompt for your password first. Then you'll get back
|
||||||
|
a response that includes a 40-digit "token" value (e.g. `6423ba8429a152ff4a7279d1e8f4674029d3ef87`).
|
||||||
|
Go to Sublime Text 2 -> Preferences -> Package Settings -> GitHub -> Settings - User,
|
||||||
|
and insert the token there. It should look like:
|
||||||
|
|
||||||
|
{
|
||||||
|
"github_token": "6423ba8429a152ff4a7279d1e8f4674029d3ef87"
|
||||||
|
}
|
||||||
|
|
||||||
|
Restart Sublime.
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
|
||||||
|
## Bugs and Feature Requests
|
||||||
|
|
||||||
|
<http://github.com/bgreenlee/sublime-github/issues>
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
Copyright © 2011+ Brad Greenlee. See LICENSE for details.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
|||||||
|
# adapted from https://github.com/wbond/sublime_package_control/blob/master/Package%20Control.py
|
||||||
|
import os.path
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class BinaryNotFoundError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def find_binary(name):
|
||||||
|
dirs = ['/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin',
|
||||||
|
'/sbin', '/bin']
|
||||||
|
for dir in dirs:
|
||||||
|
path = os.path.join(dir, name)
|
||||||
|
if os.path.exists(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
raise BinaryNotFoundError('The binary ' + name + ' could not be ' + \
|
||||||
|
'located')
|
||||||
|
|
||||||
|
|
||||||
|
def execute(args):
|
||||||
|
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
|
output = proc.stdout.read()
|
||||||
|
proc.wait()
|
||||||
|
return output
|
@@ -0,0 +1,131 @@
|
|||||||
|
import sublime
|
||||||
|
import os.path
|
||||||
|
import json
|
||||||
|
import sublime_requests as requests
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(format='%(asctime)s %(message)s')
|
||||||
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class GitHubApi(object):
|
||||||
|
"Encapsulates the GitHub API"
|
||||||
|
PER_PAGE = 100
|
||||||
|
etags = {}
|
||||||
|
cache = {}
|
||||||
|
|
||||||
|
class UnauthorizedException(Exception):
|
||||||
|
"Raised if we get a 401 from GitHub"
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UnknownException(Exception):
|
||||||
|
"Raised if we get a response code we don't recognize from GitHub"
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, base_uri="https://api.github.com", token=None, debug=False):
|
||||||
|
self.base_uri = base_uri
|
||||||
|
self.token = token
|
||||||
|
self.debug = debug
|
||||||
|
if debug:
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# set up requests session with the root CA cert bundle
|
||||||
|
cert_path = os.path.join(sublime.packages_path(), "sublime-github", "ca-bundle.crt")
|
||||||
|
if not os.path.isfile(cert_path):
|
||||||
|
logger.warning("Root CA cert bundle not found at %s! Not verifying requests." % cert_path)
|
||||||
|
cert_path = None
|
||||||
|
self.rsession = requests.session(verify=cert_path,
|
||||||
|
config={'verbose': sys.stderr if self.debug else None})
|
||||||
|
|
||||||
|
def get_token(self, username, password):
|
||||||
|
auth_data = {
|
||||||
|
"scopes": ["gist"],
|
||||||
|
"note": "Sublime GitHub",
|
||||||
|
"note_url": "https://github.com/bgreenlee/sublime-github"
|
||||||
|
}
|
||||||
|
resp = self.rsession.post(self.base_uri + "/authorizations",
|
||||||
|
auth=(username, password),
|
||||||
|
data=json.dumps(auth_data))
|
||||||
|
if resp.status_code == requests.codes.CREATED:
|
||||||
|
data = json.loads(resp.text)
|
||||||
|
return data["token"]
|
||||||
|
elif resp.status_code == requests.codes.UNAUTHORIZED:
|
||||||
|
raise self.UnauthorizedException()
|
||||||
|
else:
|
||||||
|
raise self.UnknownException("%d %s" % (resp.status_code, resp.text))
|
||||||
|
|
||||||
|
def post(self, endpoint, data=None, content_type='application/json'):
|
||||||
|
return self.request('post', endpoint, data=data, content_type=content_type)
|
||||||
|
|
||||||
|
def patch(self, endpoint, data=None, content_type='application/json'):
|
||||||
|
return self.request('patch', endpoint, data=data, content_type=content_type)
|
||||||
|
|
||||||
|
def get(self, endpoint, params=None):
|
||||||
|
return self.request('get', endpoint, params=params)
|
||||||
|
|
||||||
|
def request(self, method, url, params=None, data=None, content_type=None):
|
||||||
|
if not url.startswith("http"):
|
||||||
|
url = self.base_uri + url
|
||||||
|
if data:
|
||||||
|
data = json.dumps(data)
|
||||||
|
|
||||||
|
headers = {"Authorization": "token %s" % self.token}
|
||||||
|
|
||||||
|
if content_type:
|
||||||
|
headers["Content-Type"] = content_type
|
||||||
|
|
||||||
|
# add an etag to the header if we have one
|
||||||
|
if method == 'get' and url in self.etags:
|
||||||
|
headers["If-None-Match"] = self.etags[url]
|
||||||
|
logger.debug("request: %s %s %s %s" % (method, url, headers, params))
|
||||||
|
resp = self.rsession.request(method, url,
|
||||||
|
headers=headers,
|
||||||
|
params=params,
|
||||||
|
data=data,
|
||||||
|
allow_redirects=True)
|
||||||
|
full_url = resp.url
|
||||||
|
logger.debug("response: %s" % resp.headers)
|
||||||
|
if resp.status_code in [requests.codes.OK,
|
||||||
|
requests.codes.CREATED,
|
||||||
|
requests.codes.FOUND,
|
||||||
|
requests.codes.CONTINUE]:
|
||||||
|
if 'application/json' in resp.headers['content-type']:
|
||||||
|
resp_data = json.loads(resp.text)
|
||||||
|
else:
|
||||||
|
resp_data = resp.text
|
||||||
|
if method == 'get': # cache the response
|
||||||
|
etag = resp.headers['etag']
|
||||||
|
self.etags[full_url] = etag
|
||||||
|
self.cache[etag] = resp_data
|
||||||
|
return resp_data
|
||||||
|
elif resp.status_code == requests.codes.NOT_MODIFIED:
|
||||||
|
return self.cache[resp.headers['etag']]
|
||||||
|
elif resp.status_code == requests.codes.UNAUTHORIZED:
|
||||||
|
raise self.UnauthorizedException()
|
||||||
|
else:
|
||||||
|
raise self.UnknownException("%d %s" % (resp.status_code, resp.text))
|
||||||
|
|
||||||
|
def create_gist(self, description="", filename="", content="", public=False):
|
||||||
|
return self.post("/gists", {"description": description,
|
||||||
|
"public": public,
|
||||||
|
"files": {filename: {"content": content}}})
|
||||||
|
|
||||||
|
def update_gist(self, gist, content):
|
||||||
|
filename = gist["files"].keys()[0]
|
||||||
|
return self.patch("/gists/" + gist["id"],
|
||||||
|
{"description": gist["description"],
|
||||||
|
"files": {filename: {"content": content}}})
|
||||||
|
|
||||||
|
def list_gists(self, starred=False):
|
||||||
|
page = 1
|
||||||
|
data = []
|
||||||
|
# fetch all pages
|
||||||
|
while True:
|
||||||
|
endpoint = "/gists" + ("/starred" if starred else "")
|
||||||
|
page_data = self.get(endpoint, params={'page': page, 'per_page': self.PER_PAGE})
|
||||||
|
data.extend(page_data)
|
||||||
|
if len(page_data) < self.PER_PAGE:
|
||||||
|
break
|
||||||
|
page += 1
|
||||||
|
return data
|
@@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# __
|
||||||
|
# /__) _ _ _ _ _/ _
|
||||||
|
# / ( (- (/ (/ (- _) / _)
|
||||||
|
# /
|
||||||
|
|
||||||
|
"""
|
||||||
|
requests
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: (c) 2012 by Kenneth Reitz.
|
||||||
|
:license: ISC, see LICENSE for more details.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__title__ = 'requests'
|
||||||
|
__version__ = '0.10.2'
|
||||||
|
__build__ = 0x001002
|
||||||
|
__author__ = 'Kenneth Reitz'
|
||||||
|
__license__ = 'ISC'
|
||||||
|
__copyright__ = 'Copyright 2012 Kenneth Reitz'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
from .models import Request, Response
|
||||||
|
from .api import request, get, head, post, patch, put, delete, options
|
||||||
|
from .sessions import session, Session
|
||||||
|
from .status_codes import codes
|
||||||
|
from .exceptions import (
|
||||||
|
RequestException, Timeout, URLRequired,
|
||||||
|
TooManyRedirects, HTTPError, ConnectionError
|
||||||
|
)
|
@@ -0,0 +1,116 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
requests.api
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module implements the Requests API.
|
||||||
|
|
||||||
|
:copyright: (c) 2012 by Kenneth Reitz.
|
||||||
|
:license: ISC, see LICENSE for more details.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from . import sessions
|
||||||
|
|
||||||
|
def request(method, url, **kwargs):
|
||||||
|
"""Constructs and sends a :class:`Request <Request>`.
|
||||||
|
Returns :class:`Response <Response>` object.
|
||||||
|
|
||||||
|
:param method: method for the new :class:`Request` object.
|
||||||
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||||
|
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||||
|
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||||
|
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||||
|
:param files: (optional) Dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload.
|
||||||
|
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
||||||
|
:param timeout: (optional) Float describing the timeout of the request.
|
||||||
|
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||||
|
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||||
|
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||||
|
:param session: (optional) A :class:`Session` object to be used for the request.
|
||||||
|
:param config: (optional) A configuration dictionary.
|
||||||
|
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||||
|
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
|
||||||
|
"""
|
||||||
|
|
||||||
|
s = kwargs.pop('session') if 'session' in kwargs else sessions.session()
|
||||||
|
return s.request(method=method, url=url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get(url, **kwargs):
|
||||||
|
"""Sends a GET request. Returns :class:`Response` object.
|
||||||
|
|
||||||
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param **kwargs: Optional arguments that ``request`` takes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs.setdefault('allow_redirects', True)
|
||||||
|
return request('get', url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def options(url, **kwargs):
|
||||||
|
"""Sends a OPTIONS request. Returns :class:`Response` object.
|
||||||
|
|
||||||
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param **kwargs: Optional arguments that ``request`` takes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs.setdefault('allow_redirects', True)
|
||||||
|
return request('options', url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def head(url, **kwargs):
|
||||||
|
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||||
|
|
||||||
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param **kwargs: Optional arguments that ``request`` takes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs.setdefault('allow_redirects', True)
|
||||||
|
return request('head', url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def post(url, data=None, **kwargs):
|
||||||
|
"""Sends a POST request. Returns :class:`Response` object.
|
||||||
|
|
||||||
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||||
|
:param **kwargs: Optional arguments that ``request`` takes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return request('post', url, data=data, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def put(url, data=None, **kwargs):
|
||||||
|
"""Sends a PUT request. Returns :class:`Response` object.
|
||||||
|
|
||||||
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||||
|
:param **kwargs: Optional arguments that ``request`` takes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return request('put', url, data=data, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def patch(url, data=None, **kwargs):
|
||||||
|
"""Sends a PATCH request. Returns :class:`Response` object.
|
||||||
|
|
||||||
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||||
|
:param **kwargs: Optional arguments that ``request`` takes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return request('patch', url, data=data, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def delete(url, **kwargs):
|
||||||
|
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||||
|
|
||||||
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param **kwargs: Optional arguments that ``request`` takes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return request('delete', url, **kwargs)
|
@@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
requests.async
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module contains an asynchronous replica of ``requests.api``, powered
|
||||||
|
by gevent. All API methods return a ``Request`` instance (as opposed to
|
||||||
|
``Response``). A list of requests can be sent with ``map()``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import gevent
|
||||||
|
from gevent import monkey as curious_george
|
||||||
|
from gevent.pool import Pool
|
||||||
|
except ImportError:
|
||||||
|
raise RuntimeError('Gevent is required for requests.async.')
|
||||||
|
|
||||||
|
# Monkey-patch.
|
||||||
|
curious_george.patch_all(thread=False)
|
||||||
|
|
||||||
|
from . import api
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'map',
|
||||||
|
'get', 'options', 'head', 'post', 'put', 'patch', 'delete', 'request'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def patched(f):
|
||||||
|
"""Patches a given API function to not send."""
|
||||||
|
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
|
||||||
|
kwargs['return_response'] = False
|
||||||
|
kwargs['prefetch'] = True
|
||||||
|
|
||||||
|
config = kwargs.get('config', {})
|
||||||
|
config.update(safe_mode=True)
|
||||||
|
|
||||||
|
kwargs['config'] = config
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def send(r, pool=None, prefetch=False):
|
||||||
|
"""Sends the request object using the specified pool. If a pool isn't
|
||||||
|
specified this method blocks. Pools are useful because you can specify size
|
||||||
|
and can hence limit concurrency."""
|
||||||
|
|
||||||
|
if pool != None:
|
||||||
|
return pool.spawn(r.send, prefetch=prefetch)
|
||||||
|
|
||||||
|
return gevent.spawn(r.send, prefetch=prefetch)
|
||||||
|
|
||||||
|
|
||||||
|
# Patched requests.api functions.
|
||||||
|
get = patched(api.get)
|
||||||
|
options = patched(api.options)
|
||||||
|
head = patched(api.head)
|
||||||
|
post = patched(api.post)
|
||||||
|
put = patched(api.put)
|
||||||
|
patch = patched(api.patch)
|
||||||
|
delete = patched(api.delete)
|
||||||
|
request = patched(api.request)
|
||||||
|
|
||||||
|
|
||||||
|
def map(requests, prefetch=True, size=None):
|
||||||
|
"""Concurrently converts a list of Requests to Responses.
|
||||||
|
|
||||||
|
:param requests: a collection of Request objects.
|
||||||
|
:param prefetch: If False, the content will not be downloaded immediately.
|
||||||
|
:param size: Specifies the number of requests to make at a time. If None, no throttling occurs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
requests = list(requests)
|
||||||
|
|
||||||
|
pool = Pool(size) if size else None
|
||||||
|
jobs = [send(r, pool, prefetch=prefetch) for r in requests]
|
||||||
|
gevent.joinall(jobs)
|
||||||
|
|
||||||
|
return [r.response for r in requests]
|
@@ -0,0 +1,150 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
requests.auth
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module contains the authentication handlers for Requests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
from .compat import urlparse, str, bytes
|
||||||
|
from .utils import randombytes, parse_dict_header
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _basic_auth_str(username, password):
|
||||||
|
"""Returns a Basic Auth string."""
|
||||||
|
|
||||||
|
return 'Basic ' + b64encode(("%s:%s" % (username, password)).encode('utf-8')).strip().decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
class AuthBase(object):
|
||||||
|
"""Base class that all auth implementations derive from"""
|
||||||
|
|
||||||
|
def __call__(self, r):
|
||||||
|
raise NotImplementedError('Auth hooks must be callable.')
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPBasicAuth(AuthBase):
|
||||||
|
"""Attaches HTTP Basic Authentication to the given Request object."""
|
||||||
|
def __init__(self, username, password):
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def __call__(self, r):
|
||||||
|
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPProxyAuth(HTTPBasicAuth):
|
||||||
|
"""Attaches HTTP Proxy Authenetication to a given Request object."""
|
||||||
|
def __call__(self, r):
|
||||||
|
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPDigestAuth(AuthBase):
|
||||||
|
"""Attaches HTTP Digest Authentication to the given Request object."""
|
||||||
|
def __init__(self, username, password):
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def handle_401(self, r):
|
||||||
|
"""Takes the given response and tries digest-auth, if needed."""
|
||||||
|
|
||||||
|
s_auth = r.headers.get('www-authenticate', '')
|
||||||
|
|
||||||
|
if 'digest' in s_auth.lower():
|
||||||
|
|
||||||
|
last_nonce = ''
|
||||||
|
nonce_count = 0
|
||||||
|
|
||||||
|
chal = parse_dict_header(s_auth.replace('Digest ', ''))
|
||||||
|
|
||||||
|
realm = chal['realm']
|
||||||
|
nonce = chal['nonce']
|
||||||
|
qop = chal.get('qop')
|
||||||
|
algorithm = chal.get('algorithm', 'MD5')
|
||||||
|
opaque = chal.get('opaque', None)
|
||||||
|
|
||||||
|
algorithm = algorithm.upper()
|
||||||
|
# lambdas assume digest modules are imported at the top level
|
||||||
|
if algorithm == 'MD5':
|
||||||
|
def h(x):
|
||||||
|
if isinstance(x, str):
|
||||||
|
x = x.encode('utf-8')
|
||||||
|
return hashlib.md5(x).hexdigest()
|
||||||
|
H = h
|
||||||
|
elif algorithm == 'SHA':
|
||||||
|
def h(x):
|
||||||
|
if isinstance(x, str):
|
||||||
|
x = x.encode('utf-8')
|
||||||
|
return hashlib.sha1(x).hexdigest()
|
||||||
|
H = h
|
||||||
|
# XXX MD5-sess
|
||||||
|
KD = lambda s, d: H("%s:%s" % (s, d))
|
||||||
|
|
||||||
|
if H is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# XXX not implemented yet
|
||||||
|
entdig = None
|
||||||
|
p_parsed = urlparse(r.request.url)
|
||||||
|
path = p_parsed.path
|
||||||
|
if p_parsed.query:
|
||||||
|
path += '?' + p_parsed.query
|
||||||
|
|
||||||
|
A1 = '%s:%s:%s' % (self.username, realm, self.password)
|
||||||
|
A2 = '%s:%s' % (r.request.method, path)
|
||||||
|
|
||||||
|
if qop == 'auth':
|
||||||
|
if nonce == last_nonce:
|
||||||
|
nonce_count += 1
|
||||||
|
else:
|
||||||
|
nonce_count = 1
|
||||||
|
last_nonce = nonce
|
||||||
|
|
||||||
|
ncvalue = '%08x' % nonce_count
|
||||||
|
s = str(nonce_count).encode('utf-8')
|
||||||
|
s += nonce.encode('utf-8')
|
||||||
|
s += time.ctime().encode('utf-8')
|
||||||
|
s += randombytes(8)
|
||||||
|
|
||||||
|
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
||||||
|
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
|
||||||
|
respdig = KD(H(A1), noncebit)
|
||||||
|
elif qop is None:
|
||||||
|
respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
|
||||||
|
else:
|
||||||
|
# XXX handle auth-int.
|
||||||
|
return None
|
||||||
|
|
||||||
|
# XXX should the partial digests be encoded too?
|
||||||
|
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
|
||||||
|
'response="%s"' % (self.username, realm, nonce, path, respdig)
|
||||||
|
if opaque:
|
||||||
|
base += ', opaque="%s"' % opaque
|
||||||
|
if entdig:
|
||||||
|
base += ', digest="%s"' % entdig
|
||||||
|
base += ', algorithm="%s"' % algorithm
|
||||||
|
if qop:
|
||||||
|
base += ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce)
|
||||||
|
|
||||||
|
r.request.headers['Authorization'] = 'Digest %s' % (base)
|
||||||
|
r.request.send(anyway=True)
|
||||||
|
_r = r.request.response
|
||||||
|
_r.history.append(r)
|
||||||
|
|
||||||
|
return _r
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __call__(self, r):
|
||||||
|
r.register_hook('response', self.handle_401)
|
||||||
|
return r
|
@@ -0,0 +1,105 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
pythoncompat
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# -------
|
||||||
|
# Pythons
|
||||||
|
# -------
|
||||||
|
|
||||||
|
# Syntax sugar.
|
||||||
|
_ver = sys.version_info
|
||||||
|
|
||||||
|
#: Python 2.x?
|
||||||
|
is_py2 = (_ver[0] == 2)
|
||||||
|
|
||||||
|
#: Python 3.x?
|
||||||
|
is_py3 = (_ver[0] == 3)
|
||||||
|
|
||||||
|
#: Python 3.0.x
|
||||||
|
is_py30 = (is_py3 and _ver[1] == 0)
|
||||||
|
|
||||||
|
#: Python 3.1.x
|
||||||
|
is_py31 = (is_py3 and _ver[1] == 1)
|
||||||
|
|
||||||
|
#: Python 3.2.x
|
||||||
|
is_py32 = (is_py3 and _ver[1] == 2)
|
||||||
|
|
||||||
|
#: Python 3.3.x
|
||||||
|
is_py33 = (is_py3 and _ver[1] == 3)
|
||||||
|
|
||||||
|
#: Python 3.4.x
|
||||||
|
is_py34 = (is_py3 and _ver[1] == 4)
|
||||||
|
|
||||||
|
#: Python 2.7.x
|
||||||
|
is_py27 = (is_py2 and _ver[1] == 7)
|
||||||
|
|
||||||
|
#: Python 2.6.x
|
||||||
|
is_py26 = (is_py2 and _ver[1] == 6)
|
||||||
|
|
||||||
|
#: Python 2.5.x
|
||||||
|
is_py25 = (is_py2 and _ver[1] == 5)
|
||||||
|
|
||||||
|
#: Python 2.4.x
|
||||||
|
is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice.
|
||||||
|
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
# Platforms
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
|
||||||
|
# Syntax sugar.
|
||||||
|
_ver = sys.version.lower()
|
||||||
|
|
||||||
|
is_pypy = ('pypy' in _ver)
|
||||||
|
is_jython = ('jython' in _ver)
|
||||||
|
is_ironpython = ('iron' in _ver)
|
||||||
|
|
||||||
|
# Assume CPython, if nothing else.
|
||||||
|
is_cpython = not any((is_pypy, is_jython, is_ironpython))
|
||||||
|
|
||||||
|
# Windows-based system.
|
||||||
|
is_windows = 'win32' in str(sys.platform).lower()
|
||||||
|
|
||||||
|
# Standard Linux 2+ system.
|
||||||
|
is_linux = ('linux' in str(sys.platform).lower())
|
||||||
|
is_osx = ('darwin' in str(sys.platform).lower())
|
||||||
|
is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess.
|
||||||
|
is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess.
|
||||||
|
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
# Specifics
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
|
||||||
|
if is_py2:
|
||||||
|
from urllib import quote, unquote, urlencode
|
||||||
|
from urlparse import urlparse, urlunparse, urljoin, urlsplit
|
||||||
|
from urllib2 import parse_http_list
|
||||||
|
import cookielib
|
||||||
|
from .packages.oreos.monkeys import SimpleCookie
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
bytes = str
|
||||||
|
str = unicode
|
||||||
|
basestring = basestring
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
elif is_py3:
|
||||||
|
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote
|
||||||
|
from urllib.request import parse_http_list
|
||||||
|
from http import cookiejar as cookielib
|
||||||
|
from http.cookies import SimpleCookie
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
str = str
|
||||||
|
bytes = bytes
|
||||||
|
basestring = (str,bytes)
|
||||||
|
|
@@ -0,0 +1,40 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
requests.defaults
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module provides the Requests configuration defaults.
|
||||||
|
|
||||||
|
Configurations:
|
||||||
|
|
||||||
|
:base_headers: Default HTTP headers.
|
||||||
|
:verbose: Stream to write request logging to.
|
||||||
|
:max_redirects: Maximum number of redirects allowed within a request.s
|
||||||
|
:keep_alive: Reuse HTTP Connections?
|
||||||
|
:max_retries: The number of times a request should be retried in the event of a connection failure.
|
||||||
|
:danger_mode: If true, Requests will raise errors immediately.
|
||||||
|
:safe_mode: If true, Requests will catch all errors.
|
||||||
|
:pool_maxsize: The maximium size of an HTTP connection pool.
|
||||||
|
:pool_connections: The number of active HTTP connection pools to use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from . import __version__
|
||||||
|
|
||||||
|
defaults = dict()
|
||||||
|
|
||||||
|
|
||||||
|
defaults['base_headers'] = {
|
||||||
|
'User-Agent': 'python-requests/%s' % __version__,
|
||||||
|
'Accept-Encoding': ', '.join(('identity', 'deflate', 'compress', 'gzip')),
|
||||||
|
'Accept': '*/*'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults['verbose'] = None
|
||||||
|
defaults['max_redirects'] = 30
|
||||||
|
defaults['pool_connections'] = 10
|
||||||
|
defaults['pool_maxsize'] = 10
|
||||||
|
defaults['max_retries'] = 0
|
||||||
|
defaults['danger_mode'] = False
|
||||||
|
defaults['safe_mode'] = False
|
||||||
|
defaults['keep_alive'] = True
|
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
requests.exceptions
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module contains the set of Requests' exceptions.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class RequestException(Exception):
|
||||||
|
"""There was an ambiguous exception that occurred while handling your
|
||||||
|
request."""
|
||||||
|
|
||||||
|
class HTTPError(RequestException):
|
||||||
|
"""An HTTP error occurred."""
|
||||||
|
|
||||||
|
class ConnectionError(RequestException):
|
||||||
|
"""A Connection error occurred."""
|
||||||
|
|
||||||
|
class SSLError(ConnectionError):
|
||||||
|
"""An SSL error occurred."""
|
||||||
|
|
||||||
|
class Timeout(RequestException):
|
||||||
|
"""The request timed out."""
|
||||||
|
|
||||||
|
class URLRequired(RequestException):
|
||||||
|
"""A valid URL is required to make a request."""
|
||||||
|
|
||||||
|
class TooManyRedirects(RequestException):
|
||||||
|
"""Too many redirects."""
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user