├── .gitignore ├── COPYING.txt ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── WrapToColumn-1.9.1.jar ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── andrewbrookins │ │ └── idea │ │ └── wrap │ │ ├── CodeWrapper.kt │ │ ├── WrapAction.kt │ │ ├── WrapParagraphAction.kt │ │ ├── WrappingAlgorithms.kt │ │ ├── config │ │ ├── WrapSettingsConfigurable.kt │ │ └── WrapSettingsState.kt │ │ ├── ui │ │ ├── WrapSettingsPanel.form │ │ └── WrapSettingsPanel.java │ │ └── utils.kt └── resources │ └── META-INF │ └── plugin.xml └── test └── java └── com └── andrewbrookins └── idea └── wrap ├── CodeWrapperTest.kt └── python_example_text.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | out 3 | .ropeproject 4 | .gradle 5 | build 6 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution ofource code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software throughny other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL :ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Wrap to Column 2 | Copyright 2013-2015 Andrew Brookins 3 | 4 | This product includes code from the Apache Software Foundation. In particular, 5 | `CodeWrapper._wrap` was derived from the Apache Commons Lang's WordUtils.wrap 6 | method, released under the Apache License 2.0, included here as LICENSE.txt. 7 | 8 | The CodeWrapper class was inspired by code copyright 2006 Nir Soffer 9 | (https://pypi.python.org/pypi/codewrap/), released under the GPL v2, included 10 | here as the file COPYING.txt. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wrap to Column: An IntelliJ Plugin That Wraps Text 2 | 3 | This plugin wraps text to a maximum line width. It is intended as a replacement 4 | for the `gq` command in Vim and `fill-paragraph` in Emacs, which are both dear 5 | to my heart. 6 | 7 | ## Supported IntelliJ Editors 8 | 9 | This plugin should work in any IntelliJ editor, including IntelliJ IDEA Community, 10 | PyCharm, RubyMine, WebStorm, etc. 11 | 12 | ## Editor Actions 13 | 14 | This plugin adds two new _actions_ to IntelliJ editors that you can use to wrap text: 15 | 16 | - Wrap Line to Column: **Wraps selected text or the current line if no text is 17 | selected**. This is useful for IdeaVim users who wish to pair the command with 18 | motions like `vip` (select current paragraph). 19 | 20 | - Wrap Paragraph to Column: **Wraps all lines in the current paragraph**. A 21 | paragraph is defined as text offset by blank lines -- including lines that 22 | only start with what looks like comment syntax (e.g., `// `). Selected text 23 | is ignored (no selection is needed). 24 | 25 | To learn how to you call these actions, read the _How To Use the Plugin_ section 26 | in this readme. 27 | 28 | ## Installing 29 | 30 | Install the plugin from an IntelliJ editor (like IntelliJ Ultimate, PyCharm, 31 | etc.) inside the Preferences -> Plugins window. 32 | 33 | ### From Within an IntelliJ Editor 34 | 35 | Follow these steps to install this plugin from within an IntelliJ editor: 36 | 37 | * Open Preferences -> Plugins and click _Marketplace_ 38 | * Search for "Wrap to column" 39 | * Choose Wrap to Column 40 | * Click Install 41 | * Restart the editor when prompted 42 | 43 | ### From GitHub 44 | 45 | To install the latest zip from GitHub: 46 | 47 | * Either clone the repository or download the latest release 48 | (https://github.com/abrookins/WrapToColumn/releases) 49 | * Open your IntelliJ editor of choice 50 | * Choose Preferences -> Plugins -> Install plugin from disk 51 | * Choose the **WrapToColumn.zip** (not the .jar file) file in the source 52 | checkout or your Downloads folder 53 | 54 | ## How To Use the Plugin 55 | 56 | To use this plugin, you trigger one of its actions (Wrap Line to Column, 57 | Wrap Paragraph to Column) with a keyboard shortcut, menu item, IdeaVim 58 | command, or using [Search Everywhere](https://blog.jetbrains.com/idea/2020/05/when-the-shift-hits-the-fan-search-everywhere/). 59 | 60 | Example usage: 61 | 62 | **To wrap the line you're currently editing**, run the Wrap Line to Column action. 63 | 64 | **To wrap all of the lines in the _paragraph_ that you are editing**, run the Wrap Paragraph to Column action. 65 | 66 | **To wrap multiple lines and paragraphs in a file**, select the text to wrap, then run the Wrap Line to Column action (this plugin wraps selected text). 67 | 68 | ### Keyboard Shortcuts 69 | 70 | The keyboard shortcuts for the **Wrap Line to Column** action are: 71 | 72 | * Mac: Command + Control + Shift + W 73 | * PC: Control + Alt + Shift + W 74 | 75 | The keyboard shortcuts for the **Wrap Paragraph to Column** action are: 76 | 77 | * Mac: Command + Control + Shift + P 78 | * PC: Control + Alt + Shift + P 79 | 80 | Feel free to change these in your keymap (Preferences -> Keymap) or IeaVim 81 | configuration file! 82 | 83 | ### Menu Items 84 | 85 | Menu items should exist for both commands in the Edit drop-down menu: 86 | 87 | * Edit -> Wrap Line to Column 88 | * Edit -> Wrap Paragraph to Column 89 | 90 | ### IdeaVim 91 | 92 | When using IdeaVim, you can invoke the above commands using the following 93 | actions: 94 | * Wrap Line to Column: `com.andrewbrookins.idea.wrap.WrapAction` 95 | * Wrap Paragraph to Column: `com.andrewbrookins.idea.wrap.WrapParagraphAction` 96 | 97 | For example, you can add the following line to `.ideavimrc` to emulate Vim's 98 | `gq` command: 99 | 100 | ``` 101 | nmap gq :action com.andrewbrookins.idea.wrap.WrapAction 102 | ``` 103 | 104 | ## Settings 105 | 106 | ### How Does WrapToColumn Determine the Line Length? 107 | 108 | The maximum width of wrapped text is based on one of the following settings, in 109 | this order of priority: 110 | 111 | 1. The "Right margin override" setting found in the Wrap to Column settings panel 112 | 113 | 2. The right column setting configured for the language of the currently active 114 | editor tab 115 | 116 | 3. The editor's default right column setting 117 | 118 | Read _Overriding the maximum line length_ in this README to learn how to set the 119 | **right margin override** setting for the WrapToColumn plugin. 120 | 121 | ### Overriding the Maximum Line Length 122 | 123 | By default, this plugin uses your configured global or language-specific right 124 | margin setting as the column width to wrap at. However, you may provide a column 125 | width that will override both of these settings. 126 | 127 | This setting exists in Settings (Preferences on OS X) -> Tools -> Wrap to Column. 128 | 129 | The setting is named **Right margin override**. This should be an integer that 130 | represents the column at which the plugin will wrap text, similar to IntelliJ's 131 | "right margin" setting. 132 | 133 | ### Minimum Raggedness (Alpha!) 134 | 135 | By default, text is wrapped using a greedy line-breaking algorithm. This can 136 | result in some lines having more whitespace than others. 137 | 138 | You can turn on an alternative "minimum raggedness" algorithm in Settings -> 139 | Tools -> Wrap to Column. When this setting is on, the plugin will reconfigure 140 | text in a paragraph to produce the least amount of whitespace possible. 141 | 142 | This feature is (still!) an **alpha** and may go away. Try it and see if 143 | you like it! 144 | 145 | ### Tab Width Setting 146 | 147 | Any lines that contain tabs (or are prefixed with tabs as an indent) will be 148 | reflowed as if the tabs were characters spaced using the **tab size** you have 149 | set in IntelliJ for the language you are editing. 150 | 151 | This setting exists in the _Code Style_ section of the IntelliJ settings page. 152 | 153 | As a result of this behavior, text will look right to you if your tab width is 4, 154 | but not to your co-maintainer whose tab width is 8. This seems to be the best 155 | trade-off. 156 | 157 | ## Monospaced Versus Variable Width Fonts 158 | 159 | This plugin reflows selected text by assuming that each character takes one 160 | column's worth of space (except tabs, which are expanded to your tab width). 161 | 162 | This works fine with monospaced fonts. However, if you use a variable-width 163 | font, which seems to be common for some languages like Chinese (see issue #11), 164 | then the individual glyphs of the font take up more than one column. 165 | 166 | The plugin will still wrap your text to e.g. 80 characters wide, but the 167 | position won't match IntelliJ's right margin guide. 168 | 169 | Anyway, I recommend that you use a monospaced font if you can. 170 | 171 | 172 | ## Roadmap 173 | 174 | * Bug fixes 175 | 176 | ## Developing 177 | 178 | This project uses gradle. 179 | 180 | To run tests, use the `tests` gradle command. 181 | 182 | To build the plugin zip, use the `buildPlugin` gradle command. 183 | 184 | 185 | ## License 186 | 187 | This plugin is licensed under the GPLv2 and Apache License 2.0. See COPYING.txt 188 | and LICENSE.txt. 189 | -------------------------------------------------------------------------------- /WrapToColumn-1.9.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abrookins/WrapToColumn/c3efddf321e0811f0e4084809c34acbb96d21e9c/WrapToColumn-1.9.1.jar -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.jetbrains.kotlin.jvm") version "1.9.21" 4 | id("org.jetbrains.intellij") version "1.17.3" 5 | } 6 | 7 | group = "com.andrewbrookins.idea.wrap" 8 | version = "1.9.2" 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | testImplementation(platform("org.junit:junit-bom:5.10.2")) 16 | implementation(kotlin("stdlib-jdk8")) 17 | } 18 | 19 | // Configure Gradle IntelliJ Plugin 20 | // Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html 21 | intellij { 22 | version.set("2022.2.5") 23 | type.set("IC") 24 | updateSinceUntilBuild = false 25 | } 26 | 27 | tasks { 28 | // Set the JVM compatibility versions 29 | withType { 30 | sourceCompatibility = "17" 31 | targetCompatibility = "17" 32 | } 33 | withType { 34 | kotlinOptions.jvmTarget = "17" 35 | } 36 | 37 | patchPluginXml { 38 | sinceBuild.set("222") 39 | } 40 | 41 | signPlugin { 42 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) 43 | privateKey.set(System.getenv("PRIVATE_KEY")) 44 | password.set(System.getenv("PRIVATE_KEY_PASSWORD")) 45 | } 46 | 47 | publishPlugin { 48 | token.set(System.getenv("PUBLISH_TOKEN")) 49 | } 50 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abrookins/WrapToColumn/c3efddf321e0811f0e4084809c34acbb96d21e9c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 16 10:26:26 PDT 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | plugins { 3 | id 'org.jetbrains.kotlin.jvm' version '1.9.23' 4 | } 5 | } 6 | rootProject.name = 'WrapToColumn' 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/CodeWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap 2 | 3 | import org.apache.commons.lang.StringUtils 4 | import java.util.regex.Pattern 5 | 6 | /** 7 | * Code-aware text wrapper. 8 | * 9 | * Wrap comments like emacs fill-paragraph command. 10 | * 11 | * This code was inspired by Nir Soffer's codewrap library: * https://pypi.python.org/pypi/codewrap/ 12 | */ 13 | class CodeWrapper( 14 | private val commentRegex: Regex = "(/\\*+|\\*/|\\*|\\.|#+|//+|;+|--|'''|\"\"\"|>)?".toRegex(), 15 | 16 | private val newlineRegex: Regex = "(\\r?\\n)".toRegex(), 17 | 18 | private val htmlSeparatorsRegex: Regex = "<[pP]>|<[bB][rR] ?/?>".toRegex(), 19 | 20 | // A string that contains only two new lines demarcates a paragraph. 21 | private val paragraphSeparatorPattern: Pattern = Pattern.compile( 22 | "($newlineRegex)\\s*$commentRegex\\s*($htmlSeparatorsRegex)?$newlineRegex" 23 | ), 24 | 25 | private val tabPlaceholder: String = "☃", 26 | 27 | // A string containing a comment or empty space is considered an indent. 28 | private val indentRegex: String = "^(\\s|$tabPlaceholder)*$commentRegex\\s*($htmlSeparatorsRegex)?", 29 | private val indentPattern: Pattern = Pattern.compile(indentRegex), 30 | 31 | // New lines appended to text during wrapping will use this character. 32 | // NOTE: Intellij always uses \n character for new lines and UI 33 | // components will fail assertion checks if they receive \r\n. The 34 | // correct line ending is used when saving the file. 35 | private val lineSeparator: String = "\n", 36 | 37 | // The column width to wrap text to. 38 | val width: Int = 80, 39 | 40 | // The number of display columns that a tab character should represent. 41 | val tabWidth: Int = 4, 42 | 43 | val useMinimumRaggedness: Boolean = false, 44 | 45 | // If the first line in reflowed text contained a symbol character, like *, 46 | // align subsequent reflowed lines with the space created for that symbol. 47 | // This is only useful in text (Markdown, AsciiDoc) files that don't have 48 | // comments, but do have tons of symbol usage. 49 | val preserveLeadingSymbolSpacing: Boolean = false, 50 | 51 | // Meaningful non-comment symbols, like Markdown lists, etc. Used if 52 | // preserveLeadingSymbolSpacing is true. 53 | private val meaningfulSymbolRegex: String = "^(\\s+)?(\\*|-|(\\d+\\.))(\\s+)", 54 | private val meaningfulSymbolPattern: Pattern = Pattern.compile(meaningfulSymbolRegex) 55 | ) { 56 | 57 | /** 58 | * Data about a line that has been split into two pieces: the indent portion 59 | * of the string, if one exists, and the rest of the string. 60 | */ 61 | class LineData(indent: String, meaningfulSymbol: String, rest: String) { 62 | internal var indent = "" 63 | internal var meaningfulSymbol = "" 64 | internal var rest = "" 65 | 66 | init { 67 | this.indent = indent 68 | this.meaningfulSymbol = meaningfulSymbol 69 | this.rest = rest 70 | } 71 | } 72 | 73 | /** 74 | * Wrap `text` to the chosen width. 75 | * 76 | * Preserve the amount of white space between paragraphs after wrapping 77 | * them. A paragraph is defined as text separated by empty lines. A line is 78 | * considered empty if contains only start of comment characters and a 79 | * single `

` or `
` HTML tag (this is common in Javadoc). 80 | * 81 | * @param text the text to wrap, which may contain multiple paragraphs. 82 | * 83 | * @return text wrapped to `width`. 84 | */ 85 | fun wrap(text: String?): String { 86 | if (text == null) { 87 | return "" 88 | } 89 | val expandedTabPlaceholder = tabPlaceholder.repeat(tabWidth) 90 | val textWithTabPlaceholders = text.replace("\t", expandedTabPlaceholder) 91 | val result = StringBuilder() 92 | val paragraphMatcher = paragraphSeparatorPattern.matcher(textWithTabPlaceholders) 93 | val textLength = textWithTabPlaceholders.length 94 | var location = 0 95 | 96 | while (paragraphMatcher.find()) { 97 | val paragraph = textWithTabPlaceholders.substring(location, paragraphMatcher.start()) 98 | result.append(wrapParagraph(paragraph)) 99 | result.append(paragraphMatcher.group()) 100 | location = paragraphMatcher.end() 101 | } 102 | 103 | if (location < textLength) { 104 | result.append(wrapParagraph(textWithTabPlaceholders.substring(location, textLength))) 105 | } 106 | 107 | var builtResult = result.toString() 108 | 109 | // Keep trailing text newline. 110 | if (textWithTabPlaceholders.endsWith(lineSeparator)) { 111 | builtResult += lineSeparator 112 | } 113 | 114 | return builtResult.replace(expandedTabPlaceholder, "\t") 115 | } 116 | 117 | /** 118 | * Wrap a single paragraph of text. 119 | * 120 | * Breaks `paragraph` into an array of lines of the chosen width, then 121 | * joins them back into a single string. 122 | * 123 | * @param paragraph the paragraph to wrap 124 | * @return text reflowed to chosen width 125 | */ 126 | private fun wrapParagraph(paragraph: String): String { 127 | val resultBuilder = StringBuilder() 128 | val emptyCommentPattern = Pattern.compile("$indentRegex\$", Pattern.MULTILINE) 129 | val emptyCommentMatcher = emptyCommentPattern.matcher(paragraph) 130 | val paragraphLength = paragraph.length 131 | var location = 0 132 | 133 | while (emptyCommentMatcher.find()) { 134 | val match = emptyCommentMatcher.group() 135 | 136 | // No need to preserve a single empty new-line 137 | if (match.isEmpty()) { 138 | continue 139 | } 140 | 141 | val otherText = paragraph.substring(location, emptyCommentMatcher.start()) 142 | val wrappedLines = breakToLinesOfChosenWidth(otherText) 143 | 144 | if (paragraph.startsWith(match)) { 145 | resultBuilder.append(match + lineSeparator) 146 | } 147 | 148 | for (wrappedLine in wrappedLines) { 149 | resultBuilder.append(wrappedLine + lineSeparator) 150 | } 151 | 152 | if (paragraph.endsWith(match)) { 153 | resultBuilder.append(match) 154 | } 155 | 156 | location = emptyCommentMatcher.end() 157 | } 158 | 159 | // There were either empty comment lines, or we worked through them all. 160 | // TODO: Pull some of this code into a method that the while loop also calls. 161 | if (location < paragraphLength) { 162 | val otherText = paragraph.substring(location, paragraphLength) 163 | val wrappedLines = breakToLinesOfChosenWidth(otherText) 164 | for (wrappedLine in wrappedLines) { 165 | resultBuilder.append(wrappedLine + lineSeparator) 166 | } 167 | } 168 | 169 | var result = resultBuilder.toString() 170 | 171 | // The calling function will append new-lines to the very last line. 172 | if (result.endsWith(lineSeparator)) { 173 | result = result.substring(0, result.length - 1) 174 | } 175 | 176 | return result 177 | } 178 | 179 | /** 180 | * Reformat the single paragraph in `text` to lines of the chosen width, 181 | * and return an array of these lines. 182 | * 183 | * Note: C-style multi-line comments are always reflowed to the chosen 184 | * column width. This means that the first line might stick out because 185 | * its indent is longer (" ** " instead of "* " on continuation lines). 186 | * 187 | * 188 | * @param text single paragraph of text 189 | * @return array of lines 190 | */ 191 | private fun breakToLinesOfChosenWidth(text: String): MutableList { 192 | val firstLineIndent = splitOnIndent(text).indent 193 | val firstLineIsDocstring = text.trimStart().matches("^\"\"\"|^'''".toRegex()) 194 | val firstLineIsCommentOpener = firstLineIndent.matches("\\s*(/\\*+|\"\"\"|''')\\s*".toRegex()) 195 | val lines: Array 196 | var leadingSymbolWidth = 0 197 | var leadingSymbol = "" 198 | var width = width 199 | var correctedText = text 200 | 201 | // Remove docstring symbols from start and end of string 202 | if (firstLineIsDocstring) { 203 | correctedText = text.replaceFirst("^\"\"\"|^'''".toRegex(), "") 204 | correctedText = correctedText.replaceFirst("\"\"\"\$|'''\$".toRegex(), "") 205 | } 206 | 207 | var unwrappedText = unwrap(correctedText) 208 | 209 | if (preserveLeadingSymbolSpacing) { 210 | val symbolMatcher = meaningfulSymbolPattern.matcher(text) 211 | if (symbolMatcher.find()) { 212 | leadingSymbol = symbolMatcher.group() 213 | leadingSymbolWidth = leadingSymbol.length 214 | unwrappedText = unwrappedText.substring(leadingSymbolWidth) 215 | } 216 | width -= leadingSymbolWidth 217 | } else { 218 | width -= firstLineIndent.length 219 | } 220 | 221 | if (useMinimumRaggedness) { 222 | lines = wrapMinimumRaggedness(unwrappedText, width).dropLastWhile(String::isEmpty).toTypedArray() 223 | } else { 224 | lines = wrapGreedy(unwrappedText, width, lineSeparator) 225 | .split(lineSeparator.toRegex()) 226 | .dropLastWhile(String::isEmpty) 227 | .toTypedArray() 228 | } 229 | val result = mutableListOf() 230 | val length = lines.size 231 | var whitespaceBeforeOpener = "" 232 | 233 | if (firstLineIsCommentOpener) { 234 | val whitespaceMatcher = Pattern.compile("^\\s*").matcher(firstLineIndent) 235 | if (whitespaceMatcher.find()) { 236 | whitespaceBeforeOpener = whitespaceMatcher.group() 237 | } 238 | } 239 | 240 | for (i in 0 until length) { 241 | val line = lines[i] 242 | var lineIndent = firstLineIndent 243 | 244 | if (leadingSymbol.isNotBlank() && preserveLeadingSymbolSpacing) { 245 | if (i == 0) { 246 | lineIndent = leadingSymbol 247 | } else { 248 | lineIndent = StringUtils.repeat(" ", leadingSymbolWidth) 249 | } 250 | } 251 | 252 | if (i > 0 && firstLineIsCommentOpener) { 253 | // This is a hack. We don't know how much whitespace to use! 254 | lineIndent = "$whitespaceBeforeOpener * " 255 | } 256 | 257 | result.add(lineIndent + line) 258 | } 259 | 260 | return result 261 | } 262 | 263 | /** 264 | * Convert a hard-wrapped paragraph to one line. 265 | * 266 | * Indent and comment characters are stripped. 267 | * 268 | * @param text one paragraph of text, possibly hard-wrapped 269 | * @return one line of text 270 | */ 271 | private fun unwrap(text: String): String { 272 | if (text.isEmpty()) { 273 | return text 274 | } 275 | 276 | val lines = text.split("[\\r\\n]+".toRegex()).dropLastWhile(String::isEmpty).toTypedArray() 277 | val result = StringBuilder() 278 | var lastLineWasCarriageReturn = false 279 | val length = lines.size 280 | 281 | for (i in 0 until length) { 282 | val line = lines[i] 283 | val unindentedLine = splitOnIndent(line).rest.trim { it <= ' ' } 284 | 285 | if (line.isEmpty()) { 286 | lastLineWasCarriageReturn = true 287 | continue 288 | } 289 | 290 | // Only add a space if we're joining two sentences that contained words. 291 | if (lastLineWasCarriageReturn || length == 1 || i == 0) { 292 | result.append(unindentedLine) 293 | } else { 294 | result.append(" ").append(unindentedLine) 295 | } 296 | 297 | lastLineWasCarriageReturn = false 298 | } 299 | 300 | return result.toString() 301 | } 302 | 303 | /** 304 | * Split text on indent, including comment characters. 305 | * 306 | * Example (parsed from left margin): 307 | * // Comment -> ' // ', 'Comment' 308 | * 309 | * @param text text to remove indents from 310 | * @return indent string, rest 311 | */ 312 | fun splitOnIndent(text: String): LineData { 313 | val indentMatcher = indentPattern.matcher(text) 314 | val symbolMatcher = meaningfulSymbolPattern.matcher(text) 315 | val lineData = LineData("", "", text) 316 | 317 | // Only break on the first indent-worthy sequence found, to avoid any 318 | // weirdness with comments-embedded-in-comments. 319 | if (indentMatcher.find()) { 320 | lineData.indent = indentMatcher.group() 321 | lineData.rest = text.substring(indentMatcher.end(), text.length).trim { it <= ' ' } 322 | // We might get "/*\n", so strip the newline if so. 323 | lineData.indent = lineData.indent.replace("[\\r\\n]+".toRegex(), "") 324 | } 325 | 326 | // If we suspect a line begins with a "meaningful symbol," save that. 327 | // This is important for file types that use comment-like symbols for 328 | // things like lists, e.g., Markdown, AsciiDoc. 329 | if (symbolMatcher.find()) { 330 | lineData.meaningfulSymbol = symbolMatcher.group() 331 | } 332 | 333 | return lineData 334 | } 335 | } -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/WrapAction.kt: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap 2 | 3 | import com.intellij.openapi.actionSystem.* 4 | import com.intellij.openapi.command.WriteCommandAction 5 | import com.intellij.openapi.util.TextRange 6 | import org.jetbrains.annotations.NotNull 7 | 8 | 9 | fun isWhitespace(str: String?): Boolean { 10 | if (str == null) { 11 | return false 12 | } 13 | val sz = str.length 14 | for (i in 0 until sz) { 15 | if (!Character.isWhitespace(str[i])) { 16 | return false 17 | } 18 | } 19 | return true 20 | } 21 | 22 | 23 | class WrapAction : AnAction() { 24 | @NotNull 25 | override fun getActionUpdateThread(): ActionUpdateThread { 26 | return ActionUpdateThread.BGT 27 | } 28 | 29 | override fun update(e: AnActionEvent) { 30 | e.presentation.isEnabled = true 31 | e.presentation.isVisible = true 32 | } 33 | 34 | override fun actionPerformed(e: AnActionEvent) { 35 | val dataContext = e.dataContext 36 | val editor = e.getData(CommonDataKeys.EDITOR) ?: return 37 | val project = dataContext.let { LangDataKeys.PROJECT.getData(it) } 38 | val fileIsPlaintext = isPlaintext(dataContext) 39 | val document = editor.document 40 | val selectionModel = editor.selectionModel 41 | val wrapper = getWrapper(project, editor, fileIsPlaintext) 42 | val text: String 43 | var start: Int 44 | val end: Int 45 | 46 | if (selectionModel.hasSelection()) { 47 | start = selectionModel.selectionStart 48 | end = selectionModel.selectionEnd 49 | 50 | // Handle the case where a user selects a line but leaves out 51 | // whitespace at the start of the line. We need that whitespace 52 | // to correctly wrap new lines in the result. 53 | val line = document.getLineNumber(start) 54 | val lineStartOffset = document.getLineStartOffset(line) 55 | val possibleWhitespaceEnd = if (start == 0) start else (start - 1).coerceAtLeast(lineStartOffset) 56 | val possibleWhitespace = document.getText(TextRange(lineStartOffset, possibleWhitespaceEnd)) 57 | if (isWhitespace(possibleWhitespace)) { 58 | // The selected line has leading whitespace, so expand the selection 59 | // to include the whitespace. 60 | start = lineStartOffset 61 | } 62 | } else { 63 | val line = editor.caretModel.logicalPosition.line 64 | start = document.getLineStartOffset(line) 65 | end = document.getLineEndOffset(line) 66 | } 67 | 68 | text = document.getText(TextRange(start, end)) 69 | if (text.isBlank()) { 70 | return 71 | } 72 | val wrappedText = wrapper.wrap(text) 73 | 74 | WriteCommandAction.runWriteCommandAction(project) { 75 | document.replaceString(start, end, wrappedText) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/WrapParagraphAction.kt: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap 2 | 3 | import com.intellij.openapi.actionSystem.* 4 | import com.intellij.openapi.command.WriteCommandAction 5 | import com.intellij.openapi.editor.Caret 6 | import com.intellij.openapi.editor.Document 7 | import com.intellij.openapi.util.TextRange 8 | import org.jetbrains.annotations.NotNull 9 | import com.intellij.openapi.actionSystem.ActionUpdateThread 10 | 11 | data class TextData(val lineStart: Int, val lineEnd: Int, val lineData: CodeWrapper.LineData) 12 | 13 | fun getTextAtOffset(document: Document, wrapper: CodeWrapper, offset: Int): TextData { 14 | val lineStart = document.getLineStartOffset(offset) 15 | val lineEnd = document.getLineEndOffset(offset) 16 | val text = document.getText(TextRange(lineStart, lineEnd)) 17 | return TextData(lineStart, lineEnd, wrapper.splitOnIndent(text)) 18 | } 19 | 20 | class WrapParagraphAction : AnAction() { 21 | @NotNull 22 | override fun getActionUpdateThread(): ActionUpdateThread { 23 | return ActionUpdateThread.BGT 24 | } 25 | 26 | override fun update(e: AnActionEvent) { 27 | e.presentation.isVisible = true 28 | e.presentation.isEnabled = true 29 | } 30 | 31 | override fun actionPerformed(e: AnActionEvent) { 32 | val dataContext = e.dataContext 33 | val editor = e.getData(CommonDataKeys.EDITOR) ?: return 34 | val project = dataContext.let { LangDataKeys.PROJECT.getData(it) } 35 | val document = editor.document 36 | val caret = editor.caretModel.currentCaret as? Caret 37 | val startingLine = caret?.logicalPosition?.line ?: return 38 | val documentEnd = document.getLineNumber(document.textLength) 39 | val fileIsPlaintext = isPlaintext(dataContext) 40 | // val fileExtension = getFileExtension(dataContext) 41 | val wrapper = getWrapper(project, editor, fileIsPlaintext) 42 | val selectionModel = editor.selectionModel 43 | var start: Int 44 | var end: Int 45 | 46 | if (selectionModel.hasSelection()) { 47 | start = selectionModel.selectionStart 48 | end = selectionModel.selectionEnd 49 | 50 | // Handle the case where a user selects a line but leaves out 51 | // whitespace at the start of the line. We need that whitespace 52 | // to correctly wrap new lines in the result. 53 | val line = document.getLineNumber(start) 54 | val lineStartOffset = document.getLineStartOffset(line) 55 | val possibleWhitespaceEnd = if (start == 0) start else (start - 1).coerceAtLeast(lineStartOffset) 56 | val possibleWhitespace = document.getText(TextRange(lineStartOffset, possibleWhitespaceEnd)) 57 | if (isWhitespace(possibleWhitespace)) { 58 | // The selected line has leading whitespace, so expand the selection 59 | // to include the whitespace. 60 | start = lineStartOffset 61 | } 62 | } else { 63 | start = document.getLineStartOffset(startingLine) 64 | end = document.getLineEndOffset(startingLine) 65 | 66 | // Starting from the current line, expand the selection upwards and downwards. 67 | for (direction in arrayOf(-1, 1)) { 68 | var lineTracker = startingLine 69 | while (lineTracker in 1 until documentEnd) { 70 | lineTracker += direction 71 | val textData = getTextAtOffset(document, wrapper, lineTracker) 72 | // Pass in file extension? 73 | if (!shouldWrapLine(textData, fileIsPlaintext) || textData.lineData.rest.isBlank()) { 74 | break 75 | } 76 | if (direction == -1) start = textData.lineStart 77 | else end = textData.lineEnd 78 | } 79 | } 80 | } 81 | 82 | val text = document.getText(TextRange(start, end)) 83 | if (text.isBlank()) { 84 | return 85 | } 86 | val wrappedText = wrapper.wrap(text) 87 | 88 | WriteCommandAction.runWriteCommandAction(project) { 89 | document.replaceString(start, end, wrappedText) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/WrappingAlgorithms.kt: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap 2 | 3 | import kotlin.math.pow 4 | 5 | /** 6 | * Helper to wrap a string using a greedy algorithm. 7 | * 8 | * Adapted from Apache Commons Lang. 9 | */ 10 | fun wrapGreedy(str: String, wrapLength: Int, lineSeparator: String): String { 11 | val inputLineLength = str.length 12 | val space = ' ' 13 | val wrappedLine = StringBuffer(inputLineLength + 32) 14 | var offset = 0 15 | 16 | while (inputLineLength - offset > wrapLength) { 17 | if (str[offset] == space) { 18 | offset += 1 19 | continue 20 | } 21 | if (inputLineLength - offset <= wrapLength) { 22 | break 23 | } 24 | var spaceToWrapAt = str.lastIndexOf(space, wrapLength + offset) 25 | 26 | if (spaceToWrapAt >= offset) { 27 | wrappedLine.append(str.substring(offset, spaceToWrapAt)) 28 | wrappedLine.append(lineSeparator) 29 | offset = spaceToWrapAt + 1 30 | } else { 31 | spaceToWrapAt = str.indexOf(space, wrapLength + offset) 32 | if (spaceToWrapAt >= 0) { 33 | wrappedLine.append(str.substring(offset, spaceToWrapAt)) 34 | wrappedLine.append(lineSeparator) 35 | offset = spaceToWrapAt + 1 36 | } else { 37 | wrappedLine.append(str.substring(offset)) 38 | offset = inputLineLength 39 | } 40 | } 41 | } 42 | 43 | wrappedLine.append(str.substring(offset)) 44 | return wrappedLine.toString() 45 | } 46 | 47 | /** 48 | * Helper to wrap a string using a minimum raggedness algorithm. 49 | * 50 | * Based on Aggarwal and Tokuyama's work (1998) via http://xxyxyz.org/line-breaking/ 51 | */ 52 | fun wrapMinimumRaggedness(text: String, width: Int): Array { 53 | val words = text.split(' ') 54 | val count = words.size 55 | val offsets = arrayListOf(0.0) 56 | for (w in words) { 57 | offsets.add(offsets[offsets.size - 1] + w.length) 58 | } 59 | 60 | val minima = arrayOf(0.0) + Array(count, { Math.pow(10.0, 20.0) }) 61 | val breaks = Array(count + 1, { 0 }) 62 | 63 | fun cost(i: Int, j: Int): Double { 64 | val w = offsets[j] - offsets[i] + j - i - 1 65 | if (w > width) { 66 | return 10.0.pow(10.0) * (w - width) 67 | } 68 | return minima[i] + (width - w).pow(2.0) 69 | } 70 | 71 | fun smawk(rows: MutableList, columns: Array) { 72 | val stack = arrayListOf() 73 | var i = 0 74 | val length = rows.size 75 | while (i < length) { 76 | if (stack.size > 0) { 77 | val c = columns[stack.size - 1] 78 | if (cost(stack[stack.size - 1], c) < cost(rows[i], c)) { 79 | if (stack.size < columns.size) { 80 | stack.add(rows[i]) 81 | } 82 | i += 1 83 | } else { 84 | stack.removeAt(stack.size - 1) 85 | } 86 | } else { 87 | stack.add(rows[i]) 88 | i += 1 89 | } 90 | } 91 | 92 | if (columns.size > 1) { 93 | // Equivalent to a Python slice with step: smawk(stack, columns[1::2]) 94 | smawk(stack, columns.copyOfRange(1, columns.size).filter({ columns.indexOf(it) % 2 != 0 }).toTypedArray()) 95 | } 96 | 97 | i = 0 98 | var j = 0 99 | var end: Int 100 | while (j < columns.size) { 101 | if (j + 1 < columns.size) { 102 | end = breaks[columns[j + 1]] 103 | } else { 104 | end = stack[stack.size - 1] 105 | } 106 | val c = cost(stack[i], columns[j]) 107 | if (c < minima[columns[j]]) { 108 | minima[columns[j]] = c 109 | breaks[columns[j]] = stack[i] 110 | } 111 | if (stack[i] < end) { 112 | i += 1 113 | } else { 114 | j += 2 115 | } 116 | } 117 | } 118 | 119 | var n = count + 1 120 | var i = 0 121 | var offset = 0 122 | 123 | while (true) { 124 | val r = Math.min(n, 2 shl i) 125 | val edge = (1 shl i) + offset 126 | // Python ranges drop the last item, but Kotlin's preserve it -- so we subtract one. 127 | smawk(((0 + offset) until edge).toMutableList(), (edge until (r + offset)).toMutableList().toTypedArray()) 128 | val x = minima[(r - 1 + offset)] 129 | var costGreaterThanOrEqualToMinima = false 130 | 131 | for (j in 1 shl i until r - 1) { 132 | val y = cost(j + offset, r - 1 + offset) 133 | if (y <= x) { 134 | n -= j 135 | i = 0 136 | offset += j 137 | costGreaterThanOrEqualToMinima = true 138 | break 139 | } 140 | } 141 | if (!costGreaterThanOrEqualToMinima) { 142 | if (r == n) { 143 | break 144 | } 145 | i += 1 146 | } 147 | } 148 | 149 | val lines: MutableList = arrayListOf() 150 | var j = count 151 | while (j > 0) { 152 | i = breaks[j] 153 | // Python ranges drop the last item, but Kotlin's preserve it -- so we subtract one. 154 | lines.add(words.slice(i until j).joinToString(" ")) 155 | j = i 156 | } 157 | return lines.reversed().toTypedArray() 158 | } 159 | 160 | -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/config/WrapSettingsConfigurable.kt: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap.config 2 | 3 | import com.andrewbrookins.idea.wrap.ui.WrapSettingsPanel 4 | import com.intellij.openapi.options.ConfigurationException 5 | import com.intellij.openapi.options.SearchableConfigurable 6 | 7 | import javax.swing.JComponent 8 | 9 | 10 | class WrapSettingsConfigurable : SearchableConfigurable { 11 | 12 | private var panel: WrapSettingsPanel? = null 13 | 14 | override fun getId(): String { 15 | return "wrap.settings" 16 | } 17 | 18 | override fun enableSearch(option: String): Runnable? { 19 | return null 20 | } 21 | 22 | override fun getDisplayName(): String { 23 | return "Wrap to Column" 24 | } 25 | 26 | override fun getHelpTopic(): String? { 27 | return "wrap.settings" 28 | } 29 | 30 | override fun createComponent(): JComponent? { 31 | panel = WrapSettingsPanel() 32 | return panel?.panel 33 | } 34 | 35 | override fun isModified(): Boolean { 36 | return panel != null && panel!!.isModified 37 | } 38 | 39 | @Throws(ConfigurationException::class) 40 | override fun apply() { 41 | if (panel != null) { 42 | panel?.apply() 43 | } 44 | } 45 | 46 | override fun reset() { 47 | if (panel != null) { 48 | panel?.reset() 49 | } 50 | } 51 | 52 | override fun disposeUIResources() { 53 | panel = null 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/config/WrapSettingsState.kt: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap.config 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.components.PersistentStateComponent 5 | import com.intellij.openapi.components.State 6 | import com.intellij.openapi.components.Storage 7 | import com.intellij.util.xmlb.XmlSerializerUtil 8 | import org.jetbrains.annotations.NotNull 9 | 10 | 11 | /** 12 | * Load and save settings across IDE restarts. 13 | */ 14 | @State( 15 | name = "WrapSettingsProvider", 16 | storages = [Storage("wrap.xml")] 17 | ) 18 | class WrapSettingsState : PersistentStateComponent { 19 | var columnWidthOverride: Int = 80 20 | var useMinimumRaggednessAlgorithm: Boolean = false 21 | var plaintextFileTypes: String = ".md,.markdown,.adoc,.asciidoc,.txt" 22 | 23 | override fun getState(): WrapSettingsState { 24 | return this 25 | } 26 | 27 | override fun loadState(@NotNull state: WrapSettingsState) { 28 | XmlSerializerUtil.copyBean(state, this) 29 | } 30 | 31 | companion object { 32 | @JvmStatic 33 | fun getInstance(): WrapSettingsState { 34 | return ApplicationManager.getApplication().getComponent(WrapSettingsState::class.java) as WrapSettingsState 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/ui/WrapSettingsPanel.form: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/ui/WrapSettingsPanel.java: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap.ui; 2 | 3 | import com.andrewbrookins.idea.wrap.config.WrapSettingsState; 4 | import com.intellij.openapi.util.Comparing; 5 | 6 | import javax.swing.*; 7 | import java.util.Objects; 8 | 9 | 10 | /** 11 | * Plugin settings UI. 12 | * 13 | * NOTE: I could not figure out how to get WrapSettingsPanel.form to use this class 14 | * properly when WrapSettingsPanel is written in Kotlin! 15 | */ 16 | public class WrapSettingsPanel { 17 | 18 | private WrapSettingsState settingsProvider; 19 | private JTextField columnWidthOverrideField; 20 | private JTextField plaintextFileTypesField; 21 | private JPanel panel; 22 | private JLabel columnWidthOverrideLabel; 23 | private JCheckBox useMinimumRaggednessAlgorithmCheckBox; 24 | private JLabel plaintextFileTypesLabel; 25 | 26 | public WrapSettingsPanel() { 27 | settingsProvider = WrapSettingsState.getInstance(); 28 | } 29 | 30 | public JPanel getPanel() { 31 | return panel; 32 | } 33 | 34 | public boolean isModified() { 35 | Integer columnOverride = Objects.requireNonNull(settingsProvider.getState()).getColumnWidthOverride(); 36 | Boolean useMinimumRaggednessAlgorithm = settingsProvider.getState().getUseMinimumRaggednessAlgorithm(); 37 | String plaintextFileTypes = settingsProvider.getState().getPlaintextFileTypes(); 38 | return !Objects.equals(columnWidthOverrideField.getText(), String.valueOf(columnOverride)) | 39 | !Objects.equals(plaintextFileTypesField.getText(), plaintextFileTypes) | 40 | !Comparing.equal(useMinimumRaggednessAlgorithmCheckBox.isSelected(), useMinimumRaggednessAlgorithm); 41 | } 42 | 43 | public void apply() { 44 | // TODO: Show an error for non-integer input. 45 | Integer columnWidth; 46 | 47 | try { 48 | columnWidth = Integer.parseInt(columnWidthOverrideField.getText()); 49 | } catch (NumberFormatException e) { 50 | columnWidth = null; 51 | } 52 | 53 | Objects.requireNonNull(settingsProvider.getState()).setColumnWidthOverride(columnWidth); 54 | settingsProvider.getState().setPlaintextFileTypes(plaintextFileTypesField.getText()); 55 | settingsProvider.getState().setUseMinimumRaggednessAlgorithm(useMinimumRaggednessAlgorithmCheckBox.isSelected()); 56 | } 57 | 58 | public void reset() { 59 | Integer columnOverride = Objects.requireNonNull(settingsProvider.getState()).getColumnWidthOverride(); 60 | boolean useMinimumRaggednessAlgorithm = settingsProvider.getState().getUseMinimumRaggednessAlgorithm(); 61 | String plaintextFileTypes = settingsProvider.getState().getPlaintextFileTypes(); 62 | String overrideText = columnOverride == null ? "" : String.valueOf(columnOverride); 63 | 64 | columnWidthOverrideField.setText(overrideText); 65 | useMinimumRaggednessAlgorithmCheckBox.setSelected(useMinimumRaggednessAlgorithm); 66 | plaintextFileTypesField.setText(plaintextFileTypes); 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/main/java/com/andrewbrookins/idea/wrap/utils.kt: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap 2 | 3 | import com.andrewbrookins.idea.wrap.config.WrapSettingsState 4 | import com.intellij.openapi.actionSystem.DataContext 5 | import com.intellij.openapi.actionSystem.PlatformDataKeys 6 | import com.intellij.openapi.editor.Editor 7 | import com.intellij.openapi.project.Project 8 | 9 | 10 | fun getFileExtension(dataContext: DataContext?): String? { 11 | if (dataContext == null) return null 12 | val file = dataContext.getData(PlatformDataKeys.VIRTUAL_FILE) 13 | return file?.extension 14 | } 15 | 16 | fun isPlaintext(dataContext: DataContext?): Boolean { 17 | val plaintextFileTypes = WrapSettingsState.getInstance().state.plaintextFileTypes?.split(",") 18 | 19 | if (dataContext == null) { 20 | return false 21 | } else { 22 | val file = dataContext.getData(PlatformDataKeys.VIRTUAL_FILE) 23 | return file != null && plaintextFileTypes != null && ".${file.extension}" in plaintextFileTypes 24 | } 25 | } 26 | 27 | 28 | fun getWrapper(project: Project?, editor: Editor, fileIsPlaintext: Boolean): CodeWrapper { 29 | val columnWidthOverride = WrapSettingsState.getInstance().state.columnWidthOverride 30 | val useMinimumRaggednessAlgorithm = WrapSettingsState.getInstance().state.useMinimumRaggednessAlgorithm ?: false 31 | val tabWidth = editor.settings.getTabSize(project) 32 | val wrapper: CodeWrapper 33 | 34 | 35 | if (fileIsPlaintext) { 36 | wrapper = CodeWrapper( 37 | width = columnWidthOverride, 38 | tabWidth = tabWidth, 39 | useMinimumRaggedness = useMinimumRaggednessAlgorithm, 40 | commentRegex = "(//)?".toRegex(), 41 | preserveLeadingSymbolSpacing = true 42 | ) 43 | 44 | } else { 45 | wrapper = CodeWrapper( 46 | width = columnWidthOverride, 47 | tabWidth = tabWidth, 48 | useMinimumRaggedness = useMinimumRaggednessAlgorithm 49 | ) 50 | } 51 | 52 | return wrapper 53 | } 54 | 55 | fun shouldWrapLine(textData: TextData, isPlaintext: Boolean): Boolean { 56 | // TODO: File extension? 57 | val isCommentLine = textData.lineData.indent.isNotBlank() && textData.lineData.rest.isNotBlank() 58 | val plaintextWithoutSymbol = isPlaintext && textData.lineData.meaningfulSymbol.isBlank() 59 | return isCommentLine || plaintextWithoutSymbol 60 | } 61 | 62 | 63 | /** 64 | * Paragraph1.sldkj slkfdj sdlkj flsdkj flsdkj flsdkj flsdkj flskdj flksdj flksdj flskdj flksdj 65 | * flksdj flsdkjf 66 | *

67 | * Paragraph2. lskaj flskdj flsdkj flsdkjf lsdkj flsdkj flksdj flksdj flksdj flkdsj flsdkj flsdkj 68 | * fldksj fldskjf 69 | */ -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.andrewbrookins.wrap_to_column 3 | Wrap to Column 4 | 1.9.2 5 | Andrew Brookins 6 | 7 | 9 | Wraps text to the specified column width. Similar to the Emacs command 10 | 'Fill Paragraph' and Vim's `gq` (format lines) command. This is a replacement 11 | for the native Intellij Fill Paragraph command, which doesn't work quite 12 | how I need it to. 13 |

14 |

15 | This plugin provides two IDE actions: 16 |

    17 |
  • 18 | Wrap Line to Column: Wraps selected text or the current line if no 19 | text is selected. 20 |
  • 21 |
  • 22 | Wrap Paragraph to Column: Wraps selected text or the paragraph in 23 | which the cursor appears. A paragraph is defined as text offset by blank lines -- 24 | including lines that only start with what looks like comment syntax (e.g., `//`). 25 |
  • 26 |
27 |

28 |

29 | Note: As of version 1.0, this plugin supports only IDEA 30 | 14.1.x-based products and later (e.g., PyCharm 4.1, WebStorm 10, Android 31 | Studio 1.3). 32 |

33 | ]]> 34 | 35 | 1.9.2 37 |
    38 |
  • 39 | Fixed a bug that prevented the settings panel from loading. 40 |
  • 41 |
  • 42 | From 1.9.0: Wrap Paragraph to Column and Wrap Line to Column now work the same way 43 | when you select text: the selected text will be wrapped. They differ only when you do 44 | not select text. Wrap Paragraph to Column will wrap the current paragraph, and Wrap 45 | Line to Column will wrap the current line. 46 |
  • 47 |
  • 48 | Fixed a bug that incorrectly wrapped code adjacent to comments. You no 49 | longer have to select a comment directly adjacent to code to avoid wrapping 50 | the code as well. Instead, Wrap Paragraph to Column will wrap only the 51 | comment. 52 |
  • 53 |
  • 54 | Fixed Markdown blockquote wrapping. 55 |
  • 56 |
  • 57 | Fixed com.intellij.diagnostic.PluginException: `ActionUpdateThread.OLD_EDT` is deprecated and going to be removed soon. 'com.andrewbrookins.idea.wrap.WrapAction' must override `getActionUpdateThread()` and chose EDT or BGT. See ActionUpdateThread javadoc. [Plugin: com.andrewbrookins.wrap_to_column] 58 |
59 | 60 | 1.9.0 61 |
    62 |
  • 63 | Wrap Paragraph to Column and Wrap Line to Column now work the same way when 64 | you select text: the selected text will be wrapped. They differ only when 65 | you do not select text. Wrap Paragraph to Column will wrap the current 66 | paragraph, and Wrap Line to Column will wrap the current line. 67 |
  • 68 |
  • 69 | Refactor plugin internals to use AnAction instead of deprecated `execute` 70 | method. 71 |
  • 72 |
73 | 74 | 1.8.0 75 |
    76 |
  • Fix plugin to support new Intellij versions
  • 77 |
78 | 79 | 1.7.0 80 |
    81 |
  • Add better support for wrapping lists in Markdown, AsciiDoc, and .txt files
  • 82 |
  • Ignore most comment-like symbols in Markdown, AsciiDoc, and .txt files (these symbols are used for formatting, not comments, in these documents)
  • 83 |
  • Fix a regression in wrapping to the configured IDE right margin
  • 84 |
  • Fix many cases of Wrap Paragraph to Column action wrapping code before or after a comment
  • 85 |
  • Fix an issue that required you to select an entire line in order to reflow correctly if there was leading whitespace
  • 86 |
87 | 88 | 1.6.0 89 |
    90 |
  • Support SQL comments. Thanks, Elijah Carrel!
  • 91 |
92 | 93 | 1.5.0 94 |
    95 |
  • Fix plugin XML compatibility with newer Intellij versions.
  • 96 |
  • Special thanks to Edgars Irmejs and Aleksei Kniazev for fixes!
  • 97 |
98 | 99 | 1.4.0 100 |
    101 |
  • Add a new action: Wrap Paragraph to Column.
  • 102 |
103 | 1.3.2 104 | 105 |
    106 |
  • Add a keyboard shortcut for Windows.
  • 107 |
108 | 109 | 1.3.1 110 |
    111 |
  • Fix "kotlin/jvm/internal/Intrinsics" error found in Intellij 14.1.x-based editors.
  • 112 |
113 | 114 | 1.3.0 115 |
    116 |
  • New feature: Use an optional "minimum raggedness" algorithm. You can enable this in Tools -> Wrap to Column.
  • 117 |
118 | 119 | 1.2.0 120 |
    121 |
  • Reflow text containing tabs based on the configured tab width setting.
  • 122 |
123 | 124 | 1.1.0 125 |
    126 |
  • New feature: Set a column width override in Settings: Tools -> Wrap to Column. Overrides style and global column width settings.
  • 127 |
128 | 129 | 1.0.1 130 |
    131 |
  • Fix a bug in handling continuations of C-style multi-line comments
  • 132 |
133 | 134 | 1.0 135 |
    136 |
  • Use the language-specific right margin setting if one is configured
  • 137 |
138 | 139 | 0.1.4 140 |
    141 |
  • Strip trailing spaces before wrapping
  • 142 |
  • Preserve paragraphs within comments
  • 143 |
144 | 145 | 0.1.3 146 |
    147 |
  • Stop adding an erroneous leading space if the first line of the selection is a newline/carriage return.
  • 148 |
  • Detect the period "." as a bullet point indentation.
  • 149 |
150 | 151 | 0.1.2 152 |
    153 |
  • Fix plugin for Windows users 154 |
155 | 156 | 0.1.1 157 |
    158 |
  • Better support for multi-line C-style comments.
  • 159 |
160 | 161 | 0.1 162 |
    163 |
  • Initial commit.
  • 164 |
165 | ]]> 166 |
167 | 168 | 169 | 170 | 171 | 173 | com.intellij.modules.lang 174 | 175 | 176 | 177 | com.andrewbrookins.idea.wrap.config.WrapSettingsState 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 192 | 193 | 194 | 195 | 196 | 197 | 200 | 201 | 202 | 203 | 204 | 205 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /src/test/java/com/andrewbrookins/idea/wrap/CodeWrapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.andrewbrookins.idea.wrap.tests 2 | 3 | import org.junit.Test 4 | import org.junit.Assert.* 5 | import com.andrewbrookins.idea.wrap.* 6 | 7 | data class WrapTestCase( 8 | val description: String, 9 | private val rawInput: String?, 10 | private val rawExpectedOutput: String, 11 | val trimIndent: Boolean = true, 12 | val width: Int = 80, 13 | val tabWidth: Int = 4, 14 | val useMinimumRaggedness: Boolean = false 15 | ) { 16 | val input: String? = rawInput?.let { if (trimIndent) it.trimIndent() else it } 17 | val expectedOutput: String = if (trimIndent) rawExpectedOutput.trimIndent() else rawExpectedOutput 18 | } 19 | 20 | class CodeWrapperTests { 21 | 22 | private val testCases = listOf( 23 | WrapTestCase( 24 | "Test trimIndent option in tests (false)", 25 | """ This text should not be trimmed """, 26 | """ This text should not be trimmed""", 27 | trimIndent = false 28 | ), 29 | WrapTestCase( 30 | "Test trimIndent option in tests (true)", 31 | """ This text should be trimmed """, 32 | """ This text should be trimmed""", 33 | trimIndent = true 34 | ), 35 | WrapTestCase( 36 | "Create without options", 37 | """ 38 | // This is my text. 39 | // This is my text. 40 | """, 41 | """ 42 | // This is my text. This is my text. 43 | """ 44 | ), 45 | WrapTestCase( 46 | "Wraps to column width - comment", 47 | """ 48 | // aa a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 49 | """, 50 | """ 51 | // aa a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 52 | // a a a a a a a a a a a a a a a a a a a a a a 53 | """ 54 | ), 55 | WrapTestCase( 56 | "Wraps to column width - C-style opening comment", 57 | """ 58 | /** aa a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a aa a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 59 | */ 60 | """, 61 | """ 62 | /** aa a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a aa 63 | * a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 64 | */ 65 | """ 66 | ), 67 | WrapTestCase( 68 | "Wraps to column width - no comment", 69 | """ 70 | aa a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 71 | """, 72 | """ 73 | aa a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 74 | a a a a a a a a a a a a a a a a a a a a 75 | """ 76 | ), 77 | WrapTestCase( 78 | "Wraps one long line", 79 | """ 80 | // This is my very long line of text. This is my very long line of text. This is my very long line of text. 81 | """, 82 | """ 83 | // This is my very long line of text. This is my very long line of text. This is 84 | // my very long line of text. 85 | """ 86 | ), 87 | WrapTestCase( 88 | "Wrap retains separate paragraphs", 89 | """ 90 | // This is my very long line of text. This is my very long line of text. This is my very long line of text. 91 | 92 | // This is a second paragraph. 93 | """, 94 | """ 95 | // This is my very long line of text. This is my very long line of text. This is 96 | // my very long line of text. 97 | 98 | // This is a second paragraph. 99 | """ 100 | ), 101 | WrapTestCase( 102 | "Wrap wraps Python comments", 103 | """ 104 | # This is my very long line of text. This is my very long line of text. This is my very long line of text. 105 | 106 | # This is a second paragraph. 107 | """, 108 | """ 109 | # This is my very long line of text. This is my very long line of text. This is 110 | # my very long line of text. 111 | 112 | # This is a second paragraph. 113 | """ 114 | ), 115 | WrapTestCase( 116 | "Wrap combine two short lines", 117 | """ 118 | // This is my text. 119 | // This is my text. 120 | """, 121 | """ 122 | // This is my text. This is my text. 123 | """ 124 | ), 125 | WrapTestCase( 126 | "Wrap fills multiline opener", 127 | """ 128 | /** This is my text This is my long multi-line comment opener text. More text please. This is yet another bunch of text in my test comment, so I will get multiple lines in the comment. 129 | """, 130 | """ 131 | /** This is my text This is my long multi-line comment opener text. More text 132 | * please. This is yet another bunch of text in my test comment, so I will get 133 | * multiple lines in the comment. 134 | """ 135 | ), 136 | WrapTestCase( 137 | "Wrap fills multiline opener with beginning space", 138 | """ 139 | /* This is my text This is my long multi-line comment opener text. More text please. This is yet another bunch of text in my test comment, so I will get multiple lines in the comment. */ 140 | """, 141 | """ 142 | /* This is my text This is my long multi-line comment opener text. More text 143 | * please. This is yet another bunch of text in my test comment, so I will get 144 | * multiple lines in the comment. */ 145 | """ 146 | ), 147 | WrapTestCase( 148 | "Wrap preserves empty comment lines", 149 | """ 150 | /* 151 | * This is my text. This is my long multi-line comment opener text. More text please. This is yet another bunch of text in my test comment, so I will get multiple lines in the comment. 152 | * 153 | * This is another line of text. 154 | */ 155 | """, 156 | """ 157 | /* 158 | * This is my text. This is my long multi-line comment opener text. More text 159 | * please. This is yet another bunch of text in my test comment, so I will get 160 | * multiple lines in the comment. 161 | * 162 | * This is another line of text. 163 | */ 164 | """ 165 | ), 166 | WrapTestCase( 167 | "Wrap multiple comment paragraphs", 168 | """ 169 | /* 170 | * This is my text. This is my long multi-line comment opener text. More text please. This is yet another bunch of text in my test comment, so I will get multiple lines in the comment. 171 | * 172 | * This is another line of text. 173 | * 174 | * And yet another long line of text. Text going on and on endlessly, much longer than it really should. 175 | */ 176 | """, 177 | """ 178 | /* 179 | * This is my text. This is my long multi-line comment opener text. More text 180 | * please. This is yet another bunch of text in my test comment, so I will get 181 | * multiple lines in the comment. 182 | * 183 | * This is another line of text. 184 | * 185 | * And yet another long line of text. Text going on and on endlessly, much 186 | * longer than it really should. 187 | */ 188 | """ 189 | ), 190 | WrapTestCase( 191 | "Wrap retains space indent", 192 | " This is my long indented string. It's too long to fit on one line, uh oh! What will happen?", 193 | " This is my long indented string. It's too long to fit on one line, uh oh!\n What will happen?", 194 | trimIndent = false 195 | ), 196 | WrapTestCase( 197 | "Wrap handles lines within multiline comment", 198 | """ 199 | * This is a long line in a multi-line comment block. Note the star at the beginning. 200 | * This is another line in a multi-line comment. 201 | """, 202 | """ 203 | * This is a long line in a multi-line comment block. Note the star at the 204 | * beginning. This is another line in a multi-line comment. 205 | """, 206 | ), 207 | WrapTestCase( 208 | "Wrap removes extra blank line", 209 | """ 210 | 211 | My block of text. My block of text. My block of text. My block of text. My block of text. My block of text. 212 | """, 213 | """ 214 | My block of text. My block of text. My block of text. My block of text. My block 215 | of text. My block of text. 216 | """ 217 | ), 218 | WrapTestCase( 219 | "Wrap preserves leading indent", 220 | """ 221 | . My long bullet line. My long bullet line. My long bullet line. My long bullet line. 222 | """, 223 | """ 224 | . My long bullet line. My long bullet line. My long bullet line. My long bullet 225 | . line. 226 | """ 227 | ), 228 | WrapTestCase( 229 | "Ignores trailing spaces", 230 | """ 231 | The quick brown fox 232 | jumps over the lazy 233 | dog 234 | """, 235 | """ 236 | The quick brown fox jumps over the lazy dog 237 | """ 238 | ), 239 | WrapTestCase( 240 | "Preserves comment symbols within text", 241 | """ 242 | /** 243 | * Let's provide a javadoc comment that has a link to some method, e.g. {@link #m()}. 244 | */ 245 | """, 246 | """ 247 | /** 248 | * Let's provide a javadoc comment that has a link to some method, e.g. {@link 249 | * #m()}. 250 | */ 251 | """ 252 | ), 253 | WrapTestCase( 254 | "Wraps null strings", 255 | null, 256 | "" 257 | ), 258 | WrapTestCase( 259 | "Accounts for tab width", 260 | "\t\t\t\tThis is my very long line of text. This is my very long line of text. This is my\t very long line of text.", 261 | "\t\t\t\tThis is my very long\n\t\t\t\tline of text. This\n\t\t\t\tis my very long line\n\t\t\t\tof text. This is\n\t\t\t\tmy\t very long\n\t\t\t\tline of text.", 262 | trimIndent = false, 263 | width = 40, 264 | tabWidth = 5 265 | ), 266 | WrapTestCase( 267 | "Minimum raggedness", 268 | """ 269 | lk jsdflkj sdlkfj sdlkfj slkj flkj dslkfj sdlkfj lkjs dflkj sdlfkj sdlkfj lkj sdflkj sdlkj sdlkj fdslkjfsdlkjsd flkj sdflkj sdlfkj sdlfkj sdlkjf sdlkjf dslkj fdslkj fsdlkj flsdkjsldklkslkslkslkslsk djl jsdkf 270 | """, 271 | """ 272 | lk jsdflkj sdlkfj sdlkfj slkj flkj dslkfj 273 | sdlkfj lkjs dflkj sdlfkj sdlkfj lkj sdflkj 274 | sdlkj sdlkj fdslkjfsdlkjsd flkj sdflkj 275 | sdlfkj sdlfkj sdlkjf sdlkjf dslkj fdslkj 276 | fsdlkj flsdkjsldklkslkslkslkslsk djl jsdkf 277 | """, 278 | width = 50, 279 | useMinimumRaggedness = true 280 | ), 281 | WrapTestCase( 282 | "Supports Chinese", 283 | """ 284 | 它是如何工作的呢?实际上,每个bundle在定义自己的服务配置都是跟目前为止你看到的是一样的。换句话说,一个bundle使用一个或者多个配置资源文件(通常是XML)来指定bundle所需要的参数和服务。然而,我们不直接在配置文件中使用 imports 命令导入它们,而是仅仅在bundle中调用一个服务容器扩展来为我们做同样的工作。一个服务容器扩展是 bundle 的作者创建的一个PHP类,它主要完成两件事情 285 | """, 286 | """ 287 | 它是如何工作的呢?实际上,每个bundle在定义自己的服务配置都是跟目前为止你看到的是一样的。换句话说,一个bundle使用一个或者多个配置资源文件(通常是XML)来指定bundle所需要的参数和服务。然而,我们不直接在配置文件中使用 288 | imports 命令导入它们,而是仅仅在bundle中调用一个服务容器扩展来为我们做同样的工作。一个服务容器扩展是 bundle 289 | 的作者创建的一个PHP类,它主要完成两件事情 290 | """, 291 | ), 292 | WrapTestCase( 293 | "Treat HTML newline as paragraph separator", 294 | """ 295 | /** 296 | * Text on first paragraph. 297 | *

298 | * Text on second paragraph. In this case the line is very long and will be wrapped. 299 | *
300 | * Third paragraph. 301 | *
302 | * Fourth paragraph. 303 | *
304 | * Fifth paragraph is the last one. 305 | */ 306 | """, 307 | """ 308 | /** 309 | * Text on first paragraph. 310 | *

311 | * Text on second paragraph. In this case the line is very long and will be 312 | * wrapped. 313 | *
314 | * Third paragraph. 315 | *
316 | * Fourth paragraph. 317 | *
318 | * Fifth paragraph is the last one. 319 | */ 320 | """ 321 | ), 322 | WrapTestCase( 323 | "Wraps SQL comments", 324 | """ 325 | -- This is a SQL comment. It may not be an important comment, but it's mine. My own. My precious. 326 | """, 327 | """ 328 | -- This is a SQL comment. It may not be an important comment, but it's mine. My 329 | -- own. My precious. 330 | """ 331 | ), 332 | WrapTestCase( 333 | "Wraps Python docstrings", 334 | """ 335 | ${"\"\"\""} 336 | This is a long docstring comment. It goes on and on to explain how the function works. However, I forgot to add line breaks! 337 | 338 | Except, I didn't forget! Here, I added a line break 339 | and then wrote a very short couple of lines. 340 | ${"\"\"\""} 341 | """, 342 | """ 343 | ${"\"\"\""} 344 | This is a long docstring comment. It goes on and on to explain how the function 345 | works. However, I forgot to add line breaks! 346 | 347 | Except, I didn't forget! Here, I added a line break and then wrote a very short 348 | couple of lines. 349 | ${"\"\"\""} 350 | """ 351 | ), 352 | WrapTestCase( 353 | "Wraps Python docstrings with single quotes", 354 | """ 355 | ''' 356 | This is a long docstring comment. It goes on and on to explain how the function works. However, I forgot to add line breaks! 357 | 358 | Except, I didn't forget! Here, I added a line break 359 | and then wrote a very short couple of lines. 360 | ''' 361 | """, 362 | """ 363 | ''' 364 | This is a long docstring comment. It goes on and on to explain how the function 365 | works. However, I forgot to add line breaks! 366 | 367 | Except, I didn't forget! Here, I added a line break and then wrote a very short 368 | couple of lines. 369 | ''' 370 | """ 371 | ), 372 | WrapTestCase( 373 | "Wraps Markdown block quotes", 374 | """ 375 | > Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 376 | """, 377 | """ 378 | > Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 379 | > incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 380 | > nostrud exercitation 381 | """ 382 | ) 383 | ) 384 | 385 | @Test 386 | fun runAllTests() { 387 | testCases.forEach { testCase -> 388 | val wrapper = CodeWrapper( 389 | tabWidth = testCase.tabWidth, 390 | width = testCase.width, 391 | useMinimumRaggedness = testCase.useMinimumRaggedness 392 | ) 393 | val result = wrapper.wrap(testCase.input) 394 | assertEquals(testCase.description, testCase.expectedOutput, result) 395 | } 396 | } 397 | } -------------------------------------------------------------------------------- /src/test/java/com/andrewbrookins/idea/wrap/python_example_text.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a module with a very long line in its docstring. The line goes on and on. Who would write such a long line? Only a monster would write lines this long. 3 | Another line after that very long line comprises a paragraph. This is a paragraph of two very long lines. A monster would write such long lines, but not me, the author of this plugin. I would never. 4 | 5 | And then the docstring has a shorter line. Why? I don't know. 6 | 7 | It ends with two very short 8 | lines and then a blank line. 9 | 10 | """ 11 | 12 | 13 | class MyClass: 14 | """Then we have a class with a docstring on one line.""" 15 | 16 | def my_method(self): 17 | """ 18 | With a long docstring in a method. Here we have a very long docstring. Oh my, how long it is. 19 | """ 20 | 21 | def another_method(self, number: int): 22 | """ 23 | Does something interesting. 24 | 25 | Args: 26 | number: a number that represents some very interesting property about which I can say quite a lot of words. 27 | """ 28 | # And here is a very long single-line comment above some Python code. Wrapping this paragraph should stop at the code. 29 | print("hey") 30 | --------------------------------------------------------------------------------