Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 308 lines (232 sloc) 9.59 KB
Favorite Files.
Licensed under MIT
Copyright (c) 2012 - 2015 Isaac Muse <>
import sublime
from os.path import exists, basename, getmtime, splitext
import json
from FavoriteFiles.lib.file_strip.json import sanitize_json
from FavoriteFiles.lib.notify import error
class FavObj(object):
"""Favorite object for tracking current state."""
files = {}
projects = set([])
last_access = 0
global_file = ""
file_name = ""
class FavProjects(object):
"""Project related actions."""
def add(cls, obj, win_id):
"""Add window to projects."""
def remove(cls, obj, win_id):
"""Remove window from projects."""
if cls.is_project_tracked(obj, win_id):
def prune_projects(cls, obj):
"""Prune windows from projects that are closed."""
dead = obj.projects - set([ for x in])
for key in dead:
def is_project_tracked(cls, obj, win_id):
"""Check if the current window project is being tracked."""
return True if win_id is not None and win_id in obj.projects else False
def project_adjust(cls, obj, win_id, force=False):
"""Adjust settings for the given project."""
enabled = cls.is_project_tracked(obj, win_id)
if enabled:
project = cls.get_project(win_id)
if project is not None:
project_favs = splitext(project)[0] + "-favs.json"
if not exists(project_favs) and not force:
error('Cannot find favorite list!\nProject name probably changed.\nSwitching to global list.')
obj.file_name = obj.global_file
obj.last_access = 0
# Make sure project is the new target
if project_favs != obj.file_name:
obj.file_name = project_favs
obj.last_access = 0
elif not FavFileMgr.is_global_file(obj):
obj.file_name = obj.global_file
obj.last_access = 0
return enabled
def has_project(cls, win_id):
"""Check if window has a project."""
project = cls.get_project(win_id)
return True if project is not None else False
def get_project(cls, win_id):
"""Get the windows project."""
project = None
for w in
if == win_id:
project = w.project_file_name()
return project
class FavFileMgr(object):
"""Handle file actions."""
def is_global_file(cls, obj):
"""Check if file is a global one."""
return obj.file_name == obj.global_file
def update_list_format(cls, file_list):
"""Used for upgrading list formats."""
def clean_orphaned_favorites(cls, file_list):
"""Clean out dead links in global list and group lists and remove empty groups."""
file_list["files"] = [f for f in file_list["files"] if exists(f)]
for g in file_list["groups"]:
file_list["groups"][g] = [f for f in file_list["groups"][g] if exists(f)]
if len(file_list["groups"][g]) == 0:
del file_list["groups"][g]
def create_favorite_list(cls, obj, file_list, force=False):
"""Create the favorites list."""
errors = False
if not exists(obj.file_name) or force:
# Save as a JSON file
j = json.dumps(file_list, sort_keys=True, indent=4, separators=(',', ': '))
with open(obj.file_name, 'w') as f:
f.write(j + "\n")
obj.last_access = getmtime(obj.file_name)
except Exception:
error('Failed to write %s!' % basename(obj.file_name))
errors = True
return errors
def load_favorites(cls, obj, clean=False):
"""Load favorites list."""
errors = False
with open(obj.file_name, "r") as f:
# Allow C style comments and be forgiving of trailing commas
content = sanitize_json(, True)
file_list = json.loads(content)
# Clean out dead links
if clean:
cls.create_favorite_list(obj, file_list, force=True)
# Update internal list and access times
obj.last_access = getmtime(obj.file_name)
obj.files = file_list
except Exception:
errors = True
if cls.is_global_file(obj):
error('Failed to load %s!' % basename(obj.file_name))
'Failed to load %s!\nDid you rename your project?\n'
'Try toggling "Per Projects" off and on and try again.' % basename(obj.file_name)
return errors
def load_favorite_files(cls, obj, force=False, clean=False, win_id=None):
"""Load favorite files."""
errors = False
# Is project enabled
FavProjects.project_adjust(obj, win_id, force)
if not exists(obj.file_name):
if force:
# Create file list if it doesn't exist
if cls.create_favorite_list(obj, {"version": 1, "files": [], "groups": {}}, force=True):
error('Failed to cerate %s!' % basename(obj.file_name))
errors = True
force = True
errors = True
# Only reload if file has been written since last access (or if forced reload)
if not errors and (force or getmtime(obj.file_name) != obj.last_access):
errors = cls.load_favorites(obj, clean=clean)
return errors
class Favorites(object):
"""High level favorites handling."""
def __init__(self, global_file):
self.obj = FavObj()
self.obj.global_file = global_file
self.obj.last_access = 0
self.obj.file_name = self.obj.global_file
def open(self, win_id=None):
"""Open favorites."""
return FavFileMgr.load_favorite_files(self.obj, force=True, win_id=win_id)
def load(self, force=False, clean=False, win_id=None):
"""Load favorites."""
return FavFileMgr.load_favorite_files(self.obj, force, clean, win_id)
def save(self, force=False):
"""Save favorites."""
return FavFileMgr.create_favorite_list(self.obj, self.obj.files, force=force)
def toggle_global(self, win_id):
"""Toggle global."""
errors = False
# Clean out closed windows
if FavProjects.is_project_tracked(self.obj, win_id):
errors = True
return errors
def toggle_per_projects(self, win_id):
"""Toggle per project favorites."""
errors = False
if FavProjects.has_project(win_id):
errors = True
return errors
def remove_group(self, s):
"""Remove a group."""
if self.exists(s, group=True):
del self.obj.files["groups"][s]
def add_group(self, s):
"""Add favorite group."""
self.obj.files["groups"][s] = []
def set(self, s, group_name=None):
"""Add file in global or group list."""
if group_name is None:
def exists(self, s, group=False, group_name=None):
"""Check if froup or file exists."""
if group:
# See if group exists
return True if s in self.obj.files["groups"] else False
# See if file in global or group list exists
if group_name is None:
return True if s in set(self.obj.files["files"]) else False
return True if s in set(self.obj.files["groups"][group_name]) else False
def remove(self, s, group_name=None):
"""Remove file in group or global list."""
if group_name is None:
if self.exists(s):
if self.exists(s, group_name=group_name):
def all_files(self, group_name=None):
"""Return all files in group or global list."""
if group_name is not None:
return [[basename(path), path] for path in self.obj.files["groups"][group_name]]
return [[basename(path), path] for path in self.obj.files["files"]]
def group_count(self):
"""Return group count."""
return len(self.obj.files["groups"])
def all_groups(self):
"""Return all groups."""
return sorted([["Group: " + k, "%d files" % len(v)] for k, v in self.obj.files["groups"].items()])