├── .python-version ├── advanced_new_file ├── __init__.py ├── lib │ ├── __init__.py │ ├── package_resources.py │ └── ushlex.py ├── vcs │ ├── __init__.py │ └── git │ │ ├── __init__.py │ │ └── git_command_base.py ├── platform │ ├── __init__.py │ ├── nix_platform.py │ └── windows_platform.py ├── completions │ ├── __init__.py │ ├── nix_completion.py │ ├── windows_completion.py │ └── completion_base.py ├── commands │ ├── __init__.py │ ├── helper_commands.py │ ├── duplicate_file_base.py │ ├── cut_to_file.py │ ├── delete_file_command.py │ ├── copy_file_command.py │ ├── move_file_command.py │ ├── new_file_command.py │ └── command_base.py └── anf_util.py ├── .gitignore ├── messages ├── 6.txt ├── 1.4.3.txt ├── 1.3.0.txt ├── 1.4.0.txt ├── 1.1.1.txt ├── 1.6.0.txt ├── 1.txt ├── 5.txt ├── 7.txt ├── 1.2.0.txt ├── 8.txt ├── install.txt ├── 1.2.1.txt ├── 1.7.0.txt ├── 2.txt ├── 3.txt ├── 1.5.0.txt ├── 1.5.1.txt ├── 4.txt └── 1.1.0.txt ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── AdvancedNewFile.py ├── messages.json ├── LICENSE.txt ├── Default.sublime-commands ├── Main.sublime-menu ├── CHANGELOG.md ├── AdvancedNewFile.sublime-settings └── README.md /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 -------------------------------------------------------------------------------- /advanced_new_file/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /advanced_new_file/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /advanced_new_file/vcs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /advanced_new_file/platform/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /advanced_new_file/vcs/git/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /advanced_new_file/completions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /messages/6.txt: -------------------------------------------------------------------------------- 1 | - Merge ST2 and ST3 into a single version. 2 | -------------------------------------------------------------------------------- /messages/1.4.3.txt: -------------------------------------------------------------------------------- 1 | - Fix bugs related to ST2 and the cut to file command 2 | -------------------------------------------------------------------------------- /messages/1.3.0.txt: -------------------------------------------------------------------------------- 1 | - Add new command `advanced_new_file_copy` to copy commands. See the wiki for more details about the command. 2 | - General bug fixes. 3 | -------------------------------------------------------------------------------- /messages/1.4.0.txt: -------------------------------------------------------------------------------- 1 | - Add new command `advanced_new_file_cut_to_file` to cut highlighted region to a new file. See the wiki for more details on the command. 2 | -------------------------------------------------------------------------------- /messages/1.1.1.txt: -------------------------------------------------------------------------------- 1 | * Fix issue with update message. If you did not see the 1.1.0 update message, please visit https://github.com/skuroda/Sublime-AdvancedNewFile/blob/master/messages/1.1.0.txt -------------------------------------------------------------------------------- /messages/1.6.0.txt: -------------------------------------------------------------------------------- 1 | * New setting "empty_filename_action". With no input specified, an unnamed new file will be created when executing the new file command. Default value is false. 2 | 3 | Thanks for using AdvancedNewFile. 4 | -------------------------------------------------------------------------------- /messages/1.txt: -------------------------------------------------------------------------------- 1 | The repository has now been transferred to the proper URL. If you experienced any issues while this transition was occuring, my apologies. Please take a look at the README for new enhancments if you have not done so already. -------------------------------------------------------------------------------- /messages/5.txt: -------------------------------------------------------------------------------- 1 | - Rewrite autocomplete functionality to support *nix or windows style completions. Please see the README for details about new settings. 2 | 3 | Bug Fixes: 4 | - Snippets no longer appear when entering completions. 5 | -------------------------------------------------------------------------------- /messages/7.txt: -------------------------------------------------------------------------------- 1 | Enhancements: 2 | - Setting to allow relative paths to be based on the current working directory. Enabled by default. 3 | 4 | Bug Fixes: 5 | - Various fixes for auto completion 6 | - Prompt completion type when using Windows Completion 7 | -------------------------------------------------------------------------------- /messages/1.2.0.txt: -------------------------------------------------------------------------------- 1 | - Miscellaneous bug fixes. 2 | 3 | Enhancements: 4 | - Bug fixes. 5 | - Add "shell_input" setting, which allows for curly brace expansion and escaping of characters, such as spaces. 6 | - Add "file_templates" setting. For more information on this setting, see the README. 7 | -------------------------------------------------------------------------------- /messages/8.txt: -------------------------------------------------------------------------------- 1 | Enhancements: 2 | - Add functionality to rename. Have to manually set up a key binding. 3 | - Add functionality to rename via sidebar context menu. Have to manually set up context menu entry. 4 | - Add default extension setting. 5 | - Allow folder and file permissions to be specified. 6 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | Thank you for installing the AdvancedNewFile plugin. 2 | 3 | For more information please visit https://github.com/skuroda/Sublime-AdvancedNewFile. 4 | 5 | Note you may need to restart Sublime Text after installing this plugin. 6 | 7 | If you have any questions, comments, or run into issues, please let me know! Hope you enjoy the plugin. 8 | 9 | Thank you! -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["super+alt+n"], "command": "advanced_new_file_new"}, 3 | { "keys": ["shift+super+alt+n"], "command": "advanced_new_file_new", "args": {"is_python": true}}, 4 | { 5 | "keys": ["tab"], 6 | "command": "insert", 7 | "args": {"characters": "\t"}, 8 | "context": [{ 9 | "key": "setting.anf_panel" 10 | }] 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["super+alt+n"], "command": "advanced_new_file_new"}, 3 | { "keys": ["shift+super+alt+n"], "command": "advanced_new_file_new", "args": {"is_python": true}}, 4 | { 5 | "keys": ["tab"], 6 | "command": "insert", 7 | "args": {"characters": "\t"}, 8 | "context": [{ 9 | "key": "setting.anf_panel" 10 | }] 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+alt+n"], "command": "advanced_new_file_new"}, 3 | { "keys": ["shift+ctrl+alt+n"], "command": "advanced_new_file_new", "args": {"is_python": true}}, 4 | { 5 | "keys": ["tab"], 6 | "command": "insert", 7 | "args": {"characters": "\t"}, 8 | "context": [{ 9 | "key": "setting.anf_panel" 10 | }] 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /messages/1.2.1.txt: -------------------------------------------------------------------------------- 1 | - Fix issue with shell input breaking auto complete functionality. 2 | - Support template files to be specified relative to the packages directory in the form "Packages/User/sample.template" 3 | - Add new setting "append_extension_on_move". This setting will, when set to true, automatically append the extension of the source file when moving if one is not specified. 4 | -------------------------------------------------------------------------------- /messages/1.7.0.txt: -------------------------------------------------------------------------------- 1 | * New command `AdvancedNewFileNewAtFileCommand`. This command can be mapped to in "Side Bar.sublime-menu" to allow right clicking on files offer to create a new file from that directory, with the specified file as input. 2 | * New property `cursor_before_extension` that will place the cursor before the last occurring dot when an initial input is specified. Default value is false. 3 | 4 | Thank you for using AdvancedNewFile. 5 | -------------------------------------------------------------------------------- /messages/2.txt: -------------------------------------------------------------------------------- 1 | New Features/Enhancements: 2 | 3 | - Add setting to display path for file to be created in status bar. - Default true 4 | - Add setting to set default base path (home, current, top_folder, path). - Default "top_folder" 5 | - Add support for relative paths in alias. 6 | 7 | Please see the README for more information about the new settings. 8 | 9 | Bug Fixes: 10 | - Auto complete bug for files with spaces in their name 11 | -------------------------------------------------------------------------------- /messages/3.txt: -------------------------------------------------------------------------------- 1 | New Features/Enhancements: 2 | 3 | - Add OS Specific Aliases to settings. 4 | - Display an error when attempting to use an invalid alias. 5 | - Display an error when attempting to open a directory in a view. 6 | - Display an error if path creation fails. 7 | 8 | Please see the README for more information about the new settings. 9 | 10 | Bug Fixes: 11 | - Status bar update causing errors when no view is present. 12 | - Specifying absolute paths for Windows produced unexpected behavior. 13 | -------------------------------------------------------------------------------- /advanced_new_file/platform/nix_platform.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | 4 | from ..anf_util import * 5 | 6 | 7 | class NixPlatform(): 8 | def split(self, path): 9 | return None, path 10 | 11 | def parse_nix_path(self, root, path): 12 | return "/", 1 13 | 14 | def get_alias_absolute_path(self, root, path): 15 | if re.search(NIX_ROOT_REGEX, path) is None: 16 | return os.path.join(root, path) 17 | return None 18 | 19 | def is_absolute_path(self, path): 20 | return re.match(NIX_ROOT_REGEX, path) is not None 21 | -------------------------------------------------------------------------------- /messages/1.5.0.txt: -------------------------------------------------------------------------------- 1 | Version 1.5.0 2 | 3 | - Add option to prompt when overrwriting an existing file on move. Default value is false. To toggle this setting, add "warn_overwrite_on_move" to your user settings. 4 | - Add optoin to fallback to project folder when current cannot be resolved as default path. Default value is false. To toggle this setting, add "current_fallback_to_project" to your user settings. 5 | 6 | Thank you for using AdvancedNewFile. If you notice any bugs, please report them on https://github.com/skuroda/Sublime-AdvancedNewFile/issues. 7 | 8 | This update requires a restart of Sublime Text. 9 | -------------------------------------------------------------------------------- /AdvancedNewFile.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sys 3 | 4 | # Clear module cache to force reloading all modules of this package. 5 | 6 | # kiss-reloader: 7 | prefix = __package__ + "." # don't clear the base package 8 | for module_name in [ 9 | module_name 10 | for module_name in sys.modules 11 | if module_name.startswith(prefix) and module_name != __name__ 12 | ]: 13 | del sys.modules[module_name] 14 | prefix = None 15 | 16 | VERSION = int(sublime.version()) 17 | if VERSION > 3000: 18 | from .advanced_new_file.commands import * 19 | else: 20 | from advanced_new_file.commands import * 21 | -------------------------------------------------------------------------------- /messages/1.5.1.txt: -------------------------------------------------------------------------------- 1 | - Add setting "new_file_default_root" to set the default root for new file commands. Default value is "default_root". 2 | - Add setting "rename_file_default_root" to set the default root for rename file commands. Default value is "default_root". Recommended value is "current" 3 | - Add setting "copy_file_default_root" to set the default root for copy file commands. Default value is "default_root". Recommended value is "current" 4 | 5 | Note that after an update occurs, you will need to restart ST. This is due to how plugins are reloaded in ST. 6 | 7 | Thank you for using AdvancedNewFile. Please report any issues to https://github.com/skuroda/Sublime-AdvancedNewFile/issues 8 | -------------------------------------------------------------------------------- /messages/4.txt: -------------------------------------------------------------------------------- 1 | New Features/Enhancements: 2 | 3 | - Enabling "use_cursor_text" will fill with selected regions. The first cursor/region to match the conditions (region or quoted section) will be used. 4 | - Autocompleting with tab where there is only a single completion option will continue completion on subsequent directories. 5 | 6 | Please see the README for more information about the new settings. 7 | 8 | Bug Fixes: 9 | - Properly display completions when using "ctrl+space" to manually display auto complete options. 10 | - Prevent error pop up from occuring when explicitly creating a directory structure. 11 | - Fix bug where using cursor text causes an error. 12 | - Prevent spaces from being inserted when tab is without a possible completion. 13 | -------------------------------------------------------------------------------- /messages/1.1.0.txt: -------------------------------------------------------------------------------- 1 | This version contains a major refactor of the code. If any issues arise, please create an issue on the github (https://github.com/skuroda/Sublime-AdvancedNewFile) page. Thank you for using AdvancedNewFile. 2 | 3 | Changes: 4 | - Rename key `` has been replaced with `` 5 | - Remove setting to display context menu in side bar. Instructions on how to add menu entries have been added to the github wiki. 6 | - Miscellaneous bug fixes. 7 | 8 | Enhancements: 9 | - Add delete command. 10 | - Add rename key `` to display absolute path to the current file. 11 | - Add basic support for git when moving or deleting files. Thank you @btsai 12 | - When moving a file, specifying a directory will move the file to that location with the same name. 13 | 14 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.txt", 3 | "2012.11.08.20.00.00": "messages/1.txt", 4 | "2012.11.12.11.00.00": "messages/2.txt", 5 | "2012.11.26.11.00.00": "messages/3.txt", 6 | "2012.12.17.11.00.00": "messages/4.txt", 7 | "2013.07.29.11.00.00": "messages/5.txt", 8 | "2013.08.05.11.00.00": "messages/6.txt", 9 | "2013.09.03.11.00.00": "messages/7.txt", 10 | "2013.11.05.11.00.00": "messages/8.txt", 11 | "1.0.0": "messages/8.txt", 12 | "1.1.0": "messages/1.1.0.txt", 13 | "1.1.1": "messages/1.1.1.txt", 14 | "1.2.0": "messages/1.2.0.txt", 15 | "1.2.1": "messages/1.2.1.txt", 16 | "1.3.0": "messages/1.3.0.txt", 17 | "1.4.0": "messages/1.4.0.txt", 18 | "1.4.3": "messages/1.4.3.txt", 19 | "1.5.0": "messages/1.5.0.txt", 20 | "1.5.1": "messages/1.5.1.txt", 21 | "1.6.0": "messages/1.6.0.txt", 22 | "1.7.0": "messages/1.7.0.txt" 23 | } 24 | -------------------------------------------------------------------------------- /advanced_new_file/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .helper_commands import AnfReplaceCommand, AdvancedNewFileCommand, AnfRemoveRegionContentAndRegionCommand 2 | from .new_file_command import AdvancedNewFileNew, AdvancedNewFileNewAtCommand, AdvancedNewFileNewAtFileCommand, AdvancedNewFileNewEventListener 3 | from .delete_file_command import AdvancedNewFileDelete 4 | from .cut_to_file import AdvancedNewFileCutToFile 5 | from .move_file_command import AdvancedNewFileMove, AdvancedNewFileMoveAtCommand 6 | from .copy_file_command import AdvancedNewFileCopy, AdvancedNewFileCopyAtCommand 7 | 8 | __all__ = [ 9 | "AnfReplaceCommand", 10 | "AdvancedNewFileCommand", 11 | "AdvancedNewFileNew", 12 | "AdvancedNewFileNewAtCommand", 13 | "AdvancedNewFileNewAtFileCommand", 14 | "AdvancedNewFileNewEventListener", 15 | "AdvancedNewFileMove", 16 | "AdvancedNewFileMoveAtCommand", 17 | "AdvancedNewFileDelete", 18 | "AdvancedNewFileCopy", 19 | "AdvancedNewFileCopyAtCommand", 20 | "AnfRemoveRegionContentAndRegionCommand", 21 | "AdvancedNewFileCutToFile" 22 | ] 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 AdvancedNewFile authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /advanced_new_file/platform/windows_platform.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | 4 | from ..anf_util import * 5 | 6 | 7 | class WindowsPlatform(object): 8 | """docstring for WindowsPlatform""" 9 | def __init__(self, view): 10 | super(WindowsPlatform, self).__init__() 11 | self.view = view 12 | 13 | def split(self, path): 14 | if re.match(WIN_ROOT_REGEX, path): 15 | return path[0:3], path[3:] 16 | else: 17 | return None, path 18 | 19 | def parse_nix_path(self, root, path): 20 | path_offset = 1 21 | match = re.match(r"^/([a-zA-Z])/", path) 22 | if match: 23 | root = "%s:\\" % match.group(1) 24 | path_offset = 3 25 | else: 26 | root, _ = os.path.splitdrive(self.view.file_name()) 27 | root += "\\" 28 | 29 | return root, path_offset 30 | 31 | def get_alias_absolute_path(self, root, path): 32 | if re.search(WIN_ROOT_REGEX, path) is None: 33 | return os.path.join(root, path) 34 | return None 35 | 36 | def is_absolute_path(self, path): 37 | return re.match(WIN_ROOT_REGEX, path) is not None 38 | -------------------------------------------------------------------------------- /advanced_new_file/commands/helper_commands.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | 4 | 5 | class AnfReplaceCommand(sublime_plugin.TextCommand): 6 | def run(self, edit, content): 7 | self.view.replace(edit, sublime.Region(0, self.view.size()), content) 8 | 9 | 10 | class AdvancedNewFileCommand(sublime_plugin.WindowCommand): 11 | def run(self, is_python=False, initial_path=None, 12 | rename=False, rename_file=None): 13 | args = {} 14 | if rename: 15 | args["is_python"] = is_python 16 | args["initial_path"] = initial_path 17 | args["rename_file"] = rename_file 18 | self.window.run_command("advanced_new_file_move", args) 19 | else: 20 | args["is_python"] = is_python 21 | args["initial_path"] = initial_path 22 | self.window.run_command("advanced_new_file_new", args) 23 | 24 | 25 | class AnfRemoveRegionContentAndRegionCommand(sublime_plugin.TextCommand): 26 | def run(self, edit, region_key): 27 | regions = self.view.get_regions(region_key) 28 | for region in regions: 29 | self.view.erase(edit, region) 30 | self.view.erase_regions(region_key) 31 | 32 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "ANF: New File", 4 | "command": "advanced_new_file_new" 5 | }, 6 | { 7 | "caption": "ANF: Rename File", 8 | "command": "advanced_new_file_move" 9 | }, 10 | { 11 | "caption": "ANF: Delete File", 12 | "command": "advanced_new_file_delete" 13 | }, 14 | { 15 | "caption": "ANF: Delete Current File", 16 | "command": "advanced_new_file_delete", 17 | "args": {"current": true} 18 | }, 19 | { 20 | "caption": "ANF: Copy Current File", 21 | "command": "advanced_new_file_copy" 22 | }, 23 | { 24 | "caption": "ANF: Cut to File", 25 | "command": "advanced_new_file_cut_to_file" 26 | }, 27 | { 28 | "caption": "Preferences: AdvancedNewFile Settings", 29 | "command": "edit_settings", 30 | "args": { 31 | "base_file": "${packages}/AdvancedNewFile/AdvancedNewFile.sublime-settings", 32 | "default": "// Settings in here override those in \"AdvancedNewFile/AdvancedNewFile.sublime-settings\".\n{\n\t$0\n}\n" 33 | } 34 | }, 35 | { 36 | "caption": "Preferences: AdvancedNewFile Key Bindings", 37 | "command": "edit_settings", 38 | "args": { 39 | "base_file": "${packages}/AdvancedNewFile/Default ($platform).sublime-keymap", 40 | "default": "[\n\t$0\n]\n" 41 | } 42 | } 43 | ] 44 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "mnemonic": "n", 5 | "id": "preferences", 6 | "children": 7 | [ 8 | { 9 | "caption": "Package Settings", 10 | "mnemonic": "P", 11 | "id": "package-settings", 12 | "children": 13 | [ 14 | { 15 | "caption": "AdvancedNewFile", 16 | "children": 17 | [ 18 | { 19 | "caption": "README", 20 | "command": "open_file", 21 | "args": {"file": "${packages}/AdvancedNewFile/README.md"} 22 | }, 23 | { "caption": "-" }, 24 | { 25 | "caption": "Settings", 26 | "command": "edit_settings", 27 | "args": { 28 | "base_file": "${packages}/AdvancedNewFile/AdvancedNewFile.sublime-settings", 29 | "default": "// Settings in here override those in \"AdvancedNewFile/AdvancedNewFile.sublime-settings\".\n{\n\t$0\n}\n" 30 | } 31 | }, 32 | { 33 | "caption": "Key Bindings", 34 | "command": "edit_settings", 35 | "args": { 36 | "base_file": "${packages}/AdvancedNewFile/Default ($platform).sublime-keymap", 37 | "default": "[\n\t$0\n]\n" 38 | } 39 | } 40 | ] 41 | } 42 | ] 43 | } 44 | ] 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /advanced_new_file/completions/nix_completion.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sublime 4 | 5 | from .completion_base import GenerateCompletionListBase 6 | from ..anf_util import * 7 | 8 | 9 | class NixCompletion(GenerateCompletionListBase): 10 | def __init__(self, command): 11 | super(NixCompletion, self).__init__(command) 12 | 13 | def completion(self, path_in): 14 | pattern = r"(.*[/\\:])(.*)" 15 | 16 | (completion_list, alias_list, 17 | dir_list, file_list) = self.generate_completion_list(path_in) 18 | new_content = path_in 19 | if len(completion_list) > 0: 20 | common = os.path.commonprefix(completion_list) 21 | match = re.match(pattern, path_in) 22 | if match: 23 | new_content = re.sub(pattern, r"\1", path_in) 24 | new_content += common 25 | else: 26 | new_content = common 27 | if len(completion_list) > 1: 28 | dir_list = map(lambda s: s + "/", dir_list) 29 | alias_list = map(lambda s: s + ":", alias_list) 30 | status_message_list = sorted(list(dir_list) + 31 | list(alias_list) + file_list) 32 | sublime.status_message(", ".join(status_message_list)) 33 | else: 34 | if completion_list[0] in alias_list: 35 | new_content += ":" 36 | elif completion_list[0] in dir_list: 37 | new_content += "/" 38 | 39 | return new_content 40 | -------------------------------------------------------------------------------- /advanced_new_file/vcs/git/git_command_base.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import subprocess 3 | import os 4 | from ...anf_util import * 5 | 6 | 7 | # The Code to find git path is copied verbatim from kemayo's Git plugin. 8 | # https://github.com/kemayo/sublime-text-git 9 | def _test_paths_for_executable(paths, test_file): 10 | for directory in paths: 11 | file_path = os.path.join(directory, test_file) 12 | if os.path.exists(file_path) and os.access(file_path, os.X_OK): 13 | return file_path 14 | 15 | 16 | def find_git(): 17 | # It turns out to be difficult to reliably run git, with varying paths 18 | # and subprocess environments across different platforms. So. Let's hack 19 | # this a bit. 20 | # (Yes, I could fall back on a hardline "set your system path properly" 21 | # attitude. But that involves a lot more arguing with people.) 22 | path = os.environ.get('PATH', '').split(os.pathsep) 23 | if os.name == 'nt': 24 | git_cmd = 'git.exe' 25 | else: 26 | git_cmd = 'git' 27 | 28 | git_path = _test_paths_for_executable(path, git_cmd) 29 | 30 | if not git_path: 31 | # /usr/local/bin:/usr/local/git/bin 32 | if os.name == 'nt': 33 | extra_paths = ( 34 | os.path.join(os.environ["ProgramFiles"], "Git", "bin"), 35 | ) 36 | if IS_X64: 37 | extra_paths = extra_paths + ( 38 | os.path.join( 39 | os.environ["ProgramFiles(x86)"], "Git", "bin"), 40 | ) 41 | else: 42 | extra_paths = ( 43 | '/usr/local/bin', 44 | '/usr/local/git/bin', 45 | ) 46 | git_path = _test_paths_for_executable(extra_paths, git_cmd) 47 | return git_path 48 | 49 | GIT = find_git() 50 | 51 | 52 | # Base for git commands 53 | class GitCommandBase(object): 54 | def __init__(self, window): 55 | pass 56 | 57 | # Command specific 58 | def file_tracked_by_git(self, filepath): 59 | git = GIT 60 | if git is not None: 61 | path, file_name = os.path.split(filepath) 62 | return self.run_command( 63 | ["ls-files", file_name, "--error-unmatch"], path) == 0 64 | else: 65 | return False 66 | 67 | def run_command(self, args, cwd): 68 | use_shell = PLATFORM == "windows" 69 | return subprocess.call([GIT] + args, cwd=cwd, shell=use_shell) 70 | -------------------------------------------------------------------------------- /advanced_new_file/completions/windows_completion.py: -------------------------------------------------------------------------------- 1 | import re 2 | from .completion_base import GenerateCompletionListBase 3 | from ..anf_util import * 4 | 5 | 6 | class WindowsCompletion(GenerateCompletionListBase): 7 | def __init__(self, command): 8 | super(WindowsCompletion, self).__init__(command) 9 | self.view = command.view 10 | 11 | def completion(self, path_in): 12 | pattern = r"(.*[/\\:])(.*)" 13 | match = re.match(pattern, path_in) 14 | if "prev_text" in dir(self) and self.prev_text == path_in: 15 | self.offset = (self.offset + 1) % len(self.completion_list) 16 | else: 17 | # Generate new completion list 18 | (self.completion_list, self.alias_list, self.dir_list, 19 | self.file_list) = self.generate_completion_list(path_in) 20 | self.offset = 0 21 | 22 | if len(self.completion_list) == 0: 23 | if match: 24 | self.completion_list = [match.group(2)] 25 | else: 26 | self.completion_list = [path_in] 27 | match = re.match(pattern, path_in) 28 | if match: 29 | completion = self.completion_list[self.offset] 30 | if self.settings.get(COMPLETE_SINGLE_ENTRY_SETTING): 31 | if len(self.completion_list) == 1: 32 | if completion in self.alias_list: 33 | completion += ":" 34 | elif completion in self.dir_list: 35 | completion += "/" 36 | new_content = re.sub(pattern, r"\1", path_in) 37 | new_content += completion 38 | first_token = False 39 | else: 40 | completion = self.completion_list[self.offset] 41 | if self.settings.get(COMPLETE_SINGLE_ENTRY_SETTING): 42 | if len(self.completion_list) == 1: 43 | if completion in self.alias_list: 44 | completion += ":" 45 | elif completion in self.dir_list: 46 | completion += "/" 47 | new_content = completion 48 | first_token = True 49 | 50 | if len(self.completion_list) > 1: 51 | if first_token: 52 | if self.view is not None: 53 | if completion in self.alias_list: 54 | self.view.set_status( 55 | "AdvancedNewFile2", "Alias Completion") 56 | elif completion in self.dir_list: 57 | self.view.set_status( 58 | "AdvancedNewFile2", "Directory Completion") 59 | self.prev_text = new_content 60 | else: 61 | self.prev_text = None 62 | 63 | return new_content 64 | -------------------------------------------------------------------------------- /advanced_new_file/commands/duplicate_file_base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sublime_plugin 4 | 5 | from .command_base import AdvancedNewFileBase 6 | from ..anf_util import * 7 | 8 | class DuplicateFileBase(AdvancedNewFileBase, sublime_plugin.WindowCommand): 9 | 10 | def __init__(self, window): 11 | super(DuplicateFileBase, self).__init__(window) 12 | 13 | def run(self, is_python=False, initial_path=None, rename_file=None): 14 | self.is_python = is_python 15 | self.run_setup() 16 | self.argument_name = rename_file 17 | 18 | path = self.settings.get(self.get_default_setting(), "") 19 | path = self._expand_default_path(path) 20 | 21 | self.duplicate_setup() 22 | self.show_filename_input( 23 | path if len(path) > 0 else self.generate_initial_path()) 24 | 25 | def get_argument_name(self): 26 | return self.argument_name 27 | 28 | def duplicate_setup(self): 29 | view = self.window.active_view() 30 | self.original_name = None 31 | if view is not None: 32 | view_file_name = view.file_name() 33 | if view_file_name: 34 | self.original_name = os.path.basename(view_file_name) 35 | 36 | if self.original_name is None: 37 | self.original_name = "" 38 | 39 | def update_status_message(self, creation_path): 40 | status_prefix = self.get_status_prefix() 41 | if self.is_copy_original_name(creation_path): 42 | creation_path = os.path.join(creation_path, self.original_name) 43 | else: 44 | creation_path = self.try_append_extension(creation_path) 45 | if self.view is not None: 46 | self.view.set_status("AdvancedNewFile", "%s %s " % 47 | (status_prefix, creation_path)) 48 | else: 49 | sublime.status_message("%s %s" % 50 | (status_prefix, creation_path)) 51 | 52 | def is_copy_original_name(self, path): 53 | return (os.path.isdir(path) or 54 | os.path.basename(path) == "") 55 | 56 | def try_append_extension(self, path): 57 | append_setting = self.get_append_extension_setting() 58 | if self.settings.get(append_setting, False): 59 | if not self.is_copy_original_name(path): 60 | _, new_path_extension = os.path.splitext(path) 61 | if new_path_extension == "": 62 | argument_name = self.get_argument_name() 63 | if argument_name is None: 64 | _, extension = os.path.splitext(self.view.file_name()) 65 | else: 66 | _, extension = os.path.splitext(argument_name) 67 | path += extension 68 | return path 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /advanced_new_file/commands/cut_to_file.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import os 4 | 5 | from .command_base import AdvancedNewFileBase 6 | from ..anf_util import * 7 | 8 | 9 | class AdvancedNewFileCutToFile(AdvancedNewFileBase, sublime_plugin.WindowCommand): 10 | def __init__(self, window): 11 | super(AdvancedNewFileCutToFile, self).__init__(window) 12 | 13 | def run(self, is_python=False): 14 | self.is_python = is_python 15 | self.run_setup() 16 | cursors = [] 17 | for cursor in self.view.sel(): 18 | cursors.append(cursor) 19 | self.view.add_regions(REGION_KEY, cursors, "") 20 | 21 | path = self.settings.get(CUT_TO_FILE_DEFAULT_SETTING, "") 22 | path = self._expand_default_path(path) 23 | 24 | self.show_filename_input( 25 | path if len(path) > 0 else self.generate_initial_path()) 26 | 27 | def input_panel_caption(self): 28 | caption = 'Move selection to' 29 | if self.is_python: 30 | caption = '%s (creates __init__.py in new dirs)' % caption 31 | return caption 32 | 33 | def update_status_message(self, creation_path): 34 | status_base = "Cutting selection to" 35 | if self.view is not None: 36 | self.view.set_status("AdvancedNewFile", "%s %s " % 37 | (status_base, creation_path)) 38 | else: 39 | sublime.status_message("%s %s" % (status_base, creation_path)) 40 | 41 | def entered_file_action(self, path): 42 | file_exist = os.path.exists(path) 43 | attempt_open = True 44 | if not file_exist: 45 | attempt_open = self._create_new_file(path) 46 | if attempt_open: 47 | self._open_and_add_content_to_file(path) 48 | self.view.run_command("anf_remove_region_content_and_region", { "region_key": REGION_KEY}) 49 | self.open_file(path) 50 | 51 | 52 | def _create_new_file(self, path): 53 | attempt_open = True 54 | 55 | try: 56 | self.create(path) 57 | except OSError as e: 58 | attempt_open = False 59 | sublime.error_message("Cannot create '" + path + 60 | "'. See console for details") 61 | return attempt_open 62 | 63 | def _open_and_add_content_to_file(self, path): 64 | if os.path.isfile(path): 65 | content = "" 66 | for region in self.view.get_regions(REGION_KEY): 67 | content += self.view.substr(region) 68 | content += "\n" 69 | 70 | with open(path, "a") as file_obj: 71 | file_obj.write(content) 72 | 73 | def is_enabled(self): 74 | view = self.window.active_view() 75 | cursors = view.sel() 76 | for cursor in cursors: 77 | if not cursor.empty(): 78 | return True 79 | 80 | return False 81 | 82 | -------------------------------------------------------------------------------- /advanced_new_file/commands/delete_file_command.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sublime 3 | import sublime_plugin 4 | import sys 5 | from ..anf_util import * 6 | from ..vcs.git.git_command_base import GitCommandBase 7 | from .command_base import AdvancedNewFileBase 8 | 9 | 10 | class AdvancedNewFileDelete(AdvancedNewFileBase, sublime_plugin.WindowCommand, 11 | GitCommandBase): 12 | def __init__(self, window): 13 | super(AdvancedNewFileDelete, self).__init__(window) 14 | 15 | def run(self, current=False): 16 | self.run_setup() 17 | if current: 18 | self._delete_current_file() 19 | else: 20 | self.settings[SHOW_FILES_SETTING] = True 21 | self.show_filename_input("") 22 | 23 | def input_panel_caption(self): 24 | return 'Enter path of file to delete' 25 | 26 | def entered_file_action(self, path): 27 | self._delete_file(path) 28 | 29 | def update_status_message(self, creation_path): 30 | if self.view is not None: 31 | self.view.set_status("AdvancedNewFile", "Delete file at %s " % 32 | creation_path) 33 | else: 34 | sublime.status_message("Delete file at %s" % creation_path) 35 | 36 | def _git_rm(self, filepath): 37 | path, filename = os.path.split(filepath) 38 | result = self.run_command(["rm", filename], path) 39 | if result != 0: 40 | sublime.error_message("Git remove of %s failed." % (filepath)) 41 | 42 | def _delete_current_file(self): 43 | filepath = self.window.active_view().file_name() 44 | self._delete_file(filepath) 45 | 46 | def _delete_file(self, filepath): 47 | if not filepath: 48 | return 49 | elif not os.path.isfile(filepath): 50 | sublime.error_message("%s is not a file" % filepath) 51 | return 52 | 53 | if not sublime.ok_cancel_dialog("Delete this file?\n%s" % filepath): 54 | return 55 | 56 | vcs_tracking = (self.file_tracked_by_git(filepath) and 57 | self.settings.get(VCS_MANAGEMENT_SETTING)) 58 | 59 | self.close_view(filepath) 60 | 61 | if vcs_tracking: 62 | self._git_rm(filepath) 63 | else: 64 | self._execute_delete_file(filepath) 65 | 66 | self.refresh_sidebar() 67 | 68 | def _execute_delete_file(self, filepath): 69 | if IS_ST3 and self._side_bar_enhancements_installed(): 70 | import Default.send2trash as send2trash 71 | send2trash.send2trash(filepath) 72 | else: 73 | self.window.run_command("delete_file", {"files": [filepath]}) 74 | 75 | def _side_bar_enhancements_installed(self): 76 | return "SideBarEnhancements.SideBar" in sys.modules 77 | 78 | def close_view(self, filepath): 79 | file_view = self._find_open_file(filepath) 80 | 81 | if file_view is not None: 82 | file_view.set_scratch(True) 83 | self.window.focus_view(file_view) 84 | self.window.run_command("close") 85 | -------------------------------------------------------------------------------- /advanced_new_file/completions/completion_base.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | from ..anf_util import * 4 | 5 | 6 | class GenerateCompletionListBase(object): 7 | """docstring for GenerateCompletionListBase""" 8 | def __init__(self, command): 9 | super(GenerateCompletionListBase, self).__init__() 10 | self.top_level_split_char = ":" 11 | self.command = command 12 | self.aliases = command.aliases 13 | self.settings = command.settings 14 | 15 | def is_home(self, path): 16 | return re.match(r"^~[/\\]", path) 17 | 18 | def is_alias(self, path): 19 | return self.top_level_split_char in path 20 | 21 | def generate_completion_list(self, path_in): 22 | alias_list = [] 23 | dir_list = [] 24 | file_list = [] 25 | if self.is_alias(path_in) or self.is_home(path_in): 26 | pass 27 | else: 28 | directory, filename = os.path.split(path_in) 29 | if len(directory) == 0: 30 | alias_list += self.generate_alias_auto_complete(filename) 31 | alias_list += self.generate_project_auto_complete(filename) 32 | base, path = self.command.split_path(path_in) 33 | full_path = generate_creation_path(self.settings, base, path) 34 | 35 | directory, filename = os.path.split(full_path) 36 | if os.path.isdir(directory): 37 | for d in os.listdir(directory): 38 | full_path = os.path.join(directory, d) 39 | if os.path.isdir(full_path): 40 | is_file = False 41 | elif self.settings.get(SHOW_FILES_SETTING): 42 | is_file = True 43 | else: 44 | continue 45 | 46 | if self.compare_entries(d, filename): 47 | if is_file: 48 | file_list.append(d) 49 | else: 50 | dir_list.append(d) 51 | 52 | completion_list = alias_list + dir_list + file_list 53 | 54 | return sorted(completion_list), alias_list, dir_list, file_list 55 | 56 | def generate_project_auto_complete(self, base): 57 | folder_data = get_project_folder_data( 58 | self.settings.get(USE_FOLDER_NAME_SETTING)) 59 | if len(folder_data) > 1: 60 | folders = [x[0] for x in folder_data] 61 | return self.generate_auto_complete(base, folders) 62 | return [] 63 | 64 | def generate_alias_auto_complete(self, base): 65 | return self.generate_auto_complete(base, self.aliases) 66 | 67 | def generate_auto_complete(self, base, iterable_var): 68 | sugg = [] 69 | for entry in iterable_var: 70 | compare_entry = entry 71 | compare_base = base 72 | if self.settings.get(IGNORE_CASE_SETTING): 73 | compare_entry = compare_entry.lower() 74 | compare_base = compare_base.lower() 75 | 76 | if self.compare_entries(compare_entry, compare_base): 77 | if entry not in sugg: 78 | sugg.append(entry) 79 | return sugg 80 | 81 | def compare_entries(self, compare_entry, compare_base): 82 | if self.settings.get(IGNORE_CASE_SETTING): 83 | compare_entry = compare_entry.lower() 84 | compare_base = compare_base.lower() 85 | 86 | return compare_entry.startswith(compare_base) 87 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for AdvancedNewFile 2 | Information from future releases can be seen by browsing the `messages` directory for the version. 3 | 4 | - 9 November 2013 / 1.0.0 5 | - Switch to semantic versioning. 6 | - Rename default with special token for current file. 7 | - README updates. 8 | 9 | - 4 October 2013 10 | - Rename file using side bar context menu. 11 | - Set folder and file permissions for created folders and files. 12 | 13 | - 23 September 2013 14 | - Support renaming current file. 15 | 16 | - 9 September 2013 17 | - Bug fix for folder creation. 18 | - Bug fix for permission issue. 19 | - Add default extension setting. 20 | 21 | - 2 September 2013 22 | - Add setting to begin all relative paths from current working directory if available. 23 | 24 | - 14 August 2013 25 | - Prompt completion type for first token when using Windows completion. 26 | - Fix bug with path autocompletion. 27 | - Fix bug for tab completion with no view. 28 | 29 | - 27 July 2013 30 | - Rewrite autocomplete functionality. 31 | - Bug Fixes 32 | - Snippets no longer appear when entering completions. 33 | 34 | - 22 April 2013 35 | - Add option to refresh sidebar after creating a file. 36 | - Add side bar context menu. 37 | - Bug Fixes 38 | - Multiple autocomplete issues. 39 | - Creation of __init__.py files. 40 | - Filling text with cursor values. 41 | 42 | - 2 February 2013 43 | - Update to be compatible with Sublime Text 3. 44 | 45 | - 14 January 2013 46 | - Add `alias_root` setting, used with aliases with relative paths. 47 | - Add setting to allow user to specify which folder from the project should be used. 48 | - Bug fixes 49 | - Do not require relative alias paths to begin with `./` 50 | - Prevent duplicate entries from appearing in auto complete list. 51 | 52 | - 17 December 2012 53 | - Allow selected text to fill entry window. 54 | - Basic work for continued autocompletion (Only applies if there is a single completion option) 55 | - Bug fixes 56 | - Properly display completions when using "ctrl+space" to manually display auto complete options. 57 | - Prevent error pop up from occuring when explicitly creating a directory structure. 58 | - Fix bug where using cursor text causes an error. 59 | - Prevent spaces from being inserted when tab is without a possible completion. 60 | 61 | - 26 November 2012 62 | - Add setting to display path for file to be created in status bar. 63 | - Add setting to set default base path (home, current, top_folder, path). 64 | - Add setting to ignore case for auto completion. 65 | - Add support for relative paths in alias. 66 | - Add OS Specific Aliases to settings. 67 | - Display an error when attempting to use an invalid alias. 68 | - Display an error when attempting to open a directory in a view. 69 | - Display an error if path creation fails. 70 | - Bug Fixes 71 | - Auto complete bug for files with spaces in their name 72 | - Status bar update causing errors when no view is present. 73 | - Specifying absolute paths for Windows produced unexpected behavior. 74 | 75 | - 30 October 2012 76 | - Initial work for tab autocompletion 77 | - Files created when path entered 78 | - Add setting to fill with a default value. 79 | - Setting to prefill entry box with text in quotes. 80 | - Add setting to display non directory files 81 | - Add user defined aliases. 82 | - Bug fixes. 83 | - Prevent buffer from being opened when a directory is specified. 84 | 85 | - 20 April 2012 86 | - Add ability to specify top level folders 87 | - Bug fixes 88 | - Fix Windows keybindings 89 | - Fix save issue on Windows 90 | 91 | - 29 October 2011 92 | - Initial release of AdvancedNewFile plugin 93 | -------------------------------------------------------------------------------- /advanced_new_file/commands/copy_file_command.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import shutil 4 | import sublime_plugin 5 | 6 | from .duplicate_file_base import DuplicateFileBase 7 | from ..anf_util import * 8 | 9 | class AdvancedNewFileCopy(DuplicateFileBase): 10 | def __init__(self, window): 11 | super(AdvancedNewFileCopy, self).__init__(window) 12 | 13 | def get_default_setting(self): 14 | return COPY_DEFAULT_SETTING 15 | 16 | def input_panel_caption(self): 17 | caption = 'Enter a new path to copy file' 18 | if self.is_python: 19 | caption = '%s (creates __init__.py in new dirs)' % caption 20 | return caption 21 | 22 | def entered_file_action(self, path): 23 | attempt_copy = True 24 | path = self.try_append_extension(path) 25 | 26 | directory = os.path.dirname(path) 27 | if not os.path.exists(directory): 28 | try: 29 | self.create_folder(directory) 30 | except OSError as e: 31 | attempt_copy = False 32 | sublime.error_message("Cannot create '" + path + "'." + 33 | " See console for details") 34 | print("Exception: %s '%s'" % (e.strerror, e.filename)) 35 | 36 | if attempt_copy: 37 | copy_success, new_file = self._copy_file(path) 38 | if copy_success: 39 | self.open_file(new_file) 40 | 41 | def _try_prompt_if_dest_exists(self, target): 42 | if self.settings.get(WARN_OVERWRITE_ON_COPY_SETTING, False): 43 | if (os.path.exists(target)): 44 | return sublime.ok_cancel_dialog(target + " already exists. " + 45 | "Copy will overwrite " + 46 | "existing file. Continue?") 47 | 48 | def _copy_file(self, path): 49 | if os.path.isdir(path) or re.search(r"(/|\\)$", path): 50 | # use original name if a directory path has been passed in. 51 | path = os.path.join(path, self.original_name) 52 | 53 | window = self.window 54 | copied = True 55 | if not self._try_prompt_if_dest_exists(path): 56 | return (False, path) 57 | if self.get_argument_name(): 58 | self.copy_from_argument(path) 59 | elif self.view is not None: 60 | self.copy_from_view(self.view, path) 61 | else: 62 | copied = False 63 | sublime.error_message("Unable to copy file. No source file to move") 64 | 65 | return (copied, path) 66 | 67 | def copy_from_view(self, source_view, target): 68 | source = source_view.file_name() 69 | if source is None: 70 | self.copy_file_from_buffer(source_view, target) 71 | else: 72 | self.copy_file_from_disk(source, target) 73 | 74 | def copy_file_from_buffer(self, source_view, target): 75 | content = self.view.substr(sublime.Region(0, self.view.size())) 76 | with open(target, "w") as file_obj: 77 | file_obj.write(content) 78 | 79 | def copy_file_from_disk(self, source, target): 80 | self._copy_file_action(source, target) 81 | 82 | def copy_from_argument(self, target): 83 | source_name = self.get_argument_name() 84 | file_view = self._find_open_file(source_name) 85 | 86 | self._copy_file_action(source_name, target) 87 | 88 | 89 | def _copy_file_action(self, source, target): 90 | shutil.copy(source, target) 91 | 92 | def get_status_prefix(self): 93 | return "Copying file to" 94 | 95 | def get_append_extension_setting(self): 96 | return APPEND_EXTENSION_ON_COPY_SETTING 97 | 98 | def get_default_root_setting(self): 99 | return COPY_FILE_DEFAULT_ROOT_SETTING 100 | 101 | 102 | class AdvancedNewFileCopyAtCommand(sublime_plugin.WindowCommand): 103 | def run(self, files): 104 | if len(files) != 1: 105 | return 106 | self.window.run_command("advanced_new_file_copy", 107 | {"rename_file": files[0]}) 108 | 109 | def is_visible(self, files): 110 | return len(files) == 1 111 | -------------------------------------------------------------------------------- /advanced_new_file/commands/move_file_command.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import shutil 4 | import sublime_plugin 5 | 6 | from .duplicate_file_base import DuplicateFileBase 7 | from ..anf_util import * 8 | from ..vcs.git.git_command_base import GitCommandBase 9 | 10 | 11 | class AdvancedNewFileMove(DuplicateFileBase, GitCommandBase): 12 | def __init__(self, window): 13 | super(AdvancedNewFileMove, self).__init__(window) 14 | 15 | def get_default_setting(self): 16 | return RENAME_DEFAULT_SETTING 17 | 18 | def input_panel_caption(self): 19 | caption = 'Enter a new path for current file' 20 | if self.is_python: 21 | caption = '%s (creates __init__.py in new dirs)' % caption 22 | return caption 23 | 24 | def _git_mv(self, from_filepath, to_filepath): 25 | path, filename = os.path.split(from_filepath) 26 | args = ["mv", filename, to_filepath] 27 | result = self.run_command(args, path) 28 | if result != 0: 29 | sublime.error_message("Git move of %s to %s failed" % 30 | (from_filepath, to_filepath)) 31 | 32 | def entered_file_action(self, path): 33 | attempt_open = True 34 | path = self.try_append_extension(path) 35 | 36 | directory = os.path.dirname(path) 37 | if not os.path.exists(directory): 38 | try: 39 | self.create_folder(directory) 40 | except OSError as e: 41 | attempt_open = False 42 | sublime.error_message("Cannot create '" + path + "'." + 43 | " See console for details") 44 | print("Exception: %s '%s'" % (e.strerror, e.filename)) 45 | 46 | if attempt_open: 47 | self._rename_file(path) 48 | 49 | def get_append_extension_setting(self): 50 | return APPEND_EXTENSION_ON_MOVE_SETTING 51 | 52 | def _rename_file(self, file_path): 53 | if os.path.isdir(file_path) or re.search(r"(/|\\)$", file_path): 54 | # use original name if a directory path has been passed in. 55 | file_path = os.path.join(file_path, self.original_name) 56 | 57 | window = self.window 58 | rename_filename = self.get_argument_name() 59 | if not self._try_prompt_if_dest_exists(file_path): 60 | return 61 | if rename_filename: 62 | self.move_from_argument(rename_filename, file_path) 63 | elif self.view is not None: 64 | self.move_from_view(self.view, file_path) 65 | else: 66 | sublime.error_message("Unable to move file. No file to move.") 67 | 68 | def move_from_argument(self, source, target): 69 | file_view = self._find_open_file(source) 70 | if file_view is not None: 71 | self.view.run_command("save") 72 | window.focus_view(file_view) 73 | window.run_command("close") 74 | 75 | self._move_action(source, target) 76 | 77 | if file_view is not None: 78 | self.open_file(target) 79 | 80 | def move_from_view(self, source_view, target): 81 | source = source_view.file_name() 82 | if source is None: 83 | self.move_file_from_buffer(source_view, target) 84 | else: 85 | self.move_file_from_disk(source, target) 86 | self.open_file(target) 87 | 88 | def _try_prompt_if_dest_exists(self, target): 89 | if self.settings.get(WARN_OVERWRITE_ON_MOVE_SETTING, False): 90 | if (os.path.exists(target)): 91 | return sublime.ok_cancel_dialog(target + " already exists. " + 92 | "Move will overwrite " + 93 | "existing file. Continue?") 94 | 95 | return True 96 | 97 | def move_file_from_disk(self, source, target): 98 | window = self.window 99 | self.view.run_command("save") 100 | window.focus_view(self.view) 101 | window.run_command("close") 102 | self._move_action(source, target) 103 | 104 | def move_file_from_buffer(self, source_view, target): 105 | window = self.window 106 | content = self.view.substr(sublime.Region(0, self.view.size())) 107 | self.view.set_scratch(True) 108 | window.focus_view(self.view) 109 | window.run_command("close") 110 | with open(target, "w") as file_obj: 111 | file_obj.write(content) 112 | 113 | def _move_action(self, from_file, to_file): 114 | tracked_by_git = self.file_tracked_by_git(from_file) 115 | if tracked_by_git and self.settings.get(VCS_MANAGEMENT_SETTING): 116 | self._git_mv(from_file, to_file) 117 | else: 118 | shutil.move(from_file, to_file) 119 | 120 | def get_status_prefix(self): 121 | return "Moving file to" 122 | 123 | def get_default_root_setting(self): 124 | return RENAME_FILE_DEFAULT_ROOT_SETTING 125 | 126 | def generate_initial_path(self): 127 | if self.settings.get("autofill_path_the_existing"): 128 | file_path = self.window.active_view().file_name()[1:] 129 | base, _ = self.split_path(file_path) 130 | return file_path[len(base):] 131 | else: 132 | return super(self.__class__, self).generate_initial_path() 133 | 134 | 135 | class AdvancedNewFileMoveAtCommand(sublime_plugin.WindowCommand): 136 | def run(self, files): 137 | if len(files) != 1: 138 | return 139 | self.window.run_command("advanced_new_file_move", 140 | {"rename_file": files[0]}) 141 | 142 | def is_visible(self, files): 143 | return len(files) == 1 144 | -------------------------------------------------------------------------------- /advanced_new_file/anf_util.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import re 3 | import os 4 | 5 | ALIAS_SETTING = "alias" 6 | DEFAULT_INITIAL_SETTING = "default_initial" 7 | AUTOFILL_RENAME = "autofill_path_the_existing" 8 | USE_CURSOR_TEXT_SETTING = "use_cursor_text" 9 | SHOW_FILES_SETTING = "show_files" 10 | SHOW_PATH_SETTING = "show_path" 11 | DEFAULT_ROOT_SETTING = "default_root" 12 | DEFAULT_PATH_SETTING = "default_path" 13 | DEFAULT_FOLDER_INDEX_SETTING = "default_folder_index" 14 | OS_SPECIFIC_ALIAS_SETTING = "os_specific_alias" 15 | IGNORE_CASE_SETTING = "ignore_case" 16 | ALIAS_ROOT_SETTING = "alias_root" 17 | ALIAS_PATH_SETTING = "alias_path" 18 | ALIAS_FOLDER_INDEX_SETTING = "alias_folder_index" 19 | DEBUG_SETTING = "debug" 20 | AUTO_REFRESH_SIDEBAR_SETTING = "auto_refresh_sidebar" 21 | COMPLETION_TYPE_SETTING = "completion_type" 22 | COMPLETE_SINGLE_ENTRY_SETTING = "complete_single_entry" 23 | USE_FOLDER_NAME_SETTING = "use_folder_name" 24 | RELATIVE_FROM_CURRENT_SETTING = "relative_from_current" 25 | DEFAULT_EXTENSION_SETTING = "default_extension" 26 | FILE_PERMISSIONS_SETTING = "file_permissions" 27 | FOLDER_PERMISSIONS_SETTING = "folder_permissions" 28 | RENAME_DEFAULT_SETTING = "rename_default" 29 | VCS_MANAGEMENT_SETTING = "vcs_management" 30 | FILE_TEMPLATES_SETTING = "file_templates" 31 | SHELL_INPUT_SETTING = "shell_input" 32 | APPEND_EXTENSION_ON_MOVE_SETTING = "append_extension_on_move" 33 | RELATIVE_FALLBACK_INDEX_SETTING = "relative_fallback_index" 34 | APPEND_EXTENSION_ON_COPY_SETTING = "append_extension_on_copy" 35 | COPY_DEFAULT_SETTING = "copy_default" 36 | CUT_TO_FILE_DEFAULT_SETTING = "cut_to_file_default" 37 | CURRENT_FALLBACK_TO_PROJECT_SETTING = "current_fallback_to_project" 38 | WARN_OVERWRITE_ON_COPY_SETTING = "warn_overwrite_on_copy" 39 | WARN_OVERWRITE_ON_MOVE_SETTING = "warn_overwrite_on_move" 40 | NEW_FILE_DEFAULT_ROOT_SETTING = "new_file_default_root" 41 | RENAME_FILE_DEFAULT_ROOT_SETTING = "rename_file_default_root" 42 | COPY_FILE_DEFAULT_ROOT_SETTING = "copy_file_default_root" 43 | DEFAULT_NEW_FILE = "empty_filename_action" 44 | CURSOR_BEFORE_EXTENSION_SETTING = "cursor_before_extension" 45 | 46 | 47 | SETTINGS = [ 48 | ALIAS_SETTING, 49 | DEFAULT_INITIAL_SETTING, 50 | AUTOFILL_RENAME, 51 | USE_CURSOR_TEXT_SETTING, 52 | SHOW_FILES_SETTING, 53 | SHOW_PATH_SETTING, 54 | DEFAULT_ROOT_SETTING, 55 | DEFAULT_PATH_SETTING, 56 | DEFAULT_FOLDER_INDEX_SETTING, 57 | OS_SPECIFIC_ALIAS_SETTING, 58 | IGNORE_CASE_SETTING, 59 | ALIAS_ROOT_SETTING, 60 | ALIAS_PATH_SETTING, 61 | ALIAS_FOLDER_INDEX_SETTING, 62 | DEBUG_SETTING, 63 | AUTO_REFRESH_SIDEBAR_SETTING, 64 | COMPLETION_TYPE_SETTING, 65 | COMPLETE_SINGLE_ENTRY_SETTING, 66 | USE_FOLDER_NAME_SETTING, 67 | RELATIVE_FROM_CURRENT_SETTING, 68 | DEFAULT_EXTENSION_SETTING, 69 | FILE_PERMISSIONS_SETTING, 70 | FOLDER_PERMISSIONS_SETTING, 71 | RENAME_DEFAULT_SETTING, 72 | VCS_MANAGEMENT_SETTING, 73 | FILE_TEMPLATES_SETTING, 74 | SHELL_INPUT_SETTING, 75 | APPEND_EXTENSION_ON_MOVE_SETTING, 76 | RELATIVE_FALLBACK_INDEX_SETTING, 77 | APPEND_EXTENSION_ON_COPY_SETTING, 78 | COPY_DEFAULT_SETTING, 79 | CUT_TO_FILE_DEFAULT_SETTING, 80 | CURRENT_FALLBACK_TO_PROJECT_SETTING, 81 | WARN_OVERWRITE_ON_COPY_SETTING, 82 | WARN_OVERWRITE_ON_MOVE_SETTING, 83 | NEW_FILE_DEFAULT_ROOT_SETTING, 84 | RENAME_FILE_DEFAULT_ROOT_SETTING, 85 | COPY_FILE_DEFAULT_ROOT_SETTING, 86 | DEFAULT_NEW_FILE, 87 | CURSOR_BEFORE_EXTENSION_SETTING 88 | ] 89 | 90 | NIX_ROOT_REGEX = r"^/" 91 | WIN_ROOT_REGEX = r"[a-zA-Z]:(/|\\)" 92 | HOME_REGEX = r"^~" 93 | PLATFORM = sublime.platform() 94 | TOP_LEVEL_SPLIT_CHAR = ":" 95 | IS_ST3 = int(sublime.version()) > 3000 96 | IS_X64 = sublime.arch() == "x64" 97 | REGION_KEY = "anf_cut_to_file" 98 | 99 | 100 | def generate_creation_path(settings, base, path, append_extension=False): 101 | if PLATFORM == "windows": 102 | if not re.match(WIN_ROOT_REGEX, base): 103 | if IS_ST3: 104 | drive, _ = os.path.splitdrive(base) 105 | else: 106 | drive, _ = os.path.splitunc(base) 107 | if len(drive) == 0: 108 | return base + TOP_LEVEL_SPLIT_CHAR + path 109 | else: 110 | return os.path.join(base, path) 111 | else: 112 | if not re.match(NIX_ROOT_REGEX, base): 113 | return base + TOP_LEVEL_SPLIT_CHAR + path 114 | 115 | tokens = re.split(r"[/\\]", base) + re.split(r"[/\\]", path) 116 | if tokens[0] == "": 117 | tokens[0] = "/" 118 | if PLATFORM == "windows": 119 | tokens[0] = base[0:3] 120 | 121 | full_path = os.path.abspath(os.path.join(*tokens)) 122 | if re.search(r"[/\\]$", path) or len(path) == 0: 123 | full_path += os.path.sep 124 | elif re.search(r"\.", tokens[-1]): 125 | if re.search(r"\.$", tokens[-1]): 126 | full_path += "." 127 | elif append_extension: 128 | filename = os.path.basename(full_path) 129 | if not os.path.exists(full_path): 130 | full_path += settings.get(DEFAULT_EXTENSION_SETTING) 131 | return full_path 132 | 133 | 134 | def get_settings(view): 135 | settings = sublime.load_settings("AdvancedNewFile.sublime-settings") 136 | project_settings = {} 137 | local_settings = {} 138 | if view is not None: 139 | project_settings = view.settings().get('AdvancedNewFile', {}) 140 | 141 | for setting in SETTINGS: 142 | local_settings[setting] = settings.get(setting) 143 | 144 | if type(project_settings) != dict: 145 | print("Invalid type %s for project settings" % type(project_settings)) 146 | return local_settings 147 | 148 | for key in project_settings: 149 | if key in SETTINGS: 150 | if key == "alias": 151 | if IS_ST3: 152 | local_settings[key] = dict( 153 | local_settings[key].items() | 154 | project_settings.get(key).items() 155 | ) 156 | else: 157 | local_settings[key] = dict( 158 | local_settings[key].items() + 159 | project_settings.get(key).items() 160 | ) 161 | else: 162 | local_settings[key] = project_settings[key] 163 | else: 164 | print("AdvancedNewFile[Warning]: Invalid key " + 165 | "'%s' in project settings.", key) 166 | 167 | return local_settings 168 | 169 | 170 | def get_project_folder_data(use_folder_name): 171 | folders = [] 172 | folder_entries = [] 173 | window = sublime.active_window() 174 | project_folders = window.folders() 175 | 176 | if IS_ST3: 177 | project_data = window.project_data() 178 | 179 | if project_data is not None: 180 | if use_folder_name: 181 | for folder in project_data.get("folders", []): 182 | folder_entries.append({}) 183 | else: 184 | folder_entries = project_data.get("folders", []) 185 | else: 186 | for folder in project_folders: 187 | folder_entries.append({}) 188 | for index in range(len(folder_entries)): 189 | folder_path = project_folders[index] 190 | folder_entry = folder_entries[index] 191 | if "name" in folder_entry: 192 | folders.append((folder_entry["name"], folder_path)) 193 | else: 194 | folders.append((os.path.basename(folder_path), folder_path)) 195 | 196 | return folders 197 | -------------------------------------------------------------------------------- /advanced_new_file/commands/new_file_command.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import os 4 | import re 5 | import xml.etree.ElementTree as ET 6 | 7 | from .command_base import AdvancedNewFileBase 8 | from ..lib.package_resources import get_resource 9 | from ..anf_util import * 10 | 11 | 12 | class AdvancedNewFileNew(AdvancedNewFileBase, sublime_plugin.WindowCommand): 13 | def __init__(self, window): 14 | super(AdvancedNewFileNew, self).__init__(window) 15 | 16 | def run(self, is_python=False, initial_path=None): 17 | self.is_python = is_python 18 | self.run_setup() 19 | self.show_filename_input(self.generate_initial_path(initial_path)) 20 | 21 | def input_panel_caption(self): 22 | caption = 'Enter a path for a new file' 23 | if self.is_python: 24 | caption = '%s (creates __init__.py in new dirs)' % caption 25 | return caption 26 | 27 | def entered_file_action(self, path): 28 | if self.settings.get(SHELL_INPUT_SETTING, False): 29 | self.multi_file_action(self.curly_brace_expansion(path)) 30 | else: 31 | self.single_file_action(path) 32 | 33 | def multi_file_action(self, paths): 34 | for path in paths: 35 | self.single_file_action(path, False) 36 | 37 | def single_file_action(self, path, apply_template=True): 38 | attempt_open = True 39 | file_exist = os.path.exists(path) 40 | if not file_exist: 41 | try: 42 | self.create(path) 43 | except OSError as e: 44 | attempt_open = False 45 | sublime.error_message("Cannot create '" + path + 46 | "'. See console for details") 47 | print("Exception: %s '%s'" % (e.strerror, e.filename)) 48 | if attempt_open and os.path.isfile(path): 49 | file_view = self.open_file(path) 50 | if not file_exist and apply_template: 51 | file_view.settings().set("_anf_new", True) 52 | 53 | def curly_brace_expansion(self, path): 54 | if not self.curly_braces_balanced(path) or "{" not in path: 55 | return [path] 56 | paths = self.expand_single_curly_brace(path) 57 | 58 | while True: 59 | path_len = len(paths) 60 | temp_paths = [] 61 | for expanded_path in paths: 62 | temp_paths.append(self.expand_single_curly_brace(expanded_path)) 63 | paths = self.flatten_list(temp_paths) 64 | if path_len == len(paths): 65 | break 66 | 67 | return self.flatten_list(paths) 68 | 69 | def flatten_list(self, initial_list): 70 | if isinstance(initial_list, list): 71 | return [flattened for entry in initial_list for flattened in self.flatten_list(entry)] 72 | else: 73 | return [initial_list] 74 | 75 | # Assumes curly braces are balanced 76 | def expand_single_curly_brace(self, path): 77 | if "{" not in path: 78 | return [path] 79 | start, end = self.curly_brace_indecies(path) 80 | all_tokens = path[start + 1:end] 81 | paths = [] 82 | for token in all_tokens.split(","): 83 | temp = path[0:start] + token + path[end + 1:] 84 | paths.append(temp) 85 | return paths 86 | 87 | # Assumes curly braces are balanced. 88 | def curly_brace_indecies(self, path, count=0,open_index=None): 89 | if len(path) == 0: 90 | return None 91 | c = path[0] 92 | if c == "{": 93 | return self.curly_brace_indecies(path[1:], count + 1, count) 94 | elif c == "}": 95 | return open_index, count 96 | else: 97 | return self.curly_brace_indecies(path[1:], count + 1, open_index) 98 | 99 | def curly_braces_balanced(self, path, count=0): 100 | if len(path) == 0 or count < 0: 101 | return count == 0 102 | 103 | c = path[0] 104 | if c == "{": 105 | return self.curly_braces_balanced(path[1:], count + 1) 106 | elif c == "}": 107 | return self.curly_braces_balanced(path[1:], count - 1) 108 | else: 109 | return self.curly_braces_balanced(path[1:], count) 110 | 111 | def update_status_message(self, creation_path): 112 | if self.view is not None: 113 | self.view.set_status("AdvancedNewFile", "Creating file at %s " % 114 | creation_path) 115 | else: 116 | sublime.status_message("Creating file at %s" % creation_path) 117 | 118 | def get_default_root_setting(self): 119 | return NEW_FILE_DEFAULT_ROOT_SETTING 120 | 121 | def empty_file_action(self): 122 | self.window.new_file() 123 | 124 | 125 | class AdvancedNewFileNewAtCommand(sublime_plugin.WindowCommand): 126 | def run(self, dirs): 127 | if len(dirs) != 1: 128 | return 129 | path = dirs[0] + os.sep 130 | self.window.run_command("advanced_new_file_new", 131 | {"initial_path": path}) 132 | 133 | def is_visible(self, dirs): 134 | return len(dirs) == 1 135 | 136 | 137 | class AdvancedNewFileNewAtFileCommand(sublime_plugin.WindowCommand): 138 | def run(self, files): 139 | if len(files) != 1: 140 | return 141 | self.window.run_command("advanced_new_file_new", 142 | {"initial_path": files[0]}) 143 | 144 | def is_visible(self, files): 145 | return len(files) == 1 146 | 147 | 148 | class AdvancedNewFileNewEventListener(sublime_plugin.EventListener): 149 | def on_load(self, view): 150 | if view.settings().get("_anf_new", False): 151 | absolute_file_path = view.file_name() 152 | if absolute_file_path is None: 153 | return 154 | file_name = self.get_basename(absolute_file_path) 155 | _, full_extension = os.path.splitext(file_name) 156 | if len(full_extension) == 0: 157 | extension = file_name 158 | else: 159 | extension = full_extension[1:] 160 | settings = get_settings(view) 161 | if extension in settings.get(FILE_TEMPLATES_SETTING): 162 | template = settings.get(FILE_TEMPLATES_SETTING)[extension] 163 | if type(template) == list: 164 | if len(template) == 1: 165 | view.run_command("insert_snippet", {"contents": self.get_snippet_from_file(template[0])}) 166 | else: 167 | entries = list(map(self.get_basename, template)) 168 | self.entries = list(map(self.expand_path, template)) 169 | self.view = view 170 | sublime.set_timeout(lambda: view.window().show_quick_panel(entries, self.quick_panel_selection), 10) 171 | else: 172 | view.run_command("insert_snippet", {"contents": template}) 173 | view.settings().set("_anf_new", "") 174 | 175 | def get_basename(self, path): 176 | return os.path.basename(os.path.expanduser(path)) 177 | 178 | def expand_path(self, path): 179 | return os.path.expanduser(path) 180 | 181 | def quick_panel_selection(self, index): 182 | if index < 0: 183 | return 184 | self.view.run_command("insert_snippet", {"contents": self.get_snippet_from_file(self.entries[index])}) 185 | 186 | def get_snippet_from_file(self, path): 187 | match = re.match(r"Packages/([^/]+)/(.+)", path) 188 | if match: 189 | tree = ET.fromstring(get_resource(match.group(1), match.group(2))) 190 | else: 191 | tree = ET.parse(os.path.expanduser(path)) 192 | content = tree.find("content") 193 | return content.text 194 | -------------------------------------------------------------------------------- /AdvancedNewFile.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // This setting contains a dictionary of aliases. The keys represent the 3 | // alias names, the values represent the paths. 4 | // NOTE: These should be absolute paths. Also, these paths should 5 | // match your systems convention. For example, Windows machines should 6 | // have paths similar to "C:\\Users\\username\\Desktop". *nix systems should 7 | // have paths similar to "/home/username/desktop". 8 | "alias": {}, 9 | 10 | // This is a secondary field for aliases. These aliases will be platform specific. 11 | // The key for the entry will still represent the name to be used for the alias. 12 | // Rather than being just a string path, the value will be a nested dictionary. 13 | // The dictionary may have one of three keys, "windows", "linux", or "osx". 14 | // The path used for this alias will be based on the operating system being used. 15 | "os_specific_alias": {}, 16 | 17 | // A default initial value to fill the create new file input path with. 18 | "default_initial": "", 19 | 20 | // When renaming a file it will we pre populated with the file existing filename. 21 | "autofill_path_the_existing": false, 22 | 23 | // A boolean defining if cursor text should be used. Text bound by single or 24 | // double quotes or within a region will be used. If multiple cursors 25 | // are used, the earliest selection containing a region or existing 26 | // within quotes will be used. 27 | // NOTE: A value read from cursor will override the default 28 | // initial string setting. 29 | "use_cursor_text": false, 30 | 31 | // A boolean value specifying if regular files should be included in the auto 32 | // complete options. 33 | "show_files": false, 34 | 35 | // A boolean specifying if the file path being created should be displayed in 36 | // the status bar. 37 | "show_path": true, 38 | 39 | // This value specifies the default directory when using AdvancedNewFile. 40 | // Note it must be one of these values: 41 | // project_folder - Default will be the folder index specified by the "default_folder_index" setting. 42 | // current - Default will be the directory of the current view. 43 | // home - Default will be the home folder (~/) 44 | // path - Default will be defined by the setting "default_path" 45 | // If the current view or top folder cannot be resolved, the home directory 46 | // will be used. 47 | "default_root": "project_folder", 48 | 49 | // A string specifying the default root to use. For this to be utilized, 50 | // "default_root" must be set to "path" 51 | "default_path": "~", 52 | 53 | // An integer value representing a folder index to be used when "folder" is specified 54 | // for "default_root". If an index outside of the range of existing folders is used, 55 | // it will default to 0 (the top level folder). 56 | "default_folder_index": 0, 57 | 58 | 59 | // This value specifies the root that will be used when resolving relative paths 60 | // defined in aliases. For more information about valid values, see "default_root". 61 | // Note that if "default_path" or "default_folder_index" is used, 62 | // "alias_path" and "alias_folder_index" must be used for the respective entries. 63 | "alias_root": "current", 64 | 65 | // A string specifying the path to use for the alias root. For this to be 66 | // utilized, "alias_root" must be set to "path" 67 | "alias_path": "~", 68 | 69 | // An integer value representing the folder index to use when "folder" is specified 70 | // for "alias_root". If an index outside of the range of the existing folders is used, 71 | // it will default to 0. 72 | "alias_folder_index": 0, 73 | 74 | // A boolean specifying if case should be ignored when building 75 | // auto complete list. 76 | "ignore_case": false, 77 | 78 | // A boolean specifying if folders should automatically refresh and update the sidebar. 79 | // In some builds, the sidebar does not refresh when contents of project folder are updated. 80 | // This setting is required to refresh the sidebar in these circumstances. 81 | // false by default 82 | "auto_refresh_sidebar": false, 83 | 84 | // A string specifying the type of auto completion to use. Valid values are 85 | // "windows" or "nix" 86 | "completion_type": "windows", 87 | 88 | // A boolean setting specifying if a separator should be inserted when 89 | // there is only one completion and completion type is "windows" 90 | "complete_single_entry": true, 91 | 92 | // A boolean setting specifying if the folder name should be used 93 | // or the name specified in the project. This setting only applies to ST3. 94 | "use_folder_name": false, 95 | 96 | // Boolean setting specifying if relative paths should be based on the 97 | // current working directory. 98 | "relative_from_current": true, 99 | 100 | // String containing the default file extension. Note the extension is only applied 101 | // if the specified path does not contain a dot (.) character. 102 | "default_extension": "", 103 | 104 | // String representing permissions to be applied to newly created directories. 105 | // e.g. "777" -> RWX for user, group, and other. 106 | "folder_permissions": "", 107 | 108 | // String representing permissions to be applied to newly created files. 109 | // e.g. "777" -> RWX for user, group, and other. 110 | "file_permissions": "", 111 | 112 | // Default input for renaming a file. Special value will be replaced 113 | // with the current file name. Special value will be replaced with 114 | // the complete filepath, including the filename. Special value 115 | // will be replaced with the filepath, not including the filename. Note that a 116 | // colon as the default will resolve to the same path as , if the 117 | // file exists on disk. 118 | "rename_default": "", 119 | 120 | // Setting to control if VCS management is used when moving and removing files. 121 | "vcs_management": false, 122 | 123 | // An object containing information to use for templates when creating new files. 124 | // The key values for this object should be a file extension. Files without extensions 125 | // such as "Makefile" or ".bash_profile" use the full file name as the key. 126 | // The value may either be a string of the content to be inserted or a list of paths. 127 | // If a list of paths is specified, the name of the file will be displayed during 128 | // selection. The paths must either be absolute, from the home directory of the 129 | // user (`~/`), or relative to the Packages directory. These relative files should have 130 | // the form "Packages/User/mytest.sublime-snippet". If a string is used, or the list 131 | // contains a single entry, it will be automatically inserted into any newly created files. 132 | "file_templates": {}, 133 | 134 | // Setting this value to true will allow you to escape characters as you normally 135 | // would when using a shell. For example, given the input string "foo\ bar", false 136 | // would result in a file named " bar" in the foo directory. With the value set to 137 | // true, a file named "foo bar" would be created. In addition, setting this value 138 | // to true will allow for curly brace expansion. Currently, only comma separated 139 | // entries are supported. 140 | "shell_input": false, 141 | 142 | // Setting to control if the extension will be automatically applied to renamed files. 143 | "append_extension_on_move": false, 144 | 145 | // An integer value representing a folder index to be used when a relative path 146 | // cannot be resolved from the current active view. If an index outside of the range 147 | // of existing folders is used, it will default to 0 (the top level folder). If no 148 | // folders exist as part of the project the home directory will be used. 149 | "relative_fallback_index": 0, 150 | 151 | // Setting to control if the extension will be automatically applied to copied files. 152 | "append_extension_on_copy": true, 153 | 154 | // Same as `rename_default`, but applied to copy command. 155 | "copy_default": "", 156 | 157 | // Same as `rename_default`, but applied to cut to default command. 158 | "cut_to_file_default": "", 159 | 160 | // If default_root is set to current, the project folder should be used as the default 161 | // rather than the home directory. 162 | "current_fallback_to_project": false, 163 | 164 | // If a warning should be displayed when trying to overwrite an existing file using 165 | // the move command. 166 | "warn_overwrite_on_move": false, 167 | 168 | // If a warning should be displayed when trying to overwrite an existing file using 169 | // the copy command. 170 | "warn_overwrite_on_copy": false, 171 | 172 | // Same as `default_root` for new file commands. In addition to the valid values listed 173 | // for `default_root`, "default_root" will use the value for that setting. 174 | "new_file_default_root": "default_root", 175 | 176 | // Same as `default_root` for rename file commands. In addition to the valid values listed 177 | // for `default_root`, "default_root" will use the value for that setting. 178 | "rename_file_default_root": "default_root", 179 | 180 | // Same as `default_root` for copy file commands. In addition to the valid values listed 181 | // for `default_root`, "default_root" will use the value for that setting. 182 | "copy_file_default_root": "default_root", 183 | 184 | // On empty input of file name, execute an alternative action. 185 | // Currently only implemented for the new file command, which will open a new unnamed file. 186 | "empty_filename_action": false, 187 | 188 | // When specifying initial input, this boolean will place the cursor prior to the . 189 | "cursor_before_extension": false 190 | } 191 | -------------------------------------------------------------------------------- /advanced_new_file/lib/package_resources.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | Copyright (c) 2014 Scott Kuroda 4 | 5 | SHA: 623a4c1ec46dbbf3268bd88131bf0dfc845af787 6 | """ 7 | import sublime 8 | import os 9 | import zipfile 10 | import tempfile 11 | import re 12 | import codecs 13 | 14 | __all__ = [ 15 | "get_resource", 16 | "get_binary_resource", 17 | "find_resource", 18 | "list_package_files", 19 | "get_package_and_resource_name", 20 | "get_packages_list", 21 | "extract_package", 22 | "get_sublime_packages" 23 | ] 24 | 25 | 26 | VERSION = int(sublime.version()) 27 | 28 | def get_resource(package_name, resource, encoding="utf-8"): 29 | return _get_resource(package_name, resource, encoding=encoding) 30 | 31 | def get_binary_resource(package_name, resource): 32 | return _get_resource(package_name, resource, return_binary=True) 33 | 34 | def _get_resource(package_name, resource, return_binary=False, encoding="utf-8"): 35 | packages_path = sublime.packages_path() 36 | content = None 37 | if VERSION > 3013: 38 | try: 39 | if return_binary: 40 | content = sublime.load_binary_resource("Packages/" + package_name + "/" + resource) 41 | else: 42 | content = sublime.load_resource("Packages/" + package_name + "/" + resource) 43 | except IOError: 44 | pass 45 | else: 46 | path = None 47 | if os.path.exists(os.path.join(packages_path, package_name, resource)): 48 | path = os.path.join(packages_path, package_name, resource) 49 | content = _get_directory_item_content(path, return_binary, encoding) 50 | 51 | if VERSION >= 3006: 52 | sublime_package = package_name + ".sublime-package" 53 | 54 | packages_path = sublime.installed_packages_path() 55 | if content is None: 56 | if os.path.exists(os.path.join(packages_path, sublime_package)): 57 | content = _get_zip_item_content(os.path.join(packages_path, sublime_package), resource, return_binary, encoding) 58 | 59 | packages_path = os.path.dirname(sublime.executable_path()) + os.sep + "Packages" 60 | 61 | if content is None: 62 | if os.path.exists(os.path.join(packages_path, sublime_package)): 63 | content = _get_zip_item_content(os.path.join(packages_path, sublime_package), resource, return_binary, encoding) 64 | 65 | return content 66 | 67 | 68 | def find_resource(resource_pattern, package=None): 69 | file_set = set() 70 | if package == None: 71 | for package in get_packages_list(): 72 | file_set.update(find_resource(resource_pattern, package)) 73 | 74 | ret_list = list(file_set) 75 | else: 76 | file_set.update(_find_directory_resource(os.path.join(sublime.packages_path(), package), resource_pattern)) 77 | 78 | if VERSION >= 3006: 79 | zip_location = os.path.join(sublime.installed_packages_path(), package + ".sublime-package") 80 | file_set.update(_find_zip_resource(zip_location, resource_pattern)) 81 | zip_location = os.path.join(os.path.dirname(sublime.executable_path()), "Packages", package + ".sublime-package") 82 | file_set.update(_find_zip_resource(zip_location, resource_pattern)) 83 | ret_list = map(lambda e: package + "/" + e, file_set) 84 | 85 | return sorted(ret_list) 86 | 87 | 88 | def list_package_files(package, ignore_patterns=[]): 89 | """ 90 | List files in the specified package. 91 | """ 92 | package_path = os.path.join(sublime.packages_path(), package, "") 93 | path = None 94 | file_set = set() 95 | file_list = [] 96 | if os.path.exists(package_path): 97 | for root, directories, filenames in os.walk(package_path): 98 | temp = root.replace(package_path, "") 99 | for filename in filenames: 100 | file_list.append(os.path.join(temp, filename)) 101 | 102 | file_set.update(file_list) 103 | 104 | if VERSION >= 3006: 105 | sublime_package = package + ".sublime-package" 106 | packages_path = sublime.installed_packages_path() 107 | 108 | if os.path.exists(os.path.join(packages_path, sublime_package)): 109 | file_set.update(_list_files_in_zip(packages_path, sublime_package)) 110 | 111 | packages_path = os.path.dirname(sublime.executable_path()) + os.sep + "Packages" 112 | 113 | if os.path.exists(os.path.join(packages_path, sublime_package)): 114 | file_set.update(_list_files_in_zip(packages_path, sublime_package)) 115 | 116 | file_list = [] 117 | 118 | for filename in file_set: 119 | if not _ignore_file(filename, ignore_patterns): 120 | file_list.append(_normalize_to_sublime_path(filename)) 121 | 122 | return sorted(file_list) 123 | 124 | def _ignore_file(filename, ignore_patterns=[]): 125 | ignore = False 126 | directory, base = os.path.split(filename) 127 | for pattern in ignore_patterns: 128 | if re.match(pattern, base): 129 | return True 130 | 131 | if len(directory) > 0: 132 | ignore = _ignore_file(directory, ignore_patterns) 133 | 134 | return ignore 135 | 136 | 137 | def _normalize_to_sublime_path(path): 138 | path = os.path.normpath(path) 139 | path = re.sub(r"^([a-zA-Z]):", "/\\1", path) 140 | path = re.sub(r"\\", "/", path) 141 | return path 142 | 143 | def get_package_and_resource_name(path): 144 | """ 145 | This method will return the package name and resource name from a path. 146 | 147 | Arguments: 148 | path Path to parse for package and resource name. 149 | """ 150 | package = None 151 | resource = None 152 | path = _normalize_to_sublime_path(path) 153 | if os.path.isabs(path): 154 | packages_path = _normalize_to_sublime_path(sublime.packages_path()) 155 | if path.startswith(packages_path): 156 | package, resource = _search_for_package_and_resource(path, packages_path) 157 | 158 | if int(sublime.version()) >= 3006: 159 | packages_path = _normalize_to_sublime_path(sublime.installed_packages_path()) 160 | if path.startswith(packages_path): 161 | package, resource = _search_for_package_and_resource(path, packages_path) 162 | 163 | packages_path = _normalize_to_sublime_path(os.path.dirname(sublime.executable_path()) + os.sep + "Packages") 164 | if path.startswith(packages_path): 165 | package, resource = _search_for_package_and_resource(path, packages_path) 166 | else: 167 | path = re.sub(r"^Packages/", "", path) 168 | split = re.split(r"/", path, 1) 169 | package = split[0] 170 | package = package.replace(".sublime-package", "") 171 | resource = split[1] 172 | 173 | return (package, resource) 174 | 175 | def get_packages_list(ignore_packages=True, ignore_patterns=[]): 176 | """ 177 | Return a list of packages. 178 | """ 179 | package_set = set() 180 | package_set.update(_get_packages_from_directory(sublime.packages_path())) 181 | 182 | if int(sublime.version()) >= 3006: 183 | package_set.update(_get_packages_from_directory(sublime.installed_packages_path(), ".sublime-package")) 184 | 185 | executable_package_path = os.path.dirname(sublime.executable_path()) + os.sep + "Packages" 186 | package_set.update(_get_packages_from_directory(executable_package_path, ".sublime-package")) 187 | 188 | 189 | if ignore_packages: 190 | ignored_list = sublime.load_settings( 191 | "Preferences.sublime-settings").get("ignored_packages", []) 192 | else: 193 | ignored_list = [] 194 | 195 | for package in package_set: 196 | for pattern in ignore_patterns: 197 | if re.match(pattern, package): 198 | ignored_list.append(package) 199 | break 200 | 201 | for ignored in ignored_list: 202 | package_set.discard(ignored) 203 | 204 | return sorted(list(package_set)) 205 | 206 | def get_sublime_packages(ignore_packages=True, ignore_patterns=[]): 207 | package_list = get_packages_list(ignore_packages, ignore_patterns) 208 | extracted_list = _get_packages_from_directory(sublime.packages_path()) 209 | return [x for x in package_list if x not in extracted_list] 210 | 211 | def _get_packages_from_directory(directory, file_ext=""): 212 | package_list = [] 213 | for package in os.listdir(directory): 214 | if not package.endswith(file_ext): 215 | continue 216 | else: 217 | package = package.replace(file_ext, "") 218 | 219 | package_list.append(package) 220 | return package_list 221 | 222 | def _search_for_package_and_resource(path, packages_path): 223 | """ 224 | Derive the package and resource from a path. 225 | """ 226 | relative_package_path = path.replace(packages_path + "/", "") 227 | 228 | package, resource = re.split(r"/", relative_package_path, 1) 229 | package = package.replace(".sublime-package", "") 230 | return (package, resource) 231 | 232 | 233 | def _list_files_in_zip(package_path, package): 234 | if not os.path.exists(os.path.join(package_path, package)): 235 | return [] 236 | 237 | ret_value = [] 238 | with zipfile.ZipFile(os.path.join(package_path, package)) as zip_file: 239 | ret_value = zip_file.namelist() 240 | return ret_value 241 | 242 | def _get_zip_item_content(path_to_zip, resource, return_binary, encoding): 243 | if not os.path.exists(path_to_zip): 244 | return None 245 | 246 | ret_value = None 247 | 248 | with zipfile.ZipFile(path_to_zip) as zip_file: 249 | namelist = zip_file.namelist() 250 | if resource in namelist: 251 | ret_value = zip_file.read(resource) 252 | if not return_binary: 253 | ret_value = ret_value.decode(encoding) 254 | 255 | return ret_value 256 | 257 | def _get_directory_item_content(filename, return_binary, encoding): 258 | content = None 259 | if os.path.exists(filename): 260 | if return_binary: 261 | mode = "rb" 262 | encoding = None 263 | else: 264 | mode = "r" 265 | with codecs.open(filename, mode, encoding=encoding) as file_obj: 266 | content = file_obj.read() 267 | return content 268 | 269 | def _find_zip_resource(path_to_zip, pattern): 270 | ret_list = [] 271 | if os.path.exists(path_to_zip): 272 | with zipfile.ZipFile(path_to_zip) as zip_file: 273 | namelist = zip_file.namelist() 274 | for name in namelist: 275 | if re.search(pattern, name): 276 | ret_list.append(name) 277 | 278 | return ret_list 279 | 280 | def _find_directory_resource(path, pattern): 281 | ret_list = [] 282 | if os.path.exists(path): 283 | path = os.path.join(path, "") 284 | for root, directories, filenames in os.walk(path): 285 | temp = root.replace(path, "") 286 | for filename in filenames: 287 | if re.search(pattern, os.path.join(temp, filename)): 288 | ret_list.append(os.path.join(temp, filename)) 289 | return ret_list 290 | 291 | def extract_zip_resource(path_to_zip, resource, extract_dir=None): 292 | if extract_dir is None: 293 | extract_dir = tempfile.mkdtemp() 294 | 295 | file_location = None 296 | if os.path.exists(path_to_zip): 297 | with zipfile.ZipFile(path_to_zip) as zip_file: 298 | file_location = zip_file.extract(resource, extract_dir) 299 | 300 | return file_location 301 | 302 | def extract_package(package): 303 | if VERSION >= 3006: 304 | package_location = os.path.join(sublime.installed_packages_path(), package + ".sublime-package") 305 | if not os.path.exists(package_location): 306 | package_location = os.path.join(os.path.dirname(sublime.executable_path()), "Packages", package + ".sublime-package") 307 | if not os.path.exists(package_location): 308 | package_location = None 309 | if package_location: 310 | with zipfile.ZipFile(package_location) as zip_file: 311 | extract_location = os.path.join(sublime.packages_path(), package) 312 | zip_file.extractall(extract_location) 313 | 314 | -------------------------------------------------------------------------------- /advanced_new_file/lib/ushlex.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """A lexical analyzer class for simple shell-like syntaxes.""" 3 | # Source https://bitbucket.org/mixmastamyk/ushlex 4 | 5 | # Module and documentation by Eric S. Raymond, 21 Dec 1998 6 | # Input stacking and error message cleanup added by ESR, March 2000 7 | # push_source() and pop_source() made explicit by ESR, January 2001. 8 | # Posix compliance, split(), string arguments, and 9 | # iterator interface by Gustavo Niemeyer, April 2003. 10 | # Modified to support Unicode by Colin Walters, Dec 2007 11 | 12 | import os.path 13 | import sys 14 | import unicodedata 15 | from collections import deque 16 | from StringIO import StringIO 17 | 18 | __all__ = ["shlex", "split"] 19 | 20 | class shlex: 21 | "A lexical analyzer class for simple shell-like syntaxes." 22 | def __init__(self, instream=None, infile=None, posix=False, utf=True): 23 | if isinstance(instream, basestring): 24 | instream = StringIO(instream) 25 | if instream is not None: 26 | self.instream = instream 27 | self.infile = infile 28 | else: 29 | self.instream = sys.stdin 30 | self.infile = None 31 | self.posix = posix 32 | if posix: 33 | self.eof = None 34 | else: 35 | self.eof = '' 36 | self.utf = utf 37 | self.commenters = '#' 38 | self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' 39 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_') 40 | if self.posix and not self.utf: 41 | self.wordchars += ('ßà áâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' 42 | 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ') 43 | elif self.posix: 44 | # We dynamically determine character classes below, except 45 | # by default _ is a word character 46 | self.wordchars = '_' 47 | self.whitespace = ' \t\r\n' 48 | self.whitespace_split = False 49 | self.quotes = '\'"' 50 | self.escape = '\\' 51 | self.escapedquotes = '"' 52 | self.state = ' ' 53 | self.pushback = deque() 54 | self.lineno = 1 55 | self.debug = 0 56 | self.token = '' 57 | self.filestack = deque() 58 | self.source = None 59 | if self.debug: 60 | print 'shlex: reading from %s, line %d' \ 61 | % (self.instream, self.lineno) 62 | 63 | def push_token(self, tok): 64 | "Push a token onto the stack popped by the get_token method" 65 | if self.debug >= 1: 66 | print "shlex: pushing token " + repr(tok) 67 | self.pushback.appendleft(tok) 68 | 69 | def push_source(self, newstream, newfile=None): 70 | "Push an input source onto the lexer's input source stack." 71 | if isinstance(newstream, basestring): 72 | newstream = StringIO(newstream) 73 | self.filestack.appendleft((self.infile, self.instream, self.lineno)) 74 | self.infile = newfile 75 | self.instream = newstream 76 | self.lineno = 1 77 | if self.debug: 78 | if newfile is not None: 79 | print 'shlex: pushing to file %s' % (self.infile,) 80 | else: 81 | print 'shlex: pushing to stream %s' % (self.instream,) 82 | 83 | def pop_source(self): 84 | "Pop the input source stack." 85 | self.instream.close() 86 | (self.infile, self.instream, self.lineno) = self.filestack.popleft() 87 | if self.debug: 88 | print 'shlex: popping to %s, line %d' \ 89 | % (self.instream, self.lineno) 90 | self.state = ' ' 91 | 92 | def get_token(self): 93 | "Get a token from the input stream (or from stack if it's nonempty)" 94 | if self.pushback: 95 | tok = self.pushback.popleft() 96 | if self.debug >= 1: 97 | print "shlex: popping token " + repr(tok) 98 | return tok 99 | # No pushback. Get a token. 100 | raw = self.read_token() 101 | # Handle inclusions 102 | if self.source is not None: 103 | while raw == self.source: 104 | spec = self.sourcehook(self.read_token()) 105 | if spec: 106 | (newfile, newstream) = spec 107 | self.push_source(newstream, newfile) 108 | raw = self.get_token() 109 | # Maybe we got EOF instead? 110 | while raw == self.eof: 111 | if not self.filestack: 112 | return self.eof 113 | else: 114 | self.pop_source() 115 | raw = self.get_token() 116 | # Neither inclusion nor EOF 117 | if self.debug >= 1: 118 | if raw != self.eof: 119 | print "shlex: token=" + repr(raw) 120 | else: 121 | print "shlex: token=EOF" 122 | return raw 123 | 124 | def __is_whitespace(self, c, category): 125 | return c in self.whitespace or (self.utf and category[0] == 'Z') 126 | 127 | def __is_wordchar(self, c, category): 128 | return c in self.wordchars or (self.utf and category[0] in ('L', 'N')) 129 | 130 | def read_token(self): 131 | quoted = False 132 | escapedstate = ' ' 133 | while True: 134 | nextchar = self.instream.read(1) 135 | if nextchar and self.utf: 136 | nextcategory = unicodedata.category(nextchar) 137 | else: 138 | nextcategory = None 139 | if nextchar == '\n': 140 | self.lineno = self.lineno + 1 141 | if self.debug >= 3: 142 | print "shlex: in state", repr(self.state), \ 143 | "I see character:", repr(nextchar) 144 | if self.state is None: 145 | self.token = '' # past end of file 146 | break 147 | elif self.state == ' ': 148 | if not nextchar: 149 | self.state = None # end of file 150 | break 151 | if self.__is_whitespace(nextchar, nextcategory): 152 | if self.debug >= 2: 153 | print "shlex: I see whitespace in whitespace state" 154 | if self.token or (self.posix and quoted): 155 | break # emit current token 156 | else: 157 | continue 158 | elif nextchar in self.commenters: 159 | self.instream.readline() 160 | self.lineno = self.lineno + 1 161 | elif self.posix and nextchar in self.escape: 162 | escapedstate = 'a' 163 | self.state = nextchar 164 | elif self.__is_wordchar(nextchar, nextcategory): 165 | self.token = nextchar 166 | self.state = 'a' 167 | elif nextchar in self.quotes: 168 | if not self.posix: 169 | self.token = nextchar 170 | self.state = nextchar 171 | elif self.whitespace_split: 172 | self.token = nextchar 173 | self.state = 'a' 174 | else: 175 | self.token = nextchar 176 | if self.token or (self.posix and quoted): 177 | break # emit current token 178 | else: 179 | continue 180 | elif self.state in self.quotes: 181 | quoted = True 182 | if not nextchar: # end of file 183 | if self.debug >= 2: 184 | print "shlex: I see EOF in quotes state" 185 | # XXX what error should be raised here? 186 | raise ValueError, "No closing quotation" 187 | if nextchar == self.state: 188 | if not self.posix: 189 | self.token = self.token + nextchar 190 | self.state = ' ' 191 | break 192 | else: 193 | self.state = 'a' 194 | elif self.posix and nextchar in self.escape and \ 195 | self.state in self.escapedquotes: 196 | escapedstate = self.state 197 | self.state = nextchar 198 | else: 199 | self.token = self.token + nextchar 200 | elif self.state in self.escape: 201 | if not nextchar: # end of file 202 | if self.debug >= 2: 203 | print "shlex: I see EOF in escape state" 204 | # XXX what error should be raised here? 205 | raise ValueError, "No escaped character" 206 | # In posix shells, only the quote itself or the escape 207 | # character may be escaped within quotes. 208 | if escapedstate in self.quotes and \ 209 | nextchar != self.state and nextchar != escapedstate: 210 | self.token = self.token + self.state 211 | self.token = self.token + nextchar 212 | self.state = escapedstate 213 | elif self.state == 'a': 214 | if not nextchar: 215 | self.state = None # end of file 216 | break 217 | if self.__is_whitespace(nextchar, nextcategory): 218 | if self.debug >= 2: 219 | print "shlex: I see whitespace in word state" 220 | self.state = ' ' 221 | if self.token or (self.posix and quoted): 222 | break # emit current token 223 | else: 224 | continue 225 | elif nextchar in self.commenters: 226 | self.instream.readline() 227 | self.lineno = self.lineno + 1 228 | if self.posix: 229 | self.state = ' ' 230 | if self.token or (self.posix and quoted): 231 | break # emit current token 232 | else: 233 | continue 234 | elif self.posix and nextchar in self.quotes: 235 | self.state = nextchar 236 | elif self.posix and nextchar in self.escape: 237 | escapedstate = 'a' 238 | self.state = nextchar 239 | elif self.__is_wordchar(nextchar, nextcategory) or nextchar in self.quotes \ 240 | or self.whitespace_split: 241 | self.token = self.token + nextchar 242 | else: 243 | self.pushback.appendleft(nextchar) 244 | if self.debug >= 2: 245 | print "shlex: I see punctuation in word state" 246 | self.state = ' ' 247 | if self.token: 248 | break # emit current token 249 | else: 250 | continue 251 | result = self.token 252 | self.token = '' 253 | if self.posix and not quoted and result == '': 254 | result = None 255 | if self.debug > 1: 256 | if result: 257 | print "shlex: raw token=" + repr(result) 258 | else: 259 | print "shlex: raw token=EOF" 260 | return result 261 | 262 | def sourcehook(self, newfile, encoding='utf-8'): 263 | "Hook called on a filename to be sourced." 264 | from codecs import open 265 | if newfile[0] == '"': 266 | newfile = newfile[1:-1] 267 | # This implements cpp-like semantics for relative-path inclusion. 268 | if isinstance(self.infile, basestring) and not os.path.isabs(newfile): 269 | newfile = os.path.join(os.path.dirname(self.infile), newfile) 270 | return (newfile, open(newfile, "r", encoding)) 271 | 272 | def error_leader(self, infile=None, lineno=None): 273 | "Emit a C-compiler-like, Emacs-friendly error-message leader." 274 | if infile is None: 275 | infile = self.infile 276 | if lineno is None: 277 | lineno = self.lineno 278 | return "\"%s\", line %d: " % (infile, lineno) 279 | 280 | def __iter__(self): 281 | return self 282 | 283 | def next(self): 284 | token = self.get_token() 285 | if token == self.eof: 286 | raise StopIteration 287 | return token 288 | 289 | def split(s, comments=False, posix=True): 290 | is_str = False 291 | if type(s) is str: 292 | s = unicode(s) 293 | is_str = True 294 | lex = shlex(s, posix=posix) 295 | lex.whitespace_split = True 296 | if not comments: 297 | lex.commenters = '' 298 | if is_str: return [ str(x) for x in list(lex) ] 299 | else: return list(lex) 300 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdvancedNewFile 2 | Advanced file creation for Sublime Text. 3 | 4 | ## Overview 5 | 6 | This plugin allows for faster file creation within a project. Please see the [Features](https://github.com/SublimeText/AdvancedNewFile#features) section for more detailed information about advanced features. 7 | 8 | ## Compatability Notes 9 | ### Sublime Text 2 and 3 10 | Version 1.7.0 is the final version that will be compatible with Sublime Text 2 and 3. Compatiability with 11 | these versions of Sublime Text are no longer planned to be maintained and will not receive future enhancements. 12 | If the latest features are needed, consider upgrading to Sublime Text 4. 13 | 14 | ## Installation 15 | Note with either method, you may need to restart Sublime Text 2 for the plugin to load. 16 | 17 | ### Package Control 18 | Installation through [package control](http://wbond.net/sublime_packages/package_control) is recommended. It will handle updating your packages as they become available. To install, do the following. 19 | 20 | * In the Command Palette, enter `Package Control: Install Package` 21 | * Search for `ANF` to see the list of available commands 22 | 23 | ### Manual 24 | Clone or copy this repository into the packages directory. You will need to rename the folder to `AdvancedNewFile` if using this method. By default, the Package directory is located at: 25 | 26 | * OS X: ~/Library/Application Support/Sublime Text 2/Packages/ 27 | * Windows: %APPDATA%/Sublime Text 2/Packages/ 28 | * Linux: ~/.config/sublime-text-2/Packages/ 29 | 30 | or 31 | 32 | * OS X: ~/Library/Application Support/Sublime Text 3/Packages/ 33 | * Windows: %APPDATA%/Sublime Text 3/Packages/ 34 | * Linux: ~/.config/sublime-text-3/Packages/ 35 | 36 | Depending on your install on windows, the ST packages path may be `%APPDATA%/Sublime Text 2/...` 37 | 38 | ## Usage 39 | Simply bring up the AdvancedNewFile input through the appropriate [key binding](https://github.com/SublimeText/AdvancedNewFile). Then, enter the path, along with the file name into the input field. Upon pressing enter, the file will be created. In addition, if the directories specified do not yet exists, they will also be created. For more advanced usage of this plugin, be sure to look at [Advanced Path Usage](https://github.com/SublimeText/AdvancedNewFile#advanced-path-usage). By default, the path to the file being created will be shown in the status bar as you enter the path information. 40 | 41 | **Default directory:** 42 | The default directory is specified by the `default_root` setting. By default, it will be the top directory of the folders listed in the window. If this cannot be resolved, the home directory will be used. See [Settings](https://github.com/SublimeText/AdvancedNewFile#settings) (`default_root`) for more information. 43 | 44 | ### Commands with no Default Bindings 45 | The plugin supports renaming and deleting files. However, these are not, by default bound to any key binding. For more information on the available commands, see the GitHub [wiki](https://github.com/SublimeText/AdvancedNewFile/wiki/Commands) page. 46 | 47 | ### Adding Commands to Menu 48 | The plugin does not contain any menu commands by default. To add them yourself, please see the GitHub[wiki](https://github.com/SublimeText/AdvancedNewFile/wiki/Menu-Entries) 49 | 50 | ## Keymaps 51 | If you have issues with keymaps, consider running [FindKeyConflicts](https://github.com/skuroda/FindKeyConflicts), also available through the package manager. Alternatively, set command logging to true by entering `sublime.log_commands(True)` in the Sublime Text console. 52 | 53 | ### Windows 54 | `ctrl+alt+n`: General keymap to create new files. 55 | 56 | `ctrl+shift+alt+n`: In addition to creating the folders specified, new folders will also contain an `__init__.py` file. 57 | 58 | ### OS X and Linux 59 | The super keys for Linux and OS X are the Windows and command key respectively. 60 | 61 | `super+alt+n`: General keymap to create new files. 62 | 63 | `shift+super+alt+n`: In addition to creating the folders specified, new folders will also contain an `__init__.py` file. 64 | 65 | ## Settings 66 | Default settings can be seen by navigating to `Preferences -> Packages Settings -> AdvancedNewFile - Default`. To modify the default settings, navigate to `Preferences -> Packages Settings -> AdvancedNewFile -> User`. 67 | 68 | `alias`: 69 | 70 | A dictionary that contains a set of aliases tied to a directory. For more information, see [Aliases](https://github.com/SublimeText/AdvancedNewFile#aliases) 71 | 72 | `os_specific_alias`: 73 | 74 | A dictionary containing a set of aliases tied to a directory. These aliases will be platform specific. For more information, see [Platform Specific Aliases](https://github.com/SublimeText/AdvancedNewFile#platform-specific-aliases) 75 | 76 | `default_initial`: 77 | 78 | A string that will be automatically inserted into the new file creation input. 79 | 80 | `use_cursor_text`: 81 | 82 | A boolean value determining if text from a buffer, bound by quotes or a selected region, will be auto inserted into the new file generation input field. If multiple cursors are used, the first entry either contained in quotes, are a selected region, will be used. 83 | 84 | `show_files`: 85 | 86 | A boolean value determining if regular files should be included in the autocompletion list. 87 | 88 | `show_path`: 89 | 90 | A boolean value used to determine if the path of the file to be created should be displayed in the status bar. 91 | 92 | `default_root`: 93 | 94 | This value is used to determine the default root when using AdvancedNewFile. It must be one of the following values: 95 | 96 | * `project_folder`- The default path will be the folder specified by the 'default_folder_index' setting. 97 | * `current` - The default path will be the directory of the current active view. 98 | * `home` - The default path will be your home directory. 99 | * `path` - The default path will be defined by the setting `default_path` 100 | 101 | If the current view's directory cannot be resolved, the top level folder in the window will be used. If the top level folder in the window cannot be resolved either, the home directory will be used. 102 | 103 | `default_path`: 104 | 105 | This path is used as the default if `path` has been specified for the setting `default_root`. This path should be absolute. If a relative path is specified, it will be relative to the AdvancedNewFile package directory. 106 | 107 | `default_folder_index`: 108 | 109 | An integer value representing a folder index to be used when "folder" is specified for "default_root". If an index outside of the range of existing folders is used, it will default to 0 (the top level folder). 110 | 111 | `alias_root`: 112 | 113 | This entry defines the root that will be used when resolving aliases defined as relative paths. For more information about valid entries, see `default_root`. Note that for path, `alias_path` will be specified. 114 | 115 | `alias_path`: 116 | 117 | This path is used as the default if `path` has been specified for the setting `alias_root`. 118 | 119 | `alias_folder_index`: 120 | 121 | An integer value representing the folder index to use when "folder" is specified for "alias_root". If an index outside of the range of the existing folders is used, it will default to 0. 122 | 123 | `ignore_case`: 124 | 125 | A boolean specifying if case should be ignored when building auto complete list. 126 | 127 | `auto_refresh_sidebar`: 128 | 129 | A boolean specifying if folders should automatically refresh and update the sidebar. In some builds, the sidebar does not refresh when contents of project folder are updated. This setting is required to refresh the sidebar in these circumstances. False by default. 130 | 131 | `completion_type`: 132 | 133 | A string specifying the type of auto completion to use. Valid values are "windows" or "nix". 134 | 135 | `complete_single_entry` 136 | 137 | A boolean setting specifying if a separator should be inserted when there is only one completion and completion type is "windows" 138 | 139 | `use_folder_name`: 140 | 141 | A boolean setting specifying if the folder name should be used or the name specified in the project. This setting only applies to ST3. 142 | 143 | `relative_from_current`: 144 | 145 | Boolean setting specifying if relative paths should be based on the current working directory. 146 | 147 | `default_extension`: 148 | 149 | String containing the default file extension. Note the extension is only applied if the specified path does not contain a dot (.) character. 150 | 151 | `folder_permissions`: 152 | 153 | String representing permissions to be applied to newly created folders. E.g. "777" -> RWX for user, group, and other. 154 | 155 | `file_permissions`: 156 | 157 | String representing permissions to be applied to newly created files. E.g. "777" -> RWX for user, group, and other. 158 | 159 | `rename_default`: 160 | 161 | Default input for renaming a file. Special value `` will be replaced with the current file name. Special value `` will be replaced with the complete filepath, including the filename. Special value `` will be replaced with the filepath, not including the filename. Note that a colon as the default will resolve to the same path as ``, if the file exists on disk. 162 | 163 | `vcs_management`: 164 | 165 | Setting to control if VCS management is used when moving and removing files. 166 | 167 | `file_templates`: 168 | 169 | An object containing information to use for templates when creating new files. The key values for this object should be a file extension. Files without extensions such as "Makefile" or ".bash_profile" use the full file name as the key. The value may either be a string of the content to be inserted or a list of paths. If a list of paths is specified, the name of the file will be displayed during selection. The paths must either be absolute, from the home directory of the user (`~/`), or relative to the Packages directory. These relative files should have the form "Packages/User/mytest.sublime-snippet". If a string is used, or the list contains a single entry, it will be automatically inserted into any newly created files. 170 | 171 | `shell_input`: 172 | 173 | Setting this value to true will allow you to escape characters as you normally would when using a shell. For example, given the input string "foo\ bar", false would result in a file named " bar" in the foo directory. With the value set to true, a file named "foo bar" would be created. In addition, setting this value to true will allow for curly brace expansion. Currently, only comma separated entries are supported. 174 | 175 | `append_extension_on_move`: 176 | 177 | Setting to control if the extension will be automatically applied to renamed files. 178 | 179 | `relative_fallback_index`: 180 | 181 | An integer value representing a folder index to be used when a relative path cannot be resolved from the current active view. If an index outside of the range of existing folders is used, it will default to 0 (the top level folder). If no folders exist as part of the project the home directory will be used. 182 | 183 | `append_extension_on_copy`: 184 | 185 | Setting to control if the extension will be automatically applied to copied files. 186 | 187 | `copy_default`: 188 | 189 | Same as `rename_default`, applied to copy command. 190 | 191 | `cut_to_file_default`: 192 | 193 | Same as `rename_default`, applied to cut to file command. 194 | 195 | `current_fallback_to_project`: 196 | 197 | If default_root is set to current, the project folder should be used as the default rather than the home directory. 198 | 199 | `warn_overwrite_on_move`: 200 | 201 | If a warning should be displayed when trying to overwrite an existing file using the move command. 202 | 203 | `new_file_default_root`: 204 | Same as `default_root` for new file commands. In addition to the valid values listed for `default_root`, "default_root" will use the value for that setting. 205 | 206 | `rename_file_default_root`: 207 | Same as `default_root` for rename file commands. In addition to the valid values listed for `default_root`, "default_root" will use the value for that setting. 208 | 209 | `copy_file_default_root`: 210 | Same as `default_root` for copy file commands. In addition to the valid values listed for `default_root`, "default_root" will use the value for that setting. 211 | 212 | `empty_filename_action`: 213 | On empty input of file name, execute an alternative action. Currently only implemented for the new file command, which will open a new unnamed file. Default value is `false` 214 | 215 | 216 | `cursor_before_extension`: 217 | When specifying initial input, this boolean will place the cursor prior to the last occurring dot. Default value is False. 218 | ### Project Specific Settings 219 | All of the above settings can also be specified as part of the project specific settings. These values override any previous values set by higher level settings, with aliases being an exception. Alias settings will be merged with higher level configurations for alias. In addition, if the same alias exist for both default/user settings and project settings, the project setting will take precedence. 220 | 221 | "settings": { 222 | "AdvancedNewFile": { 223 | "default_initial": "/project/specific/path" 224 | } 225 | } 226 | 227 | 228 | ## Features 229 | #### __init__.py creation: 230 | This plugin may optionally create `__init__` in the created directories. Please reference [Key Maps](https://github.com/SublimeText/AdvancedNewFile#keymaps) to see the default key bindings to do this. 231 | 232 | #### Tab Autocompletion: 233 | After typing in a partial path, simply press tab to autocomplete it. Continue to press tab to cycle through the options. 234 | 235 | ### Advanced Path Usage 236 | #### Home directory: 237 | To begin at the home directory simply start with `~/` like you would in the shell. 238 | 239 | #### Aliases: 240 | You can create an alias to quickly navigate to a directory. Simply type in the alias followed by a colon. Then specify the path as you would normally. Note, in an event a specified alias conflicts with a [predefined alias](https://github.com/SublimeText/AdvancedNewFile#predefined-aliases), the specified alias will take precedence. 241 | 242 | Alias paths may be relative or absolute. If a relative path is specified, the `alias_root` setting will be used as the base. When specifying absolute paths, be sure to use the system specific style (e.g. Windows `C:\\Users\\username\\Desktop`, OS X and Linux `/home/username/desktop/`). In addition, you may specify an alias from the home directory by using `~/`. 243 | 244 | If an invalid alias is specified, an error pop up will be displayed when trying to create the file. 245 | 246 | Sample aliases: 247 | 248 | { 249 | "alias": { 250 | "Desktop": "~/Desktop/" 251 | } 252 | } 253 | 254 | To use the above alias, when specifying a new file enter `Desktop:testDir/testFile`, which would then create a file at `/Desktop/testDir/testFile`. 255 | 256 | ##### Platform Specific Aliases 257 | You can also create aliases that are platform specific. These follow a similar set of rules as aliases. However, rather than specifying a string path to use, a dictionary is specified. This dictionary may contain the following keys: `windows`, `linux`, and `osx`. The path for this particular alias will be used based on the operating system in use. If the same alias is specified in both `alias` and `os_specific_alias`, the path in `os_specific_alias` will be used. 258 | 259 | Sample OS Specific Aliases: 260 | 261 | { 262 | "os_specific_alias": { 263 | "subl_packages": { 264 | "windows": "~\\AppData\\Roaming\\Sublime Text 2\\Packages", 265 | "linux": "~/.config/sublime-text-2/Packages", 266 | "osx": "~/Library/Application Support/Sublime Text 2/Packages" 267 | } 268 | } 269 | } 270 | 271 | ##### Predefined Aliases 272 | ###### Top level folders in window 273 | Top level folders can be specified by typing in the name of the folder followed by a colon. Then specify the path as you would normally. 274 | 275 | **Note** 276 | 277 | In Sublime Text 2, the name of the folder will be the actual name of the folder, not an arbitrary name specified in the project. However, due to an API update, folder names in Sublime Text 3 will match the Side Bar names. To achieve a similar behavior in Sublime Text 2, you can create `Project Specific Settings` for `alias`. 278 | 279 | ###### Current Working Directory 280 | To specify the current working directory, simply type a colon, without any preceding text. Alternatively, set `relative_from_current` to `true` in your settings. Paths specified as relative paths will then begin from the current working directory. 281 | 282 | ## Notes 283 | Thanks to Dima Kukushkin ([xobb1t](https://github.com/xobb1t)) for the original work on this plugin. Also, thank you to [facelessuser](https://github.com/facelessuser), and by extension biermeester and matthjes for the idea of platform specific settings. Additional thanks to [kemayo](https://github.com/kemayo) for the work in identifying git executable. 284 | 285 | ### Libraries Used 286 | * [ushlex](https://bitbucket.org/mixmastamyk/ushlex) - Improved version of shlex, supporting unicode characters for Python 2. 287 | 288 | ### Contributors 289 | * [alirezadot](https://github.com/alirezadot) 290 | * [aventurella](https://github.com/aventurella) 291 | * [btsai](https://github.com/btsai) 292 | * [edmundask](https://github.com/edmundask) 293 | * [skuroda](https://github.com/skuroda) 294 | * [xobb1t](https://github.com/xobb1t) 295 | 296 | -------------------------------------------------------------------------------- /advanced_new_file/commands/command_base.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import os 3 | import re 4 | import sublime 5 | import sublime_plugin 6 | import shlex 7 | 8 | from ..anf_util import * 9 | from ..platform.windows_platform import WindowsPlatform 10 | from ..platform.nix_platform import NixPlatform 11 | from ..completions.nix_completion import NixCompletion 12 | from ..completions.windows_completion import WindowsCompletion 13 | 14 | if not IS_ST3: 15 | if PLATFORM == "windows": 16 | import sys 17 | sys.path.append(os.path.dirname(sys.executable)) 18 | from ..lib.ushlex import split as st2_shlex_split 19 | 20 | 21 | VIEW_NAME = "AdvancedNewFileCreation" 22 | 23 | 24 | class AdvancedNewFileBase(object): 25 | 26 | def __init__(self, window): 27 | super(AdvancedNewFileBase, self).__init__(window) 28 | 29 | if PLATFORM == "windows": 30 | self.platform = WindowsPlatform(window.active_view()) 31 | else: 32 | self.platform = NixPlatform() 33 | 34 | def __generate_default_root(self): 35 | root_setting = self._get_default_root() 36 | path, folder_index = self.__parse_path_setting( 37 | root_setting, DEFAULT_FOLDER_INDEX_SETTING) 38 | if path is None and folder_index is None: 39 | return os.path.expanduser(self.settings.get(DEFAULT_PATH_SETTING)) 40 | elif path is None: 41 | return self.__project_folder_from_index(folder_index) 42 | return path 43 | 44 | def __generate_alias_root(self): 45 | path, folder_index = self.__parse_path_setting( 46 | self.settings.get(ALIAS_ROOT_SETTING), ALIAS_FOLDER_INDEX_SETTING) 47 | if path is None and folder_index is None: 48 | return os.path.expanduser(self.settings.get(ALIAS_PATH_SETTING)) 49 | elif path is None: 50 | if folder_index >= 0: 51 | return self.window.folders()[folder_index] 52 | else: 53 | return os.path.expanduser("~/") 54 | return path 55 | 56 | def generate_initial_path(self, initial_path=None): 57 | path = None 58 | # Search for initial string 59 | if initial_path is not None: 60 | path = initial_path 61 | else: 62 | if self.settings.get(USE_CURSOR_TEXT_SETTING, False): 63 | cursor_text = self.get_cursor_path() 64 | if cursor_text != "": 65 | path = cursor_text 66 | 67 | if path is None: 68 | path = self.settings.get(DEFAULT_INITIAL_SETTING) 69 | 70 | return path 71 | 72 | def run_setup(self): 73 | self.view = self.window.active_view() 74 | self.settings = get_settings(self.view) 75 | self.root = None 76 | self.alias_root = None 77 | self.aliases = self.__get_aliases() 78 | 79 | self.root = self.__generate_default_root() 80 | self.alias_root = self.__generate_alias_root() 81 | 82 | # Need to fix this 83 | debug = self.settings.get(DEBUG_SETTING) or False 84 | 85 | completion_type = self.settings.get(COMPLETION_TYPE_SETTING) 86 | if completion_type == "windows": 87 | self.completion = WindowsCompletion(self) 88 | else: 89 | self.completion = NixCompletion(self) 90 | 91 | def __get_aliases(self): 92 | aliases = self.settings.get(ALIAS_SETTING) 93 | all_os_aliases = self.settings.get(OS_SPECIFIC_ALIAS_SETTING) 94 | for key in all_os_aliases: 95 | if PLATFORM in all_os_aliases.get(key): 96 | aliases[key] = all_os_aliases.get(key).get(PLATFORM) 97 | 98 | return aliases 99 | 100 | def __parse_path_setting(self, setting, index_setting): 101 | root = None 102 | folder_index = None 103 | if setting == "home": 104 | root = os.path.expanduser("~/") 105 | elif setting == "current": 106 | if self.view is not None: 107 | filename = self.view.file_name() 108 | if filename is not None: 109 | root = os.path.dirname(filename) 110 | if root is None: 111 | if self.settings.get(CURRENT_FALLBACK_TO_PROJECT_SETTING, False): 112 | folder_index = self.__validate_folder_index(0) 113 | if folder_index == -1: 114 | root = os.path.expanduser("~/") 115 | else: 116 | root = os.path.expanduser("~/") 117 | elif setting == "project_folder": 118 | folder_index = self.settings.get(index_setting) 119 | folder_index = self.__validate_folder_index(folder_index) 120 | elif setting == "top_folder": 121 | folder_index = self.__validate_folder_index(0) 122 | elif setting == "path": 123 | pass 124 | else: 125 | print("Invalid root specifier") 126 | 127 | return (root, folder_index) 128 | 129 | def __validate_folder_index(self, folder_index): 130 | num_folders = len(self.window.folders()) 131 | if num_folders == 0: 132 | folder_index = -1 133 | elif num_folders < folder_index: 134 | folder_index = 0 135 | return folder_index 136 | 137 | def __parse_for_shell_input(self, path): 138 | if not IS_ST3 and self.__contains_non_ascii(path): 139 | split_path = self.__split_shell_input_for_st2_non_ascii(path) 140 | else: 141 | split_path = shlex.split(str(path)) 142 | 143 | return " ".join(split_path) 144 | 145 | def __split_shell_input_for_st2_non_ascii(self, path): 146 | return st2_shlex_split(path) 147 | 148 | def __contains_non_ascii(self, string): 149 | # Don't really like this.... 150 | try: 151 | string.decode("ascii") 152 | except UnicodeEncodeError: 153 | return True 154 | return False 155 | 156 | def split_path(self, path=""): 157 | HOME_REGEX = r"^~[/\\]" 158 | root = None 159 | try: 160 | root, path = self.platform.split(path) 161 | if self.settings.get(SHELL_INPUT_SETTING, False) and len(path) > 0: 162 | path = self.__parse_for_shell_input(path) 163 | # Parse if alias 164 | if TOP_LEVEL_SPLIT_CHAR in path and root is None: 165 | parts = path.rsplit(TOP_LEVEL_SPLIT_CHAR, 1) 166 | root, path = self.__translate_alias(parts[0]) 167 | path_list = [] 168 | if path != "": 169 | path_list.append(path) 170 | if parts[1] != "": 171 | path_list.append(parts[1]) 172 | path = TOP_LEVEL_SPLIT_CHAR.join(path_list) 173 | elif re.match(r"^/", path): 174 | root, path_offset = self.platform.parse_nix_path(root, path) 175 | path = path[path_offset:] 176 | # Parse if tilde used 177 | elif re.match(HOME_REGEX, path) and root is None: 178 | root = os.path.expanduser("~") 179 | path = path[2:] 180 | elif (re.match(r"^\.{1,2}[/\\]", path) and 181 | self.settings.get(RELATIVE_FROM_CURRENT_SETTING, False)): 182 | path_index = 2 183 | if self.view.file_name() is not None: 184 | root = os.path.dirname(self.view.file_name()) 185 | else: 186 | folder_index = self.settings.get( 187 | RELATIVE_FALLBACK_INDEX_SETTING, 0) 188 | folder_index = self.__validate_folder_index(folder_index) 189 | root = self.__project_folder_from_index(folder_index) 190 | if re.match(r"^\.{2}[/\\]", path): 191 | root = os.path.dirname(root) 192 | path_index = 3 193 | path = path[path_index:] 194 | 195 | # Default 196 | if root is None: 197 | root = self.root 198 | except IndexError: 199 | root = os.path.expanduser("~") 200 | 201 | return root, path 202 | 203 | def __project_folder_from_index(self, folder_index): 204 | if folder_index >= 0: 205 | return self.window.folders()[folder_index] 206 | else: 207 | return os.path.expanduser("~/") 208 | 209 | def bash_expansion(self, path): 210 | if len(path) == 0: 211 | return path 212 | 213 | split_path = shlex.split(path) 214 | new_path = " ".join(split_path) 215 | return new_path 216 | 217 | def __translate_alias(self, path): 218 | root = None 219 | split_path = None 220 | if path == "" and self.view is not None: 221 | filename = self.view.file_name() 222 | if filename is not None: 223 | root = os.path.dirname(filename) 224 | else: 225 | split_path = path.split(TOP_LEVEL_SPLIT_CHAR) 226 | join_index = len(split_path) - 1 227 | target = path 228 | root_found = False 229 | use_folder_name = self.settings.get(USE_FOLDER_NAME_SETTING) 230 | while join_index >= 0 and not root_found: 231 | # Folder aliases 232 | for name, folder in get_project_folder_data(use_folder_name): 233 | if name == target: 234 | root = folder 235 | root_found = True 236 | break 237 | # Aliases from settings. 238 | for alias in self.aliases.keys(): 239 | if alias == target: 240 | alias_path = self.aliases.get(alias) 241 | if re.search(HOME_REGEX, alias_path) is None: 242 | root = self.platform.get_alias_absolute_path( 243 | self.alias_root, alias_path) 244 | if root is not None: 245 | break 246 | root = os.path.expanduser(alias_path) 247 | root_found = True 248 | break 249 | remove = re.escape(split_path[join_index]) 250 | target = re.sub(r":%s$" % remove, "", target) 251 | join_index -= 1 252 | 253 | if root is None: 254 | # Nothing found 255 | return None, path 256 | elif split_path is None: 257 | # Current directory as alias 258 | return os.path.abspath(root), "" 259 | else: 260 | # Add to index so we re 261 | join_index += 2 262 | return (os.path.abspath(root), 263 | TOP_LEVEL_SPLIT_CHAR.join(split_path[join_index:])) 264 | 265 | def input_panel_caption(self): 266 | return "" 267 | 268 | def show_filename_input(self, initial): 269 | caption = self.input_panel_caption() 270 | 271 | self.input_panel_view = self.window.show_input_panel( 272 | caption, initial, 273 | self.on_done, self.__update_filename_input, self.clear 274 | ) 275 | 276 | self.input_panel_view.set_name(VIEW_NAME) 277 | self.input_panel_view.settings().set("auto_complete_commit_on_tab", 278 | False) 279 | self.input_panel_view.settings().set("tab_completion", False) 280 | self.input_panel_view.settings().set("translate_tabs_to_spaces", False) 281 | self.input_panel_view.settings().set("anf_panel", True) 282 | if self.settings.get(CURSOR_BEFORE_EXTENSION_SETTING): 283 | self.__place_cursor_before_extension(self.input_panel_view) 284 | 285 | def __update_filename_input(self, path_in): 286 | new_content = path_in 287 | if self.settings.get(COMPLETION_TYPE_SETTING) == "windows": 288 | if "prev_text" in dir(self) and self.prev_text != path_in: 289 | if self.view is not None: 290 | self.view.erase_status("AdvancedNewFile2") 291 | if path_in.endswith("\t"): 292 | new_content = self.completion.completion(path_in.replace("\t", "")) 293 | if new_content != path_in: 294 | self.input_panel_view.run_command("anf_replace", 295 | {"content": new_content}) 296 | else: 297 | base, path = self.split_path(path_in) 298 | 299 | creation_path = generate_creation_path(self.settings, base, path, 300 | True) 301 | if self.settings.get(SHOW_PATH_SETTING, False): 302 | self.update_status_message(creation_path) 303 | 304 | def update_status_message(self, creation_path): 305 | pass 306 | 307 | def entered_file_action(self, path): 308 | pass 309 | 310 | def empty_file_action(self): 311 | pass 312 | 313 | def on_done(self, input_string): 314 | if len(input_string) != 0: 315 | self.entered_filename(input_string) 316 | elif self.settings.get(DEFAULT_NEW_FILE, False): 317 | self.empty_file_action() 318 | 319 | self.clear() 320 | self.refresh_sidebar() 321 | 322 | def entered_filename(self, filename): 323 | # Check if valid root specified for windows. 324 | if PLATFORM == "windows": 325 | if re.match(WIN_ROOT_REGEX, filename): 326 | root = filename[0:3] 327 | if not os.path.isdir(root): 328 | sublime.error_message(root + " is not a valid root.") 329 | self.clear() 330 | return 331 | 332 | base, path = self.split_path(filename) 333 | file_path = generate_creation_path(self.settings, base, path, True) 334 | # Check for invalid alias specified. 335 | is_valid = (TOP_LEVEL_SPLIT_CHAR in filename and 336 | not self.platform.is_absolute_path(base)) 337 | if is_valid: 338 | if base == "": 339 | error_message = "Current file cannot be resolved." 340 | else: 341 | error_message = "'" + base + "' is an invalid alias." 342 | sublime.error_message(error_message) 343 | 344 | self.entered_file_action(file_path) 345 | 346 | def open_file(self, file_path): 347 | new_view = None 348 | if os.path.isdir(file_path): 349 | if not re.search(r"(/|\\)$", file_path): 350 | sublime.error_message("Cannot open view for '" + file_path + 351 | "'. It is a directory. ") 352 | else: 353 | new_view = self.window.open_file(file_path) 354 | return new_view 355 | 356 | def refresh_sidebar(self): 357 | if self.settings.get(AUTO_REFRESH_SIDEBAR_SETTING): 358 | try: 359 | self.window.run_command("refresh_folder_list") 360 | except: 361 | pass 362 | 363 | def clear(self): 364 | if self.view is not None: 365 | self.view.erase_status("AdvancedNewFile") 366 | self.view.erase_status("AdvancedNewFile2") 367 | 368 | def create(self, filename): 369 | base, filename = os.path.split(filename) 370 | self.create_folder(base) 371 | if filename != "": 372 | creation_path = os.path.join(base, filename) 373 | self.create_file(creation_path) 374 | 375 | def create_file(self, name): 376 | open(name, "a").close() 377 | if self.settings.get(FILE_PERMISSIONS_SETTING, "") != "": 378 | file_permissions = self.settings.get(FILE_PERMISSIONS_SETTING, "") 379 | os.chmod(name, int(file_permissions, 8)) 380 | 381 | def create_folder(self, path): 382 | init_list = [] 383 | temp_path = path 384 | while not os.path.exists(temp_path): 385 | init_list.append(temp_path) 386 | temp_path = os.path.dirname(temp_path) 387 | try: 388 | if not os.path.exists(path): 389 | os.makedirs(path) 390 | except OSError as ex: 391 | if ex.errno != errno.EEXIST: 392 | raise 393 | 394 | file_permissions = self.settings.get(FILE_PERMISSIONS_SETTING, "") 395 | folder_permissions = self.settings.get(FOLDER_PERMISSIONS_SETTING, "") 396 | for entry in init_list: 397 | if self.is_python: 398 | creation_path = os.path.join(entry, '__init__.py') 399 | open(creation_path, 'a').close() 400 | if file_permissions != "": 401 | os.chmod(creation_path, int(file_permissions, 8)) 402 | if folder_permissions != "": 403 | os.chmod(entry, int(folder_permissions, 8)) 404 | 405 | def get_cursor_path(self): 406 | if self.view is None: 407 | return "" 408 | 409 | view = self.view 410 | path = "" 411 | for region in view.sel(): 412 | syntax = view.scope_name(region.begin()) 413 | if region.begin() != region.end(): 414 | path = view.substr(region) 415 | break 416 | if (re.match(".*string.quoted.double", syntax) or 417 | re.match(".*string.quoted.single", syntax)): 418 | path = view.substr(view.extract_scope(region.begin())) 419 | path = re.sub('^"|\'', '', re.sub('"|\'$', '', path.strip())) 420 | break 421 | 422 | return path 423 | 424 | def _expand_default_path(self, path): 425 | current_file = self.view.file_name() 426 | if current_file: 427 | directory, current_file_name = os.path.split(current_file) 428 | path = path.replace("", current_file) 429 | path = path.replace("", directory + os.sep) 430 | else: 431 | current_file_name = "" 432 | 433 | path = path.replace("", current_file_name) 434 | return path 435 | 436 | def _find_open_file(self, file_name): 437 | window = self.window 438 | if IS_ST3: 439 | return window.find_open_file(file_name) 440 | else: 441 | for view in window.views(): 442 | view_name = view.file_name() 443 | if view_name != "" and view_name == file_name: 444 | return view 445 | return None 446 | 447 | ## Should be overridden by sub class 448 | def get_default_root_setting(self): 449 | return DEFAULT_ROOT_SETTING 450 | 451 | def _get_default_root(self): 452 | root_setting_value = self.get_default_root_setting() 453 | root_setting = self.settings.get(root_setting_value) 454 | if root_setting == DEFAULT_ROOT_SETTING: 455 | return self.settings.get(DEFAULT_ROOT_SETTING) 456 | return root_setting 457 | 458 | def __place_cursor_before_extension(self, view): 459 | if view.settings().get("anf_panel", False): 460 | cursors = view.sel() 461 | cursor = cursors[0] 462 | line_region = view.line(cursor) 463 | content = view.substr(line_region) 464 | matcher = re.match(r"(.+)\..+", content) 465 | if matcher: 466 | initial_position = len(matcher.group(1)) 467 | cursors.clear() 468 | cursors.add(sublime.Region(initial_position, initial_position)) 469 | --------------------------------------------------------------------------------