<commit_before># DONE option to cleanup the history database (json) on start
# DONE introduce a settings file to get settings from
# DONE [ST3] use api function (only available in ST3) to get the project name/id (rather than using a hash of the project folders)
# DONE [ST3] preview the file while cycling through the quick panel history entries
# DONE [ST3] support "quick open" from the quick panel (mapped by default to the "right" key)
# DONE [ST3] removed unnecessary view settings (and associated code) by listening for the on_pre_close event rather than the on_close event
# DONE added setting for using monospaced font in the quick panel
# DONE fixed ST2 support

# DONE Fixed bug when plugin invoked in a window without a view
# DONE [ST2] Added support for cleanup on startup for ST2
# DONE Improved plugin responsiveness by opening the preview in the background (noticable with large or remote files)
# DONE Added option to display the timestamp of the history entry (either the last opened/closed timestamp or the filesystem's last modified timestamp)
# DONE [ST3] Quick panel support for removing the history entry being previewed. Note: the entry will be deleted but will still be visible in the current quick panel. Mapped to "ctrl+delete" by default ("cmd+delete" on OSX).
#
# DONE cleanup all histories, not just current project and global
# DONE support various formatting options for the history file, including specifying the indentation size or no formatting

import sublime
import sublime_plugin
import os
import hashlib
import json
import time

is_ST2 = int(sublime.version()) < 3000

def plugin_loaded():
    # Force the FileHistory singleton to be instanciated so the startup tasks will be executed
    # Depending on the "cleanup_on_startup" setting, the history may be cleaned at startup
    FileHistory.instance()


class FileHistory(object):
    _instance = None

    @classmethod
    def instance(cls):
        """Basic singleton implementation"""
        if not cls._instance:
            cls._instance = cls()
        return cls._instance

    def __init__(self):
        """Class to manage the file-access history"""
        self.sublime_settings = sublime.load_settings('Preferences.sublime-settings')
        self.SETTINGS_FILE = 'FileHistory.sublime-settings'
        self.PRINT_DEBUG = False
        self.__load_settings()
        self.__load_history()
        self.__clear_context()

        self.invoke_async = sublime.set_timeout if is_ST2 else sublime.set_timeout_async

        if self.CLEANUP_ON_STARTUP:
            self.invoke_async(lambda: self.clean_history(False) , 0)

    def __load_settings(self):
        default_date_format = '%Y-%m-%d %H:%M:%S'

        """Load the plugin settings from FileHistory.sublime-settings"""
        app_settings = sublime.load_settings(self.SETTINGS_FILE)
        settings_exist = app_settings.has('history_file')

        self.PRINT_DEBUG = self.__ensure_setting(app_settings, 'debug', True)
        self.GLOBAL_MAX_ENTRIES = self.__ensure_setting(app_settings, 'global_max_entries', 100)
        self.PROJECT_MAX_ENTRIES = self.__ensure_setting(app_settings, 'project_max_entries', 50)
        self.USE_SAVED_POSITION = self.__ensure_setting(app_settings, 'use_saved_position', True)
        self.NEW_TAB_POSITION = self.__ensure_setting(app_settings, 'new_tab_position', 'next')
        self.REMOVE_NON_EXISTENT_FILES = self.__ensure_setting(app_settings, 'remove_non_existent_files_on_preview', True)
        self.CLEANUP_ON_STARTUP = self.__ensure_setting(app_settings, 'cleanup_on_startup', True)
        history_path = self.__ensure_setting(app_settings, 'history_file', 'User/FileHistory.json')
        self.HISTORY_FILE = os.path.normpath(os.path.join(sublime.packages_path(), history_path))
        self.USE_MONOSPACE = self.__ensure_setting(app_settings, 'use_monospace_font', False)
        self.DISPLAY_TIMESTAMPS = self.__ensure_setting(app_settings, 'display_timestamps', True)
        self.TIMESTAMP_FORMAT = self.__ensure_setting(app_settings, 'timestamp_format', default_date_format)
        self.TIMESTAMP_MODE = self.__ensure_setting(app_settings, 'timestamp_mode', 'history_access')
        self.PRETTY_PRINT_HISTORY = self.__ensure_setting(app_settings, 'pretty_print_history', False)
        self.INDENT_SIZE = self.__ensure_setting(app_settings, 'indent_size', 2)

        try:
            self.get_timestamp()
        except Exception:
            self.TIMESTAMP_FORMAT = default_date_format

        # Ignore the file preview setting for ST2
        self.SHOW_FILE_PREVIEW = False if is_ST2 else self.__ensure_setting(app_settings, 'show_file_preview', True)

        if not settings_exist:
            print('[FileHistory] Unable to find the settings file "%s".  A default settings file has been created for you.' % (self.SETTINGS_FILE))
            sublime.save_settings(self.SETTINGS_FILE)

    def get_timestamp(self, filename=None):
        if filename and os.path.exists(filename) :
            timestamp = time.strftime(self.TIMESTAMP_FORMAT, time.localtime(os.path.getmtime(filename)) )
        else:
            timestamp = time.strftime(self.TIMESTAMP_FORMAT)
        return timestamp

    def get_history_timestamp(self, history_entry):
        filepath = history_entry['filename']
        if 'timestamp' in history_entry and self.TIMESTAMP_MODE == 'history_access':
            action = history_entry['action'] if 'action' in history_entry else 'accessed'
            timestamp = history_entry['timestamp']
        else:
            action = 'modified'
            timestamp = self.get_timestamp(filepath)
        return (action, timestamp)

    def __ensure_setting(self, settings, key, default_value):
        value = default_value
        if settings.has(key):
            value = settings.get(key)
            self.debug('FileHistory setting "%s" = "%s"' % (key, value))
        else:
            self.debug('FileHistory setting "%s" not found.  Using the default value of "%s"' % (key, default_value))
            settings.set(key, default_value)
        return value

    def debug(self, text):
        """Helper method for "logging" to the console."""
        if self.PRINT_DEBUG:
            print('[FileHistory] ' + text)

    def get_current_project_key(self):
        m = hashlib.md5()
        for path in sublime.active_window().folders():
            m.update(path.encode('utf-8'))
        project_key = m.hexdigest()

        # Try to use project_file_name (available in ST3 build 3014)
        # Note: Although it would be more appropriate, the name of the workspace is not available
        if hasattr(sublime.active_window(), 'project_file_name'):
            project_filename = sublime.active_window().project_file_name()
            if not project_filename:
                return project_key

            # migrate the history entry based on the "old" project key (if it exists)
            if project_key in self.history:
                self.history[project_filename] = self.history[project_key]
                del(self.history[project_key])

            # use the new project key
            project_key = project_filename

        return project_key

    def __load_history(self):
        self.history = {}

        self.debug('Loading the history from file ' + self.HISTORY_FILE)
        if not os.path.exists(self.HISTORY_FILE):
            return

        f = open(self.HISTORY_FILE, 'r')
        try:
            updated_history = json.load(f)
        finally:
            f.close()

        self.history = updated_history

    def __save_history(self):
        self.debug('Saving the history to file ' + self.HISTORY_FILE)
        f = open(self.HISTORY_FILE, mode='w+')
        try:
            history_indentation = self.INDENT_SIZE if self.PRETTY_PRINT_HISTORY else None

            json.dump(self.history, f, indent=history_indentation)

            f.flush()
        finally:
            f.close()

    def get_history(self, current_project_only=True):
        """Return the requested history (global or project-specific): closed files followed by opened files"""
        # Make sure the history is loaded
        if len(self.history) == 0:
            self.__load_history()

        # Load the requested history (global or project-specific)<commit_msg>changes to parameter names<commit_after># DONE option to cleanup the history database (json) on start
# DONE introduce a settings file to get settings from
# DONE [ST3] use api function (only available in ST3) to get the project name/id (rather than using a hash of the project folders)
# DONE [ST3] preview the file while cycling through the quick panel history entries
# DONE [ST3] support "quick open" from the quick panel (mapped by default to the "right" key)
# DONE [ST3] removed unnecessary view settings (and associated code) by listening for the on_pre_close event rather than the on_close event
# DONE added setting for using monospaced font in the quick panel
# DONE fixed ST2 support

# DONE Fixed bug when plugin invoked in a window without a view
# DONE [ST2] Added support for cleanup on startup for ST2
# DONE Improved plugin responsiveness by opening the preview in the background (noticable with large or remote files)
# DONE Added option to display the timestamp of the history entry (either the last opened/closed timestamp or the filesystem's last modified timestamp)
# DONE [ST3] Quick panel support for removing the history entry being previewed. Note: the entry will be deleted but will still be visible in the current quick panel. Mapped to "ctrl+delete" by default ("cmd+delete" on OSX).
#
# DONE cleanup all histories, not just current project and global
# DONE support various formatting options for the history file, including specifying the indentation size or no formatting

import sublime
import sublime_plugin
import os
import hashlib
import json
import time

is_ST2 = int(sublime.version()) < 3000

def plugin_loaded():
    # Force the FileHistory singleton to be instanciated so the startup tasks will be executed
    # Depending on the "cleanup_on_startup" setting, the history may be cleaned at startup
    FileHistory.instance()


class FileHistory(object):
    _instance = None

    @classmethod
    def instance(cls):
        """Basic singleton implementation"""
        if not cls._instance:
            cls._instance = cls()
        return cls._instance

    def __init__(self):
        """Class to manage the file-access history"""
        self.sublime_settings = sublime.load_settings('Preferences.sublime-settings')
        self.SETTINGS_FILE = 'FileHistory.sublime-settings'
        self.PRINT_DEBUG = False
        self.__load_settings()
        self.__load_history()
        self.__clear_context()

        self.invoke_async = sublime.set_timeout if is_ST2 else sublime.set_timeout_async

        if self.CLEANUP_ON_STARTUP:
            self.invoke_async(lambda: self.clean_history(False) , 0)

    def __load_settings(self):
        default_date_format = '%Y-%m-%d %H:%M:%S'

        """Load the plugin settings from FileHistory.sublime-settings"""
        app_settings = sublime.load_settings(self.SETTINGS_FILE)
        settings_exist = app_settings.has('history_file')

        self.PRINT_DEBUG = self.__ensure_setting(app_settings, 'debug', True)
        self.GLOBAL_MAX_ENTRIES = self.__ensure_setting(app_settings, 'global_max_entries', 100)
        self.PROJECT_MAX_ENTRIES = self.__ensure_setting(app_settings, 'project_max_entries', 50)
        self.USE_SAVED_POSITION = self.__ensure_setting(app_settings, 'use_saved_position', True)
        self.NEW_TAB_POSITION = self.__ensure_setting(app_settings, 'new_tab_position', 'next')
        self.REMOVE_NON_EXISTENT_FILES = self.__ensure_setting(app_settings, 'remove_non_existent_files_on_preview', True)
        self.CLEANUP_ON_STARTUP = self.__ensure_setting(app_settings, 'cleanup_on_startup', True)
        history_path = self.__ensure_setting(app_settings, 'history_file', 'User/FileHistory.json')
        self.HISTORY_FILE = os.path.normpath(os.path.join(sublime.packages_path(), history_path))
        self.USE_MONOSPACE = self.__ensure_setting(app_settings, 'monospace_font', False)
        self.DISPLAY_TIMESTAMPS = self.__ensure_setting(app_settings, 'display_timestamps', True)
        self.TIMESTAMP_FORMAT = self.__ensure_setting(app_settings, 'timestamp_format', default_date_format)
        self.TIMESTAMP_MODE = self.__ensure_setting(app_settings, 'timestamp_mode', 'history_access')
        self.PRETTIFY_HISTORY = self.__ensure_setting(app_settings, 'prettify_history', False)
        self.INDENT_SIZE = 4

        try:
            self.get_timestamp()
        except Exception:
            self.TIMESTAMP_FORMAT = default_date_format

        # Ignore the file preview setting for ST2
        self.SHOW_FILE_PREVIEW = False if is_ST2 else self.__ensure_setting(app_settings, 'show_file_preview', True)

        if not settings_exist:
            print('[FileHistory] Unable to find the settings file "%s".  A default settings file has been created for you.' % (self.SETTINGS_FILE))
            sublime.save_settings(self.SETTINGS_FILE)

    def get_timestamp(self, filename=None):
        if filename and os.path.exists(filename) :
            timestamp = time.strftime(self.TIMESTAMP_FORMAT, time.localtime(os.path.getmtime(filename)) )
        else:
            timestamp = time.strftime(self.TIMESTAMP_FORMAT)
        return timestamp

    def get_history_timestamp(self, history_entry):
        filepath = history_entry['filename']
        if 'timestamp' in history_entry and self.TIMESTAMP_MODE == 'history_access':
            action = history_entry['action'] if 'action' in history_entry else 'accessed'
            timestamp = history_entry['timestamp']
        else:
            action = 'modified'
            timestamp = self.get_timestamp(filepath)
        return (action, timestamp)

    def __ensure_setting(self, settings, key, default_value):
        value = default_value
        if settings.has(key):
            value = settings.get(key)
            self.debug('FileHistory setting "%s" = "%s"' % (key, value))
        else:
            self.debug('FileHistory setting "%s" not found.  Using the default value of "%s"' % (key, default_value))
            settings.set(key, default_value)
        return value

    def debug(self, text):
        """Helper method for "logging" to the console."""
        if self.PRINT_DEBUG:
            print('[FileHistory] ' + text)

    def get_current_project_key(self):
        m = hashlib.md5()
        for path in sublime.active_window().folders():
            m.update(path.encode('utf-8'))
        project_key = m.hexdigest()

        # Try to use project_file_name (available in ST3 build 3014)
        # Note: Although it would be more appropriate, the name of the workspace is not available
        if hasattr(sublime.active_window(), 'project_file_name'):
            project_filename = sublime.active_window().project_file_name()
            if not project_filename:
                return project_key

            # migrate the history entry based on the "old" project key (if it exists)
            if project_key in self.history:
                self.history[project_filename] = self.history[project_key]
                del(self.history[project_key])

            # use the new project key
            project_key = project_filename

        return project_key

    def __load_history(self):
        self.history = {}

        self.debug('Loading the history from file ' + self.HISTORY_FILE)
        if not os.path.exists(self.HISTORY_FILE):
            return

        f = open(self.HISTORY_FILE, 'r')
        try:
            updated_history = json.load(f)
        finally:
            f.close()

        self.history = updated_history

    def __save_history(self):
        self.debug('Saving the history to file ' + self.HISTORY_FILE)
        f = open(self.HISTORY_FILE, mode='w+')
        try:
            history_indentation = self.INDENT_SIZE if self.PRETTIFY_HISTORY else None

            json.dump(self.history, f, indent=history_indentation)

            f.flush()
        finally:
            f.close()

    def get_history(self, current_project_only=True):
        """Return the requested history (global or project-specific): closed files followed by opened files"""
        # Make sure the history is loaded
        if len(self.history) == 0:
            self.__load_history()

        # Load the requested history (global or project-specific)
{'fraction_non_alphanumeric': 0.08338868425819697, 'fraction_numerical': 0.0038645009359338203, 'mean_word_length': 3.125031133250311, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '603874', 'n_tokens_mistral': 4709, 'n_tokens_neox': 4558, 'n_words': 1370}