├── .editorconfig ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build-package.ps1 └── src ├── snippets.ini └── snippets.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = crlf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{md,rst,txt}] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "keypirinha-sdk"] 2 | path = keypirinha-sdk 3 | url = https://github.com/Keypirinha/SDK 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is loosely based on 6 | [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project 7 | adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | ## 2.0.0 10 | 11 | ### Added 12 | 13 | - Support for subdirectories and root level snippets 14 | 15 | ### Removed 16 | 17 | - Keyword setting, use subdirectories instead 18 | 19 | ## 1.0.1 20 | 21 | ### Changed 22 | 23 | - Improved documentation 24 | 25 | ## 1.0.0 26 | 27 | - Initial release 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dan I Smith 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keypirinha Plugin: Snippets 2 | 3 | A [Keypirinha](http://keypirinha.com) plugin to quickly copy user defined 4 | snippets to the clipboard. 5 | 6 | [![Download Latest](https://img.shields.io/badge/download-latest-green.svg)](https://github.com/dozius/keypirinha-snippets/releases/latest) 7 | [![Github All Releases](https://img.shields.io/github/downloads/dozius/keypirinha-snippets/total.svg)](https://github.com/dozius/keypirinha-snippets/releases/latest) 8 | [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/cisc) 9 | 10 | ## Installation 11 | 12 | ### With [PackageControl](https://github.com/ueffel/Keypirinha-PackageControl) 13 | 14 | Install Package "keypirinha-snippets" 15 | 16 | ### Manually 17 | 18 | * Download `Snippets.keypirinha-package` from the 19 | [releases](https://github.com/dozius/keypirinha-snippets/releases/latest) page. 20 | * Copy the file into `%APPDATA%\Keypirinha\InstalledPackages` (installed mode) or 21 | `\portable\Profile\InstalledPackages` (portable mode) 22 | 23 | ## Usage 24 | 25 | Snippets items are added to the 26 | [Catalog](http://keypirinha.com/glossary.html#term-catalog). Select these items to 27 | list and search all user defined snippets. Selecting a snippet copies it to the 28 | clipboard. 29 | 30 | Snippets items are defined in the configuration file. 31 | 32 | ### Configuration 33 | 34 | The configuration file `snippets.ini` can be auto-generated by entering 35 | `configure package snippets` in Keypirinha. 36 | 37 | The readonly documentation for Snippets will appear alongside the config file, 38 | reference the documentation to define your own snippets. 39 | 40 | ## License 41 | 42 | This package is distributed under the terms of the MIT license. 43 | -------------------------------------------------------------------------------- /build-package.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Builds the plugin into a package for release 4 | #> 5 | if (Test-Path .\build) { 6 | Remove-Item .\build -Recurse -Force 7 | } 8 | 9 | New-Item -Path .\build -ItemType Directory 10 | 11 | $compress = @{ 12 | Path = ".\src\*.*", ".\LICENSE", ".\README.md" 13 | CompressionLevel = "Optimal" 14 | DestinationPath = ".\build\Snippets.zip" 15 | } 16 | 17 | Compress-Archive @compress 18 | Rename-Item .\build\Snippets.zip Snippets.keypirinha-package 19 | -------------------------------------------------------------------------------- /src/snippets.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Snippets Package configuration file 3 | # More info at http://keypirinha.com 4 | # 5 | 6 | [main] 7 | # Plugin's main configuration section 8 | 9 | # Snippets 10 | # 11 | # * The plugin is triggered when you type the starting folder of any section 12 | # or when you type the label of a root string. 13 | # 14 | # * Snippets must be defined under a [snippets/*] section. 15 | # * The 'snippets' keyword is used to define sections used in this plugin. 16 | # * The section (minus the snippets tag) defines the folders or menus the 17 | # snippets are stored under. 18 | # * Folders/menus will be at the top of the suggestions and all items will be 19 | # ordered as in the configuration file. 20 | # 21 | # * Examples: 22 | # [snippets/Text Emojis] 23 | # shrug = ¯\_(ツ)_/¯ 24 | # bear = ʕ•ᴥ•ʔ 25 | # 26 | # [snippets/Examples] 27 | # multiline = 28 | # This is 29 | # A multiline 30 | # Snippet 31 | # 32 | # [snippets/Multiple/Folders] # You can add more folders/menus if needed 33 | # folder_example_1 = This string is behind two menus 34 | # folder_example_2 = This string is also behind two menus 35 | # 36 | # * In this example the snippets are directly catalogged, accessible immediately 37 | # [snippets/snippets] # IMPORTANT The first folder must be labelled snippets 38 | # root_string_1 = This string is not hidden behind any menu or keyword 39 | # root_string_2 = This string is also not hidden and easily accessible 40 | 41 | [var] 42 | # As in every Keypirinha's configuration file, you may optionally include a 43 | # [var] section to declare variables that you want to reuse anywhere else in 44 | # this file. 45 | # 46 | # Note that the [var] section is inherited, which means that any value defined 47 | # in the main configuration file of the application (i.e.: "Keypirinha.ini") has 48 | # already been made available to this file as well so you do not need to 49 | # duplicate it here unless you want to override it. 50 | # 51 | # REMINDER: For convenience, Keypirinha silently populates this section with 52 | # predefined values that may come handy. Here are some of them: APP_DIR, 53 | # APP_EXE, PROFILE_DIR, PROFILE_DIR_INSTALLED_PACKS, PROFILE_DIR_LIVE_PACKS, 54 | # PROFILE_DIR_USER and the KNOWNFOLDER_* and KNOWNFOLDERGUID_* values. 55 | # 56 | # See the "Configuration" chapter of the documentation for more information. 57 | 58 | [env] 59 | # For convenience, Keypirinha populates this [env] section in every loaded 60 | # configuration file so you can easily access to environment variables like 61 | # PATH for example from this file using syntax: ${env:PATH} 62 | # 63 | # If an environment variable happens to be changed while Keypirinha is running 64 | # and this modification impacts current configuration, application and packages 65 | # configuration will be reloaded if needed only. 66 | # 67 | # See the "Configuration" chapter of the documentation for more information. 68 | -------------------------------------------------------------------------------- /src/snippets.py: -------------------------------------------------------------------------------- 1 | # Keypirinha launcher (keypirinha.com) 2 | 3 | import keypirinha as kp 4 | import keypirinha_util as kpu 5 | import keypirinha_net as kpnet 6 | 7 | 8 | def get_path_as_list(path): 9 | return path.split("/") 10 | 11 | 12 | def get_path_from_section(section): 13 | return section[9:] 14 | 15 | 16 | class Snippets(kp.Plugin): 17 | """ 18 | Quickly copy user defined snippets to the clipboard. 19 | """ 20 | 21 | SNIPPETS = "snippets" 22 | ITEMCAT_RESULT = kp.ItemCategory.USER_BASE + 1 23 | 24 | structure = {} 25 | 26 | def __init__(self): 27 | super().__init__() 28 | 29 | def on_start(self): 30 | self._read_config() 31 | 32 | def on_catalog(self): 33 | catalog = [] 34 | 35 | for root_node in self.structure.keys(): 36 | if root_node != self.SNIPPETS: 37 | catalog.append( 38 | self.create_item( 39 | category=kp.ItemCategory.REFERENCE, 40 | label=root_node, 41 | short_desc=root_node + " " + self.SNIPPETS, 42 | target=root_node, 43 | args_hint=kp.ItemArgsHint.REQUIRED, 44 | hit_hint=kp.ItemHitHint.NOARGS, 45 | ) 46 | ) 47 | else: 48 | catalog += self.structure[self.SNIPPETS][self.SNIPPETS] 49 | 50 | self.set_catalog(catalog) 51 | 52 | def on_suggest(self, user_input, items_chain): 53 | if not items_chain: 54 | return 55 | 56 | if items_chain and ( 57 | items_chain[0].category() != kp.ItemCategory.REFERENCE 58 | or items_chain[0].target() not in self.structure.keys() 59 | ): 60 | return 61 | 62 | path = [node.label() for node in items_chain] 63 | 64 | structure_ref = self.get_node_from_structure(path) 65 | nodes = structure_ref.keys() 66 | 67 | suggestions = [] 68 | 69 | for node in nodes: 70 | if node != self.SNIPPETS: 71 | suggestions.append( 72 | self.create_item( 73 | category=kp.ItemCategory.REFERENCE, 74 | label=node, 75 | short_desc=node + " " + self.SNIPPETS, 76 | target=node, 77 | args_hint=kp.ItemArgsHint.REQUIRED, 78 | hit_hint=kp.ItemHitHint.NOARGS, 79 | ) 80 | ) 81 | 82 | if self.SNIPPETS in nodes: 83 | suggestions += structure_ref[self.SNIPPETS] 84 | 85 | user_input = user_input.strip() 86 | 87 | self.set_suggestions( 88 | suggestions, 89 | kp.Match.ANY if not user_input else kp.Match.FUZZY, 90 | kp.Sort.NONE if not user_input else kp.Sort.SCORE_DESC, 91 | ) 92 | 93 | def on_execute(self, item, action): 94 | if item and item.category() == self.ITEMCAT_RESULT: 95 | kpu.set_clipboard(item.target()) 96 | 97 | def on_activated(self): 98 | pass 99 | 100 | def on_deactivated(self): 101 | pass 102 | 103 | def on_events(self, flags): 104 | if flags & kp.Events.PACKCONFIG: 105 | self._read_config() 106 | self.on_catalog() 107 | 108 | def _read_config(self): 109 | settings = self.load_settings() 110 | 111 | sections = [ 112 | section 113 | for section in settings.sections() 114 | if section.lower().startswith(self.SNIPPETS) 115 | ] 116 | 117 | data = {} 118 | 119 | for section in sections: 120 | path = get_path_from_section(section) 121 | 122 | data[path] = {} 123 | 124 | snippets = settings.keys(section) 125 | for snippet_key in snippets: 126 | snippet_string = settings.get(snippet_key, section=section) 127 | if not len(snippet_string): 128 | self.warn( 129 | 'Snippet "{}" does not have "string" value (or is empty). Ignored.'.format( 130 | snippet_key 131 | ) 132 | ) 133 | continue 134 | 135 | data[path][snippet_key] = snippet_string 136 | 137 | self.generate_folder_structure(data) 138 | 139 | def get_node_from_structure(self, path): 140 | structure_ref = self.structure 141 | for node in path: 142 | structure_ref = structure_ref[node] 143 | return structure_ref 144 | 145 | def set_node_in_structure(self, path, value): 146 | structure_ref = self.structure 147 | for node in path[:-1]: 148 | structure_ref = structure_ref.setdefault(node, {}) 149 | structure_ref[path[-1]] = value 150 | 151 | def generate_folder_structure(self, data): 152 | self.structure.clear() 153 | self.add_path_to_structure(get_path_as_list(path) for path in data.keys()) 154 | 155 | for path, snippets in data.items(): 156 | path = get_path_as_list(path) 157 | 158 | self.set_node_in_structure( 159 | path, {self.SNIPPETS: self.create_result_set(snippets)} 160 | ) 161 | 162 | def add_path_to_structure(self, paths): 163 | for path in paths: 164 | structure_ref = self.structure 165 | for node in path: 166 | if node not in structure_ref: 167 | structure_ref[node] = {} 168 | structure_ref = structure_ref[node] 169 | 170 | def create_result_set(self, snippets): 171 | results = [] 172 | 173 | for label, snippet in snippets.items(): 174 | short_description = snippet.replace("\n", "↵") 175 | results.append( 176 | self.create_item( 177 | category=self.ITEMCAT_RESULT, 178 | label=label, 179 | short_desc=short_description, 180 | target=snippet, 181 | args_hint=kp.ItemArgsHint.FORBIDDEN, 182 | hit_hint=kp.ItemHitHint.IGNORE, 183 | ) 184 | ) 185 | 186 | return results 187 | --------------------------------------------------------------------------------