Add file_vcs_status segment, using powerline.lib.vcs instead of fugitive
Using pure-python implementation allows to use the same code in the terminal segments (not implemented currently). It also should be more effective. Git module is able to use pygit2 instead of command-line git which is much faster. Mercurial module assumes that mercurial is always accessible as a python module. Code for repository (not file) status is untested. It exists solely for terminal segments. Closes #26.
This commit is contained in:
parent
ca1225b864
commit
102e749093
|
@ -58,6 +58,7 @@
|
|||
"modified_indicator": { "fg": "brightyellow", "bg": "gray4", "attr": ["bold"] },
|
||||
"paste_indicator": { "fg": "white", "bg": "mediumorange", "attr": ["bold"] },
|
||||
"readonly_indicator": { "fg": "brightestred", "bg": "gray4" },
|
||||
"file_vcs_status": { "fg": "brightestred", "bg": "gray4" },
|
||||
"branch": { "fg": "gray9", "bg": "gray4" },
|
||||
"file_directory": { "fg": "gray9", "bg": "gray4" },
|
||||
"file_name": { "fg": "white", "bg": "gray4", "attr": ["bold"] },
|
||||
|
|
|
@ -5,6 +5,7 @@ import vim
|
|||
|
||||
from powerline.ext.vim.bindings import vim_get_func
|
||||
from powerline.lib.memoize import memoize
|
||||
from powerline.lib.vcs import guess
|
||||
|
||||
vim_funcs = {
|
||||
'col': vim_get_func('col', rettype=int),
|
||||
|
@ -12,9 +13,6 @@ vim_funcs = {
|
|||
'expand': vim_get_func('expand'),
|
||||
'line': vim_get_func('line', rettype=int),
|
||||
'mode': vim_get_func('mode'),
|
||||
'vcs': {
|
||||
'fugitive': vim_get_func('fugitive#head'),
|
||||
},
|
||||
}
|
||||
|
||||
vim_modes = {
|
||||
|
@ -77,23 +75,6 @@ def readonly_indicator(text=u''):
|
|||
return text if int(vim.eval('&readonly')) else None
|
||||
|
||||
|
||||
@memoize(2)
|
||||
def branch():
|
||||
'''Return VCS branch.
|
||||
|
||||
TODO: Expand this function to handle several VCS plugins.
|
||||
'''
|
||||
branch = None
|
||||
try:
|
||||
branch = vim_funcs['vcs']['fugitive'](5)
|
||||
except vim.error:
|
||||
vim_funcs['vcs']['fugitive'] = None
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
return branch if branch else None
|
||||
|
||||
|
||||
def file_directory():
|
||||
'''Return file directory (head component of the file path).
|
||||
'''
|
||||
|
@ -171,3 +152,21 @@ def col_current(virtcol=True):
|
|||
characters ignored (default), else returns byte offset.
|
||||
'''
|
||||
return vim_funcs['virtcol' if virtcol else 'col']('.')
|
||||
|
||||
|
||||
@memoize(2)
|
||||
def branch():
|
||||
repo = guess(os.path.abspath(vim.current.buffer.name or os.getcwd()))
|
||||
if repo:
|
||||
return repo.branch()
|
||||
return None
|
||||
|
||||
|
||||
# TODO Drop cache on BufWrite event
|
||||
@memoize(2)
|
||||
def file_vcs_status():
|
||||
if vim.current.buffer.name and not vim.eval('&buftype'):
|
||||
repo = guess(os.path.abspath(vim.current.buffer.name))
|
||||
if repo:
|
||||
return repo.status(os.path.relpath(vim.current.buffer.name, repo.directory))
|
||||
return None
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import importlib
|
||||
import os
|
||||
from powerline.lib.memoize import memoize
|
||||
|
||||
|
||||
def generate_directories(path):
|
||||
yield path
|
||||
while True:
|
||||
old_path = path
|
||||
path = os.path.dirname(path)
|
||||
if path == old_path:
|
||||
break
|
||||
yield path
|
||||
|
||||
|
||||
@memoize(100)
|
||||
def guess(path):
|
||||
for directory in generate_directories(path):
|
||||
for vcs, vcs_dir in (('git', '.git'), ('mercurial', '.hg')):
|
||||
if os.path.isdir(os.path.join(directory, vcs_dir)):
|
||||
try:
|
||||
if vcs not in globals():
|
||||
globals()[vcs] = importlib.import_module('powerline.lib.vcs.' + vcs)
|
||||
return globals()[vcs].Repository(directory)
|
||||
except:
|
||||
pass
|
||||
return None
|
|
@ -0,0 +1,132 @@
|
|||
try:
|
||||
import pygit2 as git
|
||||
|
||||
class Repository(object):
|
||||
__slots__ = ('repo', 'directory')
|
||||
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
self.repo = git.Repository(directory)
|
||||
|
||||
def status(self, path=None):
|
||||
'''Return status of repository or file.
|
||||
|
||||
Without file argument: returns status of the repository:
|
||||
|
||||
:First column: working directory status (D: dirty / space)
|
||||
:Second column: index status (I: index dirty / space)
|
||||
:Third column: presense of untracked files (U: untracked files / space)
|
||||
:None: repository clean
|
||||
|
||||
With file argument: returns status of this file. Output is
|
||||
equivalent to the first two columns of "git status --porcelain"
|
||||
(except for merge statuses as they are not supported by libgit2).
|
||||
'''
|
||||
if path:
|
||||
status = self.repo.status_file(path)
|
||||
|
||||
if status == git.GIT_STATUS_CURRENT:
|
||||
return None
|
||||
else:
|
||||
if status & git.GIT_STATUS_WT_NEW:
|
||||
return '??'
|
||||
if status & git.GIT_STATUS_IGNORED:
|
||||
return '!!'
|
||||
|
||||
if status & git.GIT_STATUS_INDEX_NEW:
|
||||
index_status = 'A'
|
||||
elif status & git.GIT_STATUS_INDEX_DELETED:
|
||||
index_status = 'D'
|
||||
elif status & git.GIT_STATUS_INDEX_MODIFIED:
|
||||
index_status = 'M'
|
||||
else:
|
||||
index_status = ' '
|
||||
|
||||
if status & git.GIT_STATUS_WT_DELETED:
|
||||
wt_status = 'D'
|
||||
elif status & git.GIT_STATUS_WT_MODIFIED:
|
||||
wt_status = 'M'
|
||||
else:
|
||||
wt_status = ' '
|
||||
|
||||
return index_status + wt_status
|
||||
else:
|
||||
wt_column = ' '
|
||||
index_column = ' '
|
||||
untracked_column = ' '
|
||||
for status in self.repo.status():
|
||||
if status & (git.GIT_STATUS_WT_DELETED
|
||||
| git.GIT_STATUS_WT_MODIFIED):
|
||||
wt_column = 'D'
|
||||
elif status & (git.GIT_STATUS_INDEX_NEW
|
||||
| git.GIT_STATUS_INDEX_MODIFIED
|
||||
| git.GIT_STATUS_INDEX_DELETED):
|
||||
index_column = 'I'
|
||||
elif status & git.GIT_STATUS_WT_NEW:
|
||||
untracked_column = 'U'
|
||||
return wt_column + index_column + untracked_column
|
||||
|
||||
def branch(self):
|
||||
try:
|
||||
ref = self.repo.lookup_reference('HEAD')
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
try:
|
||||
target = ref.target
|
||||
except ValueError:
|
||||
return '[DETACHED HEAD]'
|
||||
|
||||
if target.startswith('refs/heads/'):
|
||||
return target[11:]
|
||||
else:
|
||||
return '[DETACHED HEAD]'
|
||||
except ImportError:
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
def readlines(cmd, cwd):
|
||||
p = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, cwd=cwd)
|
||||
p.stderr.close()
|
||||
for line in p.stdout:
|
||||
yield line[:-1]
|
||||
|
||||
class Repository(object):
|
||||
__slots__ = ('directory',)
|
||||
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
|
||||
def _gitcmd(self, *args):
|
||||
return readlines(('git',) + args, self.directory)
|
||||
|
||||
def status(self, path=None):
|
||||
if path:
|
||||
try:
|
||||
return self._gitcmd('status', '--porcelain', '--', path).next()[:2]
|
||||
except StopIteration:
|
||||
try:
|
||||
self._gitcmd('ls-files', '--ignored', '--exclude-standard', '--others', '--', path).next()
|
||||
return '!!'
|
||||
except StopIteration:
|
||||
return None
|
||||
else:
|
||||
wt_column = ' '
|
||||
index_column = ' '
|
||||
untracked_column = ' '
|
||||
for line in self._gitcmd('status', '--porcelain'):
|
||||
if line[0] == '?':
|
||||
untracked_column = 'U'
|
||||
elif line[0] == '!':
|
||||
pass
|
||||
elif line[0] != ' ':
|
||||
index_column = 'I'
|
||||
elif line[1] != ' ':
|
||||
wt_column = 'D'
|
||||
r = wt_column + index_column + untracked_column
|
||||
return None if r == ' ' else r
|
||||
|
||||
def branch(self):
|
||||
for line in self._gitcmd('branch', '-l'):
|
||||
if line[0] == '*':
|
||||
return line[2:]
|
||||
return None
|
|
@ -0,0 +1,50 @@
|
|||
from __future__ import absolute_import
|
||||
from mercurial import hg, ui, match
|
||||
|
||||
|
||||
class Repository(object):
|
||||
__slots__ = ('directory', 'ui')
|
||||
|
||||
statuses = 'MARDUI'
|
||||
repo_statuses = (1, 1, 1, 1, 2)
|
||||
repo_statuses_str = (None, 'D ', ' U', 'DU')
|
||||
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
self.ui = ui.ui()
|
||||
|
||||
def _repo(self):
|
||||
# Cannot create this object once and use always: when repository updates
|
||||
# functions emit invalid results
|
||||
return hg.repository(self.ui, self.directory)
|
||||
|
||||
def status(self, path=None):
|
||||
'''Return status of repository or file.
|
||||
|
||||
Without file argument: returns status of the repository:
|
||||
|
||||
:"D?": dirty (tracked modified files: added, removed, deleted, modified),
|
||||
:"?U": untracked-dirty (added, but not tracked files)
|
||||
:None: clean (status is empty)
|
||||
|
||||
With file argument: returns status of this file: "M"odified, "A"dded,
|
||||
"R"emoved, "D"eleted (removed from filesystem, but still tracked),
|
||||
"U"nknown, "I"gnored, (None)Clean.
|
||||
'''
|
||||
repo = self._repo()
|
||||
|
||||
if path:
|
||||
m = match.match(None, None, [path], exact=True)
|
||||
statuses = repo.status(match=m, unknown=True, ignored=True)
|
||||
for status, paths in zip(self.statuses, statuses):
|
||||
if paths:
|
||||
return status
|
||||
return None
|
||||
else:
|
||||
resulting_status = 0
|
||||
for status, paths in zip(self.repo_statuses, repo.status(unknown=True)):
|
||||
resulting_status |= status
|
||||
return self.repo_statuses_str[resulting_status]
|
||||
|
||||
def branch(self):
|
||||
return self._repo().dirstate.branch()
|
|
@ -31,6 +31,11 @@
|
|||
"name": "file_name",
|
||||
"draw_divider": false
|
||||
},
|
||||
{
|
||||
"name": "file_vcs_status",
|
||||
"draw_divider": false,
|
||||
"before": " "
|
||||
},
|
||||
{
|
||||
"name": "modified_indicator",
|
||||
"args": { "text": "+" },
|
||||
|
|
Loading…
Reference in New Issue