├── list.sublime-commands ├── WinSCP (OSX).sublime-settings ├── .editorconfig ├── README.md ├── browseWithWinSCP.py ├── LICENSE └── dirConfig.py /list.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "SFTP: Browse With WinSCP", 4 | "command": "browse_with_winscp" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /WinSCP (OSX).sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "winscpExe": "/Applications/winscp.app/Contents/Resources/wineprefix/drive_c/Program Files/WinSCP/WinSCP.exe" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.{py,sublime-commands}] 9 | indent_style = tab 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sublime WinSCP 2 | 3 | Sublime Text WinSCP integration here is the [demo](https://www.youtube.com/watch?v=0Q7X9zmnT7Y) 4 | 5 | # How to install 6 | 7 | This package is available over package control (search for `WinSCP`) 8 | 9 | # [how to install WinSCP on linux](https://github.com/thecotne/sublime-WinSCP/wiki/how-to-install-WinSCP-on-linux) 10 | 11 | # How to use 12 | 13 | 1. open any project which has `sftp-config.json` 14 | 2. open command palette 15 | 3. search and click `SFTP: Browse With WinSCP` 16 | 4. enjoy! 17 | 18 | # Found issue? 19 | 20 | [submit here](https://github.com/thecotne/sublime-WinSCP/issues/new) 21 | -------------------------------------------------------------------------------- /browseWithWinSCP.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin, sublime, subprocess, os, sys 2 | from .dirConfig import getConfig 3 | 4 | settings = sublime.load_settings('WinSCP.sublime-settings'); 5 | 6 | configName = 'sftp-config.json' 7 | winscpExe = settings.get('winscpExe') or 'WinSCP'; 8 | 9 | if sys.platform.startswith('win'): 10 | for programFilesVar in ['ProgramFiles', 'ProgramFiles(x86)']: 11 | try: 12 | _winscpExe = os.environ[programFilesVar] + '\WinSCP\WinSCP.exe' 13 | if os.path.exists(_winscpExe): 14 | winscpExe = _winscpExe 15 | break 16 | except KeyError: 17 | pass 18 | 19 | startWinscpCommand = '"'+ winscpExe + '"' + ' {type}://"{user}":"{password}"@{host}:{port}"{remote_path}" /rawsettings LocalDirectory="{local_path}"' 20 | 21 | if sys.platform == 'darwin': 22 | startWinscpCommand = "/Applications/Wine.app/Contents/Resources/bin/wine " + startWinscpCommand 23 | 24 | class browse_with_winscpCommand(sublime_plugin.WindowCommand): 25 | def run(self, edit = None): 26 | conf = getConfig(configName) 27 | if conf is not None: 28 | subprocess.Popen(startWinscpCommand.format(**conf), shell=True) 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 cotne nazarashvili 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /dirConfig.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin, sublime, os, sys, re, json 2 | 3 | configs = {} 4 | nestingLimit = 30 5 | 6 | def isString(var): 7 | var_type = type(var) 8 | if sys.version[0] == '3': 9 | return var_type is str or var_type is bytes 10 | else: 11 | return var_type is str or var_type is unicode 12 | 13 | def getFolders(file_path): 14 | if file_path is None: 15 | return [] 16 | folders = [file_path] 17 | limit = nestingLimit 18 | while True: 19 | split = os.path.split(file_path) 20 | if len(split) == 0: 21 | break 22 | file_path = split[0] 23 | limit -= 1 24 | if len(split[1]) == 0 or limit < 0: 25 | break 26 | folders.append(split[0]) 27 | return folders 28 | 29 | def hasActiveView(): 30 | window = sublime.active_window() 31 | if window is None: 32 | return False 33 | view = window.active_view() 34 | if view is None or view.file_name() is None: 35 | return False 36 | return True 37 | 38 | def guessConfigFile(folders, configName): 39 | for folder in folders: 40 | config = getConfigFile(folder, configName) 41 | if config is not None: 42 | return config 43 | for folder in os.walk(folder): 44 | config = getConfigFile(folder[0], configName) 45 | if config is not None: 46 | return config 47 | return None 48 | 49 | def getConfigFile(file_path, configName): 50 | cacheKey = file_path 51 | if isString(cacheKey) is False: 52 | cacheKey = cacheKey.decode('utf-8') 53 | try: 54 | return configs[cacheKey] 55 | except KeyError: 56 | try: 57 | folders = getFolders(file_path) 58 | if folders is None or len(folders) == 0: 59 | return None 60 | configFolder = findConfigFile(folders, configName) 61 | if configFolder is None: 62 | return None 63 | config = os.path.join(configFolder, configName) 64 | configs[cacheKey] = config 65 | return config 66 | except AttributeError: 67 | return None 68 | 69 | def findConfigFile(folders, configName): 70 | return findFile(folders, configName) 71 | 72 | def findFile(folders, file_name): 73 | if folders is None: 74 | return None 75 | for folder in folders: 76 | if isString(folder) is False: 77 | folder = folder.decode('utf-8') 78 | if os.path.exists(os.path.join(folder, file_name)) is True: 79 | return folder 80 | return None 81 | 82 | removeLineComment = re.compile('//.*', re.I) 83 | removeComma = re.compile('\,(\s|\\n)+\}', re.I) 84 | 85 | 86 | def parseJson(file_path): 87 | contents = "" 88 | try: 89 | file = open(file_path, 'r') 90 | for line in file: 91 | contents += removeLineComment.sub('', line) 92 | finally: 93 | file.close() 94 | contents = removeComma.sub('}', contents) 95 | decoder = json.JSONDecoder() 96 | return decoder.decode(contents) 97 | 98 | 99 | def getConfig(name = 'sftp-config.json'): 100 | if hasActiveView() is False: 101 | file_path = os.path.dirname(guessConfigFile(sublime.active_window().folders(), name)) 102 | else: 103 | file_path = os.path.dirname(sublime.active_window().active_view().file_name()) 104 | _file = getConfigFile(file_path, name) 105 | try: 106 | if isString(_file): 107 | conf = parseJson(_file) 108 | conf['local_path'] = os.path.dirname(_file) 109 | return conf 110 | except: 111 | pass 112 | return None 113 | --------------------------------------------------------------------------------