[
  {
    "path": ".gitignore",
    "content": "/temp\n*.pyc\n*.psd\nFuzzyFilePath.sublime-workspace\nFuzzyFilePath.sublime-project\n"
  },
  {
    "path": ".npmignore",
    "content": "/test\n*.gif"
  },
  {
    "path": "CHANGES.md",
    "content": "19/04/03\n\n- Update triggers for sass, js and ts\n\n18/05/06\n\n- Add `additional_scopes` list for custom scope extension\n- Update edit_settings command\n- Add json as valid js extension\n\n17/04/01\n\n- fixed an error while post-replacing inserted filepath, which exited with a broken result\n\nCHANGES\n\n- improved goto-file command by quering a simplified path (webpack ~)\n- improved goto-file command to open uncached files if filepath does exist\n- filecache worker now includes subfolders defined as project-folder, even if exclude_folders does match them\n\n\n16/09/10\n\n- support for multiple folders\n- bugfixes\n- major code simplifications\n\n\n15/06/07\n\n- fixed project settings to be added on top of default settings\n- fixed update settings for modified user settings\n- fixed an issue on windows where paths were not inserted relative\n\n\n15/03\n\n- major refactoring and bugfixes\n\nFEATURES\n\n- open path under cursor\n- show popup containing context evaluation information, required to setup triggers (requires Sublime Text build 3073)\n- set project specifiv project_directory via command palette\n- after completion insertion, move to beginning of next word\n- support multiple opened projects (in separate windows)\n\nCHANGES\n\n- improve update of caching\n\t- update cache on project change\n\t- update cache window focus\n\t- add command to rebiuld cache manually\n\t- update cache if a new file is saved\n- improve replacement of current path fragments\n- display of file path suggestions will not separate file extension. this allows querying filetype\n\n\n14/11/23\n\nBREAKING CHANGES\n\n- **change** `exclude_folders` items to be matched as regex\n- **remove** `extensionsToSuggest`, now being retrieved from scope settings\n- **remove** shortcut `super+ctrl+space`\n- **remove** option `auto_trigger`\n- **remove** option `insertExtension`. Should now be done by `replace_on_insert`\n- **change** and extend default scope rules\n- **change** absolute paths to start with \"/name\"\n- **change** path prefix (./, ../, /) now overwrites scope settings\n\nCHANGES\n\n- extend insert\\_path command (shortcut) with base\\_directory and extensions\n- add support for base_directory\n- extend scope-rules by tagName, style and prefix\n- analyse context of cursor to retrieve tagName, style and prefix\n- improve retrieval of query in view\n- fix path replacement for files being in root\n- stability improvements\n- refactorings\n\n14/11/02\n\n- fix bug in instant completions (missing string-cleanup method)\n- fix trigger to update cached files\n- remove bugged merge of exlude\\_folder\\_patterns and exlude\\_folders. Thus exlude\\_folders are always applied\n- fix bug in updating cache (triggered for invalid file extensions)\n- refactorings\n\n14/10/03\n\n- extend insert\\_path command by replace\\_on\\_insert\n- add FuzzyFilePath Package Settings to Sublime Text Menu\n- setting replace\\_on\\_insert\n- setting disable\\_keymap\\_actions\n- setting disable_autocompletions\n- add integration tests"
  },
  {
    "path": "Default (Linux).sublime-keymap",
    "content": "[\n    {\n        \"keys\": [\"ctrl+alt+space\"],\n        \"command\": \"insert_path\",\n        \"args\": {\n            \"type\": \"relative\"\n        }\n    },\n\n    {\n        \"keys\": [\"ctrl+shift+space\"],\n        \"command\": \"insert_path\",\n        \"args\": {\n            \"type\": \"absolute\"\n        }\n    },\n\n    {\n        \"keys\": [\"alt+enter\"],\n        \"command\": \"ffp_goto_file\"\n    }\n]"
  },
  {
    "path": "Default (OSX).sublime-keymap",
    "content": "[\n    {\n        \"keys\": [\"ctrl+alt+space\"],\n        \"command\": \"insert_path\",\n        \"args\": {\n            \"type\": \"relative\"\n        }\n    },\n\n    {\n        \"keys\": [\"ctrl+shift+space\"],\n        \"command\": \"insert_path\",\n        \"args\": {\n            \"type\": \"absolute\"\n        }\n    },\n\n    {\n        \"keys\": [\"alt+enter\"],\n        \"command\": \"ffp_goto_file\"\n    }\n]"
  },
  {
    "path": "Default (Windows).sublime-keymap",
    "content": "[\n    {\n        \"keys\": [\"ctrl+alt+space\"],\n        \"command\": \"insert_path\",\n        \"args\": {\n            \"type\": \"relative\"\n        }\n    },\n\n    {\n        \"keys\": [\"ctrl+shift+space\"],\n        \"command\": \"insert_path\",\n        \"args\": {\n            \"type\": \"absolute\"\n        }\n    },\n\n    {\n        \"keys\": [\"alt+enter\"],\n        \"command\": \"ffp_goto_file\"\n    }\n]"
  },
  {
    "path": "Default.sublime-commands",
    "content": "[\n\t{ \"caption\": \"FuzzyFilePath: Rebuild cache\", \"command\": \"ffp_update_cache\" },\n\t{ \"caption\": \"FuzzyFilePath: Show info\", \"command\": \"ffp_show_info\" },\n\t{ \"caption\": \"FuzzyFilePath: Set project directory\", \"command\": \"ffp_set_project_directory\" },\n\t{ \"caption\": \"FuzzyFilePath: Show current settings\", \"command\": \"ffp_show_current_settings\" },\n\t{ \"caption\": \"FuzzyFilePath: Goto File\", \"command\": \"ffp_goto_file\"},\n\t{ \"caption\": \"FuzzyFilePath: Insert Relative Path\", \"command\": \"insert_path\", \"args\": {\n          \"type\": \"relative\"\n        }\n    },\n\t{ \"caption\": \"FuzzyFilePath: Insert Absolute Path\", \"command\": \"insert_path\", \"args\": {\n          \"type\": \"absolute\"\n        }\n    }\n]\n"
  },
  {
    "path": "Default.sublime-keymap",
    "content": "[\n    {\n        \"keys\": [\"ctrl+alt+space\"],\n        \"command\": \"insert_path\",\n        \"args\": {\n            \"type\": \"relative\"\n        }\n    },\n\n    {\n        \"keys\": [\"ctrl+shift+space\"],\n        \"command\": \"insert_path\",\n        \"args\": {\n            \"type\": \"absolute\"\n        }\n    },\n\n    {\n        \"keys\": [\"alt+enter\"],\n        \"command\": \"ffp_goto_file\"\n    }\n]"
  },
  {
    "path": "FuzzyFilePath.md",
    "content": "# FuzzyFilePath - autocomplete filepaths\n\n@version 0.6.1\n@author Sascha Goldhofer <post@saschagoldhofer.de>\n\n## tasks\n\n- possibly create file caches of all project directories simultaneously\n- Cleanup @TODO flags\n- suddenly Testrunner causes plugin host to expire\n\n### bugs\n\n    - Initial opened view may be falsely recognised as \"not within project\"\n\n### release\n\n    - WIP: Test changes\n    - DONE: Test windows paths\n    - Add documentation for multiple folder support\n\n### performance\n\n    - searching in large folders, where the query matches a folder containing many files, are very slow. i.e. searching for \"node_modules/path/to/package\" is much slower than searching for \"path/to/package\" (filecount 10k+)\n        - current workaround fast_query option in default settings\n        - so far the regex can not be improved to be faster and still return the same results, the first step should be to exclude unused folders which (may require an option for folder whitelisting) i.e. \"node_modules/(?!szig).*\"\n\n### features\n\n    - growing list of triggers is getting unmaintainable\n        - Probably group by main-scope in object for faster retrieval and namespacing\n        - create an object with ids for a specific trigger and use a list of ids for triggers to use (selecting object)\n        - further support trigger objects in the scope-list\n    - add custom triggers without overriding the default scopes\n\n### ideas\n\n    - maybe support different triggers based on inserted filepath? (i.e. absolute if matches node_mod...)\n    - possibly send ffp states to serve for better debugging\n"
  },
  {
    "path": "FuzzyFilePath.sublime-settings",
    "content": "{\n\t// set project_directory relative to sublime project directory\n\t\"project_directory\": \"\",\n\t// base directory for paths relative to project_directory. Used if scope-trigger contains \"base_directory\": true\n\t// watch out for side effects with project directory (if base_directory is not within project_directory)\n\t\"base_directory\": false,\n\t// disable automatic path completions\n\t\"disable_autocompletions\": false,\n\t// disable keymaps\n\t\"disable_keymap_actions\": false,\n\t// ignore folders that match following regular expressions\n\t\"exclude_folders\": [\"node_modules\", \"bower_components\"],\n\t// logs scope evaluation to console to debug configuration\n\t\"log\": false,\n\t// LIST OF TRIGGERS FOR AUTO COMPLETION\n\t// - setting \"scopes\" in user settings will override all other scopes.\n\t// - create \"additional_scopes\" in user settings to add more scopes instead.\n\t//   they will be added after the existing scopes.\n\t// - triggers are evaluated in given order. First match wins\n\t// - specialize trigger by 1. scope 2. prefix, style and/or tagname\n\t// - command (insert_path) still requires a trigger\n\t\"scopes\": [\n\t\t// MINIMAL AUTO TRIGGER - NOT RECOMMENDED\n\t\t// will always query files for auto completions\n\t\t// {\n\t\t// \t\"scope\": \".\",\n\t\t// \t\"extensions\": [\"*\"],\n\t\t// \t\"auto\": true\n\t\t// },\n\t\t/*\n\t\t\tJavascript\n\t\t */\n\t\t{\n\t\t\t// js: require()\n\t\t\t\"scope\": \"\\\\.(js|ts)\\\\s\",\t\t\t\t\t// 1. ignore if scope at cursor does not match expression (super+alt+p)\n\t\t\t\"prefix\": [\"require\", \"define\"],\t\t// 2. trigger only if: require(<cursor>, define([<cursor>\n\t\t\t// if 1 & 2 are true:\n\t\t\t\"auto\": true,\n\t\t\t\"relative\": true,\t\t\t\t\t\t// insert absolute\n\t\t\t\"base_directory\": false,\t\t\t\t// insert absolute from the set base directory (above)\n\t\t\t\"extensions\": [\"ts\", \"js\", \"html\", \"scss\", \"json\"],\t// show only .js, .html and .scss files\n\t\t\t\"replace_on_insert\": [\n\t\t\t\t[\"\\\\.(js|ts)$\", \"\"],\t\t\t\t\t\t// after insertion, remove .js from path\n\t\t\t\t[\"([^.])\\\\/index$\", \"\\\\1\"],\t\t\t// nodejs will load index.js by default => also remove index\n\t\t\t\t// remove path to module-folder, since our build tool resolves this path automatically\n\t\t\t\t[\"^([\\\\/.]+)?\\\\/(bower_components|node_modules)\\\\/\", \"\"]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t// es6 import from/import \"*\"\n\t\t\t\"scope\": \"string.*\\\\.(js|ts)\\\\s\",\n\n\t\t\t\"auto\": true,\n\t\t\t\"relative\": true,\n\t\t\t\"base_directory\": false,\n\t\t\t\"prefix\": [\"from\", \"import\"],\n\t\t\t\"extensions\": [\"ts\", \"js\", \"html\", \"scss\", \"json\"],\n\t\t\t\"replace_on_insert\": [\n\t\t\t\t[\"\\\\.(js|ts)$\", \"\"],\n\t\t\t\t[\"([^.])\\\\/index$\", \"\\\\1\"],\n\t\t\t\t[\"^([\\\\/.]+)?\\\\/(bower_components|node_modules)\\\\/\", \"\"]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t// Typescript // <reference path=\"\" />\n\t\t\t\"scope\": \"source\\\\.ts\\\\scomment\",\n\n\t\t\t\"auto\": true,\n\t\t\t\"relative\": true,\n\t\t\t\"base_directory\": false,\n\t\t\t\"prefix\": [\"path\"],\n\t\t\t\"tagName\": [\"reference\"],\n\t\t\t\"extensions\": [\"ts\", \"js\"],\n\t\t\t\"replace_on_insert\": []\n\t\t},\n\t\t{\n\t\t\t// Typescript es6 import from/import \"*\"\n\t\t\t\"scope\": \"string[^\\\\s]*\\\\.ts\\\\s\",\n\n\t\t\t\"auto\": true,\n\t\t\t\"relative\": true,\n\t\t\t\"base_directory\": false,\n\t\t\t\"prefix\": [\"from\", \"import\"],\n\t\t\t\"extensions\": [\"js\", \"ts\"],\n\t\t\t\"replace_on_insert\": [\n\t\t\t\t[\"(\\\\.js|\\\\.ts)$\", \"\"],\n\t\t\t\t[\"([^.])\\\\/index$\", \"\\\\1\"],\n\t\t\t\t[\"^(\\\\/.+)?\\\\/(bower_components|node_modules)\\\\/\", \"\"]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t// Coffeescript es6 import from/import \"*\" and require\n\t\t\t\"scope\": \"string[^\\\\s]*\\\\.coffee\\\\s\",\n\n\t\t\t\"auto\": true,\n\t\t\t\"relative\": true,\n\t\t\t\"base_directory\": false,\n\t\t\t\"prefix\": [\"from\", \"import\", \"require\"],\n\t\t\t\"extensions\": [\"coffee\"],\n\t\t\t\"replace_on_insert\": []\n\t\t},\n\t\t{\n\t\t    // js - *.src = \"\"\n\t\t    \"scope\": \"source\\\\.js.*string\",\n\t\t    \"prefix\": [\"src\"],\n\n\t\t    \"auto\": true,\n\t\t    \"base_directory\": true,\n\t\t    \"extensions\": [\"png\", \"gif\", \"jpg\", \"jpeg\"],\n\t\t    \"relative\": true\n\t\t},\n\t\t/*\n\t\t\tCSS\n\t\t */\n\t\t{\n \t\t\t// import \"\"\n\t\t\t\"scope\": \"source\\\\.(css|sass|scss|less)\",\n\n\t\t\t\"auto\": true,\n\t\t\t\"prefix\": [\"import\"],\n\t\t\t\"relative\": true,\n\t\t\t\"extensions\": [\"css\", \"sass\", \"scss\", \"less\"],\n\t\t\t\"replace_on_insert\": [\n\t\t\t\t[\"\\\\.(css|scss)$\", \"\"],\n\t\t\t\t[\"^(\\\\/.+)?\\\\/(bower_components|node_modules)\\\\/\", \"\"]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t    // *: url()\n\t\t    \"scope\": \"source\\\\.(css|sass|less)\",\n\t\t    \"prefix\": [\"url\"],\n\n\t\t    \"auto\": true,\n\t\t    \"relative\": true,\n\t\t    \"base_directory\": true,\n\t\t    \"extensions\": [\"png\", \"gif\", \"jpeg\", \"jpg\", \"woff\", \"ttf\", \"svg\", \"otf\"],\n\t\t    \"replace_on_insert\": [\n\t\t    \t[\"\\\\.(css|scss)$\", \"\"],\n\t\t    \t[\"^(\\\\/.+)?\\\\/(bower_components|node_modules)\\\\/\", \"\"]\n\t\t    ]\n\t\t},\n\t\t/*\n\t\t\tHTML\n\t\t */\n\t\t{\n\t\t    // html - <script src=\"..\">\n\t\t    \"scope\": \"string\\\\.quote.*\\\\.html\",\n\t\t    \"prefix\": [\"src\"],\n\t\t    \"tagName\": [\"script\"],\n\t\t    \"auto\": true,\n\t\t    \"base_directory\": false,\n\t\t    \"extensions\": [\"js\"],\n\t\t    \"relative\": true\n\t\t},\n\t\t{\n\t\t\t// string.quote.double.html\n\t\t    // html - <img src=\"\" and <script src=\"\" and <* style=\"*: url()\n\t\t    \"scope\": \"text\\\\.html\",\n\t\t    // <* src\n\t\t    \"prefix\": [\"src\", \"url\"],\n\n\t\t    \"auto\": true,\n\t\t    \"base_directory\": true,\n\t\t    \"extensions\": [\"js\", \"png\", \"gif\", \"jpeg\", \"jpg\", \"svg\"],\n\t\t    \"relative\": true\n\t\t},\n\t\t{\n\t\t    // html - <link href=\"\"\n\t\t    \"scope\": \"meta\\\\.tag.*string.*\\\\.html\",\n\t\t    // \"scope\": \"html\",\n\t\t    // <link href\n\t\t    \"tagName\": [\"link\"],\n\t\t    \"prefix\": [\"href\"],\n\n\t\t    \"auto\": true,\n\t\t    \"extensions\": [\"css\"],\n\t\t    \"relative\": true\n\t\t},\n\t\t/*\n\t\t\tOTHER\n\t\t */\n\t\t{\n\t\t\t// glsl\n\t\t\t\"scope\": \"source\\\\.glsl\",\n\t\t\t\"prefix\": [\"import\"],\n\t\t\t\"auto\": true,\n\t\t\t\"extensions\": [\"glsl\"],\n\t\t\t\"relative\": true,\n\t\t\t\"replace_on_insert\": [\n\t\t\t\t[\"\\\\.glsl$\", \"\"]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t// handlebars partial\n\t\t\t\"scope\": \"\\\\.handlebars\\\\s.*inline\",\n\n\t\t\t\"auto\": true,\n\t\t\t\"relative\": true,\n\t\t\t\"base_directory\": false,\n\t\t\t\"extensions\": [\"hbs\", \"handlebars\"],\n\t\t\t\"replace_on_insert\": [\n\t\t\t\t[\"^(\\\\/.+)?\\\\/(bower_components|node_modules)\\\\/\", \"\"]\n\t\t\t]\n\t\t},\n\t    {\n\t\t\t// python\n\t\t\t\"scope\": \"\\\\.python\",\n\t\t\t\"extensions\": [\"py\"],\n\t\t\t\"auto\": true,\n\t\t\t\"prefix\": [\"from\"],\n\t\t\t\"relative\": false,\n\t\t\t\"replace_on_insert\": [\n\t\t\t\t[\"^\\\\/\", \"\"],\n\t\t\t\t[\"\\\\/\", \".\"],\n\t\t\t\t// ! remove\n\t\t\t\t[\"^(.*)\\\\.py$\", \"FuzzyFilePath.\\\\1\"]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t// php\n\t\t\t\"auto\": true,\n\t\t\t\"scope\": \"\\\\.php\",\n\t\t\t\"extensions\": [\"php\"],\n\t\t\t\"prefix\": [\"require_once\", \"include\"],\n\t\t\t\"relative\": false,\n\t\t\t\"replace_on_insert\": [[\"^\\\\/\", \"\"]]\n\t\t},\n\t\t{\n\t\t    // latex \\input{}\n\t\t    \"scope\": \"meta\\\\.include\\\\.latex\",\n\t\t    \"prefix\": [\"input\"],\n\n\t\t    \"auto\": true,\n\t\t    \"relative\": true,\n\t\t    \"base_directory\": false,\n\t\t    \"extensions\": [\"png\", \"gif\", \"jpeg\", \"jpg\", \"svg\"]\n\t\t},\n\t\t{\n\t\t    // * - any source file (by command only)\n\t\t    \"scope\": \"source\",\n\n\t\t    \"auto\": false,\n\t\t    \"extensions\": [\"js\", \"html\", \"css\", \"scss\", \"less\", \"png\", \"gif\", \"jpeg\", \"jpg\", \"svg\"],\n\t\t    \"relative\": true\n\t\t},\n\t],\n\t\"additional_scopes\": [\n\t\t{\n\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                     DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                             Version 2, December 2004\n\n          Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>\n\n          Everyone is permitted to copy and distribute verbatim or modified\n          copies of this license document, and changing it is allowed as long\n          as the name is changed.\n\n                     DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n            TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n           0. You just DO WHAT THE FUCK YOU WANT TO.\n"
  },
  {
    "path": "Main.sublime-menu",
    "content": "[\n    {\n        \"caption\": \"Preferences\",\n        \"mnemonic\": \"n\",\n        \"id\": \"preferences\",\n        \"children\":\n        [\n            {\n                \"caption\": \"Package Settings\",\n                \"mnemonic\": \"P\",\n                \"id\": \"package-settings\",\n                \"children\":\n                [\n                    {\n                        \"caption\": \"FuzzyFilePath\",\n                        \"children\":\n                        [\n                            {\n                                \"caption\": \"Settings\",\n                                \"command\": \"edit_settings\", \"args\":\n                                {\n                                    \"base_file\": \"${packages}/FuzzyFilePath/FuzzyFilePath.sublime-settings\",\n                                    \"default\": \"{\\n\\t$0\\n}\\n\"\n                                }\n                            },\n                            { \"caption\": \"-\" },\n                            {\n                                \"caption\": \"Key Bindings\",\n                                \"command\": \"edit_settings\", \"args\":\n                                {\n                                    \"base_file\": \"${packages}/FuzzyFilePath/Default ($platform).sublime-keymap\",\n                                    \"default\": \"[\\n\\t$0\\n]\\n\"\n                                }\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    }\n]\n"
  },
  {
    "path": "ProjectListener.py",
    "content": "import sublime\nimport sublime_plugin\n\nimport FuzzyFilePath.controller as controller\n\n\nID = \"ProjectListener\"\n\n\nclass ProjectListener(sublime_plugin.EventListener):\n    \"\"\" listens on window changes, delegating events to controller \"\"\"\n\n    previous_project = None\n    previous_window = None\n\n    def on_activated(self, view):\n        window = view.window()\n        if not window:\n            return False\n\n        project_id = get_project_id(window)\n\n        if self.previous_project != project_id:\n            if self.previous_project is not None:\n                self.on_project_activated(view)\n            self.previous_project = project_id\n\n        elif self.previous_window is not sublime.active_window().id():\n            self.previous_window = sublime.active_window().id()\n            self.on_window_activated(view)\n\n    # project has been refocused\n    def on_window_activated(self, view):\n        controller.on_project_focus(view.window())\n\n    # another (possible) project has been opened/focused\n    def on_project_activated(self, view):\n        window = view.window()\n        if not window:\n            return False\n        controller.on_project_activated(window)\n\n\ndef get_project_id(window):\n    project_name = window.project_file_name()\n    if project_name:\n        return project_name\n    return window.id()\n"
  },
  {
    "path": "QueryCompletionListener.py",
    "content": "import re\nimport sublime_plugin\nimport FuzzyFilePath.controller as controller\nimport FuzzyFilePath.common.selection as Selection\nimport FuzzyFilePath.expression as Context\nimport FuzzyFilePath.common.settings as settings\nfrom FuzzyFilePath.common.verbose import verbose\nimport FuzzyFilePath.current_state as state\n\n\nID = \"QueryCompletionListener\"\n\n\nclass QueryCompletionListener(sublime_plugin.EventListener):\n\n    # tracks on_post_insert_completion\n    track_insert = {\n        \"active\": False,\n        \"start_line\": \"\",\n    }\n    post_remove = \"\"\n\n    def on_query_completions(self, view, prefix, locations):\n        if settings.get(\"disable_autocompletion\") and not Query.by_command():\n            return False\n\n        if self.track_insert[\"active\"] is False:\n            self.start_tracking(view)\n\n        completions = controller.get_filepath_completions(view)\n        if completions is not False:\n            return completions\n\n        self.finish_tracking(view)\n        return False\n\n    #custom\n    def on_post_insert_completion(self, view, command_name):\n        controller.on_query_completion_inserted(view, self.post_remove)\n\n    #custom\n    def on_query_abort(self):\n        controller.on_query_completion_aborted()\n\n    # track post insert insertion\n    def start_tracking(self, view, command_name=None):\n        if not state.is_valid():\n            return\n\n        self.track_insert[\"active\"] = True\n        self.track_insert[\"start_line\"] = Selection.get_line(view)\n        \"\"\"\n            - sublime inserts completions by replacing the current word\n            - this results in wrong path insertions if the query contains word_separators like slashes\n            - thus the path until current word has to be removed after insertion\n            - ... and possibly afterwards\n        \"\"\"\n        context = Context.get_context(view)\n        needle = context.get(\"needle\")\n        word = re.escape(Selection.get_word(view))\n        self.post_remove = re.sub(word + \"$\", \"\", needle)\n        verbose(ID, \"start tracking\", self.post_remove)\n\n    def finish_tracking(self, view, command_name=None):\n        self.track_insert[\"active\"] = False\n        verbose(ID, \"finish tracking\")\n\n    def abort_tracking(self):\n        self.track_insert[\"active\"] = False\n        verbose(ID, \"abort tracking\")\n\n    def on_text_command(self, view, command_name, args):\n        # check if a completion may be inserted\n        if command_name in settings.get(\"trigger_action\", []) or command_name in settings.get(\"insert_action\", []):\n            self.start_tracking(view, command_name)\n        elif command_name == \"hide_auto_complete\":\n            self.on_query_abort()\n            self.abort_tracking()\n\n    # check if a completion is inserted and trigger on_post_insert_completion\n    def on_post_text_command(self, view, command_name, args):\n        if len( view.sel() ) < 1:\n            return\n        current_line = Selection.get_line(view)\n        command_trigger = command_name in settings.get(\"trigger_action\", []) and self.track_insert[\"start_line\"] != current_line\n        if command_trigger or command_name in settings.get(\"insert_action\", []):\n            self.finish_tracking(view, command_name)\n            self.on_post_insert_completion(view, command_name)\n"
  },
  {
    "path": "README.md",
    "content": "# [FuzzyFilePath](https://github.com/sagold/FuzzyFilePath)\n\n__Sublime Text Plugin__\n\nFuzzy search and insert filenames inside your current project directory. Highly customizable.\n\n<img src=\"https://raw.githubusercontent.com/sagold/FuzzyFilePath/develop/FuzzyFilePathDemo.gif\" />\n<br />\n<em style=\"display: block; text-align: right;\">Basic settings support Javascript, HTML, CSS, PHP and glsl, but may be\nadjusted for most languages</em>\n\n\n## <a name=\"installation\">Installation</a>\n\n\n### [Package Control](https://sublime.wbond.net/)\n\nAfter [Package Control installation](https://sublime.wbond.net/installation), restart Sublime Text. Use the Command Palette <kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> (OS X) or <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> (Linux/Windows) and search for *Package Control: Install Package*. Wait until Package Control downloaded the latest package list and search for *FuzzyFilePath*.\n\n\n### [github](https://github.com/sagold/FuzzyFilePath.git)\n\nin `<SublimeConfig>/Packages/` call: `git clone https://github.com/sagold/FuzzyFilePath.git`\n\n__Sublime Text 2__\n\nin `<SublimeConfig>/Packages/FuzzyFilePath/` switch to Sublime Text 2 Branch with: `git checkout st2`\n\nAttention: Sublime Text 2 will no longer be supported.\n\n\n\n## <a name=\"usage\">Usage</a>\n\n**Filepaths will be suggested if there is a matching\n[trigger](https://github.com/sagold/FuzzyFilePath/wiki/Settings#trigger) for the current context** and its property\n_auto_ is set to _true_. For a matching [trigger](https://github.com/sagold/FuzzyFilePath/wiki/Settings#trigger),\nfilepath completions may be forced (ignoring _auto_ property) by the following shorcuts:\n\n- <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>Space</kbd> inserts filepaths relative, overriding possible settings\n- <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Space</kbd> inserts filepaths absolute, overriding possible settings\n\nThe current string may modify the suggested filepaths by the following rules:\n\n- `word` suggests all matching files by the type (relative or absolute) as specified in the matched rule\n- `./` suggests matching files within the current directory and inserts selection relative\n- `../` suggests all matching files and inserts selection relative\n- `/folder` suggests all matching files and insert selection absolute\n\nFuzzyFilePath is disabled for single files or files outside the opened folder.\n\n\n### Open File\n\nUse <kbd>Alt</kbd>+<kbd>Enter</kbd> to open the file under cursor\n\n\n### Configure Completion Panel\n\nEnsure you have [autocompletion activated for Sublime](https://www.granneman.com/webdev/editors/sublime-text/top-features-of-sublime-text/auto-completion-in-sublime-text/). In those cases, where the autocompletion panel is still\nnot opened (for any type of completions), you may extend `auto_complete_triggers` to add special rules for the\ncompletion panel to show up. i.e. enabling autocompletion for latex `\\input{\"path/to/asset\"}`, you could add:\n\n```json\n\"auto_complete_triggers\":\n[\n\t{\n\t\t\"characters\": \"abcdefghijklmnopqrstuvwxyz\",\n\t\t\"selector\": \"text.tex.latex\"\n\t}\n]\n```\n\nor enabling html completion for `<script src=\"path/to/script\">`\n\n```json\n\"auto_complete_triggers\":\n[\n\t{\n\t\t\"characters\": \"abcdefghijklmnopqrstuvwxyz\",\n\t\t\"selector\": \"string.quoted.double.html\"\n\t}\n]\n```\n\n\n### Special Characters\n\nIf your projects contains filenames with special characters, consider modifying Sublime Texts `word_separators`.\n\ni.e. in AngularJs filenames may start with `$`. In _Sublime Text | Preferences | Settings - User_ redeclare word\nseparators, removing `$`:\n```js\n\t\"word_separators\": \"./\\\\()\\\"'-:,.;<>~!@#%^&*|+=[]{}`~?\"\n```\n\n\n## Customization\n\nFor further details about troubleshooting, customization, settings and keybindings please\n[refer to the Wiki](https://github.com/sagold/FuzzyFilePath/wiki)\n\nTrying to integrate other languages? See the\n[auto complete Python package tutorial](https://github.com/sagold/FuzzyFilePath/wiki/Tutorial:-Add-support-for-python-packages)\n\n\n#### Related Plugins\n\n##### [AutoFileName](https://github.com/BoundInCode/AutoFileName)\n\n- uses file discovery based on current directory instead of fuzzy search\n- adds properties for images in autocompletion description\n\n\n\n\n\n"
  },
  {
    "path": "TestRunner.py",
    "content": "\"\"\"\n\tFuzzyFilePath unit and integration test runner\n\n\tUsage:\n\t\tBind a shortcut like\n\t\t{ \"keys\": [\"super+y\"], \"command\": \"ffp_test_runner\" }\n\t\tto execute tests. Make sure a default view is selected (not console)\n\n\"\"\"\nimport sublime\nimport sublime_plugin\nimport traceback\n\nfrom FuzzyFilePath.test.unit.tests import tests as unitTests\nfrom FuzzyFilePath.test.integration.tests import tests as integrationTests\nfrom FuzzyFilePath.test.integration.tools import ViewHelper\n\nLINE = \"----------------\"\n\n\nclass FfpTestRunner(sublime_plugin.TextCommand):\n\n\ttools = None,\n\n\tdef console_blur(self):\n\t\twindow = sublime.active_window()\n\t\twindow.focus_group(0)\n\t\twindow.focus_view(window.active_view())\n\n\tdef console_open(self):\n\t\twindow = sublime.active_window()\n\t\twindow.run_command(\"show_panel\", {\"panel\": \"console\", \"toggle\": False})\n\t\tself.console_blur()\n\n\tdef after(self):\n\t\tself.tools.move_cursor(0, 0)\n\t\t# delete text\n\t\tselection = self.tools.view.sel()[0]\n\t\tposition = selection.begin()\n\t\tregion = self.tools.view.line(position)\n\t\tself.tools.view.erase(self.tools.edit, region)\n\n\tdef setUp(self, edit):\n\t\tself.console_blur()\n\t\twindow = sublime.active_window()\n\t\tself.tools = ViewHelper(window, window.new_file(), edit)\n\n\tdef closeView(self): {\n\t\tself.tools.view.close()\n\t}\n\n\tdef tearDown(self, failed_tests, total_tests):\n\t\t# self.tools.view.close()\n\t\tprint(LINE)\n\t\tif failed_tests > 0:\n\t\t\tnotice = \"{0} of {1} tests failed\".format(failed_tests, total_tests)\n\t\t\tprint(notice)\n\t\t\tsublime.status_message(\"FFP Intergration: \" + notice)\n\t\telse:\n\t\t\tnotice = \"{0} tests successful\".format(total_tests)\n\t\t\tprint(notice)\n\t\t\tsublime.status_message(\"FFP Intergration: \" + notice)\n\n\tdef run(self, edit):\n\t\tprint(\"\\nFuzzyFilePath Integration Tests\")\n\t\ttotal_tests = 0\n\t\tfailed_tests = 0\n\n\t\t# run unit tests\n\t\tfor testCase in unitTests:\n\t\t\ttotal_tests += testCase.length\n\t\t\tfor should in testCase.tests:\n\t\t\t\ttest = getattr(testCase, should)\n\n\t\t\t\tif \"before_each\" in dir(testCase):\n\t\t\t\t\ttestCase.before_each()\n\n\t\t\t\ttry:\n\t\t\t\t\ttest()\n\t\t\t\texcept:\n\t\t\t\t\tfailed_tests += 1\n\t\t\t\t\tprint(\"\\n\" + testCase.name + \" \" + should + \":\")\n\t\t\t\t\tprint(LINE)\n\t\t\t\t\ttraceback.print_exc()\n\t\t\t\t\tprint(LINE)\n\t\t\t\t\tprint()\n\t\t\t\t\tpass\n\n\t\t\t\tif \"after_each\" in dir(testCase):\n\t\t\t\t\ttestCase.after_each()\n\n\t\t# run integration tests\n\t\tfor testCase in integrationTests:\n\t\t\tself.setUp(edit)\n\t\t\ttotal_tests += testCase.length\n\n\t\t\tfor should in testCase.tests:\n\t\t\t\ttest = getattr(testCase, should)\n\n\t\t\t\tif \"before_each\" in dir(testCase):\n\t\t\t\t\ttestCase.before_each(self.tools)\n\n\t\t\t\ttry:\n\t\t\t\t\ttest(self.tools)\n\t\t\t\texcept:\n\t\t\t\t\tfailed_tests += 1\n\t\t\t\t\tprint(\"\\n\" + testCase.name + \" \" + should + \":\")\n\t\t\t\t\tprint(LINE)\n\t\t\t\t\ttraceback.print_exc()\n\t\t\t\t\tprint(LINE)\n\t\t\t\t\tprint()\n\t\t\t\t\tpass\n\n\t\t\t\tif \"after_each\" in dir(testCase):\n\t\t\t\t\ttestCase.after_each(self.tools)\n\n\t\t\t\tif not testCase.unit_test:\n\t\t\t\t\tself.after()\n\n\t\t\t\tself.closeView()\n\n\t\tself.tearDown(failed_tests, total_tests)\n\n\t\tif failed_tests > 0:\n\t\t\tself.console_open()\n"
  },
  {
    "path": "ViewListener.py",
    "content": "import re\nimport copy\nimport sublime_plugin\n\nimport FuzzyFilePath.controller as controller\nimport FuzzyFilePath.current_state as state\n\n\ntemp_views = []\n\n\ndef is_valid(view):\n    if view.file_name():\n        return True\n    if not view.id() in temp_views:\n        temp_views.append(view.id())\n    return False\n\n\nclass ViewListener(sublime_plugin.EventListener):\n    def on_post_save_async(self, view):\n        if view.id() in temp_views:\n            self.on_file_created(view)\n            self.on_activated(view)\n\n    def on_activated(self, view):\n        # view has gained focus\n        if is_valid(view):\n            controller.on_file_focus(view)\n\n    def on_file_created(self, view):\n        temp_views.remove(view.id())\n        controller.on_file_created()\n"
  },
  {
    "path": "command_goto_file.py",
    "content": "import os\nimport re\nimport sublime\nimport sublime_plugin\nimport FuzzyFilePath.expression as Context\nimport FuzzyFilePath.common.path as Path\nfrom FuzzyFilePath.common.verbose import log\nimport FuzzyFilePath.common.selection as Selection\nimport FuzzyFilePath.current_state as state\n\n\nID = \"GotoFile\"\n\n\nclass FfpGotoFileCommand(sublime_plugin.TextCommand):\n    \"\"\" open file under cursor \"\"\"\n    def run(self, edit):\n        current_directory = os.path.dirname(self.view.file_name())\n        context = Context.get_context(self.view)\n        if context.get(\"valid\") is False:\n            return log(ID, \"abort, no valid path given:\", context.get(\"needle\"))\n\n        path = context.get(\"needle\")\n        project_folder = state.get_project_directory()\n\n        if not (path and project_folder):\n            return log(ID, \"path or project invalid\", path, project_folder)\n\n        is_relative = Path.is_relative(path)\n        if is_relative:\n            path = Path.get_absolute_path(current_directory, path)\n            path = re.sub(project_folder, \"\", path)\n\n\n        path = re.sub(\"^[\\\\\\\\/\\.]\", \"\", path)\n        realpath = path\n        # cleanup string, in case there are special characters for a path\n        # e.g. webpack uses ~, which is not part of path\n        path = re.sub(\"[^A-Za-z0-9 \\-_\\\\\\\\/.%?#]*\", \"\", path)\n        files = state.find_file(path)\n\n        if len(files) == 0:\n            # it may be an uncached file, try to open it by using the real path\n            if self.filepath_exists(os.path.join(project_folder, realpath)):\n                return self.open_file(project_folder, realpath)\n            return log(ID, \"failed finding file\", path, \"it is not within the cache and not a realpath\")\n\n        if len(files) == 1:\n            self.open_file(project_folder, files[0])\n        else:\n            # if javascript, search for index.js\n            current_scope = self.view.scope_name(Selection.get_position(self.view))\n            if re.search(\"\\.js \", current_scope):\n                for file in files:\n                    if \"index.js\" in file:\n                        return self.open_file(project_folder, file)\n\n            self.files = files\n            self.project_folder = project_folder\n            self.view.show_popup_menu(files, self.select_file)\n\n    def filepath_exists(self, filepath):\n        return os.path.isfile(filepath)\n\n    def select_file(self, index):\n        self.open_file(self.project_folder, self.files[index])\n        self.files = None\n        self.project_folder = None\n\n    def open_file(self, project_folder, filepath):\n        path = os.path.join(project_folder, filepath)\n        sublime.active_window().open_file(path)\n\n"
  },
  {
    "path": "command_insert_path.py",
    "content": "import sublime_plugin\nimport FuzzyFilePath.common.settings as settings\nimport FuzzyFilePath.query as Query\n\n\nclass InsertPathCommand(sublime_plugin.TextCommand):\n    \"\"\" trigger customized autocomplete overriding auto settings \"\"\"\n    def run(self, edit, type=\"default\", base_directory=None, replace_on_insert=[], extensions=[]):\n        if settings.get(\"DISABLE_KEYMAP_ACTIONS\") is True:\n            return False\n\n        Query.override_trigger_setting(\"filepath_type\", type)\n        Query.override_trigger_setting(\"base_directory\", base_directory)\n\n        if len(replace_on_insert) > 0:\n            Query.override_trigger_setting(\"replace_on_insert\", replace_on_insert)\n        if len(extensions) > 0:\n            Query.override_trigger_setting(\"extensions\", extensions)\n\n        self.view.run_command('auto_complete', \"insert\")\n"
  },
  {
    "path": "command_rebuild_cache.py",
    "content": "import sublime\nimport sublime_plugin\nimport FuzzyFilePath.current_state as state\n\n\nclass FfpUpdateCacheCommand(sublime_plugin.TextCommand):\n    \"\"\" force update project-files cache \"\"\"\n    def run(self, edit):\n    \tfor folder in sublime.active_window().folders():\n        \tstate.rebuild_filecache(folder)\n"
  },
  {
    "path": "command_replace_region.py",
    "content": "import re\nimport sublime\nimport sublime_plugin\nimport FuzzyFilePath.common.settings as settings\n\n\nclass FfpReplaceRegionCommand(sublime_plugin.TextCommand):\n\n    # helper: replaces range with string\n    def run(self, edit, a, b, string, move_cursor=False):\n        if settings.get(\"DISABLE_KEYMAP_ACTIONS\") is True:\n            return False\n\n        self.view.replace(edit, sublime.Region(a, b), string)\n\n        if move_cursor and settings.get(\"POST_INSERT_MOVE_CHARACTERS\"):\n        \tself.move_skip(a + len(string))\n\n    def move_skip(self, point):\n    \tlength = 0\n    \tword_region = self.view.word(point)\n    \tline_region = self.view.line(point)\n    \tpost_region = sublime.Region(word_region.b, line_region.b)\n    \tpost = self.view.substr(post_region)\n    \tto_move = re.search(settings.get(\"POST_INSERT_MOVE_CHARACTERS\"), post)\n\n    \tif to_move:\n    \t\tlength = len(to_move.group(0))\n\n    \tself.move_cursor(point + length)\n\n    def move_cursor(self, point):\n    \tself.view.sel().clear()\n    \tself.view.sel().add(sublime.Region(point))\n    \tself.view.window().focus_view(self.view)\n    \tself.view.run_command('_enter_insert_mode') # vintageous\n"
  },
  {
    "path": "command_show_context.py",
    "content": "import sublime_plugin\n\nimport FuzzyFilePath.expression as Context\nimport FuzzyFilePath.common.selection as Selection\nfrom FuzzyFilePath.completion import resolve_trigger\nimport FuzzyFilePath.query as Query\n\n\nclass FfpShowContextCommand(sublime_plugin.TextCommand):\n\t\"\"\" displays the current context, required for debugging and trigger setup \"\"\"\n\n\tcontent = None\n\n\tdef run(self, edit):\n\t\tcontext = Context.get_context(self.view)\n\t\tcurrent_scope = self.view.scope_name(Selection.get_position(self.view))\n\t\ttrigger = resolve_trigger(self.view, Query)\n\t\tprint(\"FOUND TRIGGER\", trigger)\n\n\t\tself.content = \"<h4>FuzzyFilepath - context evaluation</h4>\"\n\t\tself.add(\"filepath valid\", context.get(\"valid_needle\"))\n\t\tself.add(\"context valid\", context.get(\"is_valid\"))\n\t\tself.content += \"<br>\"\n\t\tself.add(\"path\", context.get(\"needle\"))\n\t\tself.add(\"prefix\", context.get(\"prefix\"))\n\t\tself.add(\"property\", context.get(\"style\"))\n\t\tself.add(\"tag\", context.get(\"tagName\"))\n\t\tself.add(\"word\", context.get(\"word\"))\n\t\tself.content += \"<br>\"\n\t\tself.add(\"scope\", current_scope)\n\t\tself.content += \"<br>\"\n\t\tself.content += \"<h5>Trigger</h5>\"\n\t\tif trigger is False:\n\t\t\tself.content += \"no trigger could be found matching the given context\"\n\t\telse:\n\t\t\tfor key in trigger:\n\t\t\t\tself.add(key, trigger.get(key))\n\n\t\tself.show()\n\n\tdef add(self, label, value):\n\t\tvalue = \"-\" if value is None else value\n\t\tself.content += \"<div><b>\" + label + \": </b>\" + str(value) + \"</div>\"\n\n\tdef show(self):\n\t\tself.view.show_popup(self.content)\n"
  },
  {
    "path": "command_show_current_settings.py",
    "content": "import json\nimport sublime\nimport sublime_plugin\nimport FuzzyFilePath.common.settings as settings\n\n\nclass FfpShowCurrentSettingsCommand(sublime_plugin.TextCommand):\n    \"\"\" shows a message dialog with project validation status of current file \"\"\"\n    def run(self, edit):\n        json_string = json.dumps(settings.current(), indent=4, sort_keys=True)\n        new_file = sublime.active_window().new_file()\n        new_file.insert(edit, 0, json_string)\n        new_file.set_syntax_file(\"Packages/JavaScript/JavaScript.tmLanguage\")\n"
  },
  {
    "path": "command_show_info.py",
    "content": "import sublime_plugin\nimport FuzzyFilePath.common.settings as settings\nimport FuzzyFilePath.project.validate as Validate\n\n\nclass FfpShowInfoCommand(sublime_plugin.TextCommand):\n    \"\"\" shows a message dialog with project validation status of current file \"\"\"\n    def run(self, edit):\n        Validate.view(self.view, settings.current(), True)\n"
  },
  {
    "path": "common/__init__.py",
    "content": ""
  },
  {
    "path": "common/config.py",
    "content": "config = {\n\n\t\"debug\": False,\n\t\"log\": False,\n\t\"id\": \"config\",\n\t\"ffp_settings_file\": \"FuzzyFilePath.sublime-settings\",\n\t\"escape_dollar\": '\\$',\n\t\"trigger_action\": [\"auto_complete\", \"insert_path\"],\n\t\"insert_action\": [\"commit_completion\", \"insert_best_completion\"],\n\t\"trigger_statements\": [\"prefix\", \"tagName\", \"style\"],\n\t\"fast_query\": False,\n\n\t\"base_directory\": False,\n\t\"project_directory\": False,\n\t\"disable_autocompletion\": False,\n\t\"disable_keymap_actions\": False,\n\t\"auto_trigger\": True,\n\t\"trigger\": [],\n\t\"additional_scopes\": [],\n\t\"exclude_folders\": [\"node\\\\_modules\", \"bower\\\\_components/.*/bower\\\\_components\"],\n\n\t\"post_insert_move_characters\": \"^[\\\"\\'\\);]*\"\n}\n"
  },
  {
    "path": "common/path.py",
    "content": "import re\nimport os\n\ndef sanitize(path):\n    # sanitize slashes (posix)\n    path = posix(path)\n    # sanitize ././\n    path = re.sub(\"^\\/?(./)+\", \"./\", path)\n    path = re.sub(\"^(\\/)+\", \"/\", path)\n    return path\n\ndef posix(path):\n    if path is not None:\n        path = path.replace(\"\\\\\", \"/\")\n    return path\n\ndef is_relative(string):\n    return bool(re.match(\"(\\.?\\.\\/)\", string))\n\ndef is_absolute(string):\n    return bool(re.match(\"\\/[A-Za-z0-9\\_\\-\\s\\.$]*\", string))\n\ndef sanitize_base_directory(path):\n    path = sanitize(path)\n    path = os.path.dirname(path)\n    # no leading nor trailing slash\n    path = re.sub(\"^\\/*\", \"\", path)\n    path = re.sub(\"\\/*$\", \"\", path)\n    return path\n\ndef get_absolute_path(base_path, relative_path):\n    # return absolute target of join(base_path, relative_path)\n    # http://stackoverflow.com/questions/17295086/python-joining-current-directory-and-parent-directory-with-os-path-join?rq=1\n    path = os.path.join(base_path, relative_path)\n    path = os.path.abspath(path)\n    return path\n\ndef get_relative_folder(file_name, base_directory):\n    folder = os.path.dirname(file_name)\n    folder = os.path.relpath(folder, base_directory)\n    folder = \"\" if folder == \".\" else folder\n    return sanitize(folder)\n\ndef relative_to(base_directory, folder_path):\n    folder = os.path.relpath(folder_path, base_directory)\n    return sanitize(folder)\n\n# return {string} path from base to target\n# !replace with `os.path.relpath(path[, start])`\n# => https://docs.python.org/2/library/os.path.html\ndef trace(from_folder, to_folder):\n    if not from_folder:\n        return sanitize(\"./\" + to_folder)\n\n    bases = from_folder.split(\"/\")\n    targets = to_folder.split(\"/\")\n    result = \"\"\n    index = 0\n\n    # step back base, until in same folder\n    size = min(len(bases), len(targets))\n    while (index < size and bases[index] == targets[index]):\n        index += 1\n\n    # strip common folders\n    del bases[0:index]\n    del targets[0:index]\n\n    if (len(bases) == 0):\n        # from base path?\n        result = './'\n    else:\n        result = \"../\" * len(bases)\n\n    result += \"/\".join(targets)\n    # !Do Debug \"//\"\n    result = re.sub(\"//\", \"/\", result);\n\n    return result\n"
  },
  {
    "path": "common/selection.py",
    "content": "\ndef get_position(view):\n\tselection = view.sel()\n\treturn selection[0].begin() if selection else \"\"\n\ndef get_line(view):\n    position = get_position(view)\n    line_region = view.line(position)\n    return view.substr(line_region)\n\ndef get_word(view):\n    word_region = view.word(get_position(view))\n    return view.substr(word_region)\n\ndef get_scope(view):\n    return view.scope_name(get_position(view))\n"
  },
  {
    "path": "common/settings.py",
    "content": "\"\"\"\n    manages the current setting data which is retrieved in the following order (bottom to top)\n    - project settings: folder settings\n    - project settings\n    - user settings file\n    - default settings file\n    - config\n\"\"\"\nimport sublime\nimport os\nimport FuzzyFilePath.common.path as Path\nfrom FuzzyFilePath.common.config import config\n\n# base settings: config, default, user settings file\nbase_settings = {}\n# project settings merged with base settings\nproject_settings = {}\n# final settings object\ncurrent_settings = {}\n# backwards compatibility of setting schema\nmap_settings = {\n    \"trigger\": \"scopes\"\n}\n\n\ndef get(key, default=None):\n    return current_settings.get(key.lower(), default)\n\n\ndef current():\n    return current_settings\n\n\ndef update():\n    \"\"\" merges plugin settings with user settings by default \"\"\"\n    update_base_settings()\n    update_project_settings()\n\n\ndef update_base_settings():\n    global base_settings, project_settings, current_settings\n    base_settings = get_base_settings(config)\n    project_settings = base_settings\n    current_settings = base_settings\n    #verbose(\"BASE_SETTINGS\", current_settings)\n\n\n# @TODO improve memory\ndef update_project_settings():\n    global base_settings, project_settings, current_settings\n    project_settings = get_project_settings(base_settings)\n    current_settings = project_settings\n    #verbose(\"PROJECT_SETTINGS\", current_settings)\n\n\ndef update_project_folder_settings(project_folder):\n    global project_settings, current_settings\n    current_settings = get_folder_settings(project_settings, project_folder)\n    #verbose(\"CURRENT_SETTINGS\", current_settings)\n\n\ndef get_project_settings(base):\n    \"\"\" returns project settings \"\"\"\n    data = sublime.active_window().project_data()\n    if not data:\n        return base\n    ffp_project_settings = data.get(\"settings\", {}).get(\"FuzzyFilePath\", {})\n    ffp_project_settings = sanitize(ffp_project_settings)\n    return merge(base, ffp_project_settings)\n\n\ndef get_base_settings(config):\n    user_settings = sublime.load_settings(config[\"ffp_settings_file\"])\n    # Note: user_settings is of class Settings\n    user_settings = merge(config, user_settings)\n    user_settings = merge_scopes(user_settings)\n    return sanitize(user_settings)\n\n\ndef get_folder_settings(project, project_folder=None):\n    if not project_folder:\n        return project\n\n    folder_settings = get_folder_setting(project_folder)\n    folder_settings = sanitize(folder_settings)\n    return merge(project, folder_settings)\n\n\ndef merge(settings, overwrite={}):\n    \"\"\" merge settings object with given overwrite settings \"\"\"\n    result = {}\n    for key in settings:\n        result[key] = overwrite.get(key.lower(), settings.get(key))\n    # backwards compatibility\n    for key in map_settings:\n        mappedKey = map_settings[key]\n        result[key] = overwrite.get(mappedKey, settings.get(key))\n\n    return result\n\n\ndef merge_scopes(settings):\n    \"\"\"Merge triggers from 'additional_scopes' in user settings to the main triggers, if present\"\"\"\n    triggers = settings.get(\"trigger\")\n    additional_scopes = settings.get(\"additional_scopes\", [])\n    for trigger in additional_scopes:\n        triggers.append(trigger)\n    settings[\"trigger\"] = triggers\n    return settings\n\n\n# @TODO improve memory\ndef get_folder_setting(folder=None):\n    \"\"\" returns the project config object FuzzyFilePath associated with the given folder \"\"\"\n    if not folder:\n        return {}\n    data = sublime.active_window().project_data()\n    if not data:\n        return {}\n    folders = data.get(\"folders\")\n    if not folders:\n        return {}\n    # if the project file has been saved, paths in project settings may be relative, but the given filepath is absolute\n    # (retrieved from window.folder())\n    project_directory = sublime.active_window().project_file_name()\n    if project_directory:\n        project_directory = os.path.dirname(project_directory)\n        folder = Path.relative_to(project_directory, folder)\n\n    for folder_settings in folders:\n        if folder_settings.get(\"path\") == folder:\n            settings = folder_settings.get(\"FuzzyFilePath\", {})\n            verbose(\"found folder settings\", folder, \":\", settings)\n            return settings\n\n    verbose(\"no folder settings found\", folder)\n    return {}\n\n\ndef sanitize(settings_object):\n    if \"base_directory\" in settings_object and settings_object.get(\"base_directory\"):\n        settings_object[\"base_directory\"] = Path.sanitize_base_directory(settings_object.get(\"base_directory\"))\n    if \"project_directory\" in settings_object and settings_object.get(\"project_directory\"):\n        settings_object[\"project_directory\"] = Path.sanitize_base_directory(settings_object.get(\"project_directory\"))\n    return settings_object\n\n\ndef verbose(*args):\n    if get(\"log\") is True:\n        print(\"FFP\\t\", \"Settings\", *args)\n"
  },
  {
    "path": "common/string.py",
    "content": "def get_diff(first, second):\n    # get intersection at start\n    start = get_start_diff(first, second)\n\n    # remove intersection to prevent end diff duplicates\n    first = first[len(start):]\n    second = second[len(start):]\n    end = get_end_diff(first, second)\n\n    return {\n        \"start\": start,\n        \"end\": end\n    }\n\ndef get_start_diff(first, second):\n    index = 0\n    result = \"\"\n    for c in first:\n        if c is second[index]:\n            index += 1\n            result += c\n        else:\n            break\n\n    return result\n\ndef get_end_diff(first, second):\n    first = first[::-1] # reverse string\n    second = second[::-1]\n    second_length = len(second)\n    index = 0\n    result = \"\"\n    for c in first:\n        if index < second_length and c is second[index]:\n            index += 1\n            result = c + result\n        else:\n            break\n\n    return result\n"
  },
  {
    "path": "common/verbose.py",
    "content": "import FuzzyFilePath.common.settings as settings\n\nIGNORE = [\"CurrentFile\", \"QueryCompletionListener\", \"search\", \"Expression\"]\n\ndef log(*args):\n\tif settings.get(\"log\"):\n\t\tprint(\"FFP\\t\", *args)\n\ndef verbose(*args):\n    if settings.get(\"debug\") is True and not args[0] in IGNORE:\n        print(\"FFP\\t\", *args)\n\ndef warn(*args):\n\tprint(\"FFP -WARNING-\\t\", *args)\n\ndef start_block():\n\tif settings.get(\"log\") or settings.get(\"debug\"):\n\t\tprint(\"\")\n\ndef end_block():\n\tif settings.get(\"log\") or settings.get(\"debug\"):\n\t\tprint(\"\")\n"
  },
  {
    "path": "completion.py",
    "content": "\"\"\"\n    Manage active state of current completion and post cleanup\n\"\"\"\nimport re\nimport sublime\nimport FuzzyFilePath.expression as Context\nimport FuzzyFilePath.common.path as Path\nfrom FuzzyFilePath.common.string import get_diff\nimport FuzzyFilePath.common.selection as Selection\nfrom FuzzyFilePath.common.verbose import log\nfrom FuzzyFilePath.common.verbose import verbose\nimport FuzzyFilePath.common.settings as settings\nimport FuzzyFilePath.current_state as current_state\n\n\nID = \"Completion\"\nID_TRIGGER = \"SelectedTrigger\"\n\nstart_expression = False\nscope_cache = {}\n\nstate = {\n    \"active\": False,            # completion currently in progress (serve suggestions)\n    \"onInsert\": [],             # substitutions for building final path\n    \"base_directory\": False     # base directory to set for absolute path\n}\n\n\ndef start(post_replacements=[]):\n    state[\"replace_on_insert\"] = post_replacements\n    state[\"active\"] = True\n\n\ndef stop():\n    state[\"active\"] = False\n    state[\"base_directory\"] = False\n\n\ndef is_active():\n    return state.get(\"active\")\n\n\ndef resolve_trigger(view, query):\n    global start_expression\n\n    # parse current context, may contain 'is_valid: False'\n    start_expression = Context.get_context(view)\n    if start_expression[\"error\"] and not query.by_command():\n        verbose(ID, \"abort - not a valid context\")\n        return False\n\n    current_scope = Selection.get_scope(view)\n    trigger = find_trigger(current_scope, start_expression, query.by_command())\n\n    # currently trigger is required in Query.build\n    if trigger is False:\n        verbose(ID, \"abort - no trigger found\")\n        return False\n\n    return trigger\n\n\ndef get_filepaths(view, query):\n    global start_expression\n\n    trigger = resolve_trigger(view, query)\n    log(ID_TRIGGER, trigger)\n    if query.build(start_expression.get(\"needle\"), trigger, current_state.get_directory()) is False:\n        # query is valid, but may not be triggered: not forced, no auto-options\n        verbose(ID, \"abort - no auto trigger found\")\n        return False\n\n    # remembed the path for `update_inserted_filepath`, query will be reset...\n    state[\"base_directory\"] = query.get_post_remove_path()\n\n    return current_state.search_completions(\n        query.get_needle(),\n        current_state.get_project_directory(),\n        query.get_extensions(),\n        query.get_base_path()\n    )\n\n\ndef find_trigger(current_scope, expression, byCommand=False):\n    \"\"\" Returns the first trigger matching the given scope and expression \"\"\"\n    triggers = settings.get(\"TRIGGER\")\n    if not byCommand:\n        # get any triggers that match the requirements and may start automatically\n        triggers = get_matching_autotriggers(current_scope, settings.get(\"TRIGGER\"))\n    if not bool(triggers):\n        verbose(ID, \"abort query, no valid scope-regex for current context\")\n        return False\n    # check if one of the triggers match the current context (expression, scope)\n    return Context.find_trigger(expression, current_scope, triggers)\n\n\ndef update_inserted_filepath(view, post_remove):\n    \"\"\" post completion: adjusts inserted filepath \"\"\"\n    expression = Context.get_context(view)\n    # diff of previous needle and inserted needle\n    diff = get_diff(post_remove, expression[\"needle\"])\n    # cleanup string\n    result = re.sub(\"^\" + diff[\"start\"], \"\", expression[\"needle\"])\n    # do not replace current word\n    if diff[\"end\"] != start_expression[\"word\"]:\n        result = re.sub(diff[\"end\"] + \"$\", \"\", result)\n\n    # remove path query completely\n    result = apply_post_replacements(result, state.get(\"base_directory\"), state.get(\"replace_on_insert\"))\n    log(\"post cleanup path:'\", expression.get(\"needle\"), \"' ~~> '\", result, \"'\")\n\n    # replace current query with final path\n    view.run_command(\"ffp_replace_region\", {\n        \"a\": expression[\"region\"].a,\n        \"b\": expression[\"region\"].b,\n        \"string\": result,\n        \"move_cursor\": True\n    })\n\n\ndef get_matching_autotriggers(scope, triggers):\n    \"\"\" Returns all triggers that match the given scope \"\"\"\n    # get cached evaluation\n    result = scope_cache.get(scope)\n    if result is None:\n        # evaluate triggers on current scope\n        result = [trigger for trigger in triggers if trigger.get(\"auto\") and re.search(trigger.get(\"scope\"), scope)]\n        # add to cache\n        scope_cache[scope] = result if len(result) > 0 else False\n        result = scope_cache.get(scope)\n\n    return result\n\n\ndef apply_post_replacements(path, base_directory, replace_on_insert):\n    # hack reverse\n    path = re.sub(settings.get(\"ESCAPE_DOLLAR\"), \"$\", path)\n    for replace in replace_on_insert:\n        path = re.sub(replace[0], replace[1], path)\n\n    if base_directory and path.startswith(\"/\"):\n        path = re.sub(\"^\\/\" + base_directory, \"\", path)\n        path = Path.sanitize(path)\n\n    return path\n"
  },
  {
    "path": "controller.py",
    "content": "import sublime\nimport FuzzyFilePath.completion as Completion\nimport FuzzyFilePath.query as Query\nimport FuzzyFilePath.common.settings as Settings\nfrom FuzzyFilePath.common.verbose import verbose\nfrom FuzzyFilePath.common.verbose import log\nimport FuzzyFilePath.current_state as state\n\n\nID = \"Controller\"\n\n\n#init\ndef plugin_loaded():\n    \"\"\" load settings \"\"\"\n    update_settings()\n    settings_file = sublime.load_settings(Settings.get(\"ffp_settings_file\"))\n    settings_file.add_on_change(\"update\", update_settings)\n\n\ndef update_settings():\n    \"\"\" restart projectFiles with new plugin and project settings \"\"\"\n    # invalidate cache\n    global scope_cache\n    scope_cache = {}\n    Settings.update()\n    state.enable()\n    state.update_settings()\n\n\n#query\ndef get_filepath_completions(view):\n    if not state.is_valid():\n        Query.reset()\n        return False\n\n    verbose(ID, \"get filepath completions\")\n    completions = Completion.get_filepaths(view, Query)\n\n    if completions and len(completions[0]) > 0:\n        Completion.start(Query.get_replacements())\n        view.run_command('_enter_insert_mode') # vintageous\n        log(\"{0} completions found\".format(len(completions)))\n    else:\n        if Query.get_needle() is not None:\n            sublime.status_message(\"FFP no filepaths found for '\" + Query.get_needle() + \"'\")\n        Completion.stop()\n\n    Query.reset()\n    return completions\n\n\ndef on_query_completion_inserted(view, post_remove):\n    if Completion.is_active():\n        verbose(ID, \"query completion inserted\")\n        Completion.update_inserted_filepath(view, post_remove)\n        Completion.stop()\n\n\ndef on_query_completion_aborted():\n    Completion.stop()\n\n\n#project\ndef on_project_focus(window):\n    \"\"\" window has gained focus, rebuild file cache (in case files were removed/added) \"\"\"\n    verbose(ID, \"refocus project\")\n    for folder in window.folders():\n            state.rebuild_filecache(folder)\n\n\ndef on_project_activated(window):\n    \"\"\" a new project has received focus \"\"\"\n    verbose(ID, \"activate project\")\n    state.update()\n\n\n\n#file\ndef on_file_created():\n    \"\"\" a new file has been created \"\"\"\n    verbose(ID, \"file created -- rebuild cache\")\n    state.update()\n    state.rebuild_filecache()\n\n\ndef on_file_focus(view):\n    state.update()\n"
  },
  {
    "path": "current_state.py",
    "content": "\"\"\"\n\tmanages current state, which include: current filename of view, current project folder if any, project folder\n\tdoes not manage settings, but sends a message with the current project folder to settings\n\"\"\"\nID = \"CurrentState\"\n\n\nimport sublime\nimport os\nimport re\nimport FuzzyFilePath.common.settings as settings\nfrom FuzzyFilePath.project.FileCache import FileCache\nimport FuzzyFilePath.common.path as Path\nimport FuzzyFilePath.common.verbose as logger\n\n\nis_enabled = False # set to true when plugin has initially updated settings\nvalid = False # if the current view is a valid project file\nfile_caches = {} # caches any file indices of each project folder\nstate = {} # saves current views state like filename, project_folder, cache and settings\n\n\ndef update():\n\t\"\"\" call me anytime a new view has gained focus. This includes activation of a new window, which should have an\n\t\tactive view\n\t\"\"\"\n\tglobal valid, is_enabled\n\n\tif not is_enabled:\n\t\treturn False\n\n\ttemp = False\n\twindow = sublime.active_window()\n\tif window is None:\n\t\tlogger.log(ID, \"Abort -- no active window\")\n\t\tvalid = False\n\t\treturn valid\n\tview = window.active_view()\n\tif view is None:\n\t\tlogger.log(ID, \"Abort -- no active view\")\n\t\tvalid = False\n\t\treturn valid\n\tfile = Path.posix(view.file_name())\n\tif not file:\n\t\tlogger.log(ID, \"Abort -- view has not yet been saved to file\")\n\t\ttemp = True\n\t\treturn valid\n\tif state.get(\"file\") == file:\n\t\tlogger.log(ID, \"Abort -- view already updated\")\n\t\treturn valid\n\n\tfolders = list(map(lambda f: Path.posix(f), window.folders()))\n\tproject_folder = get_closest_folder(file, folders)\n\n\tif project_folder is False:\n\t\tlogger.log(ID, \"Abort -- file not part of a project (folder)\")\n\t\tvalid = False\n\t\treturn valid\n\n\t# notify settings of new project folder\n\tif state.get(\"project_folder\") != project_folder:\n\t\tsettings.update_project_settings()\n\tsettings.update_project_folder_settings(project_folder)\n\n\tvalid = True\n\n\t# @TODO cache\n\tstate[\"file\"] = file\n\tstate[\"directory\"] = sanitize_directory(file, project_folder)\n\tstate[\"folders\"] = folders\n\tstate[\"project_folder\"] = project_folder\n\tstate[\"cache\"] = get_file_cache(project_folder)\n\n\tlogger.start_block()\n\tlogger.verbose(ID, \"Updated\", state)\n\n\treturn valid\n\n\ndef sanitize_directory(file_name, project_folder):\n\tdirectory = re.sub(project_folder, \"\", file_name)\n\tdirectory = re.sub(\"^[\\\\\\\\/\\.]*\", \"\", directory)\n\treturn os.path.dirname(directory)\n\n\ndef get_project_directory():\n\treturn state.get(\"project_folder\")\n\n\ndef get_directory():\n\treturn state.get(\"directory\")\n\n\ndef update_settings():\n\tif state.get(\"project_folder\"):\n\t\t# we expect settings to be already updated and thus only update the project folder settings\n\t\tsettings.update_project_folder_settings(state.get(\"project_folder\"))\n\n\ndef is_valid():\n\treturn valid\n\n\ndef enable():\n\tglobal is_enabled\n\tis_enabled = True\n\n\ndef get_file_cache(folder):\n\tif not folder in file_caches:\n\t\tvalid_file_extensions = get_valid_extensions(settings.get(\"trigger\"))\n\t\tlogger.verbose(ID, \"Build cache for \" + folder + \" (\", valid_file_extensions , \") excluding\", settings.get(\"exclude_folders\"))\n\t\tfile_caches[folder] = FileCache(valid_file_extensions, settings.get(\"exclude_folders\"), folder)\n\n\treturn file_caches.get(folder)\n\n\ndef rebuild_filecache(folder=None):\n\tif not folder:\n\t\tif state.get(\"cache\"):\n\t\t\tlogger.verbose(ID, \"rebuild current filecache of folder \" + state.get(\"project_folder\"))\n\t\t\tstate.get(\"cache\").rebuild()\n\t\treturn\n\n\tfolder = Path.posix(folder)\n\tif not folder in file_caches:\n\t\tlogger.log(ID, \"Abort rebuild filecache -- folder \" + folder + \" not cached\")\n\t\treturn False\n\n\tlogger.verbose(ID, \"rebuild current filecache of folder \" + folder)\n\tfile_caches.get(folder).rebuild()\n\n\ndef search_completions(needle, project_folder, valid_extensions, base_path=False):\n\treturn state.get(\"cache\").search_completions(needle, project_folder, valid_extensions, base_path)\n\n\ndef find_file(file_name):\n\treturn state.get(\"cache\").find_file(file_name)\n\n\ndef get_valid_extensions(triggers):\n\t\"\"\" Returns a list of all file extensions found in scope triggers \"\"\"\n\textensionsToSuggest = []\n\tfor scope in triggers:\n\t    ext = scope.get(\"extensions\", [])\n\t    extensionsToSuggest += ext\n\t# return without duplicates\n\treturn list(set(extensionsToSuggest))\n\n\ndef get_closest_folder(filepath, directories):\n\t\"\"\"\n\t\tReturns the (closest) project folder associated with the given file or False\n\n\t\t# the rational behind this is as follows:\n\t\tIn nodejs we might work with linked node_modules. Each node_module is a separate project. Adding nested folders\n\t\tto the root document thus owns the file and defines the project scope. A separated folder should never reach\n\t\tout (via files) on its parents folders.\n\t\"\"\"\n\tfolderpath = os.path.dirname(filepath)\n\tcurrent_folder = folderpath\n\tclosest_directory = False\n\tfor folder in directories:\n\t\tdistance = current_folder.replace(folder, \"\")\n\t\tif len(distance) < len(folderpath):\n\t\t\tfolderpath = distance\n\t\t\tclosest_directory = folder\n\treturn closest_directory\n"
  },
  {
    "path": "expression.py",
    "content": "import re\nimport sublime\nimport FuzzyFilePath.common.settings as settings\nimport FuzzyFilePath.common.selection as Selection\nimport FuzzyFilePath.common.verbose as logger\n\nID = \"Expression\"\n\nNEEDLE_SEPARATOR = \">\\\"\\'\\(\\)\\{\\}\"\nNEEDLE_SEPARATOR_BEFORE = \"\\\"\\'\\(\\{\"\nNEEDLE_SEPARATOR_AFTER = \"^\\\"\\'\\)\\}\"\nNEEDLE_CHARACTERS = \"\\.A-Za-z0-9\\-\\_$\"\nNEEDLE_INVALID_CHARACTERS = \"\\\"\\'\\)=\\:\\(<>\\n\\{\\}\"\nDELIMITER = \"\\s\\:\\(\\[\\=\\{\"\n\ndef get_context(view):\n\terror = False\n\tvalid = True\n\tvalid_needle = True\n\tposition = Selection.get_position(view)\n\n\t# regions\n\tword_region = view.word(position)\n\tline_region = view.line(position)\n\tpre_region = sublime.Region(line_region.a, word_region.a)\n\tpost_region = sublime.Region(word_region.b, line_region.b)\n\n\t# text\n\tline = view.substr(line_region)\n\tword = view.substr(word_region)\n\tpre = view.substr(pre_region)\n\tpost = view.substr(post_region)\n\n\terror = re.search(\"[\" + NEEDLE_INVALID_CHARACTERS + \"]\", word)\n\n\tneedle_region = view.word(position)\n\n\t# grab everything in 'separators'\n\tneedle = \"\"\n\tseparator = False\n\tpre_match = \"\"\n\t# search for a separator before current word, i.e. <\">path/to/<position>\n\tpre_quotes = re.search(\"([\"+NEEDLE_SEPARATOR_BEFORE+\"])([^\"+NEEDLE_SEPARATOR+\"]*)$\", pre)\n\tif pre_quotes:\n\t\tneedle += pre_quotes.group(2) + word\n\t\tseparator = pre_quotes.group(1)\n\t\tpre_match = pre_quotes.group(2)\n\t\tneedle_region.a -= len(pre_quotes.group(2))\n\telse:\n\t\t# use whitespace as separator\n\t\tpre_quotes = re.search(\"(\\s)([^\"+NEEDLE_SEPARATOR+\"\\s]*)$\", pre)\n\t\tif pre_quotes:\n\t\t\tneedle = pre_quotes.group(2) + word\n\t\t\tseparator = pre_quotes.group(1)\n\t\t\tpre_match = pre_quotes.group(2)\n\t\t\tneedle_region.a -= len(pre_quotes.group(2))\n\n\tif pre_quotes:\n\t\tpost_quotes = re.search(\"^([\"+NEEDLE_SEPARATOR_AFTER+\"]*)\", post)\n\t\tif post_quotes:\n\t\t\tneedle += post_quotes.group(1)\n\t\t\tneedle_region.b += len(post_quotes.group(1))\n\t\telse:\n\t\t\tlogger.verbose(ID, \"no post quotes found => invalid\")\n\t\t\tvalid = False\n\telif not re.search(\"[\"+NEEDLE_INVALID_CHARACTERS+\"]\", needle):\n\t\tneedle = pre + word\n\t\tneedle_region.a = pre_region.a\n\telse:\n\t\tneedle = word\n\n\t# grab prefix\n\tprefix_region = sublime.Region(line_region.a, pre_region.b - len(pre_match) - 1)\n\tprefix_line = view.substr(prefix_region)\n\t# # print(\"prefix line\", prefix_line)\n\n\t#define? ([\"...\", \"...\"]) -> before?\n\t# before: ABC =:([\n\tprefix = re.search(\"\\s*([\"+NEEDLE_CHARACTERS+\"]+)[\"+DELIMITER+\"]*$\", prefix_line)\n\tif prefix is None:\n\t\t# validate array, like define([\"...\", \".CURSOR.\"])\n\t\tprefix = re.search(\"^\\s*([\"+NEEDLE_CHARACTERS+\"]+)[\"+DELIMITER+\"]+\", prefix_line)\n\n\tif prefix:\n\t\t# print(\"prefix:\", prefix.group(1))\n\t\tprefix = prefix.group(1)\n\n\ttag = re.search(\"<\\s*([\"+NEEDLE_CHARACTERS+\"]*)\\s*[^>]*$\", prefix_line)\n\tif tag:\n\t\ttag = tag.group(1)\n\t\t# print(\"tag:\", tag)\n\n\tpropertyName = re.search(\"[\\s\\\"\\'']*([\"+NEEDLE_CHARACTERS+\"]*)[\\s\\\"\\']*\\:[^\\:]*$\", prefix_line)\n\tif propertyName:\n\t\tpropertyName = propertyName.group(1)\n\t\t# print(\"style:\", style)\n\n\tif separator is False:\n\t\tlogger.verbose(ID, \"separator undefined => invalid\", needle)\n\t\tvalid_needle = False\n\t\tvalid = False\n\telif re.search(\"[\"+NEEDLE_INVALID_CHARACTERS+\"]\", needle):\n\t\tlogger.verbose(ID, \"invalid characters in needle => invalid\", needle)\n\t\tvalid_needle = False\n\t\tvalid = False\n\telif prefix is None and separator.strip() == \"\":\n\t\tlogger.verbose(ID, \"prefix undefined => invalid\", needle)\n\t\tvalid = False\n\n\treturn {\n\t\t\"is_valid\": valid,\n\t\t\"valid_needle\": valid_needle,\n\t\t\"needle\": needle,\n\t\t\"prefix\": prefix,\n\t\t\"tagName\": tag,\n\t\t\"style\": propertyName,\n\t\t\"region\": needle_region,\n\t\t\"word\": word,\n\t\t# really do not use any of this\n\t\t\"error\": error\n\t}\n\ndef check_trigger(trigger, expression):\n\t# returns True if the expression statements match the trigger\n\tfor statement in set(settings.get(\"trigger_statements\")).intersection(trigger):\n\t\tvalues = trigger.get(statement)\n\t\t# statement values may be None (or any other value...)\n\t\tif type(values) is list and not expression.get(statement) in values:\n\t\t\treturn False\n\t\t# validate other value by comparison\n\t\t# elif not values == expression.get(statement):\n\t\t# \treturn False\n\n\treturn True\n\ndef find_trigger(expression, scope, triggers):\n\tfor trigger in triggers:\n\t\t# if the trigger is defined for the current scope\n\t\t# REQUIRED? scope = properties.get(\"scope\").replace(\"//\", \"\")\n\t\tif re.search(trigger[\"scope\"], scope):\n\t\t\t# validate its statements on the current context\n\t\t\tif check_trigger(trigger, expression):\n\t\t\t\treturn trigger\n\n\treturn False\n\ndef get_rule(view):\n\n\tselection = view.sel()[0]\n\tposition = selection.begin()\n\tword_region = view.word(position)\n\n\tcurrent_scope = view.scope_name(word_region.a)\n\tcontext = get_context(view)\n\trule = find_rule(context, current_scope)\n\n\treturn [rule, context] if rule else False\n"
  },
  {
    "path": "project/FileCache.py",
    "content": "import sublime\nimport os\nimport gc\nimport re\nfrom FuzzyFilePath.common.verbose import verbose\nfrom FuzzyFilePath.common.verbose import log\nimport FuzzyFilePath.common.path as Path\nimport FuzzyFilePath.common.settings as settings\nfrom FuzzyFilePath.project.FileCacheWorker import FileCacheWorker\n\nID = \"search\"\nID_CACHE = \"cache\"\n\nclass FileCache:\n    \"\"\"\n        Manages path suggestions by loading, caching and filtering project files. Add folders by\n        `add(<path_to_parent_folder>)`\n    \"\"\"\n\n    def __init__(self, file_extensions, exclude_folders, directory):\n        self.directory = directory\n        self.valid_extensions = file_extensions\n        self.exclude_folders = exclude_folders\n        self.cache = None\n\n        self.rebuild()\n\n    def update_settings(self, file_extensions, exclude_folders):\n        settings_have_changed = self.valid_extensions != file_extensions or self.exclude_folders != exclude_folders\n        self.valid_extensions = file_extensions\n        self.exclude_folders = exclude_folders\n        if settings_have_changed:\n            self.rebuild()\n\n    def search_completions(self, needle, project_folder, valid_extensions, base_path=False):\n        \"\"\"\n            retrieves a list of valid completions, containing fuzzy searched needle\n\n            Parameters\n            ----------\n            needle : string -- to search in files\n            project_folder : string -- folder to search in, cached via add\n            valid_extensions : array -- list of valid file extensions\n            base_path : string -- of current file, creates a relative path if not False\n            with_extension : boolean -- insert extension\n\n            return : List -- containing sublime completions\n        \"\"\"\n        log(ID, needle, project_folder, valid_extensions, base_path)\n        project_files = self.cache.files\n        if (project_files is None):\n            return False\n\n        # basic: strip any dots\n        needle = re.sub(\"\\.\\./\", \"\", needle)\n        needle = re.sub(\"\\.\\/\", \"\", needle)\n        # remove starting slash\n        needle = re.sub(\"^\\/\", \"\", needle)\n        # cleanup\n        needle = re.sub('[\"\\'\\(\\)$]', '', needle)\n        # prepare for regex extension string\n        needle = re.escape(needle);\n\n        # build search expression\n        if settings.get(\"fast_query\"):\n            regex = \".*\" + re.sub(\"\\\\\\/\", \".*\\\\\\/.*\", needle) + \".*\"\n        else:\n            regex = \".*\"\n            for i in needle:\n                regex += i + \".*\"\n\n        verbose(ID, \"scan\", len(project_files), \"files for\", regex, \"(\" + needle + \")\", valid_extensions);\n\n        # get matching files\n        result = []\n        for filepath in project_files:\n            properties = project_files.get(filepath)\n            \"\"\"\n                properties[0] = escaped filename without extension, like \"test/mock/project/index\"\n                properties[1] = file extension, like \"html\"\n                properties[2] = file displayed as suggestion, like 'test/mock/project/index     html'\n            \"\"\"\n            \"\"\"\n                funny: the regex matched here is also matched again by Sublime itself. thus returning all\n                filepaths is as good as filtering them before, with the exception that the first one is incredibly\n                faster...\n\n                The problem with the second solution is, that sublime does not include anything outside word separators\n                and thus proposing many invalid paths. Thus the regex has to be faster, by either\n                - omitting anything after the last slash, since sublime does the rest correctly (no improvement)\n                - splitting the search by folders...\n            \"\"\"\n            if ((properties[1] in valid_extensions or \"*\" in valid_extensions) and re.match(regex, filepath, re.IGNORECASE)):\n            # if ((properties[1] in valid_extensions or \"*\" in valid_extensions)):\n                completion = self.get_completion(filepath, base_path)\n                result.append(completion)\n\n        return (result, sublime.INHIBIT_EXPLICIT_COMPLETIONS | sublime.INHIBIT_WORD_COMPLETIONS)\n\n    def find_file(self, file_name):\n        project_files = self.cache.files\n        if (project_files is None):\n            return False\n\n        result = []\n        file_name_query = \".*\" + re.escape(file_name) + \".*\"\n        for filepath in project_files:\n            if re.match(file_name_query, filepath, re.IGNORECASE):\n                result.append(filepath)\n        return result\n\n    def get_completion(self, target_path, base_path=False):\n        if base_path is False:\n            # absolute path\n            return (target_path, \"/\" + target_path)\n        else:\n            # create relative path\n            return (target_path, Path.trace(base_path, target_path))\n\n    def file_is_cached(self, file_name):\n        \"\"\" returns False if the given file is not within cache\n            tests files with full path or relative from project directory\n        \"\"\"\n        name, extension = os.path.splitext(file_name)\n        extension = extension[1:]\n        if not extension in self.valid_extensions:\n            verbose(ID_CACHE, \"file to cache has no valid extension\", extension)\n            return True\n\n        file_name = re.sub(self.directory, \"\", file_name)\n        return self.cache.get(file_name, False) is not False\n\n\n    def rebuild(self):\n        self.cache = FileCacheWorker(self.exclude_folders, self.valid_extensions, self.directory)\n        self.cache.start();\n"
  },
  {
    "path": "project/FileCacheWorker.py",
    "content": "\"\"\"\n    Scans, parses and stores all files in the given folder to the dictionary `files`\n\n    Each file entry is set by its relative `filepath` and holds an array like\n        0 : filename (modified)\n        1 : file extension\n        2 : sublime text auto completion string\n\"\"\"\nimport sublime\nimport os\nimport re\nimport threading\nfrom FuzzyFilePath.common.verbose import verbose\nimport FuzzyFilePath.common.settings as settings\n\n\nID = \"cache\"\n\n\ndef posix(path):\n    return path.replace(\"\\\\\", \"/\")\n\n# stores all files and its fragments within property files\nclass FileCacheWorker(threading.Thread):\n\n    def __init__(self, exclude_folders, extensions, folder):\n        threading.Thread.__init__(self)\n\n        self.exclude_folders = exclude_folders\n        self.extensions = extensions\n        self.folder = folder\n        self.files = None\n\n    def run(self):\n        verbose(ID, \"START adding files in\", self.folder)\n        self.files = self.read(self.folder)\n        #verbose(ID, len(self.files), \"files cached\")\n        print(\"FuzzyFilePath cached {0} files in {1}\".format(len(self.files), self.folder))\n\n\n    def read(self, folder, base=None):\n        \"\"\"return all files in folder\"\"\"\n        folder_cache = {}\n        base = base if base is not None else folder\n\n        if base is not folder:\n            # ensure project_folders are not excluded by another folders exclude patterns\n            # e.g. project/node_modules excludes project/node_modules/module, which is also a project folder\n            relative_folder = os.path.relpath(folder, base)\n            # test ignore expressions on current path\n            for test in self.exclude_folders:\n                if re.search(test, relative_folder) is not None:\n                    # verbose(ID, \"skip \" + folder)\n                    return folder_cache\n\n        # ressources =\n        for ressource in os.listdir(folder):\n            current_path = os.path.join(folder, ressource)\n\n            if (os.path.isfile(current_path)):\n\n                relative_path = os.path.relpath(current_path, base)\n                filename, extension = os.path.splitext(relative_path)\n                extension = extension[1:]\n\n                # posix required for windows, else absolute paths are wrong: /asd\\ads\\\n                relative_path = posix(relative_path)\n                # substitute $ which prevents errors in further processing. is replaced again in completion.py post repl\n                relative_path = re.sub(\"\\$\", settings.get(\"escape_dollar\"), relative_path)\n\n                if extension in self.extensions:\n                    current_filename = posix(filename)\n                    folder_cache[relative_path] = [\n                        # modified filepath. $ hack is reversed in post_commit_completion\n                        re.sub(\"\\$\", settings.get(\"escape_dollar\"), current_filename),\n                        # extension of file\n                        extension,\n                        # sublime completion text\n                        current_filename + \"\\t\" + extension\n                    ]\n\n            elif (not ressource.startswith('.') and os.path.isdir(current_path)):\n                folder_cache.update(self.read(current_path, base))\n\n        return folder_cache\n"
  },
  {
    "path": "project/__init__.py",
    "content": ""
  },
  {
    "path": "project/validate.py",
    "content": "\"\"\"\n    Validates the current file`s folders based on project folders and its settings.\n    Reminder: project = sublime window and opened folder (sidebar)\n\n    Either returns False, if no completions are possible or a dictionary containing required paths and folders of the\n    project, file and completion base directory. These folders are required to build the required path completions.\n\"\"\"\nimport re\nimport os\nimport sublime\n\nimport FuzzyFilePath.common.path as Path\nfrom FuzzyFilePath.common.verbose import log\n\n\ndef view(view, config, open_dialog=False):\n    \"\"\"\n        returns sanitized directories of current file\n\n        Parameters\n        ----\n        view : Sublime.View     -- view of current file\n        open_dialog: Boolean    -- optional: show directory information in dialog\n\n        return : Dictionary     -- containing evaluted projects path, where\n            \"project\": String       modified project directory from settings:project_directory\n            \"base\": String          relative folder in project from which to resolve paths to files\n            \"current\" : String      relative folder in project directory of current file\n    \"\"\"\n    if (file_has_location(view) is False):\n        return show_dialog(\"disabled: file is temporary\", open_dialog)\n\n    if (is_project() is False):\n        return show_dialog(\"disabled: not a project\", open_dialog)\n\n    directory = project_directory(view, config[\"PROJECT_DIRECTORY\"])\n    if directory is False:\n        return show_dialog(\"\\ndisabled:{0}<div>not within project directory {1}\"\n                .format(view.file_name(), config[\"PROJECT_DIRECTORY\"]), open_dialog)\n\n    config[\"BASE_DIRECTORY\"] = sanitize_base_directory(config[\"BASE_DIRECTORY\"], directory[\"project\"], directory[\"base\"])\n    directory[\"current\"] = get_current_folder_relative(view, directory[\"project\"])\n\n    if open_dialog is True:\n        message = \"\\nproject directory:\\n'{0}'\\n\".format(directory[\"project\"])\n        message += \"\\nbase directory: '{0}'\\n\".format(config[\"BASE_DIRECTORY\"])\n        message += \"\\ncurrent directory: '{0}'\\n\".format(directory[\"current\"])\n        show_dialog(message, open_dialog)\n\n    return directory\n\n\ndef get_current_folder_relative(view, project_directory):\n    return Path.get_relative_folder(view.file_name(), project_directory)\n\n\ndef file_has_location(view):\n    return view.file_name() is not None\n\n\ndef is_project():\n    return sublime.active_window().folders() is not None\n\n\ndef get_valid_path(string):\n    return re.sub(\"^[\\\\\\/\\.]*\", \"\", string)\n\n\ndef project_directory(view, project_directory):\n    \"\"\"\n        Get evaluated project folder of file\n\n        Parameters\n        ----\n        view : Sublime.View         -- current view/file\n        project_directory : String  -- directory of current view\n\n        return : Dictionary         -- False or dictionary with folders as\n    \"\"\"\n    directory = {\n        \"settings\": \"\",     # specific project directory (relative) given in settings\n        \"base\": False,      # basepath of project (absolute)\n        \"project\": False    # final project path extended by settings: project_directory\n    }\n\n    if project_directory:\n        # strip any path characters up front, else path.join fails\n        directory[\"settings\"] = get_valid_path(project_directory)\n\n    file_name = view.file_name()\n    for folder in sublime.active_window().folders():\n        # find and build current project directory\n        directory[\"project\"] = os.path.join(folder, directory[\"settings\"])\n        if directory[\"project\"] in file_name:\n            is_project_file = True\n            directory[\"base\"] = folder\n            return directory\n            break\n\n    return False\n\n\ndef sanitize_base_directory(base_directory, project_directory, base_project_directory):\n    \"\"\"\n        validate possible base directory\n    \"\"\"\n    if not base_directory:\n        return \"\"\n\n    base_directory = get_valid_path(base_directory)\n    # base_project_directory    | /path/to/sublime/project\n    # project_directory         | /path/to/sublime/project/project_directory\n    # - path_to_base_directory  | /path/to/sublime/project/base_directory\n    # + path_to_base_directory  | /path/to/sublime/project/project_directory/base_directory\n    path_to_base_directory = os.path.join(project_directory, base_directory)\n    if not os.path.isdir(path_to_base_directory):\n        # BASE_DIRECTORY is NOT a valid folder relative to (possibly modified) project_directory\n        path_to_base_directory = os.path.join(base_project_directory, base_directory)\n\n        if not os.path.isdir(path_to_base_directory):\n            log(\"Error: setting's base_directory is not a valid directory in project\")\n            log(\"=> changing base_directory {0} to ''\".format(base_directory))\n            return \"\"\n\n        elif path_to_base_directory in project_directory:\n            # change BASE_DIRECTORY to be '' since its outside of project directory\n            log(\"Error: setting's base_directory is within project directory\")\n            log(\"=> changing base_directory {0} to ''\".format(base_directory))\n            return \"\"\n\n        else:\n            # change BASE_DIRECTORY to be relative to modified project directory\n            path_to_base_directory = path_to_base_directory.replace(project_directory, \"\")\n            log(\"Error: setting's base_directory is not relative to project directory\")\n            log(\"=> changing base_directory '{0}' to '{1}'\".format(base_directory, path_to_base_directory))\n            return get_valid_path(path_to_base_directory)\n\n    return base_directory\n\n\ndef show_dialog(message, open=False):\n    if open is True:\n       header = \"FuzzyFilePath\\n\\n\"\n       sublime.message_dialog(header + message)\n    return False\n"
  },
  {
    "path": "query.py",
    "content": "\"\"\"\n    Build current query based on received modifiers\n\"\"\"\nimport re\nimport FuzzyFilePath.common.path as Path\nfrom FuzzyFilePath.common.verbose import log\nimport FuzzyFilePath.common.settings as settings\n\n\nstate = {\n\n    \"extensions\": [\"*\"],\n    \"base_directory\": False,    # path of current query\n    \"post_remove_path\": False   # path to remove on post cleanup\n}\n\n# set by insert_path_command: overrideable properties for next query\n# \"extensions\": [],\n# \"filepath_type\": False,\n# \"base_path\": \"\",\n# \"replace_on_insert\": []\noverride = {}\n\n\ndef reset():\n    state[\"extensions\"] = [\"*\"]\n    state[\"base_directory\"] = False\n    state[\"replace_on_insert\"] = []\n    override.clear()\n\n\ndef override_trigger_setting(key, value):\n    override[key] = value\n\n\ndef by_command():\n    return bool(override.get(\"filepath_type\", False))\n\n\ndef get_base_path():\n    return state.get(\"base_directory\")\n\n\ndef get_extensions():\n    return state.get(\"extensions\")\n\n\ndef get_post_remove_path():\n    return state.get(\"post_remove_path\")\n\n\ndef get_needle():\n    return state.get(\"needle\")\n\n\ndef get_replacements():\n    return state.get(\"replace_on_insert\")\n\n\ndef build(needle, trigger, current_folder):\n    \"\"\"\n        updates state object for given trigger. The state object is then used to query the file cache:\n\n        @see completion\n        ProjectManager.search_completions(\n                query.get_needle(),\n                current_file.get_project_directory(),\n                query.get_extensions(),\n                query.get_base_path()\n            )\n    \"\"\"\n    needle = Path.sanitize(needle)\n    needle_is_absolute = Path.is_absolute(needle)\n    needle_is_path = needle_is_absolute or Path.is_relative(needle)\n\n    if not trigger or not (by_command() or (settings.get(\"auto_trigger\") if needle_is_path else trigger.get(\"auto\", settings.get(\"auto_trigger\")))):\n        return False\n\n    \"\"\" Adjust current folder by specified base folder:\n\n        BASE-FOLDER ->  CURRENT_FOLDER\n        ------------------------------------------------------------\n        True            use settings base_directory\n        String          use string as base_directory\n        False           use current file's directory (parameter)\n    \"\"\"\n    base_path = resolve_value(\"base_directory\", trigger, False)\n    if base_path is True:\n        current_folder = settings.get(\"base_directory\")\n    elif base_path:\n        current_folder = Path.sanitize_base_directory(base_path)\n\n    state[\"post_remove_path\"] = current_folder if (base_path and needle_is_absolute) else False\n    state[\"base_directory\"] = current_folder if resolve_path_type(needle, trigger) == \"relative\" else False\n    state[\"replace_on_insert\"] = resolve_value(\"replace_on_insert\", trigger, [])\n    state[\"extensions\"] = resolve_value(\"extensions\", trigger, [\"*\"])\n    state[\"needle\"] = sanitize_needle(needle, current_folder)\n\n    return True\n\n\ndef resolve_path_type(needle, trigger):\n    \"\"\" ^\n        |  Force\n        | --------\n        |  Needle\n        | --------\n        | Trigger?\n    \"\"\"\n    type_of_path = False # OR RELATIVE BY DEFAULT?\n    # test if forced by command\n    if override.get(\"filepath_type\"):\n        type_of_path = override.get(\"filepath_type\")\n    # test path to trigger auto-completion by needle\n    elif not by_command() and trigger.get(\"auto\") is False and settings.get(\"auto_trigger\") and Path.is_absolute(needle):\n        type_of_path = \"absolute\"\n    elif Path.is_absolute(needle):\n        type_of_path = \"absolute\"\n    elif Path.is_relative(needle):\n        type_of_path = \"relative\"\n    elif trigger.get(\"relative\") is True:\n        type_of_path = \"relative\"\n    elif trigger.get(\"relative\") is False:\n        type_of_path = \"absolute\"\n\n    return type_of_path\n\n\ndef resolve_value(key, trigger, default):\n    settings = trigger.get(key, default)\n    return override.get(key, settings)\n\n\ndef sanitize_needle(needle, current_folder):\n    \"\"\"\n        sanitizes requested path and replaces a starting ./ with the current (local) folder\n        returns final needle\n    \"\"\"\n    current_folder = \"\" if not current_folder else current_folder\n    needle = re.sub(\"\\.\\./\", \"\", needle)\n    needle = re.sub(\"[\\\\n\\\\t]\", \"\", needle)\n\n    # remove base path from needle\n    if state.get(\"base_directory\") and isinstance(current_folder, str) and needle.startswith(current_folder):\n        needle = needle[len(state.get(\"base_directory\")):]\n\n    needle = needle.strip()\n\n    if needle.startswith(\"./\"):\n        needle = current_folder + re.sub(\"\\.\\/\", \"\", needle)\n\n    # strip any starting dots or slashes\n    needle = re.sub(\"^[\\.\\/]*\", \"\", needle)\n\n    return needle\n"
  },
  {
    "path": "test/__init__.py",
    "content": ""
  },
  {
    "path": "test/integration/__init__.py",
    "content": ""
  },
  {
    "path": "test/integration/get_context_test.py",
    "content": "\"\"\"\n\tget word at cursor\n\"\"\"\nfrom FuzzyFilePath.test.tools import TestCase\nfrom FuzzyFilePath.expression import get_context\n\n\nclass Test(TestCase):\n\n\t#query\n\tdef should_return_line_as_needle(self, viewHelper):\n\t\tviewHelper.set_line('../line/as/path')\n\t\tviewHelper.move_cursor(0, 12)\n\n\t\tneedle = get_context(viewHelper.view).get(\"needle\")\n\n\t\tself.assert_equal(needle, '../line/as/path')\n\n\tdef should_return_word_as_needle(self, viewHelper):\n\t\tviewHelper.set_line('result = path')\n\t\tviewHelper.move_cursor(0, 12)\n\n\t\tneedle = get_context(viewHelper.view).get(\"needle\")\n\n\t\tself.assert_equal(needle, 'path')\n\n\tdef should_identify_hbs_partials(self, viewHelper):\n\t\tviewHelper.set_line('{{> src/to/folder/file.hbs}}')\n\t\tviewHelper.move_cursor(0, 10)\n\n\t\tcontext = get_context(viewHelper.view)\n\n\t\tself.assert_equal(context.get(\"needle\"), 'src/to/folder/file.hbs')\n\t\tself.assert_equal(context.get(\"region\").a, 4)\n\n\t#prefix\n\tdef should_return_prefix_before_bracket(self, viewHelper):\n\t\tviewHelper.set_line('prefix(file)')\n\t\tviewHelper.move_cursor(0, 8)\n\n\t\tresult = get_context(viewHelper.view).get(\"prefix\")\n\n\t\tself.assert_equal(result, 'prefix')\n\n\tdef should_return_prefix_before_equal(self, viewHelper):\n\t\tviewHelper.set_line('prefix=file')\n\t\tviewHelper.move_cursor(0, 10)\n\n\t\tresult = get_context(viewHelper.view).get(\"prefix\")\n\n\t\tself.assert_equal(result, 'prefix')\n\n\tdef should_return_prefix_before_colon(self, viewHelper):\n\t\tviewHelper.set_line('prefix:file')\n\t\tviewHelper.move_cursor(0, 10)\n\n\t\tresult = get_context(viewHelper.view).get(\"prefix\")\n\n\t\tself.assert_equal(result, 'prefix')\n\n\t#style\n\tdef should_return_style_in_quotes(self, vh):\n\t\tvh.set_line('\"background\": prefix(test)')\n\t\tvh.move_cursor(0, 23)\n\n\t\tresult = get_context(vh.view).get(\"style\")\n\n\t\tself.assert_equal(result, 'background')\n\n\tdef should_return_style_in_tag(self, vh):\n\t\tvh.set_line('<h1 style=\\\"\\'background\\': prefix(\\'test\\')\\\"')\n\t\tvh.move_cursor(0, 36)\n\n\t\tresult = get_context(vh.view)\n\n\t\tself.assert_equal(result.get(\"tagName\"), 'h1')\n\t\tself.assert_equal(result.get(\"style\"), 'background')\n\n\t#tagName\n\tdef should_return_tagName(self, vh):\n\t\tvh.set_line('<div id')\n\t\tvh.move_cursor(0, 10)\n\n\t\tresult = get_context(vh.view).get(\"tagName\")\n\n\t\tself.assert_equal(result, 'div')\n\n\t# blacklist\n\tdef should_not_validate_after_closing_bracket(self, vh):\n\t\tvh.set_line('require (\"require(\"../package\").subPackage');\n\t\tvh.move_cursor(0, 35)\n\n\t\tprint(get_context(vh.view))\n\t\tresult = get_context(vh.view).get(\"valid_needle\")\n\n\t\tself.assert_equal(result, False)\n"
  },
  {
    "path": "test/integration/tests.py",
    "content": "\"\"\"\n\tLoads all integration tests within this folder\n\t- In order to be loaded each test-file must have \"_test\" appended\n\t- Tests are stored and dumped by the file name (without test)\n\"\"\"\nimport os\nimport glob\nimport importlib\n\n\ntests = []\n\n\nmodules = glob.glob(os.path.dirname(__file__) + \"/*_test.py\")\nmodules = [os.path.basename(f)[:-3] for f in modules]\n\nfor f in modules:\n\tlib = importlib.import_module(\"FuzzyFilePath.test.integration.\" + f)\n\ttests.append(lib.Test(f[:-5]))"
  },
  {
    "path": "test/integration/tools.py",
    "content": "# Integration Test Helpers\nimport sublime\n\n\nclass ViewHelper:\n\n\twindow\t= None,\n\tview\t= None,\n\tedit\t= None,\n\n\tdef __init__(self, window, view, edit):\n\t\tself.window = window\n\t\tself.view = view\n\t\tself.edit = edit\n\n\tdef undo(self, count=1):\n\t\twhile count > 0:\n\t\t\tself.view.run_command(\"undo\")\n\t\t\tcount -= 1\n\n\tdef set_js_syntax(self):\n\t\tself.view.set_syntax_file(\"Packages/FuzzyFilePath/test/javascript.tmLanguage\")\n\n\tdef set_line(self, string):\n\t\tself.view.insert(self.edit, 0, string)\n\n\tdef move_cursor(self, line, column):\n\t\tpt = self.view.text_point(line, column)\n\t\tself.view.sel().clear()\n\t\tself.view.sel().add(sublime.Region(pt))\n\t\tself.window.focus_view(self.view)\n\t\tself.view.run_command('_enter_insert_mode', { \"mode\": \"insert\" }) # vintageous\n"
  },
  {
    "path": "test/mock/project/app/boot.js",
    "content": "require(\"./styles/partials/media.scss\")\nrequire(\"./src/common\")\nrequire(\"../bower_components/angular\")\n"
  },
  {
    "path": "test/mock/project/app/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>FuzzyFilePath project mock</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n\n\t\t<link href=\"./styles/styles.css\"></link>\n\n\t\t<style type=\"text/css\">\n\t\t\t@font-face {\n\t\t\t\tfont-family: \"icon\";\n\t\t\t\tsrc: url(./assets/fonts/glyph.ttf),\n\t\t\t\t\turl(\"./assets/fonts/glyph.svg\");\n\t\t\t}\n\t\t</style>\n\n\t\t<script src=\"\" type=\"text/javascript\"></script>\n\t\t<script type=\"text/javascript\" src=\"./mock/project/app/boot.js\"></script>\n\t\t<script type=\"text/javascript\">\n\t\t\tdefine([\"../bower_components/angular/index.js\"], function (angular) {\n\t\t\t\tvar main = require(\"/app/src/main\"),\n\t\t\t\t\tmain_relative = require(\"./src/main\");\n\t\t\t});\n\t\t</script>\n\n\t</head>\n\t<body>\n\n\t\t<div id=\"page\" style=\"background-image: url(./assets/background.png);\">\n\t\t\t<img src=\"../bower_components/lib/assets/logo.jpg\" />\n\t\t</div>\n\n\n\t\t<script src=\"./boot.js\"></script>\n\n\t</body>\n</html>"
  },
  {
    "path": "test/mock/project/app/latex.tex",
    "content": "\\input{\"./assets/header.png\"}"
  },
  {
    "path": "test/mock/project/app/src/coffee/coffee.coffee",
    "content": "require(\"./module.coffee\");\n`import \"./module.coffee\"`\n\n"
  },
  {
    "path": "test/mock/project/app/src/coffee/module.coffee",
    "content": ""
  },
  {
    "path": "test/mock/project/app/src/coffee/other.coffee",
    "content": ""
  },
  {
    "path": "test/mock/project/app/src/common.js",
    "content": ""
  },
  {
    "path": "test/mock/project/app/src/main.js",
    "content": "// main\nvar tools = require(\"./tools\");\nvar media = require(\"../styles/partials/media.scss\");\n"
  },
  {
    "path": "test/mock/project/app/src/tools.js",
    "content": ""
  },
  {
    "path": "test/mock/project/app/src/ts/another.ts",
    "content": ""
  },
  {
    "path": "test/mock/project/app/src/ts/module.ts",
    "content": ""
  },
  {
    "path": "test/mock/project/app/src/ts/other.ts",
    "content": ""
  },
  {
    "path": "test/mock/project/app/src/ts/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"out\": \"built/out.js\", \n        \"sourceMap\": true, \n        \"target\": \"es5\"\n    }, \n    \"files\": []\n}"
  },
  {
    "path": "test/mock/project/app/src/ts/typescript.ts",
    "content": "import * from ./other.ts\nimport {test} from \"../../../bower_components/angular\"\nfrom \"./another.ts\"\nimport \"./typescript\";\n\n// <reference path=\"\" />\n"
  },
  {
    "path": "test/mock/project/app/styles/index.css",
    "content": "\n\n.issue13 {\n\tcomposes: label from './styles.css'\n\tcomposes: label from \"sty\";\n\n\tbackground-color: rgba(0, 0, 0, 0.2);\n}"
  },
  {
    "path": "test/mock/project/app/styles/partials/common.scss",
    "content": "\n@import ./component.scss"
  },
  {
    "path": "test/mock/project/app/styles/partials/media.scss",
    "content": "// SASS\n\n@import \"../../../bower_components/lib/component/component\"\n@import \"/test/mock/project/bower_components/lib/component/component\"\n@import \"./common\""
  },
  {
    "path": "test/mock/project/app/styles/styles.css",
    "content": "* {\n\tbackground-image: url(\"./assets/header.png\");\n}\n\n@font-face {\n\tfont-family:\t\"icon\";\n\tsrc:\t\t\turl(./assets/fonts/glyph.ttf) format(\"truetype\"),\n\t\t\t\t\turl(./assets/fonts/glyph.svg) format(\"svg\");\n}"
  },
  {
    "path": "test/mock/project/bower_components/angular/index.js",
    "content": "// angular/index.js"
  },
  {
    "path": "test/mock/project/bower_components/lib/component/component.html",
    "content": "<template>\n\n\t<h1></h1>\n\n\t<img src=\"\" />\n\n\t<p>\n\t\tsimple text\n\t</p>\n\n</template>"
  },
  {
    "path": "test/mock/project/bower_components/lib/component/component.scss",
    "content": ""
  },
  {
    "path": "test/mock/project/bower_components/lib/component/componentCtrl.js",
    "content": ""
  },
  {
    "path": "test/mock/project/bower_components/lib/component/componentModel.js",
    "content": ""
  },
  {
    "path": "test/mock/project/bower_components/lib/css/index.css",
    "content": ""
  },
  {
    "path": "test/mock/project/bower_components/lib/css/styles.css",
    "content": ""
  },
  {
    "path": "test/mock/project/bower_components/lib/index.js",
    "content": ""
  },
  {
    "path": "test/tools.py",
    "content": "import inspect\n\n\"\"\"\n\tInherit from TestCase to run tests receiving argument `ViewHelper`\n\tConvention: for a test to be executed/recognized its name must contains \"should\"\n\"\"\"\nclass TestCase:\n\n\tunit_test = False\n\n\tdef __init__(self, name):\n\t\tself.name = name\n\t\tself.tests = self.get_tests()\n\t\tself.length = len(self.tests)\n\n\tdef get_tests(self):\n\t\ttests = []\n\t\tmethods = inspect.getmembers(self, predicate=inspect.ismethod)\n\t\tfor name, descr in methods:\n\t\t\tif \"should\" in name:\n\t\t\t\ttests.append(name)\n\t\treturn tests\n\n\tdef assert_equal(self, expect, value):\n\t\tassert expect == value, \"expected '{0}' to equal '{1}'\".format(expect, value)"
  },
  {
    "path": "test/unit/__init__.py",
    "content": ""
  },
  {
    "path": "test/unit/debug_test.py",
    "content": "\"\"\"\n\tget word at cursor\n\"\"\"\nfrom FuzzyFilePath.test.tools import TestCase\nimport FuzzyFilePath.query as Query\n\nvalid_trigger = False\n\nclass Test(TestCase):\n\n\tdef should_set_basepath_to_current_folder(self):\n\t\tQuery.reset()\n\t\tQuery.build(\"\", {\n\t\t\t\"relative\": True,\n\t\t\t\"auto\": True\n\t\t}, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), \"current_folder\")\n\n\tdef should_not_set_basepath_for_absolute_queries(self):\n\t\tQuery.reset()\n\t\tQuery.build(\"\", {\n\t\t\t\"relative\": False,\n\t\t\t\"auto\": True\n\t\t}, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), False)\n"
  },
  {
    "path": "test/unit/get_closest_folder_test.py",
    "content": "from FuzzyFilePath.test.tools import TestCase\nfrom FuzzyFilePath.current_state import get_closest_folder\n\nclass FolderMock:\n\tdef __init__(self, directory):\n\t\tself.directory = directory\n\n\nclass Test(TestCase):\n\n\tdef should_return_nearest_folder(self):\n\t\tfolders = [FolderMock(\"/Users/Spock\"), FolderMock(\"/Users/Spock/Pictures\")]\n\t\tresult = get_closest_folder(\"/Users/Spock/Pictures/Uhura\", folders)\n\n\t\tself.assert_equal(result.directory, \"/Users/Spock/Pictures\")\n\n\tdef should_return_valid_folders_only(self):\n\t\tfolders = [FolderMock(\"/Lib\"), FolderMock(\"/Users\"), FolderMock(\"/Users/Spock/Lib\")]\n\t\tresult = get_closest_folder(\"/Users/Spock\", folders)\n\n\t\tself.assert_equal(result.directory, \"/Users\")\n\n\tdef should_return_false_if_no_directory_found(self):\n\t\tfolders = [FolderMock(\"/Lib\"), FolderMock(\"/Users/Spock\"), FolderMock(\"/Users/Spock/Lib\")]\n\t\tresult = get_closest_folder(\"/Users/Kirk\", folders)\n\n\t\tself.assert_equal(result, False)\n\n"
  },
  {
    "path": "test/unit/query_test.py",
    "content": "\"\"\" get word at cursor \"\"\"\nfrom FuzzyFilePath.test.tools import TestCase\nimport FuzzyFilePath.query as Query\n\n\nvalid_trigger = False\n\nclass Test(TestCase):\n\n\tdef before_each(self):\n\t\tglobal valid_trigger\n\n\t\tQuery.reset()\n\t\tvalid_trigger = {\n\t\t\t\"auto\": True\n\t\t}\n\n\t#validation\n\tdef should_abort_on_empty_values(self):\n\t\tneedle = \"\"\n\t\tcurrent_folder = \"\"\n\t\ttrigger = {}\n\n\t\tvalid = Query.build(needle, trigger, current_folder)\n\n\t\tself.assert_equal(valid, False)\n\n\tdef should_be_valid_for_auto(self):\n\t\tvalid = bool(Query.build(\"\", valid_trigger, \"\"))\n\n\t\tself.assert_equal(valid, True)\n\n\t#base path\n\tdef should_set_basepath_to_current_folder(self):\n\t\tvalid_trigger[\"relative\"] = True\n\n\t\tQuery.build(\"\", valid_trigger, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), \"current_folder\")\n\n\tdef should_set_base_path_for_relative_queries(self):\n\t\tvalid_trigger[\"relative\"] = True\n\t\t# !Potential problem: path requires a trailing slash (os.path.dirname)\n\t\tvalid_trigger[\"base_path\"] = \"base_path/\"\n\n\t\tQuery.build(\"\", valid_trigger, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), \"base_path\")\n\n\tdef should_not_set_basepath_for_absolute_queries(self):\n\t\tvalid_trigger[\"relative\"] = False\n\n\t\tQuery.build(\"\", valid_trigger, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), False)\n\n\t#basepath override by needle\n\tdef should_prefer_needletype_over_relative_setting_01(self):\n\t\tvalid_trigger[\"relative\"] = True\n\n\t\tQuery.build(\"/absolute\", valid_trigger, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), False)\n\n\tdef should_prefer_needletype_over_relative_setting_02(self):\n\t\tvalid_trigger[\"relative\"] = False\n\n\t\tQuery.build(\"../relative\", valid_trigger, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), \"current_folder\")\n\n\t#basepath override by command\n\tdef should_prefer_command_over_rel(self):\n\t\tvalid_trigger[\"relative\"] = True\n\t\tQuery.override_trigger_setting(\"filepath_type\", \"absolute\")\n\n\t\tQuery.build(\"../relative\", valid_trigger, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), False)\n\n\tdef should_prefer_command_over_abs(self):\n\t\tvalid_trigger[\"relative\"] = False\n\t\tQuery.override_trigger_setting(\"filepath_type\", \"relative\")\n\n\t\tQuery.build(\"/absolute\", valid_trigger, \"current_folder\")\n\n\t\tself.assert_equal(Query.get_base_path(), \"current_folder\")\n\n\t#swap rel <-> abs\n\tdef should_transform_rel_to_abs_query(self):\n\t\tQuery.override_trigger_setting(\"filepath_type\", \"absolute\") # set query to be absolute\n\t\tQuery.build(\"../folder/sub\", valid_trigger, \"current_folder\") # but insert relative path\n\n\t\tself.assert_equal(Query.get_needle(), \"folder/sub\")\n\t\tself.assert_equal(Query.get_base_path(), False)\n\n\tdef should_transform_abs_to_rel_query(self):\n\t\tQuery.override_trigger_setting(\"filepath_type\", \"relative\") # set query to be relative\n\t\tQuery.build(\"/folder/sub\", valid_trigger, \"current_folder\") # but insert absolute path\n\n\t\tself.assert_equal(Query.get_needle(), \"folder/sub\")\n\t\tself.assert_equal(Query.get_base_path(), \"current_folder\")\n\n\n"
  },
  {
    "path": "test/unit/tests.py",
    "content": "\"\"\"\n\tLoads all integration tests within this folder\n\t- In order to be loaded each test-file must have \"_test\" appended\n\t- Tests are stored and dumped by the file name (without test)\n\"\"\"\nimport os\nimport glob\nimport importlib\n\n\ntests = []\n\n\nmodules = glob.glob(os.path.dirname(__file__) + \"/*_test.py\")\nmodules = [os.path.basename(f)[:-3] for f in modules]\n\nfor f in modules:\n\tlib = importlib.import_module(\"FuzzyFilePath.test.unit.\" + f)\n\ttests.append(lib.Test(f[:-5]))\n"
  }
]