pax_global_header00006660000000000000000000000064150110676170014516gustar00rootroot0000000000000052 comment=e03edf775af41053c8a4de98f370689d4525077b helm-4.0.3/000077500000000000000000000000001501106761700124475ustar00rootroot00000000000000helm-4.0.3/.dir-locals.el000066400000000000000000000010601501106761700150750ustar00rootroot00000000000000;;; Directory Local Variables ;;; For more information see (info "(emacs) Directory Variables") ((nil . ((bug-reference-bug-regexp . "\\(\\b\\(?:[Ii]ssue ?#?\\|[Bb]ug ?#?\\|[Pp]atch ?#\\|RFE ?#\\|PR [a-z+-]+/\\)\\([0-9]+\\(?:#[0-9]+\\)?\\)\\)") (bug-reference-url-format . "https://github.com/emacs-helm/helm/issues/%s") (byte-compile-warnings . (not obsolete docstrings docstrings-non-ascii-quotes)))) (emacs-lisp-mode . ((mode . bug-reference-prog) (indent-tabs-mode . nil) (fill-column . 80)))) helm-4.0.3/.github/000077500000000000000000000000001501106761700140075ustar00rootroot00000000000000helm-4.0.3/.github/FUNDING.yml000066400000000000000000000001611501106761700156220ustar00rootroot00000000000000# These are supported funding model platforms github: emacs-helm custom: https://www.patreon.com/user?u=86324343 helm-4.0.3/.github/ISSUE_TEMPLATE/000077500000000000000000000000001501106761700161725ustar00rootroot00000000000000helm-4.0.3/.github/ISSUE_TEMPLATE/bug-report.yml000066400000000000000000000043671501106761700210150ustar00rootroot00000000000000name: Bug Report description: File a bug report labels: ["bug"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: textarea id: what-happened attributes: label: What happened? description: Also tell us, what did you expect to happen? placeholder: Tell us what you see! validations: required: true - type: textarea id: recipe attributes: label: How to reproduce? description: Tell us how to reproduce bug step by step? placeholder: Add here a detailed recipe, thanks! validations: required: true - type: dropdown id: Helm-version attributes: label: Helm Version description: I agree using the last Helm version from options: - Master branch - Melpa or NonGnuElpa validations: required: true - type: dropdown id: Emacs-version attributes: label: Emacs Version description: What version of Emacs are you running? options: - Emacs-25* - Emacs-26* - Emacs-26.3 - Emacs-27.1 - Emacs-27.2 - Emacs-28.1 - Emacs-28.2 - Emacs-28.3 - Emacs-29.1 - Emacs-30+ validations: required: true - type: dropdown id: Platform attributes: label: OS description: What OS are you running on? options: - GNU/Linux - Freebsd - MacOSX - Windows - Other validations: required: true - type: textarea id: backtrace attributes: label: Relevant backtrace (if possible) description: Please copy and paste any relevant backtrace. This will be automatically formatted into code, so no need for backticks. placeholder: To produce a backtrace enable `debug-on-error` with M-x toggle-debug-on-error render: text - type: checkboxes id: terms attributes: label: Minimal configuration description: You are using emacs -Q with only Helm package loaded to reproduce your bug (i.e. No Spacemacs, Doom etc...), use either emacs-helm.sh or the Isolate packages action from helm-packages. options: - label: I agree using a minimal configuration required: true helm-4.0.3/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000003141501106761700201600ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: "Jump to github discussions for help" url: https://github.com/emacs-helm/helm/discussions about: Please search, ask and answer questions here. helm-4.0.3/.github/ISSUE_TEMPLATE/feature_request.yml000066400000000000000000000014731501106761700221250ustar00rootroot00000000000000name: Feature Request description: Suggest an idea for this project labels: ["feature request"] body: - type: markdown attributes: value: | Thanks for your interest in this project! - type: textarea id: Feature_request attributes: label: Suggestion description: Is your feature request related to a problem? Please describe. validations: required: true - type: textarea id: Description attributes: label: Description description: A clear and concise description of what you want to happen. placeholder: Describe the solution you'd like validations: required: true - type: textarea id: Solutions attributes: label: Solutions description: Describe alternatives you've considered validations: required: true helm-4.0.3/.gitignore000066400000000000000000000000661501106761700144410ustar00rootroot00000000000000*.elc TAGS /helm-autoloads.el /helm-core-autoloads.el helm-4.0.3/COPYING000066400000000000000000001045131501106761700135060ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . helm-4.0.3/Makefile000066400000000000000000000063631501106761700141170ustar00rootroot00000000000000# makefile for helm. # Author: Michael Markert. # Copyright (C) 2011~2012, Michael Markert, all rights reserved. ## This file is NOT part of GNU Emacs ## ## License ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3, or (at your option) ## any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, write to ## the Free Software Foundation, Inc., 51 Franklin Street, Fifth ## Floor, Boston, MA 02110-1301, USA. # Emacs invocation EMACS_COMMAND := emacs # Use -q to have /usr/local/share/emacs/site-lisp and subdirs in load-path EMACS := $(EMACS_COMMAND) -q -batch EVAL := $(EMACS) --eval PKGDIR := . # Additional emacs loadpath LOADPATH := -L $(PKGDIR) # Prefer emacs config folder in XDG_CONFIG_HOME to ~/.emacs.d # Assume emacs-user-directory is ~/.emacs.d # Try to find ELPA directory or STRAIGHT directory. XDG_ELPA_DIR := $(if $(XDG_CONFIG_HOME), $(XDG_CONFIG_HOME)/emacs/elpa, $(HOME)/.config/emacs/elpa) ELPA_DIR := $(if $(shell test -d $(XDG_ELPA_DIR)), $(XDG_ELPA_DIR), $(HOME)/.emacs.d/elpa) XDG_STRAIGHT_DIR := $(if $(XDG_CONFIG_HOME), $(XDG_CONFIG_HOME)/emacs/straight/build, $(HOME)/.config/emacs/straight/build) STRAIGHT_DIR := $(if $(shell test -d $(XDG_STRAIGHT_DIR)), $(XDG_STRAIGHT_DIR), $(HOME)/.emacs.d/straight/build) ASYNC_ELPA_DIR = $(shell \ test -d $(ELPA_DIR) && \ find -L $(ELPA_DIR) -maxdepth 1 -regex '.*/async-[.0-9]*' 2> /dev/null | \ sort | tail -n 1) ifneq "$(ASYNC_ELPA_DIR)" "" LOADPATH += -L $(ASYNC_ELPA_DIR) endif ASYNC_STRAIGHT_DIR = $(shell \ test -d $(STRAIGHT_DIR) && \ find -L $(STRAIGHT_DIR) -maxdepth 1 -regex '.*/async' 2> /dev/null | \ sort | tail -n 1) ifneq "$(ASYNC_STRAIGHT_DIR)" "" LOADPATH += -L $(ASYNC_STRAIGHT_DIR) endif # Files to compile EL := $(sort $(wildcard helm*.el)) # Compiled files ELC := $(EL:.el=.elc) .PHONY: clean autoloads batch-compile all: clean autoloads batch-compile $(ELC): %.elc: %.el $(EMACS) $(LOADPATH) -f batch-byte-compile $< # Compile needed files compile: $(ELC) # Compile all files at once batch-compile: @echo Compiling helm with $(EMACS_COMMAND) $(EMACS) $(LOADPATH) -f batch-byte-compile $(EL) # Remove all generated files clean: rm -f $(ELC) # Make autoloads file autoloads: $(EVAL) "(progn (setq generated-autoload-file (expand-file-name \"helm-autoloads.el\" \"$(PKGDIR)\")) \ (setq backup-inhibited t) (update-directory-autoloads \"$(PKGDIR)\"))" PREFIX=/usr/local/ BIN=${PREFIX}bin/ DESTDIR=${PREFIX}share/emacs/site-lisp/helm/ install: test -d ${DESTDIR} || mkdir ${DESTDIR} rm -f ${DESTDIR}*.el rm -f ${DESTDIR}*.elc cp -f *.el $(DESTDIR) cp -f *.elc $(DESTDIR) cp -f helm-autoloads.el $(DESTDIR) cp -f emacs-helm.sh $(DESTDIR) ln -fs ${DESTDIR}emacs-helm.sh ${BIN}helm uninstall: rm -vf ${DESTDIR}*.elc rm -vf ${DESTDIR}*.el rm -vf ${DESTDIR}emacs-helm.sh rm -vf ${BIN}helm helm-4.0.3/NEWS.org000066400000000000000000000240461501106761700137420ustar00rootroot00000000000000#+STARTUP:showall * NEWS ** Version 4.0.3 *** New drag-and-drop support for helm-find-files (only Emacs-29+) Use now dnd.el for the new drag-and-drop function. This allows drag-and-drop in external applications in addition to emacs dired frames and windows. See helm-find-files documentation. *** Remove old drag-and-drop support for helm-find-files *** Add a new completion metadata category command-help Handle helm-M-x prefix arg integration in this category. That's mean you can have helm-M-x prefix arg in M-X. *** Allow filtering sources in current session It is bound to C-M-e in helm-map. *** Change `helm-current-directory-alist` default value Now C-x C-f from a Gnus buffer brings to News directory. *** Exclude commands related to specific modes in helm-M-x This is available only in Emacs with an interactive version supporting MODES argument. It is configurable with `helm-M-x-exclude-unusable-commands-in-mode`, default to t. *** Fix preselection after persistent killing buffers *** Improve colors in ttys *** Add new actions to helm-find-files *** Fix require-match in completion-in-region ** Version 4.0.2 *** Fix Emacs bug with CRM when using org-set-tags *** Improve helm-source-files-in-current-dir *** Fix some compatibility problems with EIEIO in various Emacs *** Improve helm-projects-history *** Fix bug in recentf Properties may corrupt recentf data, prevent this. *** Fix bug with *-window-side-state no more working *** Remove the ability of switching to recursive search with C-j in HFF It was broken and not needed as we can just add the final "/" to expand. *** Change default value of `helm-locate-recursive-dirs-command' It is no more using `locate` but `find`, this because locate is not working anymore out of the box without some tweaks (plocate backend). It is still anyway the faster alternative once tweaked, see HFF documentation. *** Improve position of helm frame *** Provide icons in find and fdfind ** Version 4.0.1 *** Provide icons in some more places (helm-for-files.el) *** Package-vc-* commands have now affixations (package category) *** Show only basename of dot files in helm-find-files It is now the default, but it can be configured with `helm-ff-show-dot-file-path`. *** Allow using both all-the-icons and nerd-icons as icon providers Use the variable `helm-x-icons-provider`. *** Add affixations to helm-info Add a description of info file when using `helm-info` or `info-display-manual`. *** Provide minimal support for eww bookmarks *** Stop preventing dups when trashing files *** Add a helm-add-to-list function for user init file settings *** Drop support for deprecated defadvice management *** Allow destructuring in helm-acase *** Popup-info can be provided in metadata and added to any completion via the vars `helm-completing-read-command-categories` and `helm-completing-read-extra-metadata`, for example it is now enabled in M-x man. *** Maybe provide a popup-info in completion-at-point If CAPFns provide such information via `completion-extra-properties`, display it in a popup-info when helm-popup-tip-mode is enabled. `helm-lisp-completion-at-point` provides this as well. *** Add completion for Char Classes for regexps This is available in `helm-lisp-completion-at-point`. *** Enhance usage of tab-bar-mode from Helm Previously we were displaying each marked candidate in a tab, now all marked candidates go in same tab. Helm-find-files is now using the same functions as helm-buffers to display its candidates in tab. helm-imenu-all when jumping to a candidate jump to it in its tab if possible. *** Access to helm-find-files bookmarks from helm-read-file-name That's mean that when copying/renaming etc... you can use bookmarks as target. *** Fix a bug in query replace in file names It is now possible to match counter ("\#") inside the replacement string and not only before or after as before. *** Fix a bug with dummy sources in file related sources It is now possible to mark wildcard candidates from unrelated directories. ** Version 4.0 *** Enhance helm-finder Now helm-finder fetchs keywords from all packages, not only built-in and allows installing from there. *** helm-popup-tip-mode is now usable in any source having the popup-info attribute In addition of the various helm-grep-* now =helm-man-woman= and =helm-find-files= can benefit of this mode. *** New command helm-outline to navigate buffers according to outline-regexp *** New helm grep ag action to search results from the helm ag/rg session This allows filtering the current results to another pattern or to specific file. *** New helm grep ag action to launch ag/rg on parent directory of current search *** Packages can now be (re)installed/upgraded asynchronously from helm-packages It is now the default, you can customize this with =helm-packages-async=. *** Helm-find-files persistent delete uses now a read-answer prompt It is now possible to answer "!" (yes for all) when using marked candidates, this for the file deletion itself and also for the kill buffer question. *** Allow saving selection when deleting minibuffer contents This is now possible with a prefix arg given when position is at eol in minibuffer. This allow for example showing all candidates keeping current selection in helm-ls-git log after a search. *** Save files opened from etags and fd in file-name-history *** Provide help in helm-read-answer *** Disable helm for read-multiple-choice--long-answers ** Version 3.9.9 *** Change prefix arg behavior of helm-show-all-candidates-in-source With a prefix arg show all candidates, with a numeric prefix arg show ARG number of candidates, and with no prefix arg show only helm-candidate-number-limit of candidates. *** Get rid of popup.el dependency *** helm-completion-styles-alist accepts commands for completion-in-region *** Minibuffer-contents change color in HFF when updating This is controlled by =helm-ff-dim-prompt-on-update= user variable. *** Now new file and dir have a dummy source in HFF, same for completing-read's and read-file-name. *** helm-info-at-point shows index of current page in addition of default So it can safely be used as a replacement of "i" in Info. *** Add affixation function for eww (emacs-30 only) *** Allow toggling auto update in helm-top with a prefix arg *** Do not use `dired-create-destination-dirs` mechanism for directory creation as it is not working in some cases (e.g. symlinks). *** Fix persistent action in helm mark-ring when follow mode is on *** Fix Imenu preselection when point is on a name with special chars *** Provide a new progress bar for rsync with a svg widget Needs svg-lib package but it is not mandatory. ** Version 3.9.8 *** New helm-finder command *** Make `completions-detailed` working with 'emacs' helm-completion-style It was already working but was really slow and was not supporting `helm-completing-read-command-categories` mechanism which provide `completions-detailed` in much more places than Emacs vanilla. *** Helm-goto-line save position even when using persistent actions *** helm-info is now colorized *** Displaying thumnails is now slighly faster *** Add a new mode to edit bookmark annotations When viewing an annotation you had to kill the buffer, restart helm-bookmarks, and run the edit annotation action, now you can edit directly from the view buffer. *** It is now possible to rename marked bookmarks *** New compress/uncompress/compress-to action in helm-find-files All actions are asynchronous except the persistent ones. *** Allow toggling while-no-input for easier edebugging Use the new `helm-update-edebug` variable to enable this. *** And various bug fixes as always ** Version 3.9.7 *** Fix package-requires in helm.el ** Version 3.9.6 *** Fix bug with helm-mm-exact-search (bug#2616) *** Improve locate library fns *** Fix a old bug with candidate transformer candidate-transformer functions should behave the same in in-buffer and sync source: transforming all candidates. *** Fix kmacro for Emacs-29+ *** Various minor bug fixes ** Version 3.9.5 *** Add compatibility with Emacs-28< for helm-packages Tested with emacs-27. *** Add new action package-isolate for helm-packages Allow launching a new Emacs with only package(s). *** Packages list can be refreshed with C-c C-u BTW there is no more option to update from actions with prefix arg. *** Add more affixations fns for completing-read Themes, colors etc... *** Add new var helm-compleions-detailed for emacs-27 This allow using completions-detailed in Emacs-27 with various describe-* functions. *** Helm-occur now doesn't hang when trying to match empty lines With "^$". *** Highlight matches in M-x highlight only commands I.e. Items in short documentation are not highlighted. ** Version 3.9.4 *** Add two new alists to allow extending completions-detailed This allows providing detailed completions in more places, see `helm-completing-read-extra-metadata` and `helm-completing-read-command-categories`. They are not provided as user variables as it may be tricky to provide the corresponding affixations functions. As of now we have detailed completions in describe-function/variable/symbol/command/package, find-function/variable, switch-to-buffer, customize-variable/group, package-(vc)install, package-vc-checkout. *** New helm packages manager The old helm-elisp-package has been replaced by helm-packages. The old one was too slow, taking lot of memory and even crashing Emacs especially with last Emacs-29. ** Version 3.9.3 *** Ensure to use affixation functions provided by caller in helm-mode ** Version 3.9.2 *** New progress bar for Rsync action from helm-find-files *** Describe-* commands with completions-detailed are now fast *** Use Wfnames package as dependency to replace Wdired ** Version 3.9.1 *** helm-config file has been removed Therefore (require 'helm-config) will return an error, don't use this anymore. If installing from source use instead (require 'helm-autoloads), otherwise from a package install, the autoloads file will be automatically loaded. *** Allow using helm-completion-styles-alist by command *** New mode for editing large vars Will be used automatically when using the set variable action from helm-apropos. *** Improve all-the-icons in many places helm-4.0.3/README.md000066400000000000000000000044211501106761700137270ustar00rootroot00000000000000

License GPL 3 MELPA MELPA Stable

Emacs-Helm

Emacs-helm

***

Helm is an Emacs framework for incremental completions and narrowing selections. It provides an easy-to-use API for developers wishing to build their own Helm applications in Emacs, powerful search tools and dozens of already built-in commands providing completion to almost everything. It is a must-have for anyone using Emacs as a main work environment. Helm has been widely adopted by many Emacs power-users. It is available in Melpa and can be easily installed from the Emacs package manager.

***

Homepage | Downloads | Get started | Helm wiki | FAQ

***

Helm in action browsing images

Emacs-helm grep ag

[badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg

Maintainance of Helm is a lot of work please consider making a donation, thank you!

Donate monthly using Patreon [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/emacs-helm) helm-4.0.3/emacs-helm.sh000077500000000000000000000263551501106761700150340ustar00rootroot00000000000000#!/usr/bin/env sh ## Copyright (C) 2012 ~ 2025 Thierry Volpiatto ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## Commentary: # Preconfigured `emacs -Q' with a basic Helm configuration. # If TEMP env var exists, use it, otherwise declare it. test -z "$TEMP" && TEMP="/tmp" CONF_FILE="$TEMP/helm-cfg.el" EMACS=emacs QUICK=-Q TOOLBARS=-1 LOAD_PACKAGES= usage () { cat >&1 < $CONF_FILE <\`helm-find-files'\\n\ ;; - \`occur'(M-s o) =>\`helm-occur'\\n\ ;; - \`list-buffers'(C-x C-b) =>\`helm-buffers-list'\\n\ ;; - \`apropos-command'(C-h a) =>\`helm-apropos'\\n\ ;; - \`dabbrev-expand'(M-/) =>\`helm-dabbrev'\\n\ ;; - \`execute-extended-command'(M-x) =>\`helm-M-x'\\n\ ;; - \`yank-pop'(M-y) =>\`helm-show-kill-ring'\\n\\n ;; \`helm-mode' is enabled which mean that most Emacs commands using completion\\n\ ;; will use helm.\\n\ ;; To start editing a file or to create a new file, visit it with \`C-x C-f'\\n\ ;; and enter text in its buffer, to save your changes hit \`C-x C-s'. ;; Find context help for most Helm commands with \`C-h m' while helm is running.\\n\ ;; You can also retrieve the whole user documentation with \`C-x c h h'.\\n\ ;; For online documentation see \`https://github.com/emacs-helm/helm/wiki'.\\n\ ;; \(Put cursor on url, hit \`C-x C-f' and then RETurn).\\n\ ;; To quit this Emacs, hit \'C-x C-c'.\\n\ ;; Note about keybindings in Emacs: \`C-' means \'Control-' and \`M-' \'Alt-'.\\n\ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\\n\\n") (setq load-path (quote $LOAD_PATH)) (defvar default-package-manager nil) ;; /home/you/.emacs.d/.local/straight/build-27.1/helm (defvar initial-package-directory (file-name-directory (file-truename "$0"))) (defvar bootstrap-version) (let* ((packages "$LOAD_PACKAGES") (pkg-list (and packages (not (equal packages "")) (split-string packages ","))) ;; /home/you/.emacs.d/.local/straight/build-27.1 (straight-path (file-name-directory (directory-file-name initial-package-directory))) ;; /home/you/.emacs.d/.local/straight/build-27.1/async (async-path (expand-file-name "async" straight-path)) ;; /home/you/.emacs.d/.local/straight/repos/straight.el/bootstrap.el (bootstrap-file (expand-file-name "repos/straight.el/bootstrap.el" (file-name-directory (directory-file-name straight-path)))) (bootstrap-version 5)) (when (file-exists-p bootstrap-file) (setq default-package-manager 'straight) (load bootstrap-file nil 'nomessage) (add-to-list 'load-path async-path) (when pkg-list (dolist (pkg pkg-list) (let* ((pkg-path (expand-file-name pkg straight-path)) (autoload-file (expand-file-name (format "%s-autoloads.el" pkg) pkg-path))) (add-to-list 'load-path pkg-path) (if (file-exists-p autoload-file) (load autoload-file nil 'nomessage) (straight-use-package (intern pkg)))))))) (unless (eq default-package-manager 'straight) (require 'package) ;; User may be using a non standard \`package-user-dir'. ;; Modify \`package-directory-list' instead of \`package-user-dir' ;; in case the user starts Helm from a non-ELPA installation. (unless (file-equal-p package-user-dir (locate-user-emacs-file "elpa")) ;; Something like /home/you/.emacs.d/somedir/else/elpa/ ;; starting from default-directory is wrong in case helm.sh is a symlink ;; or e.g. helm --chdir foo have been used. (add-to-list 'package-directory-list (directory-file-name (file-name-directory (directory-file-name initial-package-directory))))) (let* ((str-lst "$LOAD_PACKAGES") (load-packages (and str-lst (not (string= str-lst "")) (split-string str-lst ",")))) (setq package-load-list (if (equal load-packages '("all")) '(all) (append '((helm-core t) (helm t) (async t) (wfnames t)) (mapcar (lambda (p) (list (intern p) t)) load-packages))))) (package-initialize)) (add-to-list 'load-path initial-package-directory) (unless (> $TOOLBARS 0) (setq default-frame-alist '((vertical-scroll-bars . nil) (tool-bar-lines . 0) (menu-bar-lines . 0) (fullscreen . nil)))) (blink-cursor-mode -1) (load "helm-autoloads" nil t) (helm-mode 1) (with-eval-after-load 'tramp-cache (setq tramp-cache-read-persistent-data t)) (with-eval-after-load 'auth-source (setq auth-source-save-behavior nil)) (define-key global-map [remap find-file] 'helm-find-files) (define-key global-map [remap occur] 'helm-occur) (define-key global-map [remap list-buffers] 'helm-buffers-list) (define-key global-map [remap dabbrev-expand] 'helm-dabbrev) (define-key global-map [remap execute-extended-command] 'helm-M-x) (define-key global-map [remap apropos-command] 'helm-apropos) (define-key global-map [remap yank-pop] 'helm-show-kill-ring) (add-hook 'kill-emacs-hook #'(lambda () (and (file-exists-p "$CONF_FILE") (delete-file "$CONF_FILE")))) EOF $EMACS "$QUICK" -l "$CONF_FILE" "$@" helm-4.0.3/helm-adaptive.el000066400000000000000000000324351501106761700155200ustar00rootroot00000000000000;;; helm-adaptive.el --- Adaptive Sorting of Candidates. -*- lexical-binding: t -*- ;; Original Author: Tamas Patrovics ;; Copyright (C) 2007 Tamas Patrovics ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (defgroup helm-adapt nil "Adaptative sorting of candidates for Helm." :group 'helm) (defcustom helm-adaptive-history-file (locate-user-emacs-file "helm-adaptive-history") "Path of file where history information is stored. When nil history is not saved nor restored after Emacs restart unless you save/restore `helm-adaptive-history' with something else like psession or desktop." :type 'string) (defcustom helm-adaptive-history-length 50 "Maximum number of candidates stored for a source." :type 'number) (defcustom helm-adaptive-sort-by-frequent-recent-usage t "Try to sort on an average of frequent and recent usage when non-nil. When nil sort on frequency usage only. Only frequency: When candidate have low frequency, you have to hit on it many times to make it going up on top. Frequency+recent: Even with a low frequency, candidate go up on top. If a candidate have a high frequency but it is not used since some time, it goes down slowly, but as soon you reuse it it go up on top quickly." :type 'boolean) ;; Internal (defvar helm-adaptive-done nil "nil if history information is not yet stored for the current selection.") (defvar helm-adaptive-history nil "Contains the stored history information. Format: ((SOURCE-NAME (SELECTED-CANDIDATE (PATTERN . NUMBER-OF-USE) ...) ...) ...)") (defconst helm-adaptive-freq-coefficient 5) (defconst helm-adaptive-recent-coefficient 2) (defun helm-adaptive-done-reset () (setq helm-adaptive-done nil)) ;;;###autoload (define-minor-mode helm-adaptive-mode "Toggle adaptive sorting in all sources." :global t (if helm-adaptive-mode (progn (unless helm-adaptive-history (helm-adaptive-maybe-load-history)) (add-hook 'kill-emacs-hook #'helm-adaptive-save-history) ;; Should run at beginning of `helm-initial-setup'. (add-hook 'helm-before-initialize-hook #'helm-adaptive-done-reset) ;; Should run at beginning of `helm-exit-minibuffer'. (add-hook 'helm-before-action-hook #'helm-adaptive-store-selection) ;; Should run at beginning of `helm-select-action'. (add-hook 'helm-select-action-hook #'helm-adaptive-store-selection)) (helm-adaptive-save-history) (setq helm-adaptive-history nil) (remove-hook 'kill-emacs-hook #'helm-adaptive-save-history) (remove-hook 'helm-before-initialize-hook #'helm-adaptive-done-reset) (remove-hook 'helm-before-action-hook #'helm-adaptive-store-selection) (remove-hook 'helm-select-action-hook #'helm-adaptive-store-selection))) (defun helm-adapt-use-adaptive-p (&optional source-name) "Return current source only if it use adaptive history, nil otherwise." (when helm-adaptive-mode (let* ((source (or source-name (helm-get-current-source))) (adapt-source (or (assoc-default 'filtered-candidate-transformer source) (assoc-default 'candidate-transformer source)))) (if (listp adapt-source) (and (memq 'helm-adaptive-sort adapt-source) source) (and (eq adapt-source 'helm-adaptive-sort) source))))) (defun helm-adaptive-store-selection () "Store history information for the selected candidate." (unless helm-adaptive-done (setq helm-adaptive-done t) (let ((source (helm-adapt-use-adaptive-p))) (when source (let* ((source-name (assoc-default 'name source)) (source-info (or (assoc source-name helm-adaptive-history) (progn (push (list source-name) helm-adaptive-history) (car helm-adaptive-history)))) (selection (helm-get-selection nil t)) (selection-info (progn (setcdr source-info (cons (let ((found (assoc selection (cdr source-info)))) (if (not found) ;; new entry (list selection) ;; move entry to the beginning of the ;; list, so that it doesn't get ;; trimmed when the history is ;; truncated (setcdr source-info (delete found (cdr source-info))) found)) (cdr source-info))) (cadr source-info))) (pattern-info (progn (setcdr selection-info (cons (let ((found (assoc helm-pattern (cdr selection-info)))) (if (not found) ;; new entry (cons helm-pattern 0) ;; move entry to the beginning of the ;; list, so if two patterns used the ;; same number of times then the one ;; used last appears first in the list (setcdr selection-info (delete found (cdr selection-info))) found)) (cdr selection-info))) (cadr selection-info))) (timestamp-info (helm-aif (assq 'timestamp (cdr selection-info)) it (setcdr selection-info (cons (cons 'timestamp 0) (cdr selection-info))) (cadr selection-info)))) ;; Increase usage count. (setcdr pattern-info (1+ (cdr pattern-info))) ;; Update timestamp. (setcdr timestamp-info (float-time)) ;; Truncate history if needed. (if (> (length (cdr selection-info)) helm-adaptive-history-length) (setcdr selection-info (helm-take (cdr selection-info) helm-adaptive-history-length)))))))) (defun helm-adaptive-maybe-load-history () "Load `helm-adaptive-history-file' which contain `helm-adaptive-history'. Returns nil if `helm-adaptive-history-file' doesn't exist." (when (and helm-adaptive-history-file (file-readable-p helm-adaptive-history-file)) (load-file helm-adaptive-history-file))) (defun helm-adaptive-save-history (&optional arg) "Save history information to the file given by `helm-adaptive-history-file'." (interactive "p") (when helm-adaptive-history-file (with-temp-buffer (insert ";; -*- mode: emacs-lisp -*-\n" ";; History entries used for helm adaptive display.\n") (let (print-length print-level) (prin1 `(setq helm-adaptive-history ',helm-adaptive-history) (current-buffer))) (insert ?\n) (write-region (point-min) (point-max) helm-adaptive-history-file nil (unless arg 'quiet))))) (defun helm-adaptive-sort (candidates source) "Sort the CANDIDATES for SOURCE by usage frequency. This is a filtered candidate transformer you can use with the `filtered-candidate-transformer' attribute." (let* ((source-name (assoc-default 'name source)) (source-info (assoc source-name helm-adaptive-history))) (if source-info (let ((usage ;; Loop in the SOURCE entry of `helm-adaptive-history' ;; and assemble a list containing the (CANDIDATE ;; . USAGE-COUNT) pairs. (cl-loop with cf = (if helm-adaptive-sort-by-frequent-recent-usage helm-adaptive-freq-coefficient 1) with cr = helm-adaptive-recent-coefficient for (src-cand . infos) in (cdr source-info) for count-freq = 0 for count-rec = (helm-aif (and helm-adaptive-sort-by-frequent-recent-usage (assq 'timestamp infos)) (* cr (+ (float-time) (cdr it))) 0) do (cl-loop for (pattern . score) in (remove (assq 'timestamp infos) infos) ;; If current pattern is equal to ;; the previously used one then ;; this candidate has priority ;; (that's why its count-freq is ;; boosted by 10000) and it only ;; has to compete with other ;; candidates which were also ;; selected with the same pattern. if (equal pattern helm-pattern) return (setq count-freq (+ 10000 score)) else do (cl-incf count-freq score)) and collect (cons src-cand (+ (* count-freq cf) count-rec)) into results ;; Sort the list in descending order, so ;; candidates with highest priority come ;; first. finally return (sort results (lambda (first second) (> (cdr first) (cdr second))))))) (if (consp usage) ;; Put those candidates first which have the highest usage count. (cl-loop for (cand . _freq) in usage for info = (or (and (assq 'multiline source) (replace-regexp-in-string "\n\\'" "" cand)) ;; Some transformers like in ;; bookmarks may add a leading ;; space to provide additional ;; infos like an icon as a ;; display prop, strip out this ;; leading space for ;; comparison. Same for a ;; trailing space (helm ;; boookmark add bmk location as ;; a display prop when ;; displaying it). (helm-aand (replace-regexp-in-string "\\` " "" cand) (replace-regexp-in-string " \\'" "" it))) when (cl-member info candidates :test 'helm-adaptive-compare) collect (car it) into sorted and do (setq candidates (cl-remove info candidates :test 'helm-adaptive-compare)) finally return (append sorted candidates)) (message "Your `%s' is maybe corrupted or too old, \ you should reinitialize it with `helm-reset-adaptive-history'" helm-adaptive-history-file) (sit-for 1) candidates)) ;; if there is no information stored for this source then do nothing candidates))) ;;;###autoload (defun helm-reset-adaptive-history () "Delete all `helm-adaptive-history' and his file. Useful when you have a old or corrupted `helm-adaptive-history-file'." (interactive) (when (y-or-n-p "Really delete all your `helm-adaptive-history'? ") (setq helm-adaptive-history nil) (when (and helm-adaptive-history-file (file-exists-p helm-adaptive-history-file)) (delete-file helm-adaptive-history-file)))) (defun helm-adaptive-compare (x y) "Compare display parts if some of candidates X and Y. Arguments X and Y are cons cell in (DISPLAY . REAL) format or atoms." (equal (if (listp x) (car x) x) (if (listp y) (car y) y))) (provide 'helm-adaptive) ;;; helm-adaptive.el ends here helm-4.0.3/helm-bookmark.el000066400000000000000000001177301501106761700155320ustar00rootroot00000000000000;;; helm-bookmark.el --- Helm for Emacs regular Bookmarks. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'bookmark) (require 'helm) (require 'helm-lib) (require 'helm-help) (require 'helm-types) (require 'helm-utils) (require 'helm-info) (require 'helm-adaptive) (require 'helm-net) (require 'helm-x-icons) (declare-function helm-browse-project "helm-files" (arg)) (declare-function addressbook-bookmark-edit "ext:addressbook-bookmark.el" (bookmark)) (declare-function eww-read-bookmarks "eww") (defvar eww-bookmarks) (defgroup helm-bookmark nil "Predefined configurations for `helm.el'." :group 'helm) (defcustom helm-bookmark-show-location nil "Show location of bookmark on display." :type 'boolean) (defcustom helm-bookmark-default-filtered-sources (append '(helm-source-bookmark-org helm-source-bookmark-files&dirs helm-source-bookmark-helm-find-files helm-source-bookmark-info helm-source-bookmark-gnus helm-source-bookmark-mu4e helm-source-bookmark-man helm-source-bookmark-images helm-source-bookmark-w3m) (list 'helm-source-bookmark-uncategorized 'helm-source-bookmark-set)) "List of sources to use in `helm-filtered-bookmarks'." :type '(repeat (choice symbol))) (defcustom helm-bookmark-use-icon nil "Display candidates with an icon with `all-the-icons' when non nil. Don't use `setq' to set this." :type 'boolean :set (lambda (var val) (if (require helm-x-icons-provider nil t) (set var val) (set var nil)))) (defcustom helm-bookmark-default-sort-method 'adaptive "Sort method for `helm-filtered-bookmarks'. Value can be either \\='native' or \\='adaptive'. See `helm-adaptive-sort' for infos on \\='native'. Once you use \\='native' the bookmark variable `bookmark-sort-flag' will be honored." :type '(choice (symbol :tag "Helm adaptive sort method" adaptive) (symbol :tag "Native bookmark sort method" native)) ;; Don't use the :set function until functions and variables below ;; are not loaded i.e. use set-default only for now. :initialize 'custom-initialize-changed :set (lambda (var val) (set var val) (cl-loop for s in (remove 'helm-source-bookmark-set helm-bookmark-default-filtered-sources) for fn = (intern (format "%s-builder" s)) do (set s (funcall fn))))) (defcustom helm-bookmark-annotation-sign "*" "Boomarks with annotation are prefixed with this string." :type 'string) (defgroup helm-bookmark-faces nil "Customize the appearance of helm-bookmark." :prefix "helm-" :group 'helm-bookmark :group 'helm-faces) (defface helm-bookmark-info `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "green")) "Face used for W3m Emacs bookmarks (not w3m bookmarks)." :group 'helm-bookmark-faces) (defface helm-bookmark-w3m `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "yellow")) "Face used for W3m Emacs bookmarks (not w3m bookmarks)." :group 'helm-bookmark-faces) (defface helm-bookmark-gnus `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "magenta")) "Face used for Gnus bookmarks." :group 'helm-bookmark-faces) (defface helm-bookmark-man `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Orange4")) "Face used for Woman/man bookmarks." :group 'helm-bookmark-faces) (defface helm-bookmark-file `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Deepskyblue2")) "Face used for file bookmarks." :group 'helm-bookmark-faces) (defface helm-bookmark-file-not-found `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Slategray4")) "Face used for file bookmarks." :group 'helm-bookmark-faces) (defface helm-bookmark-directory `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit helm-ff-directory)) "Face used for file bookmarks." :group 'helm-bookmark-faces) (defface helm-bookmark-addressbook `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "tomato")) "Face used for addressbook bookmarks." :group 'helm-bookmark-faces) (defvar helm-bookmark-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "C-c o") #'helm-bookmark-run-jump-other-window) (define-key map (kbd "C-c C-o") #'helm-bookmark-run-jump-other-frame) (define-key map (kbd "C-c C-t") #'helm-bookmark-run-jump-other-tab) (define-key map (kbd "C-d") #'helm-bookmark-run-delete) (define-key map (kbd "C-]") #'helm-bookmark-toggle-filename) (define-key map (kbd "M-e") #'helm-bookmark-run-edit) map) "Generic Keymap for Emacs bookmark sources.") (defclass helm-source-basic-bookmarks (helm-source-in-buffer helm-type-bookmark) ((init :initform (lambda () (bookmark-maybe-load-default-file) (helm-init-candidates-in-buffer 'global (if (and (fboundp 'bookmark-maybe-sort-alist) (fboundp 'bookmark-name-from-full-record)) (mapcar 'bookmark-name-from-full-record (bookmark-maybe-sort-alist)) (bookmark-all-names))))) (filtered-candidate-transformer :initform 'helm-bookmark-transformer) (find-file-target :initform #'helm-bookmarks-quit-an-find-file-fn))) (defvar helm-source-bookmarks (helm-make-source "Bookmarks" 'helm-source-basic-bookmarks) "See (info \"(emacs)Bookmarks\").") (defun helm-bookmark-transformer (candidates _source) (cl-loop for i in candidates for loc = (bookmark-location i) for len = (string-width i) for trunc = (if (> len bookmark-bmenu-file-column) (helm-substring i bookmark-bmenu-file-column) i) for sep = (make-string (- (+ bookmark-bmenu-file-column 2) (length trunc)) ? ) if helm-bookmark-show-location collect (cons (concat trunc sep (if (listp loc) (car loc) loc)) i) else collect i)) (defun helm-bookmark-toggle-filename-1 (_candidate) (let* ((real (helm-get-selection helm-buffer)) (trunc (if (> (string-width real) bookmark-bmenu-file-column) (helm-substring real bookmark-bmenu-file-column) real))) (setq helm-bookmark-show-location (not helm-bookmark-show-location)) (helm-update (if helm-bookmark-show-location (regexp-quote trunc) (regexp-quote real))))) (helm-make-persistent-command-from-action helm-bookmark-toggle-filename "Toggle bookmark location visibility." 'toggle-filename 'helm-bookmark-toggle-filename-1) (defun helm-bookmark-jump-1 (candidate &optional fn) (let (;; FIXME Why is prefarg necessary here? (current-prefix-arg helm-current-prefix-arg) non-essential) (bookmark-jump candidate fn))) (defun helm-bookmark-jump (candidate) "Jump to bookmark action." (helm-bookmark-jump-1 candidate)) (defun helm-bookmark-jump-other-frame (candidate) "Jump to bookmark in other frame action." (helm-bookmark-jump-1 candidate #'switch-to-buffer-other-frame)) (defun helm-bookmark-jump-other-window (candidate) "Jump to bookmark in other window action." (helm-bookmark-jump-1 candidate #'switch-to-buffer-other-window)) (defun helm-bookmark-jump-other-tab (candidate) "Jump to bookmark action." (cl-assert (fboundp 'tab-bar-mode) nil "Tab-bar-mode not available") (helm-bookmark-jump-1 candidate #'switch-to-buffer-other-tab)) ;;; bookmark-set ;; (defvar helm-source-bookmark-set (helm-build-dummy-source "Set Bookmark" :filtered-candidate-transformer (lambda (_candidates _source) (list (or (and (not (string= helm-pattern "")) helm-pattern) "Enter a bookmark name to record"))) :action `(("Set bookmark" . ,(lambda (candidate) (if (string= helm-pattern "") (message "No bookmark name given for record") (bookmark-set candidate)))))) "See (info \"(emacs)Bookmarks\").") ;;; Predicates ;; (defconst helm-bookmark--non-file-filename " - no file -" "Name to use for `filename' entry, for non-file bookmarks.") (defun helm-bookmark-gnus-bookmark-p (bookmark) "Return non-nil if BOOKMARK is a Gnus bookmark. BOOKMARK is a bookmark name or a bookmark record." (or (eq (bookmark-get-handler bookmark) 'bmkext-jump-gnus) (eq (bookmark-get-handler bookmark) 'gnus-summary-bookmark-jump) (eq (bookmark-get-handler bookmark) 'bookmarkp-jump-gnus) (eq (bookmark-get-handler bookmark) 'bmkp-jump-gnus))) (defun helm-bookmark-mu4e-bookmark-p (bookmark) "Return non nil if BOOKMARK is a mu4e bookmark. BOOKMARK is a bookmark name or a bookmark record." (memq (bookmark-get-handler bookmark) '(mu4e-bookmark-jump mu4e--jump-to-bookmark))) (defun helm-bookmark-w3m-bookmark-p (bookmark) "Return non-nil if BOOKMARK is a W3m bookmark. BOOKMARK is a bookmark name or a bookmark record." (or (eq (bookmark-get-handler bookmark) 'bmkext-jump-w3m) (eq (bookmark-get-handler bookmark) 'bookmark-w3m-bookmark-jump) (eq (bookmark-get-handler bookmark) 'bookmarkp-jump-w3m) (eq (bookmark-get-handler bookmark) 'bmkp-jump-w3m))) (defun helm-bookmark-eww-bookmark-p (bookmark) "Return non-nil if BOOKMARK is an Eww bookmark. BOOKMARK is a bookmark name or a bookmark record." (eq (bookmark-get-handler bookmark) 'eww-bookmark-jump)) (defun helm-bookmark-woman-bookmark-p (bookmark) "Return non-nil if BOOKMARK is a Woman bookmark. BOOKMARK is a bookmark name or a bookmark record." (or (eq (bookmark-get-handler bookmark) 'bmkext-jump-woman) (eq (bookmark-get-handler bookmark) 'woman-bookmark-jump) (eq (bookmark-get-handler bookmark) 'bookmarkp-jump-woman) (eq (bookmark-get-handler bookmark) 'bmkp-jump-woman))) (defun helm-bookmark-man-bookmark-p (bookmark) "Return non-nil if BOOKMARK is a Man bookmark. BOOKMARK is a bookmark name or a bookmark record." (or (eq (bookmark-get-handler bookmark) 'bmkext-jump-man) (eq (bookmark-get-handler bookmark) 'Man-bookmark-jump) (eq (bookmark-get-handler bookmark) 'bookmarkp-jump-man) (eq (bookmark-get-handler bookmark) 'bmkp-jump-man))) (defun helm-bookmark-woman-man-bookmark-p (bookmark) "Return non-nil if BOOKMARK is a Man or Woman bookmark. BOOKMARK is a bookmark name or a bookmark record." (or (helm-bookmark-man-bookmark-p bookmark) (helm-bookmark-woman-bookmark-p bookmark))) (defun helm-bookmark-info-bookmark-p (bookmark) "Return non-nil if BOOKMARK is an Info bookmark. BOOKMARK is a bookmark name or a bookmark record." (eq (bookmark-get-handler bookmark) 'Info-bookmark-jump)) (defun helm-bookmark-image-bookmark-p (bookmark) "Return non-nil if BOOKMARK bookmarks an image file." (if (stringp bookmark) (assq 'image-type (assq bookmark bookmark-alist)) (assq 'image-type bookmark))) (defun helm-bookmark-file-p (bookmark) "Return non-nil if BOOKMARK bookmarks a file or directory. BOOKMARK is a bookmark name or a bookmark record. This excludes bookmarks of a more specific kind (Info, Gnus, and W3m)." (let* ((filename (bookmark-get-filename bookmark)) (isnonfile (equal filename helm-bookmark--non-file-filename))) (and filename (not isnonfile) (not (helm-bookmark-org-file-p bookmark)) (not (bookmark-get-handler bookmark))))) (defun helm-bookmark-org-file-p (bookmark) (let* ((filename (bookmark-get-filename bookmark))) (or (string-suffix-p ".org" filename t) (string-suffix-p ".org_archive" filename t)))) (defun helm-bookmark-helm-find-files-p (bookmark) "Return non-nil if BOOKMARK bookmarks a `helm-find-files' session. BOOKMARK is a bookmark name or a bookmark record." (eq (bookmark-get-handler bookmark) 'helm-ff-bookmark-jump)) (defun helm-bookmark-addressbook-p (bookmark) "Return non--nil if BOOKMARK is a contact recorded with addressbook-bookmark. BOOKMARK is a bookmark name or a bookmark record." (if (listp bookmark) (string= (assoc-default 'type bookmark) "addressbook") (string= (assoc-default 'type (assoc bookmark bookmark-alist)) "addressbook"))) (defun helm-bookmark-uncategorized-bookmark-p (bookmark) "Return non--nil if BOOKMARK match no known category." (cl-loop for pred in '(helm-bookmark-org-file-p helm-bookmark-addressbook-p helm-bookmark-gnus-bookmark-p helm-bookmark-mu4e-bookmark-p helm-bookmark-w3m-bookmark-p helm-bookmark-woman-man-bookmark-p helm-bookmark-info-bookmark-p helm-bookmark-image-bookmark-p helm-bookmark-file-p helm-bookmark-helm-find-files-p helm-bookmark-addressbook-p helm-bookmark-eww-bookmark-p) never (funcall pred bookmark))) (defun helm-bookmark-filter-setup-alist (fn) "Return a filtered `bookmark-alist' sorted alphabetically." (cl-loop for b in (if (and (fboundp 'bookmark-maybe-sort-alist) (eq helm-bookmark-default-sort-method 'native)) (bookmark-maybe-sort-alist) bookmark-alist) for name = (car b) when (funcall fn b) collect (propertize name 'location (bookmark-location name)))) ;;; Bookmark handlers ;; (defvar w3m-async-exec) (defun helm-bookmark-jump-w3m (bookmark) "Jump to W3m bookmark BOOKMARK, setting a new tab. If `browse-url-browser-function' is set to something else than `w3m-browse-url' use it." (require 'helm-net) (let* ((file (or (bookmark-prop-get bookmark 'filename) (bookmark-prop-get bookmark 'url))) (buf (generate-new-buffer-name "*w3m*")) (w3m-async-exec nil) ;; If user don't have anymore w3m installed let it browse its ;; bookmarks with default browser otherwise assume bookmark ;; have been bookmarked from w3m and use w3m. (browse-url-browser-function (or (and (fboundp 'w3m-browse-url) (executable-find "w3m") 'w3m-browse-url) browse-url-browser-function)) (really-use-w3m (equal browse-url-browser-function 'w3m-browse-url))) (helm-browse-url file really-use-w3m) (when really-use-w3m (bookmark-default-handler `("" (buffer . ,buf) . ,(bookmark-get-bookmark-record bookmark)))))) ;; All bookmarks recorded with the handler provided with w3m ;; (`bookmark-w3m-bookmark-jump') will use our handler which open ;; the bookmark in a new tab or in an external browser depending ;; on `browse-url-browser-function'. (defalias 'bookmark-w3m-bookmark-jump #'helm-bookmark-jump-w3m) ;; Provide compatibility with old handlers provided in external ;; packages bookmark-extensions.el and bookmark+. (defalias 'bmkext-jump-woman #'woman-bookmark-jump) (defalias 'bmkext-jump-man #'Man-bookmark-jump) (defalias 'bmkext-jump-w3m #'helm-bookmark-jump-w3m) (defalias 'bmkext-jump-gnus #'gnus-summary-bookmark-jump) (defalias 'bookmarkp-jump-gnus #'gnus-summary-bookmark-jump) (defalias 'bookmarkp-jump-w3m #'helm-bookmark-jump-w3m) (defalias 'bookmarkp-jump-woman #'woman-bookmark-jump) (defalias 'bookmarkp-jump-man #'Man-bookmark-jump) (defalias 'bmkp-jump-gnus #'gnus-summary-bookmark-jump) (defalias 'bmkp-jump-w3m #'helm-bookmark-jump-w3m) (defalias 'bmkp-jump-woman #'woman-bookmark-jump) (defalias 'bmkp-jump-man #'Man-bookmark-jump) ;;;; Filtered bookmark sources ;; ;; (defclass helm-source-filtered-bookmarks (helm-source-in-buffer helm-type-bookmark) ((filtered-candidate-transformer :initform (delq nil `(,(and (eq helm-bookmark-default-sort-method 'adaptive) 'helm-adaptive-sort) helm-highlight-bookmark))) (find-file-target :initform #'helm-bookmarks-quit-an-find-file-fn))) (defun helm-bookmarks-quit-an-find-file-fn (source) (let* ((sel (helm-get-selection nil nil source)) (bmk (assoc (replace-regexp-in-string "\\`\\*" "" sel) bookmark-alist))) (helm-aif (bookmark-get-filename bmk) (if (and helm--url-regexp (string-match helm--url-regexp it)) it (expand-file-name it)) (expand-file-name default-directory)))) (defun helm-bookmark-build-source (name buildfn &optional class &rest args) (apply #'helm-make-source name (or class 'helm-source-filtered-bookmarks) :init (lambda () (bookmark-maybe-load-default-file) (helm-init-candidates-in-buffer 'global (funcall buildfn))) args)) ;;; W3m bookmarks. ;; (defun helm-bookmark-w3m-setup-alist () "Specialized filter function for bookmarks w3m." (helm-bookmark-filter-setup-alist 'helm-bookmark-w3m-bookmark-p)) (defun helm-source-bookmark-w3m-builder () (helm-bookmark-build-source "Bookmark W3m" #'helm-bookmark-w3m-setup-alist)) (defvar helm-source-bookmark-w3m (helm-source-bookmark-w3m-builder)) ;;; Images ;; (defun helm-bookmark-images-setup-alist () "Specialized filter function for images bookmarks." (helm-bookmark-filter-setup-alist 'helm-bookmark-image-bookmark-p)) (defun helm-source-bookmark-images-builder () (helm-bookmark-build-source "Bookmark Images" #'helm-bookmark-images-setup-alist)) (defvar helm-source-bookmark-images (helm-source-bookmark-images-builder)) ;;; EWW bookmarks ;; (defun helm-bookmark-normalize-eww-bmks () (unless eww-bookmarks (eww-read-bookmarks)) (cl-loop for (_url url _title title _time _time) in eww-bookmarks do (unless (bookmark-get-bookmark title t) (push (list title (cons 'location url) '(imported . t) '(handler . eww-bookmark-jump)) bookmark-alist) (cl-incf bookmark-alist-modification-count)))) ;;;###autoload (defun helm-bookmark-import-eww-bookmarks () "Import EWW bookmarks into bookmark-alist." (interactive) (when (y-or-n-p "Really import eww-bookmarks into bookmark-alist?") (bookmark-maybe-load-default-file) (helm-bookmark-normalize-eww-bmks))) (defun helm-bookmark-eww-setup-alist () "Specialized filter function for Eww bookmarks." (helm-bookmark-filter-setup-alist 'helm-bookmark-eww-bookmark-p)) (defun helm-source-bookmark-eww-builder () (helm-bookmark-build-source "Bookmark EWW" #'helm-bookmark-eww-setup-alist)) (defvar helm-source-bookmark-eww (helm-source-bookmark-eww-builder)) ;;; Woman Man ;; (defun helm-bookmark-man-setup-alist () "Specialized filter function for Man pages bookmarks." (helm-bookmark-filter-setup-alist 'helm-bookmark-woman-man-bookmark-p)) (defun helm-source-bookmark-man-builder () (helm-bookmark-build-source "Bookmark Woman&Man" #'helm-bookmark-man-setup-alist)) (defvar helm-source-bookmark-man (helm-source-bookmark-man-builder)) ;;; Org files ;; (defun helm-bookmark-org-setup-alist () "Specialized filter function for Org file bookmarks." (helm-bookmark-filter-setup-alist 'helm-bookmark-org-file-p)) (defun helm-source-bookmark-org-builder () (helm-bookmark-build-source "Bookmark Org files" #'helm-bookmark-org-setup-alist)) (defvar helm-source-bookmark-org (helm-source-bookmark-org-builder)) ;;; Gnus ;; (defun helm-bookmark-gnus-setup-alist () "Specialized filter function for bookmarks gnus." (helm-bookmark-filter-setup-alist 'helm-bookmark-gnus-bookmark-p)) (defun helm-source-bookmark-gnus-builder () (helm-bookmark-build-source "Bookmark Gnus" #'helm-bookmark-gnus-setup-alist)) (defvar helm-source-bookmark-gnus (helm-source-bookmark-gnus-builder)) ;;; Mu4e ;; (defun helm-bookmark-mu4e-setup-alist () (helm-bookmark-filter-setup-alist 'helm-bookmark-mu4e-bookmark-p)) (defun helm-source-bookmark-mu4e-builder () (helm-bookmark-build-source "Bookmark Mu4e" #'helm-bookmark-mu4e-setup-alist)) (defvar helm-source-bookmark-mu4e (helm-source-bookmark-mu4e-builder)) ;;; Info ;; (defun helm-bookmark-info-setup-alist () "Specialized filter function for bookmarks info." (helm-bookmark-filter-setup-alist 'helm-bookmark-info-bookmark-p)) (defun helm-source-bookmark-info-builder () (helm-bookmark-build-source "Bookmark Info" #'helm-bookmark-info-setup-alist)) (defvar helm-source-bookmark-info (helm-source-bookmark-info-builder)) ;;; Files and directories ;; (defun helm-bookmark-local-files-setup-alist () "Specialized filter function for bookmarks locals files." (helm-bookmark-filter-setup-alist 'helm-bookmark-file-p)) (defun helm-source-bookmark-files&dirs-builder () (helm-bookmark-build-source "Bookmark Files&Directories" #'helm-bookmark-local-files-setup-alist)) (defvar helm-source-bookmark-files&dirs (helm-source-bookmark-files&dirs-builder)) ;;; Helm find files sessions. ;; (defun helm-bookmark-helm-find-files-setup-alist () "Specialized filter function for `helm-find-files' bookmarks." (helm-bookmark-filter-setup-alist 'helm-bookmark-helm-find-files-p)) (defun helm-bookmark-browse-project (candidate) "Run `helm-browse-project' from action." (with-helm-default-directory (bookmark-get-filename candidate) (helm-browse-project nil))) (helm-make-command-from-action helm-bookmark-run-browse-project "Run `helm-bookmark-browse-project' from keyboard." 'helm-bookmark-browse-project) (defvar helm-bookmark-find-files-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-bookmark-map) (define-key map (kbd "C-x C-d") #'helm-bookmark-run-browse-project) map)) (defclass helm-bookmark-find-files-class (helm-source-filtered-bookmarks) ()) (cl-defmethod helm--setup-source ((source helm-bookmark-find-files-class)) ;; Ensure `helm-source-in-buffer' method is called. (cl-call-next-method) (setf (slot-value source 'action) (helm-append-at-nth (cl-loop for (name . action) in helm-type-bookmark-actions ;; We don't want those actions in helm-find-files bookmarks. unless (memq action '(helm-bookmark-jump-other-frame helm-bookmark-jump-other-window helm-bookmark-jump-other-tab bookmark-set)) collect (cons name action)) '(("Browse project" . helm-bookmark-browse-project)) 1)) (setf (slot-value source 'keymap) helm-bookmark-find-files-map)) (defun helm-source-bookmark-helm-find-files-builder () (helm-bookmark-build-source "Bookmark helm-find-files sessions" #'helm-bookmark-helm-find-files-setup-alist 'helm-bookmark-find-files-class :persistent-action (lambda (_candidate) (ignore)) :persistent-help "Do nothing")) (defvar helm-source-bookmark-helm-find-files (helm-source-bookmark-helm-find-files-builder)) ;;; Uncategorized bookmarks ;; (defun helm-bookmark-uncategorized-setup-alist () "Specialized filter function for uncategorized bookmarks." (helm-bookmark-filter-setup-alist 'helm-bookmark-uncategorized-bookmark-p)) (defun helm-source-bookmark-uncategorized-builder () (helm-bookmark-build-source "Bookmark uncategorized" #'helm-bookmark-uncategorized-setup-alist)) (defvar helm-source-bookmark-uncategorized (helm-source-bookmark-uncategorized-builder)) ;;; Transformer ;; (defun helm-highlight-bookmark (bookmarks _source) "Used as `filtered-candidate-transformer' to colorize bookmarks." (let ((non-essential t)) (cl-loop for i in bookmarks for isfile = (bookmark-get-filename i) for hff = (helm-bookmark-helm-find-files-p i) for handlerp = (and (fboundp 'bookmark-get-handler) (bookmark-get-handler i)) for isw3m = (and (fboundp 'helm-bookmark-w3m-bookmark-p) (helm-bookmark-w3m-bookmark-p i)) for isgnus = (and (fboundp 'helm-bookmark-gnus-bookmark-p) (helm-bookmark-gnus-bookmark-p i)) for ismu4e = (and (fboundp 'helm-bookmark-mu4e-bookmark-p) (helm-bookmark-mu4e-bookmark-p i)) for isman = (and (fboundp 'helm-bookmark-man-bookmark-p) ; Man (helm-bookmark-man-bookmark-p i)) for iswoman = (and (fboundp 'helm-bookmark-woman-bookmark-p) ; Woman (helm-bookmark-woman-bookmark-p i)) for isannotation = (bookmark-get-annotation i) for isabook = (string= (bookmark-prop-get i 'type) "addressbook") for isinfo = (eq handlerp 'Info-bookmark-jump) for iseww = (eq handlerp 'eww-bookmark-jump) for loc = (bookmark-location i) for len = (string-width i) for trunc = (if (and helm-bookmark-show-location (> len bookmark-bmenu-file-column)) (helm-substring i bookmark-bmenu-file-column) i) for icon = (when helm-bookmark-use-icon (cond ((and isfile hff) (helm-aif (or (helm-x-icons-match-to-alist (helm-basename (helm-basedir isfile t)) 'dir) (helm-x-icons-match-to-alist (helm-basename isfile) 'dir)) (apply (car it) (cdr it)) (helm-x-icons-generic "file-directory"))) ((or isw3m iseww) (helm-x-icons-generic "firefox")) ((and isfile isinfo) (helm-x-icons-generic "info")) ((or iswoman isman) (helm-x-icons-generic "man-page")) ((or isgnus ismu4e) (helm-x-icons-generic "mail-read")) (isfile (helm-x-icons-icon-for-file (helm-basename isfile))))) ;; Add a * if bookmark have annotation if (and isannotation (not (string-equal isannotation ""))) do (setq trunc (concat helm-bookmark-annotation-sign (if helm-bookmark-show-location trunc i))) for sep = (and helm-bookmark-show-location (make-string (- (+ bookmark-bmenu-file-column 2) (string-width trunc)) ? )) for bmk = (cond ( ;; info buffers isinfo (propertize trunc 'face 'helm-bookmark-info 'help-echo isfile)) ( ;; w3m buffers isw3m (propertize trunc 'face 'helm-bookmark-w3m 'help-echo isfile)) ( ;; eww buffers iseww (propertize trunc 'face 'helm-bookmark-w3m 'help-echo isfile)) ( ;; gnus buffers isgnus (propertize trunc 'face 'helm-bookmark-gnus 'help-echo isfile)) ( ;; Man Woman (or iswoman isman) (propertize trunc 'face 'helm-bookmark-man 'help-echo isfile)) ( ;; Addressbook isabook (propertize trunc 'face 'helm-bookmark-addressbook)) (;; Directories (helm-find-files) hff (if (and (file-remote-p isfile) (not (file-remote-p isfile nil t))) (propertize trunc 'face 'helm-bookmark-file-not-found 'help-echo isfile) (propertize trunc 'face 'helm-bookmark-directory 'help-echo isfile))) ( ;; Directories (dired) (and isfile ;; This is needed because `non-essential' ;; is not working on Emacs-24.2 and the behavior ;; of tramp seems to have changed since previous ;; versions (Need to reenter password even if a ;; first connection have been established, ;; probably when host is named differently ;; i.e machine/localhost) (and (not (file-remote-p isfile)) (file-directory-p isfile))) (propertize trunc 'face 'helm-bookmark-directory 'help-echo isfile)) ( ;; Non existing files. (and isfile ;; Be safe and call `file-exists-p' ;; only if file is not remote or ;; remote but connected. (or (and (file-remote-p isfile) (not (file-remote-p isfile nil t))) (not (file-exists-p isfile)))) (propertize trunc 'face 'helm-bookmark-file-not-found 'help-echo isfile)) ( ;; regular files t (propertize trunc 'face 'helm-bookmark-file 'help-echo isfile))) collect (if helm-bookmark-show-location (cons (concat (and icon (propertize " " 'display (concat icon " "))) bmk (propertize " " 'display (concat sep (if (listp loc) (car loc) loc)))) i) (cons (concat (and icon (propertize " " 'display (concat icon " "))) bmk) i))))) ;;; Edit/rename/save bookmarks. ;; ;; (defun helm-bookmark-edit-bookmark (bookmark-name) "Edit bookmark's name and file name, and maybe save them. BOOKMARK-NAME is the current (old) name of the bookmark to be renamed." (let ((bmk (helm-bookmark-get-bookmark-from-name bookmark-name)) (handler (bookmark-prop-get bookmark-name 'handler))) (if (eq handler 'addressbook-bookmark-jump) (addressbook-bookmark-edit (assoc bmk bookmark-alist)) (helm-bookmark-edit-bookmark-1 bookmark-name handler)))) (defun helm-bookmark-edit-bookmark-1 (bookmark-name handler) (let* ((helm--reading-passwd-or-string t) (bookmark-fname (bookmark-get-filename bookmark-name)) (bookmark-loc (bookmark-prop-get bookmark-name 'location)) (message-id (bookmark-prop-get bookmark-name 'message-id)) (new-name (read-from-minibuffer "Name: " bookmark-name)) (new-loc (and (or bookmark-fname bookmark-loc) (read-from-minibuffer "FileName or Location: " (or bookmark-fname (if (consp bookmark-loc) (car bookmark-loc) bookmark-loc))))) (new-message-id (and (memq handler '(mu4e--jump-to-bookmark mu4e-bookmark-jump)) (read-string "Message-id: " message-id)))) (when (and (not (equal new-name "")) (or (not (equal new-loc "")) (not (equal new-message-id ""))) (y-or-n-p "Save changes? ")) (if bookmark-fname (progn (helm-bookmark-rename bookmark-name new-name 'batch) (bookmark-set-filename new-name new-loc)) (bookmark-prop-set (bookmark-get-bookmark bookmark-name) (cond (new-loc 'location) (new-message-id 'message-id)) (or new-loc new-message-id)) (helm-bookmark-rename bookmark-name new-name 'batch)) (helm-bookmark-maybe-save-bookmark) (list new-name new-loc)))) (defun helm-bookmark-maybe-save-bookmark () "Increment save counter and maybe save `bookmark-alist'." (setq bookmark-alist-modification-count (1+ bookmark-alist-modification-count)) (when (bookmark-time-to-save-p) (bookmark-save))) (defun helm-bookmark-rename (old &optional new _batch) "Change bookmark's name from OLD to NEW. If NEW is nil, then prompt for its string value. Unused arg _BATCH is kept for backward compatibility. While the user enters the new name, repeated `C-w' inserts consecutive words from the buffer into the new bookmark name." (bookmark-maybe-historicize-string old) (bookmark-maybe-load-default-file) (save-excursion (skip-chars-forward " ") (setq bookmark-yank-point (point))) (setq bookmark-current-buffer (current-buffer)) (catch 'skip (let ((newname (or new (read-from-minibuffer ;; `format-prompt' is not available in old Emacs. (format "New name [C-RET to skip] (default %s): " old) nil (let ((now-map (copy-keymap minibuffer-local-map))) (define-key now-map "\C-w" #'bookmark-yank-word) (define-key now-map (kbd "C-") #'(lambda () (interactive) (throw 'skip 'skip))) now-map) nil 'bookmark-history old)))) (bookmark-set-name old newname) (helm-bookmark-maybe-save-bookmark) newname))) (defun helm-bookmark-rename-marked (_candidate) "Rename marked bookmarks." (let* ((bmks (helm-marked-candidates)) (count 0) (len (length bmks))) (cl-loop for bmk in bmks unless (eq (helm-bookmark-rename bmk) 'skip) do (cl-incf count)) (message "(%s/%s) bookmark(s) renamed" count len))) (helm-make-command-from-action helm-bookmark-run-edit "Run `helm-bookmark-edit-bookmark' from keyboard." 'helm-bookmark-edit-bookmark) (helm-make-command-from-action helm-bookmark-run-jump-other-frame "Jump to bookmark other frame from keyboard." 'helm-bookmark-jump-other-frame) (helm-make-command-from-action helm-bookmark-run-jump-other-window "Jump to bookmark other window from keyboard." 'helm-bookmark-jump-other-window) (helm-make-command-from-action helm-bookmark-run-jump-other-tab "Jump to bookmark other tab from keyboard." 'helm-bookmark-jump-other-tab) (helm-make-command-from-action helm-bookmark-run-delete "Delete bookmark from keyboard." 'helm-delete-marked-bookmarks) (defun helm-bookmark-get-bookmark-from-name (bmk) "Return bookmark name even if it is a bookmark with annotation. E.g. prepended with *." (let ((bookmark (replace-regexp-in-string "\\`\\*" "" bmk))) (if (assoc bookmark bookmark-alist) bookmark bmk))) (defun helm-delete-marked-bookmarks (_ignore) "Delete this bookmark or all marked bookmarks." (dolist (i (helm-marked-candidates)) (bookmark-delete (helm-bookmark-get-bookmark-from-name i) 'batch))) (defun helm-bookmark-get-defaults () "Get default bookmark names at point for `bookmark-set'." (let* (bookmark-current-bookmark no-defaults (record (condition-case _err (bookmark-make-record) (error (setq no-defaults t))))) ;; Not sure `bookmark-make-record' set 'defaults prop in older Emacs. (or (bookmark-prop-get record 'defaults) (unless no-defaults (list (buffer-name helm-current-buffer)))))) ;;; bookmark annotations ;; (defun helm-bookmark-show-annotation (bookmark-name-or-record) "Display the annotation for BOOKMARK-NAME-OR-RECORD in a buffer." (let ((annotation (bookmark-get-annotation bookmark-name-or-record))) (when (and annotation (not (string-equal annotation ""))) (let ((buf (get-buffer-create "*Bookmark Annotation*"))) (with-current-buffer buf (let ((inhibit-read-only t)) (erase-buffer) (insert annotation) (goto-char (point-min)) (set-buffer-modified-p nil) (helm-bookmark-annotation-mode) (insert (substitute-command-keys "# Edit this buffer with \\[helm-bookmark-edit-annotation]") (substitute-command-keys "\n# Quit this buffer with \\[helm-bookmark-quit-annotation]\n")) (set (make-local-variable 'bookmark-annotation-name) bookmark-name-or-record) (put 'bookmark-annotation-name 'permanent-local t))) (pop-to-buffer buf))))) (defun helm-bookmark-edit-annotation () "Edit bookmark annotation from the show annotation buffer." (interactive) (setq buffer-read-only nil) (bookmark-edit-annotation-mode) (save-excursion (goto-char (point-min)) (delete-region (point) (save-excursion (forward-line 2) (point))) (insert (funcall bookmark-edit-annotation-text-func bookmark-annotation-name)))) (put 'helm-bookmark-edit-annotation 'no-helm-mx t) (defun helm-bookmark-quit-annotation () "Quit bookmark annotation buffer." (interactive) (quit-window t)) (put 'helm-bookmark-quit-annotation 'no-helm-mx t) (defvar helm-bookmark-annotation-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map text-mode-map) (define-key map (kbd "q") #'helm-bookmark-quit-annotation) (define-key map (kbd "e") #'helm-bookmark-edit-annotation) map) "Map used in show annotation bookmark buffer.") (define-derived-mode helm-bookmark-annotation-mode text-mode "helm-annotation-mode" "Mode to display bookmark annotations. Special commands: \\{helm-bookmark-annotation-mode-map}" :interactive nil (setq-local buffer-read-only t)) ;;;###autoload (defun helm-bookmarks () "Preconfigured `helm' for bookmarks." (interactive) (helm :sources '(helm-source-bookmarks helm-source-bookmark-set) :buffer "*helm bookmarks*" :default (buffer-name helm-current-buffer))) ;;;###autoload (defun helm-filtered-bookmarks () "Preconfigured `helm' for bookmarks (filtered by category). Optional source `helm-source-bookmark-addressbook' is loaded only if external addressbook-bookmark package is installed." (interactive) (helm :sources helm-bookmark-default-filtered-sources :prompt "Search Bookmark: " :buffer "*helm filtered bookmarks*" :default (helm-bookmark-get-defaults))) (provide 'helm-bookmark) ;;; helm-bookmark.el ends here helm-4.0.3/helm-buffers.el000066400000000000000000001462651501106761700153660ustar00rootroot00000000000000;;; helm-buffers.el --- helm support for buffers. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-types) (require 'helm-utils) (require 'helm-grep) (require 'helm-regexp) (require 'helm-help) (require 'helm-occur) (require 'helm-x-icons) (declare-function helm-comp-read "helm-mode") (declare-function helm-browse-project "helm-files") (declare-function helm-ff-switch-to-shell "helm-files") (defvar dired-buffers) (defvar org-directory) (defvar helm-ff-default-directory) (defvar major-mode-remap-alist) (defgroup helm-buffers nil "Buffers related Applications and libraries for Helm." :group 'helm) (defcustom helm-boring-buffer-regexp-list '("\\` " "\\`\\*helm" "\\`\\*Echo Area" "\\`\\*Minibuf") "The regexp list that match boring buffers. Buffer candidates matching these regular expression will be filtered from the list of candidates if the `helm-skip-boring-buffers' candidate transformer is used." :type '(repeat (choice regexp))) (defcustom helm-white-buffer-regexp-list nil "The regexp list of not boring buffers. These buffers will be displayed even if they match one of `helm-boring-buffer-regexp-list'." :type '(repeat (choice regexp))) (defcustom helm-buffers-favorite-modes '(lisp-interaction-mode emacs-lisp-mode text-mode org-mode) "List of preferred mode to open new buffers with." :type '(repeat (choice function))) (defcustom helm-buffer-max-length 20 "Max length of buffer names before truncate. When disabled (nil) use the longest `buffer-name' length found." :type '(choice (const :tag "Disabled" nil) (integer :tag "Length before truncate"))) (defcustom helm-buffer-details-flag t "Always show details in buffer list when non-nil." :type 'boolean) (defcustom helm-buffers-fuzzy-matching nil "Fuzzy matching buffer names when non-nil. Only buffer names are fuzzy matched when this is enabled, `major-mode' matching is not affected by this." :type 'boolean) (defcustom helm-buffer-skip-remote-checking nil "Ignore checking for `file-exists-p' on remote files." :type 'boolean) (defcustom helm-buffers-truncate-lines t "Truncate lines in `helm-buffers-list' when non-nil." :type 'boolean) (defcustom helm-buffers-left-margin-width helm-left-margin-width "`left-margin-width' value for `helm-mini' and `helm-buffers-list'." :type 'integer) (defcustom helm-mini-default-sources '(helm-source-buffers-list helm-source-recentf helm-source-buffer-not-found) "Default sources list used in `helm-mini'. When adding a source here it is up to you to ensure the library of this source is accessible and properly loaded." :type '(repeat (choice symbol))) (defcustom helm-buffers-end-truncated-string ;; `truncate-string-ellipsis', the function is not available in 27.1 ;; See issue#2673. (if (char-displayable-p ?…) "…" "...") "The string to display at end of truncated buffer names." :type 'string) (defcustom helm-buffers-column-separator " " "Separator for columns in buffer listing." :type 'string) (defcustom helm-buffer--pretty-names '((dired-mode . "Dired") (lisp-interaction-mode . "Lisp Inter")) "An alist specifying pretty names for modes. Most of the time buffer's `mode-name' is a string so no need to add it here as there is no need to compute it, but sometimes it may be a mode-line specification which may be costly to compute, in this case add here the pretty name as a string to avoid this costly computation. Also if some pretty names are too long you can add your own abbreviation here." :type '(alist :key-type symbol :value-type string)) (defcustom helm-buffers-maybe-switch-to-tab nil "Switch to buffer in its tab when non nil. Setting this change `tab-bar-tab-name-function' to `tab-bar-tab-name-all'. Do not use `setq' to set this variable. This variable takes effect only when `tab-bar-mode' is available (emacs-27.1+)." :type 'boolean :set (lambda (var val) ;; We should be able to retrieve all buffers assigned to a tab whatever ;; the value used for `tab-bar-tab-name-function', unfortunately this ;; is not the case, it seems the alist contains the buffer names only ;; when `tab-bar-tab-name-all' is used and set globally. Then when the ;; mode-line/header-line is rebuilded some code (probably C code in ;; `force-mode-line-update' or elsewhare) changes the alist so just ;; let-binding `tab-bar-tab-name-function' is not enough. This is ;; reproductible when we have more than one window visible and we turn ;; on `tab-bar-mode', the alist is showing only the first buffer of ;; window-list omitting the others, however when starting with only one ;; window, calling `tab-bar-mode' and splitting window afterward the ;; alist is updated. Looks it is a bug or a limitation of ;; `tab-bar-mode'. (set var val) (if val (customize-set-variable 'tab-bar-tab-name-function #'tab-bar-tab-name-all) (let* ((sym 'tab-bar-tab-name-function) (standard-value (eval (car (get sym 'standard-value)) t))) (unless (equal standard-value (symbol-value sym)) (set sym standard-value)))))) (defcustom helm-buffer-list-reorder-fn #'helm-buffers-reorder-buffer-list "A function in charge of ordering the initial buffer list. It takes two arguments VISIBLES buffers and OTHERS buffers. Arg VISIBLES handles the buffers visibles in this frame. Arg OTHERS handles all the other buffers. You can write a function that reorder VISIBLES and OTHERS as you want. Default function returns OTHERS buffers on top and VISIBLES buffer at the end. See `helm-buffers-reorder-buffer-list'." :type 'function) (defcustom helm-buffers-sort-fn helm-fuzzy-sort-fn "The sort function to use in `helm-buffers-list'. Default to `helm-fuzzy-sort-fn' you can use `helm-fuzzy-matching-sort-fn-preserve-ties-order' as alternative if you want to keep the recentest order when narrowing candidates." :type 'function) (defcustom helm-buffers-show-icons nil "Prefix buffer names with an icon when non nil. Don't use `setq' to set this." :type 'boolean :set (lambda (var val) (if (require helm-x-icons-provider nil t) (set var val) (set var nil)))) ;;; Faces ;; ;; (defgroup helm-buffers-faces nil "Customize the appearance of helm-buffers." :prefix "helm-" :group 'helm-buffers :group 'helm-faces) (defface helm-buffer-saved-out `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "red" :background "black")) "Face used for buffer files modified outside of emacs." :group 'helm-buffers-faces) (defface helm-buffer-not-saved `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Indianred2")) "Face used for buffer files not already saved on disk." :group 'helm-buffers-faces) (defface helm-buffer-modified `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit font-lock-comment-face)) "Face used for modified buffers." :group 'helm-buffers-faces) (defface helm-no-file-buffer-modified `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "orange" :background "black")) "Face used for modified buffers." :group 'helm-buffers-faces) (defface helm-indirect-buffer `((t :foreground "DimGray" :background "black")) "Face used for indirect buffers." :group 'helm-buffers-faces) (defface helm-buffer-size `((((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "RosyBrown") (((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "SlateGray")) "Face used for buffer size." :group 'helm-buffers-faces) (defface helm-buffer-process `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Sienna3")) "Face used for process status in buffer." :group 'helm-buffers-faces) (defface helm-buffer-directory `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "DarkRed" :background "LightGray")) "Face used for directories in `helm-buffers-list'." :group 'helm-buffers-faces) (defface helm-buffer-file `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit font-lock-builtin-face)) "Face for buffer file names in `helm-buffers-list'." :group 'helm-buffers-faces) (defface helm-buffer-archive `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Gold")) "Face for archive file names in `helm-buffers-list'." :group 'helm-buffers-faces) (defface helm-non-file-buffer `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit italic)) "Face used for non-file buffers in `helm-buffers-list'." :group 'helm-buffers-faces) (defvar helm-buffers-tick-counter nil "Allows recording local changes to a non-file buffer. Typical usage of this var is for modes that want to see if their buffers have changed since last visit. Such programs may want to record tick counter after visiting their buffers like this: (setq helm-buffers-tick-counter (buffer-modified-tick)) See bug#1917. Note that this variable is buffer-local.") (make-variable-buffer-local 'helm-buffers-tick-counter) ;;; Buffers keymap ;; (defvar helm-buffer-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) ;; No need to have separate command for grep and zgrep ;; as we don't use recursivity for buffers. ;; So use zgrep for both as it is capable to handle non--compressed files. (define-key map (kbd "M-g s") #'helm-buffer-run-zgrep) (define-key map (kbd "C-s") #'helm-buffers-run-occur) (define-key map (kbd "C-x C-d") #'helm-buffers-run-browse-project) (define-key map (kbd "C-c o") #'helm-buffer-switch-other-window) (define-key map (kbd "C-c C-o") #'helm-buffer-switch-other-frame) (define-key map (kbd "M-g M-g") #'helm-buffer-run-goto-line) (define-key map (kbd "C-c =") #'helm-buffer-run-ediff) (define-key map (kbd "M-=") #'helm-buffer-run-ediff-merge) (define-key map (kbd "C-=") #'helm-buffer-diff-persistent) (define-key map (kbd "M-G") #'helm-buffer-revert-persistent) (define-key map (kbd "C-c d") #'helm-buffer-run-kill-persistent) (define-key map (kbd "M-D") #'helm-buffer-run-kill-buffers) (define-key map (kbd "C-x C-s") #'helm-buffer-save-persistent) (define-key map (kbd "C-x s") #'helm-buffer-run-save-some-buffers) (define-key map (kbd "C-M-%") #'helm-buffer-run-query-replace-regexp) (define-key map (kbd "M-%") #'helm-buffer-run-query-replace) (define-key map (kbd "M-R") #'helm-buffer-run-rename-buffer) (define-key map (kbd "M-e") #'helm-buffer-run-switch-to-shell) (define-key map (kbd "C-]") #'helm-toggle-buffers-details) (define-key map (kbd "C-c a") #'helm-buffers-toggle-show-hidden-buffers) (define-key map (kbd "C-M-SPC") #'helm-buffers-mark-similar-buffers) (when (fboundp 'tab-bar-mode) (define-key map (kbd "C-c C-t") #'helm-buffers-switch-to-buffer-new-tab)) map) "Keymap for buffer sources in helm.") (defvar helm-buffer-max-len-mode nil) (defvar helm-buffers-in-project-p nil) (defvar helm-source-buffers-list nil) (defun helm-buffers-list--init () (require 'dired) ;; Bug#51 Create the list before `helm-buffer' creation. ;; We were using a global cache in the past and 'candidates was ;; bound to this cache, this was a problem when using more than one ;; source with a different 'buffer-list fn as the same cache was ;; reused in each source (Bug#1907), now 'candidates attr is set ;; directly so that each list of candidates is local to source. (helm-set-attr 'candidates (funcall (helm-get-attr 'buffer-list))) (let ((result (cl-loop with allbufs = (memq 'helm-shadow-boring-buffers (helm-get-attr 'filtered-candidate-transformer helm-source-buffers-list)) for b in (if allbufs (helm-get-attr 'candidates) (helm-skip-boring-buffers (helm-get-attr 'candidates) helm-source-buffers-list)) maximize (length b) into len-buf maximize (length (helm-buffer--format-mode-name b)) into len-mode finally return (cons len-buf len-mode)))) (unless (default-value 'helm-buffer-max-length) (helm-set-local-variable 'helm-buffer-max-length (car result))) (unless (default-value 'helm-buffer-max-len-mode) (helm-set-local-variable 'helm-buffer-max-len-mode (cdr result))))) (defclass helm-source-buffers (helm-source-sync helm-type-buffer) ((buffer-list :initarg :buffer-list :initform #'helm-buffer-list :custom function :documentation " A function with no arguments to create buffer list.") (init :initform 'helm-buffers-list--init) (multimatch :initform nil) (match :initform 'helm-buffers-match-function) (persistent-action :initform 'helm-buffers-list-persistent-action) (keymap :initform 'helm-buffer-map) (find-file-target :initform #'helm-buffers-quit-and-find-file-fn) (migemo :initform 'nomultimatch) (volatile :initform t) (nohighlight :initform t) (resume :initform (lambda () (setq helm-buffers-in-project-p nil))) (help-message :initform 'helm-buffer-help-message))) (cl-defun helm-buffers-create-new-buffer-1 (candidate &optional (display-func 'switch-to-buffer)) (let ((mjm (or (and helm-current-prefix-arg (intern-soft (helm-comp-read "Major-mode: " helm-buffers-favorite-modes))) (cl-loop for (r . m) in auto-mode-alist when (string-match r candidate) return m))) (buffer (get-buffer-create candidate))) (helm-aif (and (boundp 'major-mode-remap-alist) (cdr (assq mjm major-mode-remap-alist))) (setq mjm it)) (if mjm (with-current-buffer buffer (funcall mjm)) (set-buffer-major-mode buffer)) (funcall display-func buffer))) (defun helm-buffers-create-new-buffer (candidate) (helm-buffers-create-new-buffer-1 candidate)) (defun helm-buffers-create-new-buffer-ow (candidate) (helm-buffers-create-new-buffer-1 candidate 'switch-to-buffer-other-window)) (helm-make-command-from-action helm-buffers-not-found-run-switch-ow "Run create new buffer other window action from keymap." 'helm-buffers-create-new-buffer-ow) (defun helm-buffers-create-new-buffer-of (candidate) (helm-buffers-create-new-buffer-1 candidate 'switch-to-buffer-other-frame)) (helm-make-command-from-action helm-buffers-not-found-run-switch-of "Run create new buffer other frame action from keymap." 'helm-buffers-create-new-buffer-of) (defvar helm-buffer-not-found-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "C-c o") #'helm-buffers-not-found-run-switch-ow) (define-key map (kbd "C-c C-o") #'helm-buffers-not-found-run-switch-of) map) "Keymap for `helm-source-buffer-not-found' source.") (defvar helm-source-buffer-not-found (helm-build-dummy-source "Create buffer" :action (helm-make-actions "Create buffer (C-u choose mode)" #'helm-buffers-create-new-buffer "Create buffer other window (C-u choose mode)" #'helm-buffers-create-new-buffer-ow "Create buffer other frame (C-u choose mode)" #'helm-buffers-create-new-buffer-of) :keymap helm-buffer-not-found-map)) (defun helm-buffers-get-visible-buffers () "Returns buffers visible on visible frames." (let (result) (walk-windows (lambda (x) (push (buffer-name (window-buffer x)) result)) nil 'visible) result)) (defun helm-buffer-list-1 (&optional visibles) "Return list of all buffers except VISIBLES buffers." (cl-loop for b in (buffer-list) for bn = (buffer-name b) unless (member bn visibles) collect bn)) (defun helm-buffers-reorder-buffer-list (visibles others) "Default function to reorder buffer-list. Arg VISIBLES handles the buffers visibles in this frame. Arg OTHERS handles all the other buffers. This function returns OTHERS buffers on top and VISIBLES buffer at the end." (nconc others visibles)) (defun helm-buffer-list () "Return the current list of buffers. The list is reordered with `helm-buffer-list-reorder-fn'." (let* ((visibles (helm-buffers-get-visible-buffers)) (others (helm-buffer-list-1 visibles))) (funcall helm-buffer-list-reorder-fn visibles others))) (defun helm-buffer-size (buffer) "Return size of BUFFER." (with-current-buffer buffer (save-restriction (widen) (helm-file-human-size (- (position-bytes (point-max)) (position-bytes (point-min))))))) (defun helm-buffer--show-details (buf-name prefix help-echo size mode dir face1 face2 proc details type) (append (list (let* ((buf-fname (buffer-file-name (get-buffer buf-name))) (ext (if buf-fname (helm-file-name-extension buf-fname) "")) (bmode (with-current-buffer buf-name major-mode)) (icon-alist (helm-x-icons-resolve-alist 'mode)) (icon (when helm-buffers-show-icons (helm-aif (and (not (eq type 'indirect)) (assq bmode icon-alist)) (and helm-x-icons-provider (apply (cadr it) (cddr it))) (cond ((eq type 'dired) (helm-x-icons-generic "file-directory")) ((eq type 'indirect) (helm-x-icons-generic "clone")) (buf-fname (helm-x-icons-icon-for-file buf-name)) (t (helm-x-icons-generic "star" :v-adjust 0.0)))))) (buf-name (propertize buf-name 'face face1 'help-echo help-echo 'type type))) (when (condition-case _err (string-match (format "\\.\\(%s\\)" ext) buf-name) (invalid-regexp nil)) (add-face-text-property (match-beginning 1) (match-end 1) 'helm-ff-file-extension nil buf-name)) (if icon (concat icon " " prefix buf-name) (concat prefix buf-name)))) (and details (list size mode (propertize (if proc (format "(%s %s in `%s')" (process-name proc) (process-status proc) dir) (format "`%s'" dir)) 'face face2))))) (defun helm-buffer--format-mode-name (buf) "Prevent using `format-mode-line' as much as possible." (with-current-buffer buf (helm-acond ((assq major-mode helm-buffer--pretty-names) (cdr it)) ((stringp mode-name) mode-name) (t (format-mode-line mode-name nil nil (get-buffer buf)))))) (defun helm-buffer--details (buffer &optional details) (require 'dired) (let* ((mode (helm-buffer--format-mode-name buffer)) (buf (get-buffer buffer)) (size (propertize (helm-buffer-size buf) 'face 'helm-buffer-size)) (proc (get-buffer-process buf)) (dir (with-current-buffer buffer (helm-aif default-directory (abbreviate-file-name it)))) (file-name (helm-aif (buffer-file-name buf) (abbreviate-file-name it))) (name (buffer-name buf)) (name-prefix (when (and dir (file-remote-p dir)) (propertize "@ " 'face 'helm-ff-prefix))) (archive-p (and (fboundp 'tramp-archive-file-name-p) (tramp-archive-file-name-p dir)))) (when name-prefix ;; Remote tramp buffer names may be hexified, make them more readable. (setq dir (helm-url-unhex-string dir) name (helm-url-unhex-string name))) ;; Handle tramp archive buffers specially. (if archive-p (helm-buffer--show-details name name-prefix file-name size mode dir 'helm-buffer-archive 'helm-buffer-process nil details 'filebuf) ;; No fancy things on remote buffers. (if (and name-prefix helm-buffer-skip-remote-checking) (helm-buffer--show-details name name-prefix file-name size mode dir 'helm-buffer-file 'helm-buffer-process nil details 'filebuf) (helm-acond (;; A dired buffer. (rassoc buf dired-buffers) (helm-buffer--show-details name name-prefix dir size mode dir 'helm-buffer-directory 'helm-buffer-process nil details 'dired)) ;; A buffer file modified somewhere outside of emacs.=>red ((and file-name (file-exists-p file-name) (not (verify-visited-file-modtime buf))) (helm-buffer--show-details name name-prefix file-name size mode dir 'helm-buffer-saved-out 'helm-buffer-process nil details 'modout)) ;; A new buffer file not already saved on disk (or a deleted file) .=>indianred2 ((and file-name (not (file-exists-p file-name))) (helm-buffer--show-details name name-prefix file-name size mode dir 'helm-buffer-not-saved 'helm-buffer-process nil details 'notsaved)) ;; A buffer file modified and not saved on disk.=>orange ((and file-name (buffer-modified-p buf)) (helm-buffer--show-details name name-prefix file-name size mode dir 'helm-buffer-modified 'helm-buffer-process nil details 'mod)) ;; A buffer file not modified and saved on disk.=>green (file-name (helm-buffer--show-details name name-prefix file-name size mode dir 'helm-buffer-file 'helm-buffer-process nil details 'filebuf)) ;; Indirect buffer.=>DimGray ((buffer-base-buffer buf) (let ((face (if (or (buffer-modified-p it) (with-current-buffer it (and helm-buffers-tick-counter (/= helm-buffers-tick-counter (buffer-modified-tick))))) 'helm-buffer-modified 'helm-indirect-buffer))) (helm-buffer--show-details name name-prefix dir size mode dir face 'helm-buffer-process nil details 'indirect))) ;; A non-file, modified buffer See bug#1917 ((with-current-buffer name (and helm-buffers-tick-counter (/= helm-buffers-tick-counter (buffer-modified-tick)))) (helm-buffer--show-details name (and proc name-prefix) dir size mode dir 'helm-no-file-buffer-modified 'helm-buffer-process proc details 'nofile-mod)) ;; Any non--file buffer.=>italic (t (helm-buffer--show-details name (and proc name-prefix) dir size mode dir 'helm-non-file-buffer 'helm-buffer-process proc details 'nofile))))))) (defun helm-highlight-buffers (buffers _source) "Transformer function to highlight BUFFERS list. Should be called after others transformers i.e. (boring buffers)." (cl-assert helm-fuzzy-matching-highlight-fn nil "Wrong type argument functionp: nil") (cl-loop for i in buffers for (name size mode meta) = (if helm-buffer-details-flag (helm-buffer--details i 'details) (helm-buffer--details i)) for truncbuf = (if (> (string-width name) helm-buffer-max-length) (helm-substring-by-width name helm-buffer-max-length helm-buffers-end-truncated-string) (concat name (make-string (- (+ helm-buffer-max-length (length helm-buffers-end-truncated-string)) (string-width name)) ? ))) for len = (length mode) when (> len helm-buffer-max-len-mode) do (setq helm-buffer-max-len-mode len) for fmode = (concat (make-string (- (max helm-buffer-max-len-mode len) len) ? ) mode) ;; The max length of a number should be 1023.9X where X is the ;; units, this is 7 characters. for formatted-size = (and size (format "%7s" size)) collect (let ((helm-pattern (helm-buffers--pattern-sans-filters (and helm-buffers-fuzzy-matching "")))) (cons (if helm-buffer-details-flag (concat (funcall helm-fuzzy-matching-highlight-fn truncbuf) helm-buffers-column-separator formatted-size helm-buffers-column-separator fmode helm-buffers-column-separator meta) (funcall helm-fuzzy-matching-highlight-fn name)) (get-buffer i))))) (defun helm-buffer--get-preselection (buffer) (let* ((bufname (buffer-name buffer)) (dispbuf (car (helm-buffer--details buffer))) (len-dispbuf (string-width dispbuf)) (len-prefix (- len-dispbuf (string-width bufname)))) (when (and bufname (file-remote-p (with-current-buffer bufname default-directory))) (setq bufname (concat "@ " (helm-url-unhex-string bufname)))) (concat "^[[:multibyte:] ]*" (if (and (null helm-buffer-details-flag) (numberp helm-buffer-max-length) (> len-dispbuf helm-buffer-max-length)) (regexp-quote (helm-substring-by-width bufname (- helm-buffer-max-length len-prefix) helm-buffers-end-truncated-string)) (concat (regexp-quote bufname) (if helm-buffer-details-flag "$" "[[:blank:]]+")))))) (defun helm-toggle-buffers-details () (interactive) (with-helm-alive-p (let* ((buf (helm-get-selection)) (preselect (helm-buffer--get-preselection buf))) (setq helm-buffer-details-flag (not helm-buffer-details-flag)) (helm-force-update (lambda () (helm-awhile (re-search-forward preselect nil t) (helm-mark-current-line) (when (equal buf (helm-get-selection)) (cl-return t)))))))) (put 'helm-toggle-buffers-details 'helm-only t) (defun helm-buffers--pattern-sans-filters (&optional separator) (cl-loop for p in (helm-mm-split-pattern helm-pattern) unless (member (substring p 0 1) '("*" "/" "@" "!")) collect p into lst finally return (mapconcat #'identity lst (or separator " ")))) (defun helm-buffers-sort-transformer (candidates source) (cl-assert helm-buffers-sort-fn nil "Wrong type argument functionp: nil") (if (string= helm-pattern "") candidates (let ((helm-pattern (helm-buffers--pattern-sans-filters))) (funcall helm-buffers-sort-fn candidates source)))) (defun helm-buffers-mark-similar-buffers-1 (&optional type) (with-helm-window (let* ((src (helm-get-current-source)) (sel (helm-get-selection nil 'withprop src)) (type (or type (get-text-property (min 2 (length sel)) 'type sel)))) (helm-map-candidates-in-source src (lambda (_cand) (helm-make-visible-mark)) (lambda (cand) (and (not (helm-this-visible-mark)) (eq (get-text-property 2 'type cand) type)))) (helm-mark-current-line) (helm-display-mode-line src t) (when helm-marked-candidates (message "%s candidates marked" (length helm-marked-candidates)) (set-window-margins (selected-window) 1))))) (defun helm-buffers-mark-similar-buffers () "Mark All buffers that have same property `type' than current. I.e. same color." (interactive) (with-helm-alive-p (let ((marked (helm-marked-candidates))) (if (and (>= (length marked) 1) (with-helm-window helm-visible-mark-overlays)) (helm-unmark-all) (helm-buffers-mark-similar-buffers-1))))) (put 'helm-buffers-mark-similar-buffers 'helm-only t) ;;; match functions ;; (defun helm-buffer--match-mjm (pattern mjm) (when (string-match "\\`\\*" pattern) (cl-loop with patterns = (split-string (substring pattern 1) ",") for pat in patterns if (string-match "\\`!" pat) collect (string-match (substring pat 1) mjm) into neg else collect (string-match pat mjm) into pos finally return (let ((neg-test (cl-loop for i in neg thereis (numberp i))) (pos-test (cl-loop for i in pos thereis (numberp i)))) (or (and neg (not pos) (not neg-test)) (and pos pos-test) (and neg neg-test (not neg-test))))))) (defvar helm-buffer--memo-hash (make-hash-table :test 'equal)) (defun helm-buffer--memo-pattern (pattern) (or (gethash pattern helm-buffer--memo-hash) (puthash pattern (helm--mapconcat-pattern pattern) helm-buffer--memo-hash))) (defun helm-buffer--match-pattern (pattern candidate &optional nofuzzy) (let ((bfn (if (and helm-buffers-fuzzy-matching (not nofuzzy) (not helm-migemo-mode) (not (string-match "\\`\\^" pattern))) #'helm-buffer--memo-pattern #'identity)) (mfn (if helm-migemo-mode #'helm-mm-migemo-string-match #'string-match))) (if (string-match "\\`!" pattern) (not (funcall mfn (funcall bfn (substring pattern 1)) candidate)) (funcall mfn (funcall bfn pattern) candidate)))) (defun helm-buffers--match-from-mjm (candidate) (let* ((cand (replace-regexp-in-string "^\\s-\\{1\\}" "" candidate)) (buf (get-buffer cand)) (regexp (cl-loop with pattern = helm-pattern for p in (helm-mm-split-pattern pattern) when (string-match "\\`\\*" p) return p))) (if regexp (when buf (with-current-buffer buf (let ((mjm (symbol-name major-mode))) (helm-buffer--match-mjm regexp mjm)))) t))) (defun helm-buffers--match-from-pat (candidate) (let* ((regexp-list (cl-loop with pattern = helm-pattern for p in (helm-mm-split-pattern pattern) unless (string-match "\\`\\(\\*\\|/\\|@\\)" p) collect p)) (nofuzzy (cdr regexp-list))) (if regexp-list (cl-loop for re in regexp-list always (helm-buffer--match-pattern re candidate nofuzzy)) t))) (defun helm-buffers--match-from-inside (candidate) (let* ((cand (replace-regexp-in-string "^\\s-\\{1\\}" "" candidate)) (buf (get-buffer cand)) (pattern (cl-loop with pat = helm-pattern for p in (helm-mm-split-pattern pat) when (string-match "\\`@\\(.*\\)" p) collect (match-string 1 p) into lst finally return (mapconcat #'identity lst " "))) (patterns (helm-mm-3-get-patterns pattern))) (if (and buf patterns) (with-current-buffer buf (save-excursion (goto-char (point-min)) (cl-loop for (pred . regexp) in patterns always (save-excursion (funcall pred (if helm-migemo-mode (helm-mm-migemo-forward regexp nil t) (re-search-forward regexp nil t))))))) t))) (defun helm-buffers--match-from-directory (candidate) (let* ((cand (replace-regexp-in-string "^\\s-\\{1\\}" "" candidate)) (buf (get-buffer cand)) (buf-fname (or (buffer-file-name buf) (car-safe (rassoc buf dired-buffers)))) (regexps (cl-loop with pattern = helm-pattern for p in (helm-mm-split-pattern pattern) when (string-match "\\`/" p) collect p))) (if regexps (cl-loop for re in regexps thereis (and buf-fname (string-match (substring re 1) (helm-basedir buf-fname)))) t))) (defun helm-buffers-match-function (candidate) "Default function to match buffers." (and (helm-buffers--match-from-pat candidate) (helm-buffers--match-from-mjm candidate) (helm-buffers--match-from-inside candidate) (helm-buffers--match-from-directory candidate))) (defun helm-buffer-query-replace-1 (&optional regexp-flag buffers) "Query replace in marked buffers. If REGEXP-FLAG is given use `query-replace-regexp'." (let ((prompt (if regexp-flag "Query replace regexp" "Query replace")) (bufs (or buffers (helm-marked-candidates))) (helm--reading-passwd-or-string t)) (cl-loop with args = (query-replace-read-args prompt regexp-flag t) for buf in bufs do (save-window-excursion (switch-to-buffer buf) (save-excursion (let ((case-fold-search t)) (goto-char (point-min)) (apply #'perform-replace (list (nth 0 args) (nth 1 args) t regexp-flag (nth 2 args) nil multi-query-replace-map)))))))) (defun helm-buffer-query-replace-regexp (_candidate) (helm-buffer-query-replace-1 'regexp)) (defun helm-buffer-query-replace (_candidate) (helm-buffer-query-replace-1)) (defun helm-buffer-toggle-diff (candidate) "Toggle diff buffer CANDIDATE with it's file." (helm-aif (get-buffer-window "*Diff*" 'visible) (progn (kill-buffer "*Diff*") (set-window-buffer it helm-current-buffer)) (let ((buf (get-buffer candidate))) (if (buffer-file-name buf) (diff-buffer-with-file buf) (user-error "Buffer `%s' is not associated to a file" (buffer-name buf)))))) (helm-make-persistent-command-from-action helm-buffer-diff-persistent "Toggle diff buffer without quitting helm." 'diff-action 'helm-buffer-toggle-diff) (defun helm-revert-buffer (candidate) (with-current-buffer candidate (helm-aif (or (buffer-file-name) (buffer-file-name (buffer-base-buffer))) (and (file-exists-p it) (revert-buffer t t))))) (defun helm-revert-marked-buffers (_ignore) (mapc #'helm-revert-buffer (helm-marked-candidates))) (defun helm-buffer-revert-and-update (_candidate) (with-helm-buffer (let ((marked (helm-marked-candidates)) (preselect (helm-buffers--quote-truncated-buffer (helm-get-selection)))) (cl-loop for buf in marked do (helm-revert-buffer buf)) (when helm-marked-candidates (helm-unmark-all)) (helm-force-update preselect)))) (helm-make-persistent-command-from-action helm-buffer-revert-persistent "Revert buffer without quitting helm." 'revert-action 'helm-buffer-revert-and-update) (defun helm-buffer-save-and-update (_candidate) (with-helm-buffer (let ((marked (helm-marked-candidates)) (preselect (helm-get-selection nil t)) (enable-recursive-minibuffers t)) (cl-assert marked nil "No buffers need to be saved") (cl-loop for buf in marked do (with-current-buffer (get-buffer buf) (when (or (buffer-file-name) (buffer-file-name (buffer-base-buffer))) (save-buffer)))) (when helm-marked-candidates (helm-unmark-all)) (helm-force-update (regexp-quote preselect))))) (defun helm-buffer-save-some-buffers (_candidate) (helm-buffers-mark-similar-buffers-1 'mod) (helm-buffer-save-and-update nil)) (helm-make-persistent-command-from-action helm-buffer-run-save-some-buffers "Save unsaved file buffers without quitting Helm." 'save-some-action 'helm-buffer-save-some-buffers) (helm-make-persistent-command-from-action helm-buffer-save-persistent "Save buffer without quitting Helm." 'save-action 'helm-buffer-save-and-update) (defun helm-buffers-rename-buffer (candidate) (with-current-buffer candidate (rename-buffer (helm-read-string "New name: " (buffer-name)) t))) (helm-make-command-from-action helm-buffer-run-rename-buffer "Run rename buffer action from `helm-source-buffers-list'." 'helm-buffers-rename-buffer) (defun helm-switch-to-buffer-at-linum (candidate) (let ((linum (read-number "Line number: " (with-current-buffer candidate (line-number-at-pos))))) (switch-to-buffer candidate) (goto-char (point-min)) (forward-line (1- linum)))) (helm-make-command-from-action helm-buffer-run-goto-line "Switch to buffer at line number." 'helm-switch-to-buffer-at-linum) (helm-make-persistent-command-from-action helm-buffer-run-kill-persistent "Kill buffer without quitting Helm." 'kill-action 'helm-buffers-persistent-kill) (defun helm-kill-marked-buffers (_ignore) (let* ((bufs (helm-marked-candidates)) (killed-bufs (cl-count-if 'kill-buffer bufs))) (when (buffer-live-p helm-buffer) (with-helm-buffer (setq helm-marked-candidates nil helm-visible-mark-overlays nil))) (message "Killed %s buffer(s)" killed-bufs))) (helm-make-command-from-action helm-buffer-run-kill-buffers "Run kill buffer action from `helm-source-buffers-list'." 'helm-kill-marked-buffers) (defun helm-buffer-switch-to-shell (candidate) (require 'helm-files) (let ((helm-ff-default-directory (with-current-buffer candidate default-directory))) (helm-ff-switch-to-shell nil))) (helm-make-command-from-action helm-buffer-run-switch-to-shell "Run switch to shell action from helm-buffers-list." 'helm-buffer-switch-to-shell) (helm-make-command-from-action helm-buffer-run-grep "Run Grep action from `helm-source-buffers-list'." 'helm-grep-buffers) (helm-make-command-from-action helm-buffer-run-zgrep "Run Zgrep action from `helm-source-buffers-list'." 'helm-zgrep-buffers) (helm-make-command-from-action helm-buffer-run-query-replace-regexp "Run Query replace regexp action from `helm-source-buffers-list'." 'helm-buffer-query-replace-regexp) (helm-make-command-from-action helm-buffer-run-query-replace "Run Query replace action from `helm-source-buffers-list'." 'helm-buffer-query-replace) (helm-make-command-from-action helm-buffer-switch-other-window "Run switch to other window action from `helm-source-buffers-list'." 'helm-buffer-switch-buffers-other-window) (defun helm-buffer-switch-to-buffer-other-frame (_candidate) "Display marked buffers in other frame." (let ((bufs (helm-marked-candidates))) (select-frame (make-frame)) (helm-window-show-buffers bufs))) (defun helm-buffers-maybe-raise-buffer-frame (candidate) "Raise buffer frame handling buffer CANDIDATE and switch to it." (let ((oframe (window-frame (get-buffer-window candidate 0)))) (unless (eql oframe (selected-frame)) (raise-frame oframe)) (with-selected-frame oframe (switch-to-buffer candidate)))) (helm-make-command-from-action helm-buffer-switch-other-frame "Run switch to other frame action from `helm-source-buffers-list'." 'helm-buffer-switch-to-buffer-other-frame) (defun helm-buffers-switch-buffers-in-tab (_candidate) "Display marked buffers in a new tab. See `helm-buffers-switch-buffers-in-tab-1' for more infos." (helm-buffers-switch-buffers-in-tab-1 (helm-marked-candidates))) (helm-make-command-from-action helm-buffers-switch-to-buffer-new-tab "Run switch to buffer in other tab action from `helm-source-buffers-list'." 'helm-buffers-switch-buffers-in-tab (cl-assert (fboundp 'tab-bar-mode) nil "Tab-bar-mode not available")) (defun helm-buffer-switch-buffers (_candidate) "Switch to buffer candidates and replace current buffer. If more than one buffer marked switch to these buffers in separate windows. If a prefix arg is given split windows vertically." (let ((buffers (helm-marked-candidates))) (helm-window-show-buffers buffers))) (defun helm-buffer-switch-buffers-other-window (_candidate) "Switch to marked buffers in other windows." (let ((buffers (helm-marked-candidates))) (helm-window-show-buffers buffers t))) (helm-make-command-from-action helm-buffer-run-ediff "Run ediff action from `helm-source-buffers-list'." 'helm-ediff-marked-buffers) (helm-make-command-from-action helm-buffer-run-ediff-merge "Run ediff action from `helm-source-buffers-list'." 'helm-ediff-marked-buffers-merge) (defun helm-buffers-persistent-kill-1 (buffer-or-name) "Persistent action to kill buffer." (let ((buf (get-buffer buffer-or-name)) helm-buf-or-cur) (if (or (and (eql buf (get-buffer helm-current-buffer)) (setq helm-buf-or-cur "helm-current-buffer")) (and (eql buf (get-buffer helm-buffer)) (setq helm-buf-or-cur "helm-buffer"))) (progn (message "Can't kill `%s' without quitting session" helm-buf-or-cur) (sit-for 1)) (kill-buffer buf) (helm-delete-current-selection)))) (defun helm-buffers--quote-truncated-buffer (buffer) "Quote the truncated buffer-name of BUFFER. buffer-name is truncated according to `helm-buffer-max-length' minus the length of icon if one." (let ((bufname (and (bufferp buffer) (buffer-name buffer))) (maxlen (if helm-buffers-show-icons (- helm-buffer-max-length 2) helm-buffer-max-length))) (when (and bufname (file-remote-p (with-current-buffer bufname default-directory))) (setq bufname (concat "@ " (helm-url-unhex-string bufname)))) (when bufname (regexp-quote (if (and helm-buffer-max-length helm-buffer-details-flag) (helm-substring-by-width bufname maxlen "") bufname))))) (defun helm-buffers-persistent-kill (_buffer) (let* ((marked (helm-marked-candidates)) (msg "Buffer `%s' modified, please save it before kill") ;; After marking, selection should be after the last marked unless user ;; has not moved, if deleting current, use current selection after ;; having deleted. (sel (and (cdr marked) (helm-get-selection)))) (with-helm-buffer (unwind-protect (dolist (b marked) (if (and (buffer-file-name b) (buffer-modified-p b)) (progn (message msg (buffer-name b)) (sit-for 1)) ;; We need to preselect each marked because ;; helm-buffers-persistent-kill-1 is deleting ;; current selection. (helm-preselect (format "^[[:multibyte:] ]*%s" (helm-buffers--quote-truncated-buffer b))) (helm-buffers-persistent-kill-1 b) (helm--remove-marked-and-update-mode-line b))) (setq helm-marked-candidates nil helm-visible-mark-overlays nil)) (helm-force-update (format "^[[:multibyte:] ]*%s" (helm-buffers--quote-truncated-buffer ;; Ensure user has not moved selection on one ;; of marked. (or (and (buffer-live-p sel) sel) (helm-get-selection)))))))) (defun helm-buffers-list-persistent-action (candidate) (let ((current (window-buffer helm-persistent-action-display-window))) (if (or (helm-follow-mode-p) (eql current (get-buffer helm-current-buffer)) (not (eql current (get-buffer candidate)))) (display-buffer candidate) (if (and helm-persistent-action-display-window (window-dedicated-p (next-window helm-persistent-action-display-window 1))) (delete-window helm-persistent-action-display-window) (switch-to-buffer helm-current-buffer))))) (defun helm-ediff-marked-buffers (_candidate &optional merge) "Ediff 2 marked buffers or CANDIDATE and `helm-current-buffer'. With optional arg MERGE call `ediff-merge-buffers'." (let* ((mkd (helm-marked-candidates)) (lg-lst (length mkd)) buf1 buf2) (cl-case lg-lst (0 (error "Error:You have to mark at least 1 buffer")) (1 (setq buf1 helm-current-buffer buf2 (cl-first mkd))) (2 (setq buf1 (cl-first mkd) buf2 (cl-second mkd))) (t (error "Error:Too many buffers marked!"))) (if merge (ediff-merge-buffers buf1 buf2) (ediff-buffers buf1 buf2)))) (defun helm-ediff-marked-buffers-merge (candidate) "Ediff merge `helm-current-buffer' with CANDIDATE. See `helm-ediff-marked-buffers'." (helm-ediff-marked-buffers candidate t)) (defun helm-multi-occur-as-action (_candidate) "Multi occur action for `helm-source-buffers-list'. Can be used by any source that list buffers." (let ((helm-occur-always-search-in-current (if helm-current-prefix-arg (not helm-occur-always-search-in-current) helm-occur-always-search-in-current)) (buffers (helm-marked-candidates)) (input (cl-loop for i in (split-string (or (buffer-local-value 'helm-input-local (get-buffer helm-buffer)) helm-pattern) " " t) thereis (and (string-match "\\`@\\([^!]*\\)" i) (match-string 1 i))))) (helm-multi-occur-1 buffers input))) (helm-make-command-from-action helm-buffers-run-occur "Run `helm-multi-occur-as-action' by key." 'helm-multi-occur-as-action) (defun helm-buffers-toggle-show-hidden-buffers () (interactive) (with-helm-alive-p (let ((filter-attrs (helm-get-attr 'filtered-candidate-transformer helm-source-buffers-list)) (sel (helm-get-selection))) (if (memq 'helm-shadow-boring-buffers filter-attrs) (helm-set-attr 'filtered-candidate-transformer (cons 'helm-skip-boring-buffers (remove 'helm-shadow-boring-buffers filter-attrs)) helm-source-buffers-list) (helm-set-attr 'filtered-candidate-transformer (cons 'helm-shadow-boring-buffers (remove 'helm-skip-boring-buffers filter-attrs)) helm-source-buffers-list)) (helm-force-update (helm-buffers--quote-truncated-buffer sel))))) (put 'helm-buffers-toggle-show-hidden-buffers 'helm-only t) (defun helm-buffers-browse-project (buf) "Browse project from buffer BUF." (with-current-buffer buf (helm-browse-project helm-current-prefix-arg))) (helm-make-command-from-action helm-buffers-run-browse-project "Run `helm-buffers-browse-project' from key." 'helm-buffers-browse-project (cl-assert (not helm-buffers-in-project-p) nil "You are already browsing this project")) ;;;###autoload (defun helm-buffers-quit-and-find-file-fn (source) (let* ((sel (get-buffer (helm-get-selection nil nil source))) (bname (and (bufferp sel) (buffer-name sel)))) (when bname (or (buffer-file-name sel) (car (rassoc bname dired-buffers)) (and (with-current-buffer bname (eq major-mode 'org-agenda-mode)) org-directory (expand-file-name org-directory)) (with-current-buffer bname (expand-file-name default-directory)))))) ;;; Candidate Transformers ;; ;; (defun helm-skip-boring-buffers (buffers _source) "Remove buffers matching `helm-boring-buffer-regexp-list' in BUFFERS. Where BUFFERS is a list of buffer names." (helm-skip-entries buffers helm-boring-buffer-regexp-list helm-white-buffer-regexp-list)) (defun helm-shadow-boring-buffers (buffers _source) "Buffers matching `helm-boring-buffer-regexp' will be displayed with the `file-name-shadow' face if available." (helm-shadow-entries buffers helm-boring-buffer-regexp-list)) ;;;###autoload (defun helm-buffers-list () "Preconfigured `helm' to list buffers." (interactive) (unless helm-source-buffers-list (setq helm-source-buffers-list (helm-make-source "Buffers" 'helm-source-buffers))) (helm :sources '(helm-source-buffers-list helm-source-buffer-not-found) :buffer "*helm buffers*" :truncate-lines helm-buffers-truncate-lines :left-margin-width helm-buffers-left-margin-width)) ;;;###autoload (defun helm-mini () "Preconfigured `helm' displaying `helm-mini-default-sources'." (interactive) (require 'helm-x-files) (unless helm-source-buffers-list (setq helm-source-buffers-list (helm-make-source "Buffers" 'helm-source-buffers))) (helm :sources helm-mini-default-sources :buffer "*helm mini*" :ff-transformer-show-only-basename nil :truncate-lines helm-buffers-truncate-lines :left-margin-width helm-buffers-left-margin-width)) (defun helm-quit-and-helm-mini () "Drop into `helm-mini' from `helm'." (interactive) (with-helm-alive-p (helm-run-after-exit 'helm-mini))) (provide 'helm-buffers) ;;; helm-buffers.el ends here helm-4.0.3/helm-color.el000066400000000000000000000116721501106761700150410ustar00rootroot00000000000000;;; helm-color.el --- colors and faces -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'helm-elisp) (declare-function list-colors-display "facemenu") ;;; Customize Face ;; ;; (defun helm-custom-faces-init () "Initialize buffer for `helm-source-customize-face'." (unless (helm-candidate-buffer) (save-selected-window (list-faces-display) (message nil)) (helm-init-candidates-in-buffer 'global (with-current-buffer (get-buffer "*Faces*") (buffer-substring (next-single-char-property-change (point-min) 'category) (point-max)))) (kill-buffer "*Faces*"))) (defvar helm-source-customize-face (helm-build-in-buffer-source "Customize Face" :init 'helm-custom-faces-init :get-line 'buffer-substring :persistent-action (lambda (candidate) (helm-elisp--persistent-help (intern (car (split-string candidate))) 'helm-describe-face)) :persistent-help "Describe face" :action `(("Customize" . ,(lambda (line) (customize-face (intern (car (split-string line)))))) ("Copy name" . ,(lambda (line) (kill-new (car (split-string line " " t))))))) "See (info \"(emacs)Faces\")") ;;; Colors browser ;; ;; (defun helm-colors-init () (require 'facemenu) (unless (helm-candidate-buffer) (save-selected-window (list-colors-display) (message nil)) (helm-init-candidates-in-buffer 'global (with-current-buffer (get-buffer "*Colors*") (buffer-string))) (kill-buffer "*Colors*"))) (defun helm-color-insert-name (candidate) (with-helm-current-buffer (insert (helm-colors-get-name candidate)))) (defun helm-color-kill-name (candidate) (kill-new (helm-colors-get-name candidate))) (defun helm-color-insert-rgb (candidate) (with-helm-current-buffer (insert (helm-colors-get-rgb candidate)))) (defun helm-color-kill-rgb (candidate) (kill-new (helm-colors-get-rgb candidate))) (helm-make-command-from-action helm-color-run-insert-name "Insert name of color from `helm-source-colors'." 'helm-color-insert-name) (helm-make-command-from-action helm-color-run-kill-name "Kill name of color from `helm-source-colors'." 'helm-color-kill-name) (helm-make-command-from-action helm-color-run-insert-rgb "Insert RGB of color from `helm-source-colors'." 'helm-color-insert-rgb) (helm-make-command-from-action helm-color-run-kill-rgb "Kill RGB of color from `helm-source-colors'." 'helm-color-kill-rgb) (defvar helm-color-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "C-c n") #'helm-color-run-insert-name) (define-key map (kbd "C-c N") #'helm-color-run-kill-name) (define-key map (kbd "C-c r") #'helm-color-run-insert-rgb) (define-key map (kbd "C-c R") #'helm-color-run-kill-rgb) map)) (defvar helm-source-colors (helm-build-in-buffer-source "Colors" :init 'helm-colors-init :get-line 'buffer-substring :keymap helm-color-map :persistent-help "Kill entry in RGB format." :persistent-action 'helm-color-kill-rgb :help-message 'helm-colors-help-message :action '(("Copy Name (C-c N)" . helm-color-kill-name) ("Copy RGB (C-c R)" . helm-color-kill-rgb) ("Insert Name (C-c n)" . helm-color-insert-name) ("Insert RGB (C-c r)" . helm-color-insert-rgb)))) (defun helm-colors-get-name (candidate) "Get color name." (replace-regexp-in-string " " "" (with-temp-buffer (insert (capitalize candidate)) (goto-char (point-min)) (search-forward-regexp "\\s-\\{2,\\}") (delete-region (point) (point-max)) (buffer-string)))) (defun helm-colors-get-rgb (candidate) "Get color RGB." (replace-regexp-in-string " " "" (with-temp-buffer (insert (capitalize candidate)) (goto-char (point-max)) (search-backward-regexp "\\s-\\{2,\\}") (delete-region (point) (point-min)) (buffer-string)))) ;;;###autoload (defun helm-colors () "Preconfigured `helm' for color." (interactive) (helm :sources '(helm-source-colors helm-source-customize-face) :buffer "*helm colors*")) (provide 'helm-color) ;;; helm-color.el ends here helm-4.0.3/helm-command.el000066400000000000000000000460771501106761700153500ustar00rootroot00000000000000;;; helm-command.el --- Helm execute-exended-command. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'helm-mode) (require 'helm-elisp) (defvar helm-M-x-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-comp-read-map) (define-key map (kbd "C-u") nil) (define-key map (kbd "C-u") #'helm-M-x-universal-argument) (define-key map (kbd "C-]") #'helm-M-x-toggle-short-doc) map)) (defgroup helm-command nil "Emacs command related Applications and libraries for Helm." :group 'helm) (defcustom helm-M-x-always-save-history nil "`helm-M-x' save command in `extended-command-history' even when it fails." :type 'boolean) (defcustom helm-M-x-reverse-history nil "The history source of `helm-M-x' appear in second position when non-nil." :type 'boolean) (defcustom helm-M-x-fuzzy-match t "Helm-M-x fuzzy matching when non nil." :type 'boolean) (defcustom helm-M-x-show-short-doc nil "Show short docstring of command when non nil. This value can be toggled with \\\\[helm-M-x-toggle-short-doc] while in helm-M-x session." :type 'boolean) (defcustom helm-M-x-history-transformer-sort t "When nil, do not sort helm-M-x's commands history." :type 'boolean) (defcustom helm-M-x-exclude-unusable-commands-in-mode t "When non nil exclude commands not usable in current buffer. This will exclude only commands defined with `interactive' MODES argument, for other commands, they will be displayed even if unusable as long as they satisfies `commandp'. NOTE: As `interactive' MODES argument is relatively recent, not all commands are using it when they should, so do not expect ALL unuseful commands to be excluded in `helm-M-x'. Also in Emacsen with a version of `interactive' not handling MODES, this will have no effect. Regardless of this Helm commands unrelated to Helm will never appear in `helm-M-x' whatever the value of this var is." :type 'boolean) ;;; Faces ;; ;; (defgroup helm-command-faces nil "Customize the appearance of helm-command." :prefix "helm-" :group 'helm-command :group 'helm-faces) (defface helm-M-x-key `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "orange" :box (:line-width -1))) "Face used in helm-M-x to show keybinding." :group 'helm-command-faces) (defface helm-command-active-mode '((t :inherit font-lock-builtin-face)) "Face used by `helm-M-x' for activated modes." :group 'helm-command-faces) (defface helm-M-x-short-doc '((t :box (:line-width -1) :foreground "DimGray")) "Face used by `helm-M-x' for short docstring." :group 'helm-command-faces) (defvar helm-M-x-input-history nil) (defvar helm-M-x-prefix-argument nil "Prefix argument before calling `helm-M-x'.") (defvar helm-M-x--timer nil) (defvar helm-M-x--unwind-forms-done nil) (defun helm-M-x-get-major-mode-command-alist (mode-map) "Return alist of MODE-MAP." (when mode-map (cl-loop for key being the key-seqs of mode-map using (key-bindings com) for str-key = (key-description key) for ismenu = (string-match "" str-key) unless ismenu collect (cons str-key com)))) (defun helm-get-mode-map-from-mode (mode) "Guess the mode-map name according to MODE. Some modes don't use conventional mode-map name so we need to guess mode-map name. E.g. `python-mode' ==> py-mode-map. Return nil if no mode-map found." (cl-loop ;; Start with a conventional mode-map name. with mode-map = (intern-soft (format "%s-map" mode)) with mode-string = (symbol-name mode) with mode-name = (replace-regexp-in-string "-mode" "" mode-string) while (not mode-map) for count downfrom (length mode-name) ;; Return when no result after parsing entire string. when (eq count 0) return nil for sub-name = (substring mode-name 0 count) do (setq mode-map (intern-soft (format "%s-map" (concat sub-name "-mode")))) finally return mode-map)) (defun helm-M-x-current-mode-map-alist () "Return mode-map alist of current `major-mode'." (let ((map-sym (helm-get-mode-map-from-mode major-mode))) (when (and map-sym (boundp map-sym)) (helm-M-x-get-major-mode-command-alist (symbol-value map-sym))))) (defun helm-M-x-toggle-short-doc () "Toggle short doc display in helm-M-x." (interactive) (setq helm-M-x-show-short-doc (not helm-M-x-show-short-doc)) (helm-force-update (concat "^" (helm-get-selection)) (helm-get-current-source))) (put 'helm-M-x-toggle-short-doc 'no-helm-mx t) (defun helm-M-x-transformer-1 (candidates &optional sort ignore-props) "Transformer function to show bindings in emacs commands. Show global bindings and local bindings according to current `major-mode'. If SORT is non nil sort list with `helm-generic-sort-fn'. Note that SORT should not be used when fuzzy matching because fuzzy matching is running its own sort function with a different algorithm." (with-helm-current-buffer (cl-loop with local-map = (helm-M-x-current-mode-map-alist) for cand in candidates for local-key = (car (rassq cand local-map)) for key = (substitute-command-keys (format "\\[%s]" cand)) for sym = (intern (if (consp cand) (car cand) cand)) for doc = (when helm-M-x-show-short-doc (helm-get-first-line-documentation (intern-soft cand))) for disp = (if (or (eq sym major-mode) (and (memq sym minor-mode-list) (boundp sym) (buffer-local-value sym helm-current-buffer))) (propertize cand 'face 'helm-command-active-mode) cand) unless (and (null ignore-props) (or (get sym 'helm-only) (get sym 'no-helm-mx) (eq sym 'helm-M-x))) collect (cons (cond ((and (string-match "^M-x" key) local-key) (propertize (format "%s%s%s %s" disp (if doc (helm-make-separator cand) "") (if doc (propertize doc 'face 'helm-M-x-short-doc) "") (propertize " " 'display (propertize local-key 'face 'helm-M-x-key))) 'match-part disp)) ((and (string-match "^M-x" key) (not (string= key "M-x"))) (propertize (format "%s%s%s" disp (if doc (helm-make-separator cand) "") (if doc (propertize doc 'face 'helm-M-x-short-doc) "")) 'match-part disp)) (t (propertize (format "%s%s%s %s" disp (if doc (helm-make-separator cand) "") (if doc (propertize doc 'face 'helm-M-x-short-doc) "") (propertize " " 'display (propertize key 'face 'helm-M-x-key))) 'match-part disp))) cand) into ls finally return (if sort (sort ls #'helm-generic-sort-fn) ls)))) (defun helm-M-x-transformer (candidates _source) "Transformer function for `helm-M-x' candidates." ;; Generic sort function is handling helm-flex. (helm-M-x-transformer-1 candidates (null helm--in-fuzzy))) (defun helm-M-x-transformer-no-sort (candidates _source) "Transformer function for `helm-M-x' candidates." (helm-M-x-transformer-1 candidates)) (defun helm-M-x-transformer-no-sort-no-props (candidates _source) "Transformer function for `helm-M-x' candidates." (helm-M-x-transformer-1 candidates nil t)) (defun helm-M-x--notify-prefix-arg () ;; Notify a prefix-arg set AFTER calling M-x. (when prefix-arg (with-helm-window (helm-display-mode-line (helm-get-current-source) 'force)))) (defun helm-cmd--get-current-function-name () (save-excursion (beginning-of-defun) (cadr (split-string (buffer-substring-no-properties (pos-bol) (pos-eol)))))) (defun helm-cmd--get-preconfigured-commands (&optional dir) (let* ((helm-dir (or dir (helm-basedir (locate-library "helm")))) (helm-autoload-file (expand-file-name "helm-autoloads.el" helm-dir)) results) (when (file-exists-p helm-autoload-file) (with-temp-buffer (insert-file-contents helm-autoload-file) (while (re-search-forward "Preconfigured" nil t) (push (substring (helm-cmd--get-current-function-name) 1) results)))) results)) (defun helm-M-x-universal-argument () "Same as `universal-argument' but for `helm-M-x'." (interactive) (if helm-M-x-prefix-argument (progn (setq helm-M-x-prefix-argument nil) (let ((inhibit-read-only t)) (with-selected-window (minibuffer-window) (save-excursion (goto-char (point-min)) (delete-char (- (minibuffer-prompt-width) (length "M-x ")))))) (message "Initial prefix arg disabled")) (setq prefix-arg (list 4)) (universal-argument--mode))) (put 'helm-M-x-universal-argument 'helm-only t) (defun helm-M-x-persistent-action (candidate) (helm-elisp--persistent-help candidate 'helm-describe-function)) (defun helm-M-x--move-selection-after-hook () (setq current-prefix-arg nil)) (defun helm-M-x--before-action-hook () (remove-hook 'helm-move-selection-after-hook #'helm-M-x--move-selection-after-hook)) (defclass helm-M-x-class (helm-source-in-buffer helm-type-command) ((requires-pattern :initform 0) (must-match :initform t) (filtered-candidate-transformer :initform #'helm-M-x-transformer) (persistent-help :initform "Describe this command") (help-message :initform 'helm-M-x-help-message) (nomark :initform t) (cleanup :initform #'helm-M-x--unwind-forms) (keymap :initform 'helm-M-x-map) (resume :initform 'helm-M-x-resume-fn))) (defun helm-M-x-resume-fn () (when (and helm-M-x--timer (timerp helm-M-x--timer)) (cancel-timer helm-M-x--timer) (setq helm-M-x--timer nil)) (setq helm-M-x--timer (run-at-time 1 0.1 #'helm-M-x--notify-prefix-arg)) (setq helm--mode-line-display-prefarg t) ;; Prevent displaying a wrong prefix arg when helm-resume is called ;; from prefix arg. (setq current-prefix-arg nil)) (defun helm-M-x-read-extended-command (collection &optional predicate history) "Read or execute action on command name in COLLECTION or HISTORY. Helm completion is not provided when executing or defining kbd macros. Arg COLLECTION should be an `obarray'. Arg PREDICATE is a function that default to `commandp'. Arg HISTORY default to `extended-command-history'." (setq helm--mode-line-display-prefarg t) (let* ((pred (or predicate #'commandp)) (helm-fuzzy-sort-fn (lambda (candidates _source) (if helm-M-x-history-transformer-sort ;; Sort on real candidate otherwise ;; "symbol ()" is used when sorting. (helm-fuzzy-matching-default-sort-fn-1 candidates t) candidates))) (sources `(,(helm-make-source "Emacs Commands history" 'helm-M-x-class :data (lambda () (helm-comp-read-get-candidates ;; History should be quoted to ;; force `helm-comp-read-get-candidates' ;; to use predicate against ;; symbol and not string. (or history 'extended-command-history) ;; Ensure using empty string to ;; not defeat helm matching fns [1] pred nil nil "")) :filtered-candidate-transformer (if helm-M-x-history-transformer-sort #'helm-M-x-transformer #'helm-M-x-transformer-no-sort) :fuzzy-match helm-M-x-fuzzy-match) ,(helm-make-source "Emacs Commands" 'helm-M-x-class :data (lambda () (helm-comp-read-get-candidates ;; [1] Same comment as above. collection pred nil nil "")) :fuzzy-match helm-M-x-fuzzy-match))) (prompt (concat (helm-acase helm-M-x-prefix-argument (- "-") ((dst* (l &rest args)) (if (eq l 4) "C-u " (format "%d " l))) ((guard* (integerp it)) (format "%d " it))) "M-x "))) (setq helm-M-x--timer (run-at-time 1 0.1 #'helm-M-x--notify-prefix-arg)) ;; Fix Bug#2250, add `helm-move-selection-after-hook' which ;; reset prefix arg to nil only for this helm session. (add-hook 'helm-move-selection-after-hook #'helm-M-x--move-selection-after-hook) (add-hook 'helm-before-action-hook #'helm-M-x--before-action-hook) (when (and sources helm-M-x-reverse-history) (setq sources (nreverse sources))) (unwind-protect (progn (setq current-prefix-arg nil) (helm :sources sources :prompt prompt :buffer "*helm M-x*" :history 'helm-M-x-input-history :truncate-lines t)) (helm-M-x--unwind-forms)))) ;; When running a command involving again helm from helm-M-x, the ;; unwind-protect UNWINDS forms are executed only once this helm ;; command exit leaving the helm-M-x timer running and other variables ;; and hooks not unset, so the timer is now in a global var and all ;; the forms that should normally run in unwind-protect are running as ;; well as soon as helm-M-x-execute-command is called. (defun helm-M-x--unwind-forms (&optional done) ;; helm-M-x--unwind-forms-done is non nil when it have been called ;; once from helm-M-x-execute-command. (unless helm-M-x--unwind-forms-done (when (timerp helm-M-x--timer) (cancel-timer helm-M-x--timer) (setq helm-M-x--timer nil)) (setq helm--mode-line-display-prefarg nil helm-fuzzy-sort-fn (default-toplevel-value 'helm-fuzzy-sort-fn)) ;; Be sure to remove it here as well in case of quit. (remove-hook 'helm-move-selection-after-hook #'helm-M-x--move-selection-after-hook) (remove-hook 'helm-before-action-hook #'helm-M-x--before-action-hook)) ;; Reset helm-M-x--unwind-forms-done to nil when DONE is ;; unspecified. (setq helm-M-x--unwind-forms-done done)) (defun helm-M-x-execute-command (command) "Execute COMMAND as an editor command. COMMAND must be a symbol that satisfies the `commandp' predicate. Save COMMAND to `extended-command-history'." (helm-M-x--unwind-forms t) (when command ;; Avoid having `this-command' set to *exit-minibuffer. (setq this-command command ;; Handle C-x z (repeat) Bug#322 real-this-command command) ;; If helm-M-x is called with regular emacs completion (kmacro) ;; use the value of arg otherwise use helm-current-prefix-arg. (let ((prefix-arg (or helm-current-prefix-arg helm-M-x-prefix-argument)) (command-name (symbol-name command))) (condition-case-unless-debug err (progn (command-execute command 'record) (add-to-history 'extended-command-history command-name)) (error (when helm-M-x-always-save-history (add-to-history 'extended-command-history command-name)) (signal (car err) (cdr err))))))) (defun helm-M-x--vanilla-M-x () (helm-M-x-execute-command (intern-soft (if helm-mode (unwind-protect (progn (helm-mode -1) (read-extended-command)) (helm-mode 1)) (read-extended-command))))) (defun helm-M-x--mode-predicate (symbol mj-mode lmm-modes) "Check if SYMBOL is suitable for current buffer. MJ-MODE is used to pass major-mode and LMM-MODES to pass local-minor-modes. This predicate honors commands defined with the `interactive' MODES argument." (let* ((sym (helm-symbolify symbol)) (modes (command-modes sym))) (and (commandp sym) (if modes (or (memq mj-mode modes) (cl-loop for m in modes thereis (or (memq m lmm-modes) (memq m global-minor-modes)))) t)))) ;;;###autoload (defun helm-M-x (_arg) "Preconfigured `helm' for Emacs commands. It is `helm' replacement of regular `M-x' `execute-extended-command'. Unlike regular `M-x' Emacs vanilla `execute-extended-command' command, the prefix args if needed, can be passed AFTER starting `helm-M-x'. When a prefix arg is passed BEFORE starting `helm-M-x', the first `C-u' while in `helm-M-x' session will disable it. You can get help on each command by persistent action." (interactive (progn (setq helm-M-x-prefix-argument current-prefix-arg) (list current-prefix-arg))) (if (or defining-kbd-macro executing-kbd-macro) (helm-M-x--vanilla-M-x) (let ((lmm-modes (buffer-local-value 'local-minor-modes (current-buffer))) (mj-mode major-mode)) (helm-M-x-read-extended-command obarray (if (and (fboundp 'command-modes) helm-M-x-exclude-unusable-commands-in-mode) (lambda (sym) (helm-M-x--mode-predicate sym mj-mode lmm-modes)) #'commandp))))) (put 'helm-M-x 'interactive-only 'command-execute) (provide 'helm-command) ;;; helm-command.el ends here helm-4.0.3/helm-core.el000066400000000000000000012730421501106761700146550ustar00rootroot00000000000000;;; helm-core.el --- Development files for Helm -*- lexical-binding: t -*- ;; Copyright (C) 2022 ~ 2025 Thierry Volpiatto ;; Author: Thierry Volpiatto ;; URL: https://emacs-helm.github.io/helm/ ;; Version: 4.0.3 ;; Package-Requires: ((emacs "25.1") (async "1.9.9")) ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Contains the main code for Helm. ;; As a package helm-core provides the files helm-core.el, helm-lib.el, ;; helm-source.el and helm-multi-match.el. ;;; Code: (require 'cl-lib) (require 'async) (require 'helm-lib) (require 'helm-multi-match) (require 'helm-source) ;; Setup completion styles for helm-mode (helm--setup-completion-styles-alist) (declare-function helm-comp-read "helm-mode.el") (declare-function custom-unlispify-tag-name "cus-edit.el") (declare-function helm-quit-and-find-file "helm-utils.el") (declare-function linum-mode "linum.el") (declare-function minibuffer-depth-setup "mb-depth.el") (declare-function transient--delete-window "ext:transient" ()) (declare-function transient--preserve-window-p "ext:transient" (&optional nohide)) (defvar helm-marked-buffer-name) (defvar display-buffer-function) (defvar minibuffer-follows-selected-frame) (defvar minibuffer-depth-indicate-mode) ;;; Internal Variables ;; ;; (defvar helm-source-filter nil "A list of source names to be displayed. Other sources won't appear in the search results. If nil, no filtering is done. Don't set this directly, use `helm-set-source-filter' during a Helm session to modify it.") (defvar helm-saved-action nil "Saved value of the currently selected action by key.") (defvar helm-saved-current-source nil "Value of the current source when the action list is shown.") (defvar helm-in-persistent-action nil "Flag whether in persistent-action or not.") (defvar helm-last-buffer nil "`helm-buffer' of a previous Helm session.") (defvar helm-saved-selection nil "Value of the currently selected object when the action list is shown.") (defvar helm-sources nil "[INTERNAL] Value of current sources in use, a list of alists. The list of sources (symbols or alists) is normalized to alists in `helm-initialize'.") (defvar helm-buffer-file-name nil "Variable `buffer-file-name' when Helm is invoked.") (defvar helm-candidate-cache (make-hash-table :test 'equal) "Holds the available candidate within a single Helm invocation.") (defvar helm--candidate-buffer-alist nil) (defvar helm-input "" "The input typed in the candidates panel.") (defvar helm-input-local nil "Internal, store locally `helm-pattern' value for later use in `helm-resume'.") (defvar helm--source-name nil) (defvar helm-current-source nil) (defvar helm-issued-errors nil) (defvar helm--last-log-file nil "The name of the log file of the last Helm session.") (defvar helm--local-variables nil) (defvar helm-split-window-state nil) (defvar helm--window-side-state nil) (defvar helm-selection-point nil "The value of point at selection.") (defvar helm-alive-p nil) (defvar helm-visible-mark-overlays nil) (defvar helm--force-updating-p nil "[INTERNAL] Don't use this in your programs.") (defvar helm-exit-status 0 "Flag to inform if Helm did exit or quit. 0 means Helm did exit when executing an action. 1 means Helm did quit with \\[keyboard-quit] Knowing this exit-status could help restore a window config when Helm aborts in some special circumstances. See `helm-exit-minibuffer' and `helm-keyboard-quit'.") (defvar helm-minibuffer-confirm-state nil) (defvar helm--quit nil) (defvar helm-buffers nil "Helm buffers listed in order of most recently used.") (defvar helm-current-position nil "Cons of (point . window-start) when Helm is invoked. `helm-current-buffer' uses this to restore position after `helm-keyboard-quit'") (defvar helm-last-frame-or-window-configuration nil "Used to store window or frame configuration at Helm start.") (defvar helm-onewindow-p nil) (defvar helm-types nil) (defvar helm--mode-line-string-real nil) ; The string to display in mode-line. (defvar helm-persistent-action-display-window nil) (defvar helm-marked-candidates nil "Marked candidates. List of (source . real) pair.") (defvar helm--mode-line-display-prefarg nil) (defvar helm--temp-follow-flag nil "[INTERNAL] A simple flag to notify persistent action we are following.") (defvar helm--reading-passwd-or-string nil) (defvar helm--in-update nil) (defvar helm--in-fuzzy nil) (defvar helm-maybe-use-default-as-input nil "Flag to notify the use of use-default-as-input. Use only in let-bindings. Use :default arg of `helm' as input to update display. Note that if also :input is specified as `helm' arg, it will take precedence on :default.") (defvar helm--temp-hooks nil "Store temporary hooks added by `with-helm-temp-hook'.") (defvar helm--prompt nil) (defvar helm--file-completion-sources '("Find Files" "Read File Name" "New file or directory") "Sources that use the *find-files mechanism can be added here. Sources generated by `helm-mode' don't need to be added here because they are automatically added. You should not modify this yourself unless you know what you are doing.") (defvar helm--completing-file-name nil "Non nil when `helm-read-file-name' is running. Used for `helm-file-completion-source-p'.") ;; Same as `ffap-url-regexp' but keep it here to ensure `ffap-url-regexp' is not nil. (defvar helm--url-regexp "\\`\\(news\\(post\\)?:\\|mailto:\\|file:\\|\\(ftp\\|https?\\|telnet\\|gopher\\|www\\|wais\\)://\\)") (defvar helm--ignore-errors nil "Flag to prevent Helm popping up errors in candidates functions. Should be set in candidates functions if needed, and will be restored at end of session.") (defvar helm--action-prompt "Select action: ") (defvar helm--cycle-resume-iterator nil) (defvar helm--buffer-in-new-frame-p nil) (defvar helm-initial-frame nil "[INTERNAL] The selected frame before starting Helm. Helm use this internally to know in which frame it started, don't modify this yourself.") (defvar helm-popup-frame nil "The frame where Helm is displayed. This is only used when Helm is using `helm-display-buffer-in-own-frame' as `helm-display-function' and `helm-display-buffer-reuse-frame' is non nil.") (defvar helm--nested nil) (defconst helm--frame-default-attributes '(width height tool-bar-lines left top title undecorated vertical-scroll-bars visibility fullscreen menu-bar-lines undecorated alpha foreground-color background-color) "Frame parameters to save in `helm--last-frame-parameters'.") (defvar helm--last-frame-parameters nil "Frame parameters to save for later resuming. Local to `helm-buffer'.") (defvar helm--executing-helm-action nil "Non nil when action is triggering a new helm-session. This may be let bounded in other places to notify the display function to reuse the same frame parameters as the previous Helm session just like resume would do.") (defvar helm--current-buffer-narrowed nil) (defvar helm--suspend-update-interactive-flag nil) (defvar helm-persistent-action-window-buffer nil "[INTERNAL] Store the buffer where helm is started. It is generally `helm-current-buffer', but when this one is displayed in a dedicated buffer, helm can't start in this window and use another window handling a buffer, it is this one we store.") (defvar helm--tramp-archive-maybe-loaded nil) (defvar helm--original-dedicated-windows-alist nil "[INTERNAL] Store all dedicated windows with their dedicated state on startup") (defvar helm--deleting-minibuffer-contents-from nil "[INTERNAL] Recenter when deleting minibuffer-contents and preselecting. This is a flag used internally.") (defvar helm--minibuffer-completing-file-name nil "A flag notifying Helm is in file completion. It is let-bounded in `helm-read-file-name'. Same as `minibuffer-completing-file-name' but doesn't affect `file-directory-p'.") ;;; Multi keys ;; ;; ;;;###autoload (defun helm-define-multi-key (keymap key functions &optional delay) "In KEYMAP, define key sequence KEY for function list FUNCTIONS. Each function runs sequentially for each KEY press. If DELAY is specified, switch back to initial function of FUNCTIONS list after DELAY seconds. The functions in FUNCTIONS list take no args. E.g. (defun foo () (interactive) (message \"Run foo\")) (defun bar () (interactive) (message \"Run bar\")) (defun baz () (interactive) (message \"Run baz\")) \(helm-define-multi-key global-map (kbd \" q\") \\='(foo bar baz) 2) Each time \" q\" is pressed, the next function is executed. Waiting more than 2 seconds between key presses switches back to executing the first function on the next hit." (define-key keymap key (helm-make-multi-command functions delay))) ;;;###autoload (defmacro helm-multi-key-defun (name docstring funs &optional delay) "Define NAME as a multi-key command running FUNS. After DELAY seconds, the FUNS list is reinitialized. See `helm-define-multi-key'." (declare (indent 2) (doc-string 2)) (setq docstring (if docstring (concat docstring "\n\n") "This is a helm-ish multi-key command.")) `(defalias (quote ,name) (helm-make-multi-command ,funs ,delay) ,docstring)) (defun helm-make-multi-command (functions &optional delay) "Return an anonymous multi-key command running FUNCTIONS. Run each function in the FUNCTIONS list in turn when called within DELAY seconds." (declare (indent 1)) (let ((funs functions) (iter (list nil)) ; ref-cell[1]. (timeout delay)) (lambda () (interactive) (helm-run-multi-key-command funs iter timeout)))) (defun helm-run-multi-key-command (functions iterator delay) (let ((fn (lambda () (cl-loop for count from 1 to (length functions) collect count))) next) ;; By passing a list containing a single 'nil' element [1] as ITERATOR we ;; avoid using a global var. (unless (and (car iterator) ;; Reset iterator when another key is pressed. (eq this-command real-last-command)) (setcar iterator (helm-iter-circular (funcall fn)))) (setq next (helm-iter-next (car iterator))) (and next (car iterator) (call-interactively (nth (1- next) functions))) (when delay (run-with-idle-timer delay nil (lambda () (setcar iterator nil)))))) (helm-multi-key-defun helm-toggle-resplit-and-swap-windows "Multi key command to re-split and swap Helm window. First call runs `helm-toggle-resplit-window', and second call within 1s runs `helm-swap-windows'." '(helm-toggle-resplit-window helm-swap-windows) 1) (put 'helm-toggle-resplit-and-swap-windows 'helm-only t) (defun helm-command-with-subkeys (map subkey command &optional other-subkeys prompt exit-fn delay) "Build a command that run COMMAND when SUBKEY is read. The command runs a loop reading keys and exit when user stops typing after DELAY seconds. After this DELAY EXIT-FN run if specified. Arg OTHER-SUBKEYS should be an alist composed of (command . short-key) where command is another command than COMMAND bound to short-key. A PROMPT can be used to describe bindings of COMMAND and OTHER-SUBKEYS. Return an anonymous interactive command to use with `helm-define-key-with-subkeys'." (lambda () (interactive) (let (timer) (call-interactively command) (unless (or defining-kbd-macro executing-kbd-macro) (unwind-protect (progn (when delay (setq timer (run-with-idle-timer delay nil (lambda () (keyboard-quit))))) (while (let ((input (read-key prompt)) other kb com) (setq last-command-event input) (cond ((eq input subkey) (call-interactively command) (setq last-command command) t) ((setq other (assoc input other-subkeys)) (call-interactively (cdr other)) (setq last-command (cdr other)) t) (t (setq kb (vector last-command-event)) (setq com (lookup-key map kb)) (if (commandp com) (call-interactively com) (setq unread-command-events (nconc (mapcar #'identity kb) unread-command-events))) nil))))) (when timer (cancel-timer timer)) (and exit-fn (funcall exit-fn))))))) ;;;###autoload (defun helm-define-key-with-subkeys (map key subkey command &optional other-subkeys prompt exit-fn delay docstring) "Define in MAP a KEY and SUBKEY to COMMAND. This allows typing KEY to call COMMAND the first time and type only SUBKEY on subsequent calls. Arg MAP is the keymap to use, SUBKEY is the initial short key binding to call COMMAND. Arg OTHER-SUBKEYS is an alist specifying other short key bindings to use once started, e.g.: (helm-define-key-with-subkeys global-map (kbd \"C-x v n\") ?n \\='git-gutter:next-hunk \\='((?p . git-gutter:previous-hunk))) In this example, `C-x v n' will run `git-gutter:next-hunk' subsequent \"n\" will run this command again and subsequent \"p\" will run `git-gutter:previous-hunk'. If specified PROMPT can be displayed in minibuffer to describe SUBKEY and OTHER-SUBKEYS. Arg EXIT-FN specifies a function to run on exit. For any other key pressed, run their assigned command as defined in MAP and then exit the loop running EXIT-FN, if specified. If DELAY is specified the command expires after DELAY seconds. NOTE: SUBKEY and OTHER-SUBKEYS bindings support only char syntax and vectors, so don't use strings to define them. While defining or executing a kbd macro no SUBKEY or OTHER-SUBKEYS are provided, i.e. the loop is not entered after running COMMAND." (declare (indent 1)) (let ((fn (helm-command-with-subkeys map subkey command other-subkeys prompt exit-fn delay)) (com (intern (format "helm-%s-with-subkeys" (symbol-name command))))) (defalias com fn (or docstring ;; When no DOCSTRING, generate a basic one specifying ;; COMMAND, SUBKEY and OTHER-SUBKEYS. (concat (format "Run `%s' and bound it to `%s' for subsequent calls." command (if (numberp subkey) (single-key-description subkey) subkey)) (if other-subkeys (helm-basic-docstring-from-alist other-subkeys) "")))) (define-key map key com))) (defun helm-basic-docstring-from-alist (alist) (let* ((len (length alist)) (osk (cl-loop for (k . v) in alist for count from 1 for sep = (cond ((and (= count len) (> len 1)) " and ") ((> count 1) ",") (t "")) for key = (if (numberp k) (single-key-description k) k) concat (format "%s`%s'" sep key) into ks concat (format "%s`%s'" sep v) into kv finally return (list ks kv)))) (format "\nBound as well %s to %s%s." (car osk) (if (> len 1) "respectively " "") (cadr osk)))) ;;; Keymap ;; ;; (defvar helm-map (let ((map (make-sparse-keymap))) (set-keymap-parent map minibuffer-local-map) (define-key map (kbd "") #'helm-next-line) (define-key map (kbd "") #'helm-previous-line) (define-key map (kbd "C-n") #'helm-next-line) (define-key map (kbd "C-p") #'helm-previous-line) (define-key map (kbd "") #'helm-follow-action-forward) (define-key map (kbd "") #'helm-follow-action-backward) (define-key map (kbd "") #'helm-previous-page) (define-key map (kbd "") #'helm-next-page) (define-key map (kbd "M-v") #'helm-scroll-up) (define-key map (kbd "C-v") #'helm-scroll-down) (define-key map (kbd "M-<") #'helm-beginning-of-buffer) (define-key map (kbd "M->") #'helm-end-of-buffer) (define-key map (kbd "C-g") #'helm-keyboard-quit) (define-key map (kbd "") #'helm-maybe-exit-minibuffer) (define-key map (kbd "C-i") #'helm-select-action) (define-key map (kbd "C-j") #'helm-execute-persistent-action) (define-key map (kbd "C-o") #'helm-next-source) (define-key map (kbd "M-o") #'helm-previous-source) (define-key map (kbd "") #'helm-next-source) (define-key map (kbd "") #'helm-previous-source) (define-key map (kbd "C-l") #'helm-recenter-top-bottom-other-window) (define-key map (kbd "M-C-l") #'helm-reposition-window-other-window) (define-key map (kbd "C-M-v") #'helm-scroll-other-window) (define-key map (kbd "M-") #'helm-scroll-other-window) (define-key map (kbd "C-M-y") #'helm-scroll-other-window-down) (define-key map (kbd "C-M-S-v") #'helm-scroll-other-window-down) (define-key map (kbd "M-") #'helm-scroll-other-window-down) (define-key map (kbd "") #'helm-scroll-other-window) (define-key map (kbd "") #'helm-scroll-other-window-down) (define-key map (kbd "C-@") #'helm-toggle-visible-mark) (define-key map (kbd "C-SPC") #'helm-toggle-visible-mark-forward) (define-key map (kbd "M-SPC") #'helm-toggle-visible-mark-backward) (define-key map (kbd "M-[") nil) (define-key map (kbd "M-(") #'helm-prev-visible-mark) (define-key map (kbd "M-)") #'helm-next-visible-mark) (define-key map (kbd "C-k") #'helm-delete-minibuffer-contents) (define-key map (kbd "DEL") #'helm-delete-char-backward) (define-key map (kbd "C-x C-f") #'helm-quit-and-find-file) (define-key map (kbd "M-m") #'helm-toggle-all-marks) (define-key map (kbd "M-a") #'helm-mark-all) (define-key map (kbd "M-U") #'helm-unmark-all) (define-key map (kbd "C-M-a") #'helm-show-all-candidates-in-source) (define-key map (kbd "C-M-f") #'helm-limit-to-sources) (define-key map (kbd "C-M-e") #'helm-display-all-sources) (define-key map (kbd "C-s") #'undefined) (define-key map (kbd "M-s") #'undefined) (define-key map (kbd "C-r") #'undefined) (define-key map (kbd "C-M-r") #'undefined) (define-key map (kbd "C-M-s") #'undefined) (define-key map (kbd "C-}") #'helm-narrow-window) (define-key map (kbd "C-{") #'helm-enlarge-window) (define-key map (kbd "C-c -") #'helm-swap-windows) (define-key map (kbd "C-c _") #'helm-toggle-full-frame) (define-key map (kbd "C-z") #'helm-toggle-full-frame) (define-key map (kbd "C-c %") #'helm-exchange-minibuffer-and-header-line) (define-key map (kbd "C-c C-y") #'helm-yank-selection) (define-key map (kbd "C-c C-k") #'helm-kill-selection-and-quit) (define-key map (kbd "C-c C-i") #'helm-insert-or-copy) (define-key map (kbd "C-c C-f") #'helm-follow-mode) (define-key map (kbd "C-c C-u") #'helm-refresh) (define-key map (kbd "C-c >") #'helm-toggle-truncate-line) (define-key map (kbd "C-c l") #'helm-display-line-numbers-mode) (define-key map (kbd "M-p") #'previous-history-element) (define-key map (kbd "M-n") #'next-history-element) ;; Unbind `previous-matching-history-element' which is non sense for helm. (define-key map (kbd "M-r") #'undefined) (define-key map (kbd "C-!") #'helm-toggle-suspend-update) (define-key map (kbd "C-x b") #'helm-resume-previous-session-after-quit) (define-key map (kbd "C-x C-b") #'helm-resume-list-buffers-after-quit) (helm-define-key-with-subkeys map (kbd "C-c n") ?n #'helm-run-cycle-resume) ;; Disable `file-cache-minibuffer-complete'. (define-key map (kbd "") #'undefined) ;; Multi keys (define-key map (kbd "C-t") #'helm-toggle-resplit-and-swap-windows) ;; Debugging command (define-key map (kbd "C-h C-d") #'helm-enable-or-switch-to-debug) (define-key map (kbd "C-h c") #'helm-customize-group) (define-key map (kbd "C-h d") #'helm-debug-output) ;; Allow to eval keymap without errors. (define-key map [f1] nil) (define-key map (kbd "C-h C-h") #'undefined) (define-key map (kbd "C-h h") #'undefined) (helm-define-key-with-subkeys map (kbd "C-w") ?\C-w #'helm-yank-text-at-point '((?\C-_ . helm-undo-yank-text-at-point))) ;; Use `describe-mode' key in `global-map'. (dolist (k (where-is-internal #'describe-mode global-map)) (define-key map k #'helm-help)) ;; Bind all actions from f1 to f12, `helm-select-nth-action' ;; counts from 0, i.e. (helm-select-nth-action 0) = action 1. (dotimes (n 12) (define-key map (kbd (format "" (1+ n))) (lambda () (interactive) (helm-select-nth-action n)))) map) "Keymap for helm.") (defun helm-customize-group-1 (group) (require 'cus-edit) (let ((name (format "*Customize Group: %s*" (custom-unlispify-tag-name group)))) (if (buffer-live-p (get-buffer name)) (switch-to-buffer name) (custom-buffer-create (list (list group 'custom-group)) name (concat " for group " (custom-unlispify-tag-name group)))))) (defun helm-customize-group () "Jump to customization group of current source. Default to Helm group when group is not defined in source." (interactive) (let ((source (or (helm-get-current-source) (helm-comp-read "Customize variables for: " (cl-loop for src in (with-helm-buffer helm-sources) collect `(,(assoc-default 'name src) . ,src)) :allow-nest t :exec-when-only-one t)))) (helm-run-after-exit 'helm-customize-group-1 (helm-get-attr 'group source)))) (put 'helm-customize-group 'helm-only t) (defun helm--action-at-nth-set-fn-1 (value &optional negative) (dotimes (n 9) (let ((key (format value (1+ n))) (fn (lambda () (interactive) (helm-execute-selection-action-at-nth (if negative (- (1+ n)) (1+ n)))))) (define-key helm-map (kbd key) nil) (define-key helm-map (kbd key) fn)))) (defun helm--action-at-nth-set-fn- (var val) (set var val) (helm--action-at-nth-set-fn-1 val 'negative)) (defun helm--action-at-nth-set-fn+ (var val) (set var val) (helm--action-at-nth-set-fn-1 val)) (defcustom helm-action-at-nth-negative-prefix-key "C-x %d" "The prefix key to execute default action on nth <-n> candidate. This is a format spec where %d will be replaced by the candidate number. This is useful when `helm-display-line-numbers-mode' is turned on. NOTE: `setq' have no effect until you restart Emacs, use customize for immediate effect." :group 'helm :type 'string :set #'helm--action-at-nth-set-fn-) (defcustom helm-action-at-nth-positive-prefix-key "C-c %d" "The prefix key to execute default action on nth <+n> candidate. This is a format spec where %d will be replaced by the candidate number. This is useful when `helm-display-line-numbers-mode' is turned on. NOTE: `setq' have no effect until you restart Emacs, use customize for immediate effect." :group 'helm :type 'string :set #'helm--action-at-nth-set-fn+) (defgroup helm nil "Open Helm." :prefix "helm-" :group 'convenience) ;; Easy access to customize ;;;###autoload (defun helm-configuration () "Customize Helm." (interactive) (customize-group "helm")) (defcustom helm-completion-window-scroll-margin 5 "`scroll-margin' to use for Helm completion window. Set to 0 to disable. NOTE: This has no effect when `helm-display-source-at-screen-top' id is non-nil." :group 'helm :type 'integer) (defcustom helm-left-margin-width 0 "`left-margin-width' value for the `helm-buffer'." :group 'helm :type 'integer) (defcustom helm-display-source-at-screen-top t "Display candidates at the top of screen. This happens with `helm-next-source' and `helm-previous-source'. NOTE: When non-nil (default), disable `helm-completion-window-scroll-margin'." :group 'helm :type 'boolean) (defcustom helm-candidate-number-limit 500 "Global limit for number of candidates displayed. When the pattern is empty, the number of candidates shown will be as set here instead of the entire list, which may be hundreds or thousands. Since narrowing and filtering rapidly reduces available candidates, having a small list will keep the interface responsive. Set this value to nil for no limit." :group 'helm :type '(choice (const :tag "Disabled" nil) integer)) (defcustom helm-input-idle-delay 0.01 "Idle time before updating, specified in seconds." :group 'helm :type 'float) (defcustom helm-exit-idle-delay 0 "Idle time before exiting minibuffer while Helm is updating. Has no affect when helm-buffer is up to date (i.e. exit without delay in this condition)." :group 'helm :type 'float) (defvaralias 'helm-samewindow 'helm-full-frame) (make-obsolete-variable 'helm-samewindow 'helm-full-frame "1.4.8.1") (defcustom helm-full-frame nil "Use current window for showing candidates. If t, then Helm does not pop-up a new window." :group 'helm :type 'boolean) (defcustom helm-candidate-separator (if (fontp (char-displayable-p (read "#x2015"))) "――――――――――――――――――――――――――――――――――――――" "--------------------------------------") "Candidates separator of `multiline' source." :group 'helm :type 'string) (defcustom helm-save-configuration-functions '(set-window-configuration . helm-current-window-configuration) "Functions used to restore or save configurations for frames and windows. Specified as a pair of functions, where car is the restore function and cdr is the save function. To save and restore frame configuration, set this variable to \\='(set-frame-configuration . helm-current-frame-configuration) NOTE: This may not work properly with own-frame minibuffer settings. Older versions saves/restores frame configuration, but the default has changed now to avoid flickering." :group 'helm :type 'sexp) (defcustom helm-display-function 'helm-default-display-buffer "Function used to display `helm-buffer'. Local value in `helm-buffer' will take precedence on this default value. Commands that are in `helm-commands-using-frame' will have `helm-buffer' displayed in frame, `helm-display-function' being ignored. If no local value is found and current command is not one of `helm-commands-using-frame' use this default value. The function in charge of deciding which value use is `helm-resolve-display-function'. To set it locally to `helm-buffer' in Helm sources use `helm-set-local-variable' in init function or use :display-function slot in `helm' call." :group 'helm :type 'symbol) (defcustom helm-case-fold-search 'smart "Adds \\='smart' option to `case-fold-search'. Smart option ignores case for searches as long as there are no upper case characters in the pattern. Use nil or t to turn off smart behavior and use `case-fold-search' behavior. Default is smart. NOTE: Case fold search has no effect when searching asynchronous sources, which relies on customized features implemented directly into their execution process. See helm-grep.el for an example." :group 'helm :type '(choice (const :tag "Ignore case" t) (const :tag "Respect case" nil) (other :tag "Smart" smart))) (defcustom helm-file-name-case-fold-search (if (memq system-type '(cygwin windows-nt ms-dos darwin)) t helm-case-fold-search) "Local setting of `helm-case-fold-search' for reading filenames. See `helm-case-fold-search' for more info." :group 'helm :type 'symbol) (defcustom helm-reuse-last-window-split-state nil "Use the same state of window split, vertical or horizontal. `helm-toggle-resplit-window' for the next helm session will use the same window scheme as the previous session unless `helm-split-window-default-side' is \\='same or \\='other." :group 'helm :type 'boolean) (defcustom helm-split-width-threshold nil "The value of `split-width-threshold' for helm windows. This affect the behavior of `helm-split-window-default-fn'. When the value is an integer, `split-window-sensibly' is used inconditionally and all the helm variables that affect window splitting are ignored." :group 'helm :type '(choice (const :tag "Maybe use `split-window-sensibly'" nil) (integer :tag "Inconditionally use `split-window-sensibly'"))) (defcustom helm-split-window-preferred-function 'helm-split-window-default-fn "Default function used for splitting window." :group 'helm :type 'function) (defcustom helm-split-window-default-side 'below "The default side to display `helm-buffer'. Must be one acceptable arg for `split-window' SIDE, that is `below', `above', `left' or `right'. Other acceptable values are `same' which always displays `helm-buffer' in current window and `other' that displays `helm-buffer' below if only one window or in `other-window-for-scrolling' when available. A nil value has same effect as `below'. If `helm-full-frame' is non-nil, it take precedence over this setting. See also `helm-split-window-inside-p' and `helm-always-two-windows' that take precedence over this. NOTE: this has no effect if `helm-split-window-preferred-function' is not `helm-split-window-default-fn' unless this new function can handle this." :group 'helm :type 'symbol) (defcustom helm-split-window-other-side-when-one-window 'below "Place for `helm-window' when `helm-split-window-default-side' is \\='other. The default side to display `helm-buffer' when (1) `helm-split-window-default-side' is \\='other and (2) the current frame only has one window. Possible values are acceptable args for `split-window' SIDE, that is `below', `above', `left' or `right'. If `helm-full-frame' is non-nil, it takes precedence over this setting. See also `helm-split-window-inside-p' and `helm-always-two-windows' that takes precedence over this. NOTE: this has no effect if `helm-split-window-preferred-function' is not `helm-split-window-default-fn' unless this new function can handle this." :group 'helm :type 'symbol) (defcustom helm-display-buffer-default-height nil "Initial height of `helm-buffer', specified as an integer or a function. The function should take one arg and be responsible for re-sizing the window; function's return value is ignored. Note that this has no effect when the split is vertical. See `display-buffer' for more info." :group 'helm :type '(choice integer function)) (defcustom helm-display-buffer-default-width nil "Initial width of `helm-buffer', specified as an integer or a function. The function should take one arg and be responsible for re-sizing the window; function's return value is ignored. Note that this have no effect when the split is horizontal. See `display-buffer' for more info." :group 'helm :type '(choice integer function)) (defvaralias 'helm-split-window-in-side-p 'helm-split-window-inside-p) (make-obsolete-variable 'helm-split-window-in-side-p 'helm-split-window-inside-p "2.8.6") (defcustom helm-split-window-inside-p nil "Force split inside selected window when non-nil. See also `helm-split-window-default-side'. NOTE: this has no effect if `helm-split-window-preferred-function' is not `helm-split-window-default-fn' unless this new function can handle this." :group 'helm :type 'boolean) (defcustom helm-always-two-windows t "When non-nil Helm uses two windows in this frame. I.e. `helm-buffer' in one window and `helm-current-buffer' in the other. Note: this has no effect when `helm-split-window-inside-p' is non-nil, or when `helm-split-window-default-side' is set to \\='same. When `helm-autoresize-mode' is enabled, setting this to nil will have no effect. Also when non-nil it overrides the effect of `helm-split-window-default-side' set to `other'." :group 'helm :type 'boolean) (defcustom helm-display-buffer-width 72 "Frame width when displaying helm-buffer in own frame." :group 'helm :type 'integer) (defcustom helm-display-buffer-height 20 "Frame height when displaying helm-buffer in own frame." :group 'helm :type 'integer) (defcustom helm-default-display-buffer-functions nil "Action functions to pass to `display-buffer'. See (info \"(elisp) Buffer Display Action Functions\"). It may override others helm window related variables settings like `helm-always-two-windows', `helm-split-window-inside-p' etc..." :group 'helm :type '(repeat symbol)) (defcustom helm-default-display-buffer-alist nil "Additional alist to pass to `display-buffer' action. See (info \"(elisp) Action Alists for Buffer Display\"). It may override others helm window related variables settings like `helm-always-two-windows', `helm-split-window-inside-p' etc... Note that window-height and window-width have to be configured in `helm-display-buffer-height' and `helm-display-buffer-width'." :group 'helm :type '(alist :key-type symbol :value-type sexp)) (defcustom helm-sources-using-default-as-input '(helm-source-imenu helm-source-imenu-all helm-source-info-elisp helm-source-etags-select helm-source-man-pages helm-source-occur helm-source-moccur helm-source-grep-ag helm-source-grep-git helm-source-grep) "List of Helm sources that need to use `helm-maybe-use-default-as-input'. When a source is a member of this list, default `thing-at-point' will be used as input." :group 'helm :type '(repeat (choice symbol))) (defcustom helm-delete-minibuffer-contents-from-point t "When non-nil, `helm-delete-minibuffer-contents' deletes region from `point'. Otherwise it deletes `minibuffer-contents'. See documentation for `helm-delete-minibuffer-contents'." :group 'helm :type 'boolean) (defcustom helm-follow-mode-persistent nil "When non-nil, save last state of `helm-follow-mode' for the next Emacs sessions. Each time you turn on or off `helm-follow-mode', the current source name will be stored or removed from `helm-source-names-using-follow'. Note that this may be disabled in some places where it is unsafe to use because persistent action is changing according to context." :group 'helm :type 'boolean) (defcustom helm-source-names-using-follow nil "A list of source names to have follow enabled. This list of source names will be used only when `helm-follow-mode-persistent' is non-nil. You don't have to customize this yourself unless you really want and know what you are doing, instead just set `helm-follow-mode-persistent' to non-nil and as soon as you turn on or off `helm-follow-mode' (C-c C-f) in a source, Helm will save or remove source name in this variable." :group 'helm :type '(repeat (choice string))) (defcustom helm-prevent-escaping-from-minibuffer t "Prevent escaping from minibuffer with `other-window' during the Helm session." :group 'helm :type 'boolean) (defcustom helm-allow-mouse t "Allow mouse usage during the Helm session when non-nil. Note that this also allows moving out of minibuffer when clicking outside of `helm-buffer', so it is up to you to get back to Helm by clicking back in `helm-buffer' or minibuffer." :group 'helm :type 'boolean) (defcustom helm-move-to-line-cycle-in-source t "Cycle to the beginning or end of the list after reaching the bottom or top. This applies when using `helm-next/previous-line'." :group 'helm :type 'boolean) (defcustom helm-fuzzy-match-fn 'helm-fuzzy-match "The function for fuzzy matching in `helm-source-sync' based sources." :group 'helm :type 'function) (defcustom helm-fuzzy-search-fn 'helm-fuzzy-search "The function for fuzzy matching in `helm-source-in-buffer' based sources." :group 'helm :type 'function) (defcustom helm-fuzzy-sort-fn 'helm-fuzzy-matching-default-sort-fn "The sort transformer function used in fuzzy matching." :group 'helm :type 'function) (defcustom helm-fuzzy-matching-highlight-fn #'helm-fuzzy-default-highlight-match "The function to highlight fuzzy matches. The function must have the same signature as `helm-fuzzy-default-highlight-match' which is the default." :group 'helm :type 'function) (defcustom helm-autoresize-max-height 40 "Specify maximum height and defaults to percent of Helm window's frame height. See `fit-window-to-buffer' for more infos." :group 'helm :type 'integer) (defcustom helm-autoresize-min-height 10 "Specify minimum height and defaults to percent of Helm window's frame height. If nil, `window-min-height' is used. See `fit-window-to-buffer' for details." :group 'helm :type 'integer) (defcustom helm-input-method-verbose-flag nil "The default value for `input-method-verbose-flag' used in Helm minibuffer. It is nil by default, which does not turn off input method. Helm updates and exits without interruption -- necessary for complex methods. If set to any other value as per `input-method-verbose-flag', then use `C-\\' to disable the `current-input-method' to exit or update Helm." :group 'helm :type '(radio :tag "A flag to control extra guidance for input methods in helm." (const :tag "Never provide guidance" nil) (const :tag "Always provide guidance" t) (const :tag "Provide guidance only for complex methods" complex-only))) (defcustom helm-display-header-line t "Display header-line when non nil. It has to be non nil when you want to display minibuffer contents in there with `helm-echo-input-in-header-line'." :group 'helm :type 'boolean) (defcustom helm-inherit-input-method t "Inherit `current-input-method' from `current-buffer' when non-nil. The default is to enable this by default and then toggle `toggle-input-method'." :group 'helm :type 'boolean) (defcustom helm-echo-input-in-header-line nil "Send current input to header-line when non-nil. Note that `helm-display-header-line' has to be non nil as well for this to take effect." :group 'helm :type 'boolean) (defcustom helm-header-line-space-before-prompt 'left-fringe "Specify the space before prompt in header-line. This will be used when `helm-echo-input-in-header-line' is non-nil. Value can be one of the symbols \\='left-fringe or \\='left-margin or an integer specifying the number of spaces before prompt. Note that on input longer that `window-width' the continuation string will be shown on left side of window without taking care of this." :group 'helm :type '(choice (symbol (const :tag "Fringe" left-fringe) (const :tag "Margin" left-margin)) integer)) (defcustom helm-tramp-connection-min-time-diff 5 "Value of `tramp-connection-min-time-diff' for Helm remote processes. If set to zero Helm remote processes are not delayed. Setting this to a value less than 5 or disabling it with a zero value is risky, however on Emacs versions starting at 24.5 it seems it is now possible to disable it. Anyway at any time in Helm you can suspend your processes while typing by hitting \\ `\\[helm-toggle-suspend-update]'. Only async sources than use a sentinel calling `helm-process-deferred-sentinel-hook' are affected by this." :type 'integer :group 'helm) (defcustom helm-show-action-window-other-window 'left "Show action buffer beside `helm-buffer' when non-nil. If nil don't split and replace helm-buffer by the action buffer in same window. Possible value are left, right, below and above." :group 'helm :type '(choice (const :tag "Split at left" left) (const :tag "Split at right" right) (const :tag "Split below" below) (const :tag "Split above" above) (const :tag "Don't split" nil))) (defcustom helm-cycle-resume-delay 1.0 "Delay used before resuming in `helm-run-cycle-resume'." :type 'float :group 'helm) (defcustom helm-display-buffer-reuse-frame nil "When non nil Helm frame is not deleted and reused in next sessions. This was used to workaround a bug in Emacs where frames where popping up slowly, now that the bug have been fixed upstream \(emacs-27) probably you don't want to use this any more. On emacs-26 set `x-wait-for-event-timeout' to nil to have your frames popping up fast." :group 'helm :type 'boolean) (defcustom helm-commands-using-frame nil "A list of commands where `helm-buffer' is displayed in a frame." :group 'helm :type '(repeat symbol)) (defcustom helm-actions-inherit-frame-settings t "Actions inherit Helm frame settings of initial command when non nil." :group 'helm :type 'boolean) (defcustom helm-use-undecorated-frame-option t "Display Helm frame undecorated when non nil. This option has no effect with Emacs versions lower than 26." :group 'helm :type 'boolean) (defcustom helm-frame-background-color nil "Background color for Helm frames, a string. Fallback to default face background when nil." :group 'helm :type 'string) (defcustom helm-frame-foreground-color nil "Foreground color for Helm frames, a string. Fallback to default face foreground when nil" :group 'helm :type 'string) (defcustom helm-frame-alpha 100 "Alpha parameter for Helm frames, an integer. Fallback to 100 when nil." :group 'helm :type 'integer) (defcustom helm-use-frame-when-more-than-two-windows nil "Display Helm buffer in frame when more than two windows." :group 'helm :type 'boolean) (defvaralias 'helm-use-frame-when-dedicated-window 'helm-use-frame-when-no-suitable-window) (defcustom helm-use-frame-when-no-suitable-window nil "Display Helm buffer in frame when Helm is started from a dedicated window." :group 'helm :type 'boolean) (make-obsolete-variable 'helm-use-frame-when-dedicated-window 'helm-use-frame-when-no-suitable-window "3.8.1") (defcustom helm-default-prompt-display-function #'helm-set-default-prompt-display "The function to use to set face of fake cursor in header-line." :group 'helm :type 'function) (defcustom helm-truncate-lines nil "The value of `truncate-lines' when Helm starts. You can toggle later `truncate-lines' with \\\\[helm-toggle-truncate-line]." :group 'helm :type 'boolean) (defcustom helm-visible-mark-prefix "*" "Prefix used in margin for marked candidates. Set this to an empty string if you don't want prefix in margin when marking." :group 'helm :type 'string) (defvar helm-update-edebug nil "Development feature. If set to true then all functions invoked after `helm-update' can be instrumented by `edebug' for stepping. `helm--maybe-use-while-no-input' then doesn't use `while-no-input', because `while-no-input' throws on `edebug' command key input.") ;;; Faces ;; ;; (defgroup helm-faces nil "Customize the appearance of Helm." :prefix "helm-" :group 'faces :group 'helm) (defface helm-source-header `((((type tty pc)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "blue" :foreground "white") (((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "#22083397778B" :foreground "white" :weight bold :height 1.3 :family "Sans Serif") (((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "#abd7f0" :foreground "black" :weight bold :height 1.3 :family "Sans Serif")) "Face for source header in the Helm buffer." :group 'helm-faces) (defface helm-visible-mark `((((min-colors 88) (background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "green1" :foreground "black") (((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "green" :foreground "black") (((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "#d1f5ea") (((min-colors 88)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "green1") (t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "green")) "Face for visible mark." :group 'helm-faces) (defface helm-header `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit header-line)) "Face for header lines in the Helm buffer." :group 'helm-faces) (defface helm-candidate-number `((((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "yellow" :foreground "black") (((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "#faffb5" :foreground "black")) "Face for candidate number in mode-line." :group 'helm-faces) (defface helm-candidate-number-suspended `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit helm-candidate-number :inverse-video t)) "Face for candidate number in mode-line when Helm is suspended." :group 'helm-faces) (defface helm-selection `((((type tty pc)) ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit isearch) (((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "ForestGreen" :distant-foreground "black") (((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :background "#b5ffd1" :distant-foreground "black")) "Face for currently selected item in the Helm buffer." :group 'helm-faces) (defface helm-separator `((((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "red") (((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "#ffbfb5")) "Face for multiline source separator." :group 'helm-faces) (defface helm-action `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :underline t)) "Face for action lines in the Helm action buffer." :group 'helm-faces) (defface helm-prefarg `((((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "green") (((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "red")) "Face for showing prefix arg in mode-line." :group 'helm-faces) (defface helm-match `((((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "#b00000") (((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "gold1")) "Face used to highlight matches." :group 'helm-faces) (defface helm-header-line-left-margin `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "black" :background "yellow")) "Face used to highlight helm-header sign in left-margin. This face is used only when using `helm-echo-input-in-header-line' and pattern is wider then screen." :group 'helm-faces) (defface helm-minibuffer-prompt `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit minibuffer-prompt)) "Face used for the minibuffer/headline prompt (such as Pattern:) in Helm." :group 'helm-faces) (defface helm-eob-line `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit default)) "Face for empty line at end of sources in the Helm buffer. Allow specifying the height of this line." :group 'helm-faces) (defface helm-mark-prefix `((t :inherit default)) "Face for string `helm-visible-mark-prefix'." :group 'helm-faces) (defface helm-dim-prompt `((((class color) (min-colors 88) (background dark)) :foreground "DimGray") (t :inherit shadow)) "Face used for shadowing prompt while updating." :group 'helm-faces) ;;; Variables. ;; ;; (defvar helm-selection-overlay nil "Overlay used to highlight the currently selected item.") (defvar helm-async-processes nil "List of information about asynchronous processes managed by Helm.") (defvar helm-before-initialize-hook nil "Runs before Helm initialization. This hook runs before init functions in `helm-sources', which is before creation of `helm-buffer'. Set local variables for `helm-buffer' that need a value from `current-buffer' with `helm-set-local-variable'.") (defvar helm-after-initialize-hook nil "Runs after Helm initialization. This hook runs after `helm-buffer' is created but not from `helm-buffer'. The hook needs to specify in which buffer to run.") (defvaralias 'helm-update-hook 'helm-after-update-hook) (make-obsolete-variable 'helm-update-hook 'helm-after-update-hook "1.9.9") (defvar helm-after-update-hook nil "Runs after updating the Helm buffer with the new input pattern.") (defvar helm-before-update-hook nil "Runs before updating the Helm buffer with the new input pattern.") (defvar helm-cleanup-hook nil "Runs after exiting the minibuffer and before performing an action. This hook runs even if Helm exits the minibuffer abnormally (e.g. via `helm-keyboard-quit').") (defvar helm-select-action-hook nil "Runs when opening the action buffer.") (defvar helm-before-action-hook nil "Runs before executing action. Unlike `helm-cleanup-hook', this hook runs before Helm closes the minibuffer and also before performing an action.") (defvar helm-after-action-hook nil "Runs after executing action.") (defvar helm-exit-minibuffer-hook nil "Runs just before exiting the minibuffer. This hook runs when Helm exits the minibuffer normally (e.g., via candidate selection), but does NOT run if Helm exits the minibuffer abnormally (e.g. via `helm-keyboard-quit').") (defvar helm-after-persistent-action-hook nil "Runs after executing persistent action.") (defvar helm-move-selection-before-hook nil "Runs before moving selection in `helm-buffer'.") (defvar helm-move-selection-after-hook nil "Runs after moving selection in `helm-buffer'.") (defvar helm-after-preselection-hook nil "Runs after pre-selection in `helm-buffer'.") (defvar helm-window-configuration-hook nil "Runs when switching to and from the action buffer. Should run also at end of `helm-display-function'.") (defvar helm-execute-action-at-once-if-one nil "When non-nil execute the default action and then exit if only one candidate. If symbol \\='current-source is given as value exit if only one candidate in current source. This variable accepts a function with no args that should returns a boolean value or \\='current-source.") (defvar helm-quit-if-no-candidate nil "When non-nil, quit if there are no candidates. This variable accepts a function.") (defvar helm-debug-function #'helm-default-debug-function "A Function that returns a list of values to print in `helm-debug-output' buffer.") (defvar helm-debug-output-buffer "*Helm Debug*") (defvar helm-debug-buffer "*Debug Helm Log*") (defvar helm-debug nil "If non-nil, write log message to `helm-debug-buffer'. Default is nil, which disables writing log messages because the size of `helm-debug-buffer' grows quickly.") (defvar helm-mode-line-string "\ \\\ \\[helm-help]:Help \ \\[helm-select-action]:Act \ \\[helm-maybe-exit-minibuffer]/\ f1..f12:NthAct \ \\[helm-toggle-suspend-update]:Tog.suspend \ \\[helm-customize-group]:Conf" "Help string displayed by Helm in the mode-line. It is either a string or a list of two string arguments where the first string is the name and the second string is displayed in the mode-line. When nil, it defaults to `mode-line-format'.") (defvar helm-minibuffer-set-up-hook '(helm-hide-minibuffer-maybe) "Hook that runs at minibuffer initialization. A hook useful for modifying minibuffer settings in Helm. Uses `helm-hide-minibuffer-maybe' by default which hide minibuffer contents with header-line contents when `helm-echo-input-in-header-line' is non nil.") (defvar helm-help-message "* Helm Generic Help ** Basics To navigate in this Help buffer see [[Helm help][here]]. Helm narrows down the list of candidates as you type a filter pattern. See [[Matching in Helm][Matching in Helm]]. Helm accepts multiple space-separated patterns, each pattern can be negated with \"!\". Helm also supports fuzzy matching in some places when specified, you will find several variables to enable fuzzy matching in diverse [[Helm sources][sources]], see [[https://github.com/emacs-helm/helm/wiki/Fuzzy-matching][fuzzy-matching]] in helm-wiki for more infos. Helm generally uses familiar Emacs keys to navigate the list. Here follow some of the less obvious bindings: - `\\\\[helm-maybe-exit-minibuffer]' selects the candidate from the list, executes the default action upon exiting the Helm session. - `\\\\[helm-execute-persistent-action]' executes the default action but without exiting the Helm session. Not all sources support this. - `\\\\[helm-select-action]' displays a list of actions available on current candidate or all marked candidates. The default binding is ordinarily used for completion, but that would be redundant since Helm completes upon every character entered in the prompt. See [[https://github.com/emacs-helm/helm/wiki#helm-completion-vs-emacs-completion][Helm wiki]]. Note: In addition to the default actions list, additional actions appear depending on the type of the selected candidate(s). They are called filtered actions. ** Helm sources Helm uses what's called sources to provide different kinds of completions. Each Helm session can handle one or more source. A source is an alist object which is build from various classes, see [[Writing your own Helm sources][here]] and [[https://github.com/emacs-helm/helm/wiki/Developing#creating-a-source][Helm wiki]] for more infos. *** Configure sources You will find in Helm sources already built and bound to a variable called generally `helm-source-'. In this case it is an alist and you can change the attributes (keys) values using `helm-set-attr' function in your configuration. Of course you have to ensure before calling `helm-set-attr' that the file containing source is loaded, e.g. with `with-eval-after-load'. Of course you can also completely redefine the source but this is generally not elegant as it duplicate for its most part code already defined in Helm. You will find also sources that are not built and even not bound to any variables because they are rebuilded at each start of a Helm session. In this case you can add a defmethod called `helm-setup-user-source' to your config: #+begin_src elisp (cl-defmethod helm-setup-user-source ((source helm-moccur-class)) (setf (slot-value source 'follow) -1)) #+end_src See [[https://github.com/emacs-helm/helm/wiki/FAQ#why-is-a-customizable-helm-source-nil][here]] for more infos, and for more complex examples of configuration [[https://github.com/thierryvolpiatto/emacs-tv-config/blob/master/init-helm.el#L340][here]]. ** Modify keybindings in Helm Helm main keymap is `helm-map', all keys bound in this map apply to all Helm sources. However, most sources have their own keymap, with each binding overriding its counterpart in `helm-map', you can see all bindings in effect in the [[Commands][Commands]] section (available only if the source has its own keymap and documentation of course). ** Matching in Helm All that you write in minibuffer is interpreted as a regexp or multiple regexps if separated by a space. This is true for most sources unless the developer of the source has disabled it or have choosen to use fuzzy matching. Even if a source has fuzzy matching enabled, Helm will switch to multi match as soon as it detects a space in the pattern. It may also switch to multi match as well if pattern starts with a \"^\" beginning of line sign. In those cases each pattern separated with space should be a regexp and not a fuzzy pattern. When using multi match patterns, each pattern starting with \"!\" is interpreted as a negation i.e. match everything but this. ** Moving selection to a specific candidate Once you found the candidate you were searching by narrowing candidates, you may want to keep the selection on this candidate after deleting minibuffer contents and showing the whole list of candidates, this is useful for example when examining and searching git log or similar. To achieve this, ensure all candidates are shown at startup or at least enough candidates to be sure your candidate is not beyond candidate-number-limit, you can use \\\\[helm-show-all-candidates-in-source] for this with a prefix arg or a numeric prefix arg. Once this is done narrow down your list by typing in minibuffer, once the selection is on the candidate you want to keep, hit C-u \\\\[helm-delete-minibuffer-contents]. Another way to achieve this is to mark the candidate, then delete minibuffer contents with \\\\[helm-delete-minibuffer-contents] and reach back the mark with \\\\[helm-next-visible-mark]. *** Completion-styles UPDATE: At version 3.8.0 Helm default is now to NOT use `completion-styles' i.e. now `helm-completion-style' default to 'helm and no more to 'emacs. If you want to use `completion-styles' in Helm customize `helm-completion-style' to 'emacs. Helm generally fetches its candidates with the :candidates function up to `helm-candidate-number-limit' and then applies match functions to these candidates according to `helm-pattern'. But Helm allows matching candidates directly from the :candidates function using its own `completion-styles'. Helm provides 'helm completion style but also 'helm-flex completion style for Emacs<27 that don't have 'flex completion style, otherwise (emacs-27) 'flex completion style is used to provide fuzzy aka flex completion. When using helm-fuzzy as `helm-completion-style' helm use its own fuzzy implementation which have nothing to do with emacs `flex' style. Any Helm sources can use `completion-styles' by using :match-dynamic slot and building their :candidates function with `helm-dynamic-completion'. Example: #+begin_src elisp (helm :sources (helm-build-sync-source \"test\" :candidates (helm-dynamic-completion '(foo bar baz foab) 'symbolp) :match-dynamic t) :buffer \"*helm test*\") #+end_src By default Helm sets up `completion-styles' and always adds 'helm to it. However the flex completion styles are not added. This is up to the user if she wants to have such completion to enable this. As specified above use 'flex for emacs-27 and 'helm-flex for emacs-26. Anyway, 'helm-flex is not provided in `completion-styles-alist' if 'flex is present. Finally Helm provides two user variables to control `completion-styles' usage: `helm-completion-style' and `helm-completion-styles-alist'. Both variables are customizable. The former allows retrieving previous Helm behavior if needed, by setting it to `helm' or `helm-fuzzy', default being `emacs' which allows dynamic completion and usage of `completion-styles'. The second allows setting `helm-completion-style' per mode and also specifying `completion-styles' per mode (see its docstring). Note that these two variables take effect only in helm-mode i.e. in all that uses `completion-read' or `completion-in-region', IOW all helmized commands. File completion in `read-file-name' family doesn't obey completion-styles and has its own file completion implementation. Native Helm commands using `completion-styles' doesn't obey `helm-completion-style' and `helm-completion-styles-alist' (e.g., helm-M-x). Also for a better control of styles in native Helm sources (not helmized by helm-mode) using :match-dynamic, `helm-dynamic-completion' provides a STYLES argument that allows specifying explicitely styles for this source. NOTE: Some old completion styles are not working fine with Helm and are disabled by default in `helm-blacklist-completion-styles'. They are anyway not useful in Helm because 'helm style supersedes these styles. ** Helm mode `helm-mode' toggles Helm completion in native Emacs functions, so when you turn `helm-mode' on, commands like `switch-to-buffer' will use Helm completion instead of the usual Emacs completion buffer. *** What gets or does not get \"helmized\" when `helm-mode' is enabled? Helm provides generic completion on all Emacs functions using `completing-read', `completion-in-region' and their derivatives, e.g. `read-file-name'. Helm exposes a user variable to control which function to use for a specific Emacs command: `helm-completing-read-handlers-alist'. If the function for a specific command is nil, it turns off Helm completion. See the variable documentation for more infos. *** Helm functions vs helmized Emacs functions While there are Helm functions that perform the same completion as other helmized Emacs functions, e.g. `switch-to-buffer' and `helm-buffers-list', the native Helm functions like `helm-buffers-list' can receive new features that allow marking candidates, have several actions, etc. Whereas the helmized Emacs functions only have Helm completion, Emacs can provide no more than one action for this function. This is the intended behavior. Generally you are better off using the native Helm command than the helmized Emacs equivalent. *** Completion behavior with Helm and completion-at-point Helm is NOT completing dynamically. That means that when you are completing some text at point, completion is done against this text and subsequent characters you add AFTER this text. This allows you to use matching methods provided by Helm, that is multi matching or fuzzy matching (see [[Matching in Helm][Matching in Helm]]). Completion is not done dynamically (against `helm-pattern') because backend functions (i.e. `completion-at-point-functions') are not aware of Helm matching methods. By behaving like this, the benefit is that you can fully use Helm matching methods but you can't start a full completion against a prefix different than the initial text you have at point. Helm warns you against this by colorizing the initial input and sends a user-error message when trying to delete backward text beyond this limit at first hit on DEL. A second hit on DEL within a short delay (1s) quits Helm and delete-backward char in current-buffer. ** Helm help \\[helm-documentation]: Show all Helm documentations concatenated in one org file. From a Helm session, just hit \\\\[helm-help] to have the documentation for the current source followed by the global Helm documentation. While in the help buffer, most of the Emacs regular key bindings are available; the most important ones are shown in minibuffer. However, due to implementation restrictions, no regular Emacs keymap is used (it runs in a loop when reading the help buffer). Only simple bindings are available and they are defined in `helm-help-hkmap', which is a simple alist of (key . function). You can define or redefine bindings in help with `helm-help-define-key' or by adding/removing entries directly in `helm-help-hkmap'. See `helm-help-hkmap' for restrictions on bindings and functions. The documentation of default bindings are: | Key | Alternative keys | Command | |-----------+------------------+---------------------| | C-v | Space next | Scroll up | | M-v | b prior | Scroll down | | C-s | | Isearch forward | | C-r | | Isearch backward | | C-a | | Beginning of line | | C-e | | End of line | | C-f | right | Forward char | | C-b | left | Backward char | | C-n | down | Next line | | C-p | up | Previous line | | M-a | | Backward sentence | | M-e | | Forward sentence | | M-f | | Forward word | | M-b | | Backward word | | M-> | | End of buffer | | M-< | | Beginning of buffer | | C- | | Toggle mark | | C-M-SPACE | | Mark sexp | | RET | | Follow org link | | C-% | | Push org mark | | C-& | | Goto org mark-ring | | TAB | | Org cycle | | M- | | Toggle visibility | | M-w | | Copy region | | q | | Quit | ** Customize Helm Helm provides a lot of user variables for extensive customization. From any Helm session, type \\\\[helm-customize-group] to jump to the current source `custom' group. Helm also has a special group for faces you can access via `M-x customize-group RET helm-faces'. Note: Some sources may not have their group set and default to the `helm' group. ** Display Helm in windows and frames You can display the Helm completion buffer in many different window configurations, see the custom interface to discover the different windows configurations available (See [[Customize Helm][Customize Helm]] to jump to custom interface). When using Emacs in a graphic display (i.e. not in a terminal) you can as well display your Helm buffers in separated frames globally for all Helm commands or separately for specific Helm commands. See `helm-display-function' and `helm-commands-using-frame'. See also [[https://github.com/emacs-helm/helm/wiki/frame][helm wiki]] for more infos. There is a variable to allow reusing frame instead of deleting and creating a new one at each session, see `helm-display-buffer-reuse-frame'. Normally you don't have to use this, it have been made to workaround slow frame popup in Emacs-26, to workaround this slowness in Emacs-26 use instead #+begin_src elisp (when (= emacs-major-version 26) (setq x-wait-for-event-timeout nil)) #+end_src WARNING: There is a package called Posframe and also one called Helm-posframe, you DO NOT need these packages to display helm buffers in frames. Thus Posframe package use child frames which have no minibuffers and are by the way not compatible with Helm. ** Helm's basic operations and default key bindings | Key| Command| |----+--------| | | | \\[helm-previous-line]| Previous line | | \\[helm-next-line]| Next line | | \\[helm-scroll-up]| Scroll up | | \\[helm-scroll-down]| Scroll down | | \\[helm-scroll-other-window]| Scroll up other-window | | \\[helm-scroll-other-window-down]| Scroll down other-window | | \\[helm-maybe-exit-minibuffer]| Execute first (default) action / Select [1] | | \\[helm-beginning-of-buffer]| Goto beginning of buffer | | \\[helm-end-of-buffer]| Goto end of buffer | | \\[helm-select-action]| Show actions menu | | \\[helm-previous-source]| Previous source | | \\[helm-next-source]| Next source | | \\[helm-delete-minibuffer-contents]| Delete pattern (with prefix arg delete from point to end or all [2]) | | \\[helm-execute-persistent-action]| Persistent action (Execute and keep Helm session) | |\\[helm-toggle-resplit-and-swap-windows]|Rotate or swap windows. | |\\[helm-exchange-minibuffer-and-header-line]|Exchange minibuffer and header-line. | |\\[helm-quit-and-find-file]|Drop into `helm-find-files'. | |\\[helm-kill-selection-and-quit]|Kill display value of candidate and quit (with prefix arg, kill the real value). | |\\[helm-yank-selection]|Yank current selection into pattern. | |\\[helm-insert-or-copy]|Insert or copy marked candidates (C-u) . | |\\[helm-follow-mode]|Toggle automatic execution of persistent action. | |\\[helm-follow-action-forward]|Run persistent action then select next line. | |\\[helm-follow-action-backward]|Run persistent action then select previous line. | |\\[helm-refresh]|Recalculate and redisplay candidates. | |\\[helm-toggle-suspend-update]|Toggle candidate updates. | \[1] Behavior may change depending context in some source e.g. `helm-find-files'. \[2] Delete from point to end or all depending on the value of `helm-delete-minibuffer-contents-from-point'. When the cursor is at end of minibuffer and a prefix arg is given, delete the whole contents of minibuffer and keep selection as well when possible (depends of candidate-number-limit) when helm update to the whole list of candidates. This depends also of requires-pattern i.e. you may have a source that display nothing when there is an input < to requires-pattern. NOTE: Any of these bindings are from `helm-map' and may be overriten by the map specific to the current source in use (each source can have its own keymap). ** The actions menu You can display the action menu in the same window as helm candidates (default) or in a side window according to `helm-show-action-window-other-window' value. When the action menu popup, the helm prompt is used to narrow down this menu, no more candidates. When `helm-allow-mouse' is non nil, you can use as well mouse-3 (right click) in the candidate zone to select actions with the mouse once your candidate is selected. ** Action transformers You may be surprized to see your actions list changing depending on the context. This happen when a source has an action transformer function which checks the current selected candidate and adds specific actions for this candidate. ** Shortcuts for n-th first actions f1-f12: Execute n-th action where n is 1 to 12. ** Shortcuts for executing the default action on the n-th candidate Helm does not display line numbers by default, with Emacs-26+ you can enable it permanently in all helm buffers with: (add-hook 'helm-after-initialize-hook 'helm-init-relative-display-line-numbers) You can also toggle line numbers with \\\\[helm-display-line-numbers-mode] in current Helm buffer. Of course when enabling `global-display-line-numbers-mode' Helm buffers will have line numbers as well. \(Don't forget to customize `display-line-numbers-type' to relative). In Emacs versions < to 26 you will have to use [[https://github.com/coldnew/linum-relative][linum-relative]] package and `helm-linum-relative-mode'. Then when line numbers are enabled with one of the methods above the following keys are available([1]): C-x : Execute default action on the n-th candidate before currently selected candidate. C-c : Execute default action on the n-th candidate after current selected candidate. \"n\" is limited to 1-9. For larger jumps use other navigation keys. \[1] Note that the key bindings are always available even if line numbers are not displayed. They are just useless in this case. ** Mouse control in Helm A basic support for the mouse is provided when the user sets `helm-allow-mouse' to non-nil. - mouse-1 selects the candidate. - mouse-2 executes the default action on selected candidate. - mouse-3 pops up the action menu. Note: When mouse control is enabled in Helm, it also lets you click around and lose the minibuffer focus: you'll have to click on the Helm buffer or the minibuffer to retrieve control of your Helm session. ** Marked candidates You can mark candidates to execute an action on all of them instead of the current selected candidate only. (See bindings below.) Most Helm actions operate on marked candidates unless candidate-marking is explicitely forbidden for a specific source. - To mark/unmark a candidate, use \\[helm-toggle-visible-mark]. (See bindings below.) With a numeric prefix arg mark ARG candidates forward, if ARG is negative mark ARG candidates backward. - To mark all visible unmarked candidates at once in current source use \\[helm-mark-all]. With a prefix argument, mark all candidates in all sources. - To unmark all visible marked candidates at once use \\[helm-unmark-all]. - To mark/unmark all candidates at once use \\[helm-toggle-all-marks]. With a prefix argument, mark/unmark all candidates in all sources. Note: When multiple candidates are selected across different sources, only the candidates of the current source will be used when executing most actions (as different sources can have different actions). Some actions support multi-source marking however. ** Follow candidates When `helm-follow-mode' is on (\\\\[helm-follow-mode] to toggle it), moving up and down Helm session or updating the list of candidates will automatically execute the persistent-action as specified for the current source. If `helm-follow-mode-persistent' is non-nil, the state of the mode will be restored for the following Helm sessions. If you just want to follow candidates occasionally without enabling `helm-follow-mode', you can use \\\\[helm-follow-action-forward] or \\[helm-follow-action-backward] instead. Conversely, when `helm-follow-mode' is enabled, those commands go to previous/next line without executing the persistent action. ** Special yes, no or yes for all answers You may be prompted in the minibuffer to answer by [y,n,!,q] in some places for confirmation. - y mean yes - no mean no - ! mean yes for all - q mean quit or abort current operation. When using ! you will not be prompted for the same thing in current operation any more, e.g. file deletion, file copy etc... ** Moving in `helm-buffer' You can move in `helm-buffer' with the usual commands used in Emacs: \(\\\\[helm-next-line], \\\\[helm-previous-line], etc. See above basic commands. When `helm-buffer' contains more than one source, change source with \\\\[helm-next-source] and \\[helm-previous-source]. Note: When reaching the end of a source, \\\\[helm-next-line] will *not* go to the next source when variable `helm-move-to-line-cycle-in-source' is non-nil, so you will have to use \\\\[helm-next-source] and \\[helm-previous-source]. ** Resume previous session from current Helm session You can use `C-c n' (`helm-run-cycle-resume') to cycle in resumables sources. `C-c n' is a special key set with `helm-define-key-with-subkeys' which, after pressing it, allows you to keep cycling with further `n'. Tip: You can bound the same key in `global-map' to `helm-cycle-resume' with `helm-define-key-with-subkeys' to let you transparently cycle sessions, Helm fired up or not. You can also bind the cycling commands to single key presses (e.g., `S-') this time with a simple `define-key'. (Note that `S-' is not available in terminals.) Note: `helm-define-key-with-subkeys' is available only once Helm is loaded. You can also use \\\\[helm-resume-previous-session-after-quit] to resume the previous session, or \\\\[helm-resume-list-buffers-after-quit] to have completion on all resumable buffers. ** Global commands *** Resume Helm session from outside Helm \\\\[helm-resume] revives the last Helm session. Binding a key to this command will greatly improve Helm interactivity, e.g. when quitting Helm accidentally. You can call \\\\[helm-resume] with a prefix argument to choose \(with completion!) which session you'd like to resume. You can also cycle in these sources with `helm-cycle-resume' (see above). ** Debugging Helm Helm exposes the special variable `helm-debug': setting it to non-nil will enable Helm logging in a special outline-mode buffer. Helm resets the variable to nil at the end of each session. For convenience, \\\\[helm-enable-or-switch-to-debug] allows you to turn on debugging for this session only. To avoid accumulating log entries while you are typing patterns, you can use \\\\[helm-toggle-suspend-update] to turn off updating. When you are ready turn it on again to resume logging. Once you exit your Helm session you can access the debug buffer with `helm-debug-open-last-log'. Note: Be aware that Helm log buffers grow really fast, so use `helm-debug' only when needed. ** Writing your own Helm sources Writing simple sources for your own usage is easy. When calling the `helm' function, the sources are added the :sources slot which can be a symbol or a list of sources. Sources can be built with different EIEIO classes depending on what you want to do. To simplify this, several `helm-build-*' macros are provided. Below there are simple examples to start with. We will not go further here, see [[https://github.com/emacs-helm/helm/wiki/Developing][Helm wiki]] and the source code for more information and more complex examples. #+begin_src elisp ;; Candidates are stored in a list. (helm :sources (helm-build-sync-source \"test\" ;; A function can be used as well ;; to provide candidates. :candidates '(\"foo\" \"bar\" \"baz\")) :buffer \"*helm test*\") ;; Candidates are stored in a buffer. ;; Generally faster but doesn't allow a dynamic updating ;; of the candidates list i.e the list is fixed on start. (helm :sources (helm-build-in-buffer-source \"test\" :data '(\"foo\" \"bar\" \"baz\")) :buffer \"*helm test*\") #+end_src ** Helm Map \\{helm-map}" "Message string containing detailed help for `helm'. It also accepts function or variable symbol.") (defvar helm-autoresize-mode) ;; Undefined in `helm-default-display-buffer'. (defvar helm-async-outer-limit-hook nil "Run in async sources when process output is out of `candidate-number-limit'. Should be set locally to `helm-buffer' with `helm-set-local-variable'.") (defvar helm-quit-hook nil "A hook that runs when quitting Helm.") (defvar helm-resume-after-hook nil "A hook that runs after resuming a Helm session. The hook should takes one arg SOURCES.") (defvar helm-help-buffer-name "*Helm Help*" "The name of helm help buffer.") ;; See bug#2503. (defvar helm-process-output-split-string-separator "\n" "Separator to use when splitting helm async output.") (defvar helm-last-query "" "The value of `helm-pattern' is stored here exit or quit.") (defvar helm-dim-prompt-on-update nil "Dim prompt when updating. Do not set this globally. Do not use this in async sources or in commands using an async source in their sources. Use this either let-bounded or helm buffer local.") ;; Utility: logging (defun helm-log (from format-string &rest args) "Log message if `helm-debug' is non-nil. Messages are written to the `helm-debug-buffer' buffer. FROM is the place from where it is called. Argument FORMAT-STRING is a string to use with `format'. Use optional arguments ARGS like in `format'." (when helm-debug (with-current-buffer (get-buffer-create helm-debug-buffer) (outline-mode) (buffer-disable-undo) (let ((inhibit-read-only t)) (goto-char (point-max)) (insert (let ((tm (current-time))) (format (concat (if (string-match "Start session" format-string) "* " "** ") "%s.%06d (%s)\n %s\n") (format-time-string "%H:%M:%S" tm) (nth 2 tm) from (apply #'format (cons format-string args))))))))) (defun helm-log-run-hook (from hook) "Run HOOK like `run-hooks' and log these actions to Helm log buffer. FROM is the place from where it is called." (helm-log from "Executing %s with value = %S" hook (symbol-value hook)) (helm-log from "Executing %s with global value = %S" hook (default-value hook)) (run-hooks hook) (helm-log from "executed %s" hook)) (defun helm-log-error (from &rest args) "Accumulate error messages into `helm-issued-errors'. FROM is the place from where it is called. ARGS are args given to `format'. E.g. (helm-log-error \"Error %s: %s\" (car err) (cdr err))." (apply 'helm-log from (concat "ERROR: " (car args)) (cdr args)) (let ((msg (apply 'format args))) (unless (member msg helm-issued-errors) (cl-pushnew msg helm-issued-errors :test 'equal)))) ;;;###autoload (defun helm-debug-open-last-log () "Open Helm log file or buffer of last Helm session." (interactive) (if helm--last-log-file (progn (find-file helm--last-log-file) (outline-mode) (view-mode 1) (visual-line-mode 1)) (switch-to-buffer helm-debug-buffer) (view-mode 1) (visual-line-mode 1))) (defun helm-print-error-messages () "Print error messages in `helm-issued-errors'." (and helm-issued-errors (message "Helm issued errors: %s" (mapconcat 'identity (reverse helm-issued-errors) "\n")))) ;; Test tools (defmacro with-helm-time-after-update (&rest body) (helm-with-gensyms (start-time time-elapsed) `(let ((,start-time (float-time)) ,time-elapsed) (add-hook 'helm-after-update-hook (lambda () (setq ,time-elapsed (- (float-time) ,start-time)) (keyboard-quit))) (unwind-protect ,@body (remove-hook 'helm-after-update-hook (lambda () (setq ,time-elapsed (- (float-time) ,start-time)) (keyboard-quit)))) ,time-elapsed))) ;; Helm API (defmacro with-helm-default-directory (directory &rest body) (declare (indent 1) (debug t)) `(let ((default-directory (or (and ,directory (file-name-as-directory ,directory)) default-directory))) ,@body)) (defun helm-default-directory () "Return the local value of `default-directory' in `helm-buffer'." (buffer-local-value 'default-directory (get-buffer helm-buffer))) (defmacro with-helm-temp-hook (hook &rest body) "Execute temporarily BODY as a function for HOOK." (declare (indent 1) (debug t)) `(letrec ((helm--hook (lambda () (unwind-protect (progn ,@body) (remove-hook ,hook helm--hook))))) (push (cons helm--hook ,hook) helm--temp-hooks) (add-hook ,hook helm--hook))) (defmacro with-helm-after-update-hook (&rest body) "Execute BODY at end of `helm-update'." (declare (indent 0) (debug t)) `(with-helm-temp-hook 'helm-after-update-hook ,@body)) (defmacro with-helm-alive-p (&rest body) "Return error when BODY run outside Helm context." (declare (indent 0) (debug t)) `(progn (if helm-alive-p (progn ,@body) (error "Running helm command outside of context")))) (defmacro with-helm-in-frame (&rest body) "Execute Helm function in BODY displaying `helm-buffer' in separate frame." (declare (debug t) (indent 0)) `(progn (helm-set-local-variable 'helm-display-function 'helm-display-buffer-in-own-frame) ,@body)) (defmacro helm-make-command-from-action (symbol doc action &rest body) "Make a command SYMBOL from ACTION with docstring DOC. The command SYMBOL will quit helm before execute. Argument ACTION should be an existing helm action. BODY form will run before calling action. Example: (helm-make-command-from-action foo-command \"Docstring\" \\='foo-action (run body)) will produce a command like this: (defun foo-command () \"docstring\" (interactive) (with-helm-alive-p (run body) (helm-exit-and-execute-action \\='foo-action))) And automatically put the symbol \\='helm-only on SYMBOL." (declare (indent defun) (debug t)) `(progn (defun ,symbol () ,doc (interactive) (with-helm-alive-p (progn ,@body) (helm-exit-and-execute-action ,action))) (put ',symbol 'helm-only t))) (defmacro helm-make-persistent-command-from-action (symbol doc psymbol action) "Make a persistent command SYMBOL bound to PSYMBOL from ACTION." (declare (indent defun) (debug t)) `(progn (defun ,symbol () ,doc (interactive) (with-helm-alive-p (helm-set-attr ,psymbol (cons ,action 'never-split)) (helm-execute-persistent-action ,psymbol))) (put ',symbol 'helm-only t))) ;;; helm-attributes ;; (defun helm-get-attr (attribute-name &optional source compute) "Get the value of ATTRIBUTE-NAME of SRC. If SRC is omitted, use current source. If COMPUTE is non-`nil' compute value of ATTRIBUTE-NAME with `helm-interpret-value'. COMPUTE can have also \\='ignorefn as value, in this case `helm-interpret-value' will return a function as value unchanged, but will eval a symbol which is bound. You can use `setf' to modify value of ATTRIBUTE-NAME unless COMPUTE is specified, if attribute ATTRIBUTE-NAME is not found in SOURCE `setf' will create new attribute ATTRIBUTE-NAME with specified value. You can also use `helm-set-attr' to modify ATTRIBUTE-NAME." (declare (gv-setter (lambda (val) `(let* ((src (or ,source (helm-get-current-source))) (attr (assq ,attribute-name src))) (cl-assert (null ,compute) nil "`setf' can't set the computed value of attribute `%s'" ,attribute-name) (if attr (setcdr attr ,val) (and (setcdr src (cons (cons ,attribute-name ,val) (cdr src))) ,val)))))) (let ((src (or source (helm-get-current-source)))) (helm-aif (assq attribute-name src) (if compute (helm-interpret-value (cdr it) src compute) (cdr it))))) (defalias 'helm-attr 'helm-get-attr) (make-obsolete 'helm-attr 'helm-get-attr "3.7.0") (cl-defun helm-set-attr (attribute-name value &optional (src (helm-get-current-source))) "Set the value of ATTRIBUTE-NAME of source SRC to VALUE. If ATTRIBUTE-NAME doesn't exists in source it is created with value VALUE. If SRC is omitted, use current source. If operation succeed, return value, otherwise nil. Using this function is same as using `setf' on `helm-get-attr'." (setf (helm-get-attr attribute-name src) value)) (defalias 'helm-attrset 'helm-set-attr) (make-obsolete 'helm-attrset 'helm-set-attr "3.7.0") (defun helm-add-action-to-source (name fn source &optional index) "Add new action NAME linked to function FN to SOURCE. Function FN should be a valid function that takes one arg i.e. candidate, argument NAME is a string that will appear in action menu and SOURCE should be an existing helm source already loaded. If INDEX is specified, action is added to the action list at INDEX, otherwise added at end. This allows users to add specific actions to an existing source without modifying source code." (let ((actions (helm-get-attr 'action source 'ignorefn)) (new-action (list (cons name fn)))) (when (functionp actions) (setq actions (list (cons "Default action" actions)))) (helm-set-attr 'action (if index (helm-append-at-nth actions new-action index) (append actions new-action)) source))) (defun helm-delete-action-from-source (action-or-name source) "Delete ACTION-OR-NAME from SOURCE. ACTION-OR-NAME can either be the name of action or the symbol function associated to name." (let* ((actions (helm-get-attr 'action source 'ignorefn)) (del-action (if (symbolp action-or-name) (rassoc action-or-name actions) (assoc action-or-name actions)))) (helm-set-attr 'action (delete del-action actions) source))) (cl-defun helm-add-action-to-source-if (name fn source predicate &optional (index 4) test-only) "Add new action NAME linked to function FN to SOURCE. Action NAME will be available when the current candidate matches PREDICATE. This function adds an entry in the `action-transformer' attribute of SOURCE (or creates one if not found). Function PREDICATE must take one candidate as arg. Function FN should be a valid function that takes one arg i.e. candidate, argument NAME is a string that will appear in action menu and SOURCE should be an existing Helm source already loaded. If INDEX is specified, action is added in action list at INDEX. Value of INDEX should be always >=1, default to 4. This allows user to add a specific `action-transformer' to an existing source without modifying source code. E.g. Add the action \"Byte compile file async\" linked to function `async-byte-compile-file' to source `helm-source-find-files' only when predicate helm-ff-candidates-lisp-p returns non-nil: \(helm-add-action-to-source-if \"Byte compile file async\" \\='async-byte-compile-file helm-source-find-files \\='helm-ff-candidates-lisp-p)." (let* ((actions (helm-get-attr 'action source 'ignorefn)) (action-transformers (helm-get-attr 'action-transformer source)) (new-action (list (cons name fn))) (transformer (lambda (actions _candidate) (let ((candidate (car (helm-marked-candidates)))) (cond ((funcall predicate candidate) (helm-append-at-nth actions new-action index)) (t actions)))))) (when (functionp actions) (helm-set-attr 'action (list (cons "Default action" actions)) source)) (when (or (symbolp action-transformers) (functionp action-transformers)) (setq action-transformers (list action-transformers))) (if test-only ; debug (delq nil (append (list transformer) action-transformers)) (helm-set-attr 'action-transformer (helm-fast-remove-dups (delq nil (append (list transformer) action-transformers)) :test 'equal) source)))) ;;; Source filter ;; (defun helm-set-source-filter (sources) "Set the value of `helm-source-filter' to SOURCES and update. This function sets a filter for Helm sources and it may be called while Helm is running. It can be used to toggle displaying of sources dynamically. For example, additional keys can be bound into `helm-map' to display only the file-related results if there are too many matches from other sources and you're after files only: Shift+F shows only file results from some sources: \(define-key helm-map \"F\" \\='helm-my-show-files-only) \(defun helm-my-show-files-only () (interactive) (helm-set-source-filter \\='(\"File Name History\" \"Files from Current Directory\"))) Shift+A shows all results: \(define-key helm-map \"A\" \\='helm-my-show-all) \(defun helm-my-show-all () (interactive) (helm-set-source-filter nil)) The -my- part is added to avoid collisions with existing Helm function names." (with-helm-buffer (let ((cur-disp-sel (helm-get-selection nil t))) (set (make-local-variable 'helm-source-filter) (helm--normalize-filter-sources sources)) (helm-log "helm-set-source-filter" "helm-source-filter = %S" helm-source-filter) ;; Use force-update to run init/update functions. (helm-update (and (stringp cur-disp-sel) (regexp-quote cur-disp-sel)))))) (defun helm--normalize-filter-sources (sources) (cl-loop for s in sources collect (cl-typecase s (symbol (assoc-default 'name (symbol-value s))) (list (assoc-default 'name s)) (string s)))) (defun helm-set-sources (sources &optional no-init no-update) "Set SOURCES during `helm' invocation. If NO-INIT is non-nil, skip executing init functions of SOURCES. If NO-UPDATE is non-nil, skip executing `helm-update'." (with-current-buffer helm-buffer (setq helm-sources (helm-get-sources sources)) (helm-log "helm-set-sources" "helm-sources = %S" helm-sources)) (unless no-init (helm-compute-attr-in-sources 'init)) (unless no-update (helm-update))) (defun helm-show-all-candidates-in-source (arg) "Toggle all sources or only current source visibility. With a prefix arg show all candidates in current source disregarding candidate-number-limit and with a numeric prefix arg show ARG number of candidates." (interactive "P") (with-helm-alive-p (with-helm-buffer (if helm-source-filter (progn (setq-local helm-candidate-number-limit (default-value 'helm-candidate-number-limit)) (helm-set-source-filter nil)) (with-helm-default-directory (helm-default-directory) (setq-local helm-candidate-number-limit (helm-acase arg ((guard* (consp arg)) nil) ((guard* (numberp arg)) it) (t (default-value 'helm-candidate-number-limit)))) (helm-set-source-filter (list (helm-get-current-source)))))))) (put 'helm-show-all-candidates-in-source 'helm-only t) (defun helm-display-all-sources () "Display all sources previously hidden by `helm-set-source-filter'." (interactive) (with-helm-alive-p (helm-set-source-filter nil))) (put 'helm-display-all-sources 'helm-only t) (defun helm-limit-to-sources () "Limit sources to display from current session. This is a toggle command, when hit a second time reset to all sources." (interactive) (with-helm-alive-p (with-helm-buffer (if (null helm-source-filter) (when (cdr helm-sources) (let ((headers (helm-comp-read "Limit to source(s): " (mapcar (lambda (s) (let* ((name (assoc-default 'name s)) (disp (helm-aif (assoc-default 'header-name s) (funcall it name) name))) (cons disp name))) helm-sources) :marked-candidates t :allow-nest t :buffer "*helm sources*"))) (helm-set-source-filter headers))) (helm-set-source-filter nil))))) (put 'helm-limit-to-sources 'helm-only t) ;;; Source infos fns. ;; (defun helm-get-selection (&optional buffer force-display-part source) "Return the currently selected candidate from BUFFER. If BUFFER is nil or unspecified, use `helm-buffer' as default value. If FORCE-DISPLAY-PART is non-nil, return the display part of candidate. If FORCE-DISPLAY-PART value is `withprop' the display part of candidate is returned with its properties. When FORCE-DISPLAY-PART is nil the real part of candidate is returned. SOURCE default to current-source when unspecified but it is better to specify SOURCE when it is already available to avoid to call `helm-get-current-source' uselessly. Note that FORCE-DISPLAY-PART when specified takes precedence over `display-to-real' attribute, that's mean don't use FORCE-DISPLAY-PART when you want the `display-to-real' function(s) to be applied." (with-current-buffer (or buffer helm-buffer) (unless (or (helm-empty-buffer-p (current-buffer)) (helm-pos-header-line-p)) (let* ((beg (overlay-start helm-selection-overlay)) (end (overlay-end helm-selection-overlay)) (disp-fn (if (eq force-display-part 'withprop) 'buffer-substring 'buffer-substring-no-properties)) ;; If there is no selection at point, the ;; overlay is at its initial pos, (point-min) ;; (point-min), that's mean the helm-buffer ;; is not empty but have no selection yet, ;; this happen with grep sentinel sending an ;; error message in helm-buffer when no matches. (disp (unless (= beg end) (funcall disp-fn beg (1- end)))) (src (or source (helm-get-current-source))) (selection (helm-acond (force-display-part disp) ;; helm-realvalue always takes precedence ;; over display-to-real. ((get-text-property beg 'helm-realvalue) it) ((assoc-default 'display-to-real src) (helm-apply-functions-from-source source it disp)) (t disp)))) (unless (equal selection "") (helm-log "helm-get-selection" "selection = %S" selection) selection))))) (defun helm-get-actions-from-current-source (&optional source) "Return the associated action for the selected candidate. It is a function symbol (sole action) or list of (action-display . function)." (unless (helm-empty-buffer-p (helm-buffer-get)) (let* ((src (or source (helm-get-current-source))) (marked (helm-marked-candidates)) (action-transformer (helm-get-attr 'action-transformer src)) (actions (helm-get-attr 'action src 'ignorefn))) (if action-transformer (helm-apply-functions-from-source src action-transformer actions ;; When there is marked candidates assume the set of ;; candidates user selected contains candidates of the same ;; type so that the actions added by transformer fit with ;; all marked (previously we were looping on each marked ;; but it is too costly for the benefit it brings). (car marked)) actions)))) (defun helm-get-current-source () "Return the source for the current selection. Return nil when `helm-buffer' is empty." (or helm-current-source (with-helm-buffer (or (get-text-property (point) 'helm-cur-source) (progn ;; This is needed to not loose selection. (goto-char (overlay-start helm-selection-overlay)) (let ((header-pos (or (helm-get-previous-header-pos) (helm-get-next-header-pos)))) ;; Return nil when no--candidates. (when header-pos (cl-loop with source-name = (save-excursion (goto-char header-pos) (helm-current-line-contents)) for source in helm-sources thereis (and (equal (assoc-default 'name source) source-name) source))))))))) (defun helm-run-after-exit (function &rest args) "Execute FUNCTION with ARGS after exiting Helm. The action is to call FUNCTION with arguments ARGS. Unlike `helm-exit-and-execute-action', this can be used to call non-actions functions with any ARGS or no ARGS at all. Use this on commands invoked from key bindings, but not on action functions invoked as action from the action menu, i.e. functions called with RET." (helm-kill-async-processes) (helm-log "helm-run-after-exit" "function = %S" function) (helm-log "helm-run-after-exit" "args = %S" args) (helm-exit-and-execute-action (lambda (_candidate) (apply function args)))) (defun helm-exit-and-execute-action (action) "Exit current Helm session and execute ACTION. Argument ACTION is a function called with one arg (candidate) and part of the actions of current source. Use this on commands invoked from key bindings, but not on action functions invoked as action from the action menu, i.e. functions called with RET." ;; If ACTION is not an action available in source 'action attribute, ;; return an error. This allow to remove unneeded actions from ;; source that inherit actions from type, note that ACTION have to ;; be bound to a symbol and not to be an anonymous action ;; i.e. lambda or byte-code. (helm-log "helm-exit-and-execute-action" "Start executing action") (let ((actions (helm-get-actions-from-current-source))) (when actions (cl-assert (or ;; Single actions defined as symbol. (eq action actions) ;; Compiled lambdas (byte-code-function-p action) ;; Natively compiled (libgccjit) (helm-subr-native-elisp-p action) ;; Lambdas (lambdas are no more represented as list in ;; Emacs-29+) Bug#2666. (and (not (symbolp action)) (functionp action)) ;; One of current actions. (rassq action actions)) nil "No such action `%s' for this source" action))) (setq helm-saved-action action) (setq helm-saved-selection (or (helm-get-selection) "")) (setq helm--executing-helm-action t) ;; When toggling minibuffer and header-line, we want next action ;; inherit this setting. (helm-set-local-variable 'helm-echo-input-in-header-line (with-helm-buffer helm-echo-input-in-header-line)) ;; Ensure next action use same display function as initial helm-buffer when ;; helm-actions-inherit-frame-settings is non nil. (when (and helm-actions-inherit-frame-settings helm--buffer-in-new-frame-p) (helm-set-local-variable 'helm-display-function (with-helm-buffer helm-display-function) 'helm--last-frame-parameters (with-helm-buffer (helm--get-frame-parameters))) ;; The helm-buffer keeps `helm-display-function' and ;; `helm--get-frame-parameters' values during 0.5 seconds, just ;; the time to execute the possible helm action with those values. ;; If no helm based action run within 0.5 seconds, the next helm ;; session will have to resolve again those variable values. (run-with-idle-timer 0.5 nil (lambda () (helm-set-local-variable 'helm-display-function nil 'helm--last-frame-parameters nil)))) (helm-exit-minibuffer)) (defun helm--get-frame-parameters (&optional frame) (cl-loop with params = (frame-parameters frame) for p in helm--frame-default-attributes when (assq p params) collect it)) (defalias 'helm-run-after-quit 'helm-run-after-exit) (make-obsolete 'helm-run-after-quit 'helm-run-after-exit "1.7.7") (defalias 'helm-quit-and-execute-action 'helm-exit-and-execute-action) (make-obsolete 'helm-quit-and-execute-action 'helm-exit-and-execute-action "1.7.7") (defun helm-interpret-value (value &optional source compute) "Interpret VALUE as variable, function or literal and return it. If VALUE is a function, call it with no arguments and return the value unless COMPUTE value is \\='ignorefn. If SOURCE compute VALUE for this source. If VALUE is a variable, return the value. If VALUE is a symbol, but it is not a function or a variable, cause an error. Otherwise, return VALUE itself." (cond ((and source (functionp value) (not (eq compute 'ignorefn))) (helm-apply-functions-from-source source value)) ((and (functionp value) (not (eq compute 'ignorefn))) (funcall value)) ((and (symbolp value) (boundp value)) (symbol-value value)) ((and (symbolp value) (not (functionp value))) (error "helm-interpret-value: Symbol must be a function or a variable")) (t value))) (defun helm-set-local-variable (&rest args) "Bind each pair in ARGS locally to `helm-buffer'. Use this to set local vars before calling helm. When used from an init or update function \(i.e. when `helm-force-update' is running) the variables are set using `make-local-variable' within the `helm-buffer'. Usage: helm-set-local-variable ([VAR VALUE]...) Just like `setq' except that the vars are not set sequentially. IOW Don't use VALUE of previous VAR to set the VALUE of next VAR. \(fn VAR VALUE ...)" (if helm--force-updating-p (with-helm-buffer (cl-loop for i on args by #'cddr do (set (make-local-variable (car i)) (cadr i)))) (setq helm--local-variables (append (cl-loop for i on args by #'cddr collect (cons (car i) (cadr i))) helm--local-variables)))) (defun helm--set-local-variables-internal () (cl-loop for (var . val) in helm--local-variables ;; If `helm-set-local-variable' is called twice or more ;; on same variable use the last value entered which is ;; the first on stack e.g. ;; (helm-set-local-variable 'helm-foo 1) ;; (helm-set-local-variable 'helm-foo 2) ;; helm--local-variables => ;; '((helm-foo . 2) (helm-foo. 1)) ;; (helm-foo . 2) is retained and (helm-foo . 1) ;; ignored. unless (memq var computed) do (set (make-local-variable var) val) collect var into computed finally (setq helm--local-variables nil))) ;; API helper (cl-defun helm-empty-buffer-p (&optional (buffer helm-buffer)) "Check if BUFFER have candidates. Default value for BUFFER is `helm-buffer'." (zerop (buffer-size (and buffer (get-buffer buffer))))) (defun helm-empty-source-p () "Check if current source contains candidates. This could happen when for example the last element of a source was deleted and the candidates list not updated." (when (helm-window) (with-helm-window (or (helm-empty-buffer-p) (and (helm-end-of-source-p) (eq (pos-bol) (pos-eol)) (or (save-excursion (forward-line -1) (helm-pos-header-line-p)) (bobp))))))) ;; Tools ;; (defun helm-apply-functions-from-source (source functions &rest args) "From SOURCE apply FUNCTIONS on ARGS. This function is used to process filter functions. When filter is a `filtered-candidate-transformer', we pass to ARGS candidates+source whereas when the filter is `candidate-transformer' we pass to ARGS candidates only. This function is also used to process functions called with no args, e.g. init functions. In this case it is called without ARGS. See `helm-process-filtered-candidate-transformer' `helm-compute-attr-in-sources' `helm-process-candidate-transformer'. Arg FUNCTIONS is either a symbol or a list of functions, each function being applied on ARGS and called on the result of the precedent function. Return the result of last function call." (let ((helm--source-name (assoc-default 'name source)) (helm-current-source source) (funs (if (functionp functions) (list functions) functions))) (cl-loop with result for fn in funs ;; In filter functions, ARGS is a list of one or two elements where ;; the first element is the list of candidates and the second ;; a list containing the source. do (setq result (apply fn args)) when (and args (cdr funs)) ;; When more than one fn, set the candidates list to what returns ;; this fn to compute the modified candidates with the next fn ;; and so on. do (setcar args result) finally return result))) (defalias 'helm-funcall-with-source 'helm-apply-functions-from-source) (make-obsolete 'helm-funcall-with-source 'helm-apply-functions-from-source "2.9.7") (defun helm-compute-attr-in-sources (attr &optional sources) "Call the associated function(s) to ATTR for each source if any." (let ((sources (or (helm-get-sources sources) ;; Fix error no buffer named *helm... by checking ;; if helm-buffer exists. (and (buffer-live-p (get-buffer (helm-buffer-get))) ;; `helm-sources' are local to helm-buffer. (with-helm-buffer helm-sources))))) (when sources (dolist (source sources) (helm-aif (assoc-default attr source) (helm-apply-functions-from-source source it)))))) (defalias 'helm-funcall-foreach 'helm-compute-attr-in-sources) (make-obsolete 'helm-funcall-foreach 'helm-compute-attr-in-sources "2.9.7") (defun helm-normalize-sources (sources) "If SOURCES is only one source, make a list of one element." (if (or (and sources (symbolp sources)) (and (listp sources) (assq 'name sources))) (list sources) sources)) (defun helm-get-candidate-number (&optional in-current-source) "Return candidates number in `helm-buffer'. If IN-CURRENT-SOURCE is provided return the number of candidates of current source only." (with-helm-buffer (if (or (helm-empty-buffer-p) (helm-empty-source-p)) 0 (save-excursion (helm-aif (and in-current-source (helm-get-previous-header-pos)) (goto-char it) (goto-char (point-min))) (forward-line 1) (if (helm-pos-multiline-p) (cl-loop with count-multi = 1 while (and (not (if in-current-source (save-excursion (forward-line 2) (or (helm-pos-header-line-p) (eobp))) (eobp))) (search-forward helm-candidate-separator nil t)) do (cl-incf count-multi) finally return count-multi) (cl-loop with ln = 0 while (not (if in-current-source (or (helm-pos-header-line-p) (eobp)) (eobp))) ;; Don't count empty lines maybe added by popup (bug#1370). unless (or (eq (pos-bol) (pos-eol)) (helm-pos-header-line-p)) do (cl-incf ln) do (forward-line 1) finally return ln)))))) ;;; Main functions ;; (defconst helm-argument-keys ;; `:allow-nest' is not in this list because it is treated before. '(:sources :input :prompt :resume :preselect :buffer :keymap :default :history)) ;;;###autoload (defun helm (&rest plist) "Main function to execute helm sources. PLIST is a list like \(:key1 val1 :key2 val2 ...) or \(&optional sources input prompt resume preselect buffer keymap default history allow-nest). ** Keywords Keywords supported: - :sources - :input - :prompt - :resume - :preselect - :buffer - :keymap - :default - :history - :allow-nest Extra LOCAL-VARS keywords are supported, see the \"** Other keywords\" section below. Basic keywords are the following: *** :sources One of the following: - List of sources - Symbol whose value is a list of sources - Alist representing a Helm source. - In this case the source has no name and is referenced in `helm-sources' as a whole alist. *** :input Initial input of minibuffer (temporary value of `helm-pattern') *** :prompt Minibuffer prompt. Default value is `helm--prompt'. *** :resume If t, allow resumption of the previous session of this Helm command, skipping initialization. If \\='noresume, this instance of `helm' cannot be resumed. *** :preselect Initially selected candidate (string or regexp). *** :buffer Buffer name for this Helm session. `helm-buffer' will take this value. *** :keymap \[Obsolete] Keymap used at the start of this Helm session. It is overridden by keymaps specified in sources, and is kept only for backward compatibility. Keymaps should be specified in sources using the :keymap slot instead. See `helm-source'. This keymap is not restored by `helm-resume'. *** :default Default value inserted into the minibuffer \ with \\\\[next-history-element]. It can be a string or a list of strings, in this case \\\\[next-history-element] cycles through the list items, starting with the first. If nil, `thing-at-point' is used. If `helm-maybe-use-default-as-input' is non-nil, display is updated using this value if this value matches, otherwise it is ignored. If :input is specified, it takes precedence on :default. *** :history Minibuffer input, by default, is pushed to `minibuffer-history'. When an argument HISTORY is provided, input is pushed to HISTORY. HISTORY should be a valid symbol. *** :allow-nest Allow running this Helm command in a running Helm session. ** Other keywords Other keywords are interpreted as local variables of this Helm session. The `helm-' prefix can be omitted. For example, \(helm :sources \\='helm-source-buffers-list :buffer \"*helm buffers*\" :candidate-number-limit 10) Starts a Helm session with the variable `helm-candidate-number-limit' set to 10. ** Backward compatibility For backward compatibility, positional parameters are supported: \(helm sources input prompt resume preselect buffer keymap default history allow-nest) However, the use of non-keyword args is deprecated. \(fn &key SOURCES INPUT PROMPT RESUME PRESELECT BUFFER KEYMAP DEFAULT HISTORY ALLOW-NEST OTHER-LOCAL-VARS)" (let ((fn (cond ((or (and helm-alive-p (plist-get plist :allow-nest)) (and helm-alive-p (memq 'allow-nest plist))) #'helm--nest) ((keywordp (car plist)) #'helm) (t #'helm-internal)))) (if (and helm-alive-p (eq fn #'helm)) (if (helm--alive-p) ;; A helm session is normally running. (error "Error: Trying to run helm within a running helm session") ;; A helm session is already running and user jump somewhere else ;; without deactivating it. (with-helm-buffer (prog1 (message "Aborting an helm session running in background") ;; `helm-alive-p' will be reset in unwind-protect forms. (helm-keyboard-quit)))) (if (keywordp (car plist)) ;; Parse `plist' and move not regular `helm-argument-keys' ;; to `helm--local-variables', then calling helm on itself ;; with normal arguments (the non--arguments-keys removed) ;; will end up in [1]. (progn (setq helm--local-variables (append helm--local-variables ;; Vars passed by keyword on helm call ;; take precedence on same vars ;; that may have been passed before helm call. (helm-parse-keys plist))) (apply fn (mapcar (lambda (key) (plist-get plist key)) helm-argument-keys))) (apply fn plist))))) ; [1] fn == helm-internal. (defun helm--alive-p () "[INTERNAL] Check if `helm' is alive. An Helm session is considered alive if `helm-alive-p' value is non-nil, the `helm-buffer' is visible, and cursor is in the minibuffer." (and helm-alive-p (get-buffer-window (helm-buffer-get) 'visible) (minibuffer-window-active-p (minibuffer-window)) (minibufferp (current-buffer)))) (defun helm-parse-keys (keys) "Parse the KEYS arguments of `helm'. Return only those keys not in `helm-argument-keys', prefix them with \"helm\", and then convert them to an alist. This allows adding arguments that are not part of `helm-argument-keys', but are valid helm variables nevertheless. For example, :candidate-number-limit is bound to `helm-candidate-number-limit' in the source. (helm-parse-keys \\='(:sources ((name . \"test\") (candidates . (a b c))) :buffer \"toto\" :candidate-number-limit 4)) ==> ((helm-candidate-number-limit . 4))." (cl-loop for (key value) on keys by #'cddr for symname = (substring (symbol-name key) 1) for sym = (intern (if (string-match "^helm-" symname) symname (concat "helm-" symname))) unless (memq key helm-argument-keys) collect (cons sym value))) (defun helm-internal (&optional sources input prompt resume preselect buffer keymap default history) "The internal Helm function called by `helm'. For SOURCES INPUT PROMPT RESUME PRESELECT BUFFER KEYMAP DEFAULT and HISTORY args see `helm'." (cl-assert (or (stringp input) (null input)) nil "Error in %S buffer: Initial input should be a string or nil" buffer) ;; Set all windows NON dedicated to avoid headaches with PA and ;; helm-window (bug#2443) (cl-loop for win in (window-list nil 1) for state = (window-dedicated-p win) when state do (progn (set-window-dedicated-p win nil) (push `(,win . ,state) helm--original-dedicated-windows-alist))) (unless helm--nested (setq helm-initial-frame (selected-frame))) ;; Launch tramp-archive with dbus-event in `while-no-input-ignore-events'. (helm--maybe-load-tramp-archive) ;; Activate the advices. ;; Advices will be available only in >=emacs-24.4, but ;; allow compiling without errors on lower emacs. (when (fboundp 'advice-add) (advice-add 'tramp-read-passwd :around #'helm--suspend-read-passwd) (advice-add 'ange-ftp-get-passwd :around #'helm--suspend-read-passwd) (advice-add 'epa-passphrase-callback-function :around #'helm--suspend-read-passwd) ;; Ensure linum-mode is disabled in Helm buffers to preserve ;; performances (Bug#1894). (advice-add 'linum-on :override #'helm--advice-linum-on '((depth . 100)))) (helm-log "helm-internal" (concat "[Start session] " (make-string 41 ?+))) (helm-log "helm-internal" "prompt = %S" prompt) (helm-log "helm-internal" "preselect = %S" preselect) (helm-log "helm-internal" "buffer = %S" buffer) (helm-log "helm-internal" "keymap = %S" keymap) (helm-log "helm-internal" "default = %S" default) (helm-log "helm-internal" "history = %S" history) (setq helm--prompt (or prompt "pattern: ")) (let ((non-essential t) ;; Prevent mouse jumping to the upper-right ;; hand corner of the frame (bug#1538). mouse-autoselect-window focus-follows-mouse mode-line-in-non-selected-windows minibuffer-completion-confirm (ori--minibuffer-follows-selected-frame (and (boundp 'minibuffer-follows-selected-frame) (default-toplevel-value 'minibuffer-follows-selected-frame))) (input-method-verbose-flag helm-input-method-verbose-flag) (helm-maybe-use-default-as-input (and (null input) (or helm-maybe-use-default-as-input ; it is let-bounded so use it. (cl-loop for s in (helm-normalize-sources sources) thereis (memq s helm-sources-using-default-as-input)))))) (unwind-protect (condition-case-unless-debug _v (let ( ;; `helm--source-name' is non-`nil' ;; when `helm' is invoked by action, reset it. helm--source-name helm-current-source helm-in-persistent-action helm--quit (helm-buffer (or buffer helm-buffer))) (helm-initialize resume input default sources) ;; This allows giving the focus to a nested helm session which use ;; a frame, like completion in ;; `helm-eval-expression'. Unfortunately ;; `minibuffer-follows-selected-frame' is available only in ;; emacs-28+ (bug#2536). ;; When non-nil (the default) the current active ;; minibuffer is used in new frame, which is not what we ;; want in helm when starting from an active minibuffer, ;; either a helm minibuffer or something line M-:. (and ori--minibuffer-follows-selected-frame (setq minibuffer-follows-selected-frame (unless (or helm--nested ;; Allow keeping initial minibuffer visible ;; e.g. completion-at-point from M-:. (minibufferp helm-current-buffer)) t))) ;; We don't display helm-buffer here to avoid popping ;; up a window or a frame when exiting immediately when ;; only one candidate (this avoid having the helm frame ;; flashing), lets first compute candidates and if more ;; than one display helm-buffer (this is done later in ;; helm-read-from-minibuffer). (unless helm-execute-action-at-once-if-one (helm-display-buffer helm-buffer resume) (select-window (helm-window)) (when (and resume helm-visible-mark-overlays) (set-window-margins (selected-window) (+ (string-width helm-visible-mark-prefix) helm-left-margin-width)))) ;; We are now in helm-buffer. (unless helm-allow-mouse (helm--remap-mouse-mode 1)) ; Disable mouse bindings. (add-hook 'post-command-hook 'helm--maybe-update-keymap) ;; Add also to update hook otherwise keymap is not updated ;; until a key is hit (Bug#1670). (add-hook 'helm-after-update-hook 'helm--maybe-update-keymap) (add-hook 'post-command-hook 'helm--update-header-line) (helm-log "helm-internal" "show prompt") (unwind-protect (helm-read-from-minibuffer prompt input preselect resume keymap default history) (helm-cleanup)) (prog1 (unless helm--quit (helm-execute-selection-action)) (helm-log "helm-internal" (concat "[End session] " (make-string 41 ?-))))) (quit (helm-restore-position-on-quit) (helm-log-run-hook "helm-internal" 'helm-quit-hook) (helm-log "helm-internal" (concat "[End session (quit)] " (make-string 34 ?-))) nil)) (when (fboundp 'advice-remove) (advice-remove 'tramp-read-passwd #'helm--suspend-read-passwd) (advice-remove 'ange-ftp-get-passwd #'helm--suspend-read-passwd) (advice-remove 'epa-passphrase-callback-function #'helm--suspend-read-passwd) (advice-remove 'linum-on #'helm--advice-linum-on)) (helm-log "helm-internal" "helm-alive-p = %S" (setq helm-alive-p nil)) (helm--remap-mouse-mode -1) ; Reenable mouse bindings. (setq helm-alive-p nil) (and ori--minibuffer-follows-selected-frame (set-default-toplevel-value 'minibuffer-follows-selected-frame ori--minibuffer-follows-selected-frame)) ;; Prevent error "No buffer named *helm*" triggered by ;; `helm-set-local-variable'. (setq helm--force-updating-p nil) (setq helm--buffer-in-new-frame-p nil) ;; Reset helm-pattern so that lambda's using it ;; before running helm will not start with its old value. (setq helm-pattern "") (setq helm--ignore-errors nil helm-debug nil)))) (defun helm--maybe-load-tramp-archive () ;; Should fix bug#2393 and bug#2394. `while-no-input-ignore-events' ;; is also let-bounded in `helm--maybe-use-while-no-input'. (let ((while-no-input-ignore-events (and (boundp 'while-no-input-ignore-events) (cons 'dbus-event while-no-input-ignore-events)))) (unless helm--tramp-archive-maybe-loaded ;; This for Emacs-27 not requiring tramp-archive. (and (boundp 'tramp-archive-enabled) (require 'tramp-archive nil t)) (setq helm--tramp-archive-maybe-loaded t)))) (defun helm--advice-linum-on () (unless (or (minibufferp) (string-match "\\`\\*helm" (buffer-name)) (and (daemonp) (null (frame-parameter nil 'client)))) (linum-mode 1))) ;;; Helm resume ;; ;; (defun helm-resume (arg) "Resume a previous Helm session. Call with a prefix arg to choose among existing Helm buffers (sessions). When calling from Lisp, specify a `buffer-name' as a string with ARG." (interactive "P") (let (buffer cur-dir narrow-pos (helm-full-frame (default-value 'helm-full-frame)) sources) (if arg (if (and (stringp arg) (bufferp (get-buffer arg))) (setq buffer arg) (setq buffer (helm-resume-select-buffer))) (setq buffer helm-last-buffer)) (cl-assert buffer nil "helm-resume: No helm buffers found to resume") (setq sources (buffer-local-value 'helm-sources (get-buffer buffer))) ;; Reset `cursor-type' to nil as it have been set to t ;; when quitting previous session. (with-current-buffer buffer (setq cursor-type nil)) (setq helm-full-frame (buffer-local-value 'helm-full-frame (get-buffer buffer))) (setq cur-dir (buffer-local-value 'default-directory (get-buffer buffer))) (setq helm-saved-selection nil helm-saved-action nil) ;; Always resume from current-buffer, if a source needs to resume from a ;; specific buffer it should be specified from this source, not here. (setq helm-current-buffer (current-buffer)) (helm-aif (with-current-buffer buffer helm--current-buffer-narrowed) (progn (set-buffer (car it)) (setq narrow-pos (cdr it)))) ;; This happen when calling C-x b within helm. (helm-aif (get-buffer-window helm-marked-buffer-name 'visible) (progn (delete-window it) (kill-buffer helm-marked-buffer-name))) (save-restriction (when narrow-pos (apply #'narrow-to-region narrow-pos)) ;; Restart with same `default-directory' value this session ;; was initially started with. (with-helm-default-directory cur-dir (unwind-protect (helm :sources sources :input (buffer-local-value 'helm-input-local (get-buffer buffer)) :prompt (buffer-local-value 'helm--prompt (get-buffer buffer)) :resume t :buffer buffer) (run-hook-with-args 'helm-resume-after-hook sources)))))) (defun helm-resume-previous-session-after-quit () "Resume previous Helm session within a running Helm." (interactive) (with-helm-alive-p (let ((arg (if (null (member helm-buffer helm-buffers)) 0 1))) (cond ((> (minibuffer-depth) 1) (message "Can't resume from recursive minibuffers")) ((> (length helm-buffers) arg) (helm-run-after-exit (lambda () (helm-resume (nth arg helm-buffers))))) (t (message "No previous helm sessions available for resuming")))))) (put 'helm-resume-previous-session-after-quit 'helm-only t) (defun helm-resume-list-buffers-after-quit () "List Helm buffers that can be resumed within a running Helm." (interactive) (with-helm-alive-p (cond ((> (minibuffer-depth) 1) (message "Can't resume from recursive minibuffers")) ((> (length helm-buffers) 0) (helm-run-after-exit (lambda () (helm-resume t)))) (t (message "No previous helm sessions available for resuming"))))) (put 'helm-resume-list-buffers-after-quit 'helm-only t) (defun helm-resume-p (resume) "Whether current Helm session is resumed or not." (eq resume t)) (defun helm-resume-select-buffer () "Select an `helm-buffer' in `helm-buffers' list to resume a helm session. Return nil if no `helm-buffer' found." (when helm-buffers (or (helm :sources (helm-build-sync-source "Resume helm buffer" :candidates helm-buffers) :resume 'noresume :buffer "*helm resume*") (keyboard-quit)))) ;;;###autoload (defun helm-cycle-resume () "Cycle in `helm-buffers' list and resume when waiting more than 1.2s." (interactive) (cl-assert (and helm-buffers helm-last-buffer) nil "No helm buffers to resume") ;; Setup a new iterator only on first hit on ;; `helm-run-cycle-resume', subsequents hits should reuse same ;; iterator. (unless (and (eq last-command 'helm-cycle-resume) helm--cycle-resume-iterator) (setq helm--cycle-resume-iterator (helm-iter-sub-next-circular helm-buffers helm-last-buffer :test 'equal))) (helm--resume-or-iter)) (defun helm--resume-or-iter (&optional from-helm) (message "Resuming helm buffer `%s'" helm-last-buffer) (if (sit-for helm-cycle-resume-delay) ;; Delay expire, run helm-resume. (if from-helm (helm-run-after-exit (lambda () (helm-resume helm-last-buffer))) (helm-resume helm-last-buffer)) ;; key pressed before delay, cycle. (unless from-helm ; cycling to next item already done. (message "Resuming helm buffer `%s'" (setq helm-last-buffer (helm-iter-next helm--cycle-resume-iterator)))))) (defun helm-run-cycle-resume () "Same as `helm-cycle-resume' but intended to be called only from Helm." (interactive) (when (cdr helm-buffers) ; only one session registered. ;; Setup a new iterator only on first hit on ;; `helm-run-cycle-resume', subsequents hits should reuse same ;; iterator. (unless (and (eq last-command 'helm-run-cycle-resume) helm--cycle-resume-iterator) (setq helm--cycle-resume-iterator (helm-iter-sub-next-circular helm-buffers helm-last-buffer :test 'equal))) ;; start at next buffer as we already are at `helm-last-buffer'. (setq helm-last-buffer (helm-iter-next helm--cycle-resume-iterator)) (helm--resume-or-iter 'from-helm))) (put 'helm-run-cycle-resume 'helm-only t) ;;; Nested sessions ;; ;; (defun helm--nest (&rest same-as-helm) "[INTERNAL] Allow calling `helm' within a running Helm session. Arguments SAME-AS-HELM are the same as `helm'. Don't use this directly, use instead `helm' with the keyword :allow-nest. \(fn &key SOURCES INPUT PROMPT RESUME PRESELECT BUFFER KEYMAP DEFAULT HISTORY OTHER-LOCAL-VARS)" (with-helm-window (let ((orig-helm-current-buffer helm-current-buffer) (orig-helm-buffer helm-buffer) (orig-helm--prompt helm--prompt) (orig-helm-sources helm-sources) (orig-helm--in-fuzzy helm--in-fuzzy) (orig-helm--display-frame helm--buffer-in-new-frame-p) (orig-helm-last-frame-or-window-configuration helm-last-frame-or-window-configuration) (orig-one-window-p helm-onewindow-p) (helm--nested (if helm--buffer-in-new-frame-p 'share t))) ;; FIXME Using helm-full-frame here allow showing the new ;; helm-buffer in the same window as old helm-buffer, why? (helm-set-local-variable 'helm-full-frame t) (unwind-protect (let (helm-current-position helm-current-buffer helm-pattern (helm-buffer (or (cl-getf same-as-helm :buffer) (nth 5 same-as-helm) "*Helm*")) (enable-recursive-minibuffers t)) (setq helm-sources nil) (apply #'helm same-as-helm)) (with-current-buffer orig-helm-buffer (setq helm-sources orig-helm-sources) (setq helm--nested nil) (setq helm--buffer-in-new-frame-p orig-helm--display-frame) (setq helm-alive-p t) ; Nested session set this to nil on exit. (setq helm-buffer orig-helm-buffer) (setq helm-full-frame nil) (setq helm--prompt orig-helm--prompt) (setq helm--in-fuzzy orig-helm--in-fuzzy) (helm-initialize-overlays helm-buffer) (unless (helm-empty-buffer-p) (helm-mark-current-line t)) (setq helm-last-frame-or-window-configuration orig-helm-last-frame-or-window-configuration) (setq cursor-type nil) (setq helm-current-buffer orig-helm-current-buffer) (setq helm-onewindow-p orig-one-window-p) ;; Be sure advices, hooks, and local modes keep running. (advice-add 'tramp-read-passwd :around #'helm--suspend-read-passwd) (advice-add 'ange-ftp-get-passwd :around #'helm--suspend-read-passwd) (advice-add 'epa-passphrase-callback-function :around #'helm--suspend-read-passwd) (unless helm-allow-mouse (helm--remap-mouse-mode 1)) (unless (cl-loop for h in post-command-hook thereis (memq h '(helm--maybe-update-keymap helm--update-header-line))) (add-hook 'post-command-hook 'helm--maybe-update-keymap) (add-hook 'post-command-hook 'helm--update-header-line)) (helm-display-mode-line (helm-get-current-source))))))) ;;; Windows and frames ;; ;; (defun helm-frame-or-window-configuration (save-or-restore) "Save or restore last frame or window configuration. Argument SAVE-OR-RESTORE is either save or restore of window or frame configuration as per `helm-save-configuration-functions'." (helm-log "helm-frame-or-window-configuration" "helm-save-configuration-functions = %S" helm-save-configuration-functions) (let ((window-persistent-parameters (append '((no-other-window . t)) window-persistent-parameters))) (cl-case save-or-restore (save (setq helm-last-frame-or-window-configuration (funcall (cdr helm-save-configuration-functions)))) (restore (funcall (car helm-save-configuration-functions) helm-last-frame-or-window-configuration) ;; Restore dedicated windows (bug#2443). (when helm--original-dedicated-windows-alist (cl-loop for (win . state) in helm--original-dedicated-windows-alist when (window-live-p win) do (set-window-dedicated-p win state)) (setq helm--original-dedicated-windows-alist nil)) ;; Restore frame focus. ;; This is needed for minibuffer own-frame config ;; when recursive minibuffers are in use. ;; e.g M-: + helm-minibuffer-history. (cl-letf ((frame (if (minibufferp helm-current-buffer) (selected-frame) (last-nonminibuffer-frame))) ;; This is a workaround, because the i3 window ;; manager developers are refusing to fix their ;; broken timestamp and event handling. ;; ;; We basically just disable the part of ;; select-frame-set-input-focus that would call ;; XSetInputFocus in Xlib (x-focus-frame), that ;; resets a timestamp in the xserver which the i3 ;; developers fail to notice. ;; ;; Since they don't know about the new timestamp, ;; their keyboard handling can break after a helm ;; user quits emacs, as reported in bug#1641. ;; ;; Fortunately for us, we really don't need this ;; XSetInputFocus call, since we already have focus ;; for Emacs, the user is just using helm! We call ;; select-frame-set-input-focus for the other ;; side-effects, not for x-focus-frame. ((symbol-function 'x-focus-frame) #'ignore)) (select-frame-set-input-focus frame)))))) (defun helm-current-window-configuration () "Like `current-window-configuration' but deal with Transient incompatibility. See https://github.com/magit/transient/discussions/361 for details." (when (and (window-live-p (bound-and-true-p transient--window)) (not (transient--preserve-window-p))) (transient--delete-window)) (current-window-configuration)) (defun helm-split-window-default-fn (window) "Default function to split windows before displaying `helm-buffer'. It is used as default value for `helm-split-window-preferred-function' which is then the let-bounded value of `split-window-preferred-function' in `helm-display-buffer'. When `helm-display-function' which default to `helm-default-display-buffer' is called from `helm-display-buffer' the value of `split-window-preferred-function' will be used by `display-buffer'." (let* ((split-width-threshold (and (integerp helm-split-width-threshold) helm-split-width-threshold)) (win (if (and (fboundp 'window-in-direction) ;; Don't try to split when starting in a minibuffer ;; e.g M-: and try to use helm-show-kill-ring. (not (minibufferp helm-current-buffer)) (null helm-split-width-threshold)) (if (or (one-window-p t) helm-split-window-inside-p) (split-window (selected-window) nil (if (eq helm-split-window-default-side 'other) helm-split-window-other-side-when-one-window helm-split-window-default-side)) ;; If more than one window reuse one of them. (cl-case helm-split-window-default-side (left (or (helm-window-in-direction 'left) (helm-window-in-direction 'above) (selected-window))) (above (or (helm-window-in-direction 'above) (helm-window-in-direction 'left) (selected-window))) (right (or (helm-window-in-direction 'right) (helm-window-in-direction 'below) (selected-window))) (below (or (helm-window-in-direction 'below) (helm-window-in-direction 'right) (selected-window))) (same (selected-window)) (other (or (helm-other-window-for-scrolling) (selected-window))) (t (or (window-next-sibling) (selected-window))))) (split-window-sensibly window)))) (setq helm-persistent-action-window-buffer (window-buffer win)) win)) (defun helm-window-in-direction (direction) "Same as `window-in-direction' but check if window is dedicated. Return nil when window is dedicated." (helm-aif (window-in-direction direction) (and (not (window-dedicated-p it)) it))) (defun helm-other-window-for-scrolling () "Same as `other-window-for-scrolling' but check if window is dedicated. Returns nil when window is dedicated." (helm-aif (other-window-for-scrolling) (and (not (window-dedicated-p it)) it))) (defun helm-resolve-display-function (com) "Decide which display function to use according to `helm-commands-using-frame'. The `helm-display-function' buffer local value takes precedence on `helm-commands-using-frame'. If `helm-initial-frame' has no minibuffer, use `helm-display-buffer-in-own-frame' function. Fallback to global value of `helm-display-function' when no local value found and current command is not in `helm-commands-using-frame'." (let ((win (get-buffer-window helm-current-buffer))) (or (with-helm-buffer helm-display-function) (and (or (memq com helm-commands-using-frame) (and helm-use-frame-when-no-suitable-window (or (window-dedicated-p win) (window-parameter win 'window-side))) (and helm-use-frame-when-more-than-two-windows (null helm--nested) (> (length (window-list)) 2)) ;; Frame parameter is unreliable for minibuffer on emacs-26. (null (member helm-initial-frame (minibuffer-frame-list)))) #'helm-display-buffer-in-own-frame) (default-value 'helm-display-function)))) (defun helm-display-buffer (buffer &optional resume) "Display BUFFER. The function used to display `helm-buffer' by calling `helm-display-function' which splits window with `helm-split-window-preferred-function'." (let ((split-window-preferred-function helm-split-window-preferred-function) (helm-split-window-default-side (if (and (not helm-full-frame) helm-reuse-last-window-split-state) (helm-acase helm-split-window-default-side ((same other) it) ; take precedence on *-window-side-state. ((guard* (progn helm--window-side-state)) guard) (t it)) helm-split-window-default-side)) (disp-fn (with-current-buffer buffer (helm-resolve-display-function (if helm-actions-inherit-frame-settings (helm-this-command) this-command))))) (prog1 (funcall disp-fn buffer (or (helm-resume-p resume) (and helm-actions-inherit-frame-settings helm--executing-helm-action))) (with-helm-buffer (setq-local helm-display-function disp-fn)) (setq helm-onewindow-p (one-window-p t)) ;; Don't allow other-window and friends switching out of minibuffer. (when helm-prevent-escaping-from-minibuffer (helm-prevent-switching-other-window))))) (cl-defun helm-prevent-switching-other-window (&key (enabled t)) "Allow setting `no-other-window' parameter for all windows. Arg ENABLE is the value of `no-other-window' window property." (walk-windows (lambda (w) (unless (window-dedicated-p w) (set-window-parameter w 'no-other-window enabled))) 0)) (defun helm-default-display-buffer (buffer &optional _resume) "Default function to display `helm-buffer' BUFFER. It is the default value of `helm-display-function'. It uses `switch-to-buffer' or `display-buffer' depending on the value of `helm-full-frame' or `helm-split-window-default-side'." (let (pop-up-frames (curwin (get-buffer-window helm-current-buffer))) (if (or (buffer-local-value 'helm-full-frame (get-buffer buffer)) (and (eq helm-split-window-default-side 'same) (one-window-p t))) (progn (and (not (minibufferp helm-current-buffer)) ;; side-windows can't be the only window in frame, ;; emacs refuse to delete other windows when ;; current is a side-window [1]. (not (window-parameter curwin 'window-side)) (delete-other-windows)) (switch-to-buffer buffer)) (when (and (or helm-always-two-windows helm-autoresize-mode) (not (eq helm-split-window-default-side 'same)) (not (minibufferp helm-current-buffer)) (not helm-split-window-inside-p) ;; Same comment as in [1]. (not (window-parameter curwin 'window-side))) (delete-other-windows)) (display-buffer buffer `(,helm-default-display-buffer-functions . ,(append helm-default-display-buffer-alist `((window-height . ,helm-display-buffer-default-height) (window-width . ,helm-display-buffer-default-width))))) (helm-log-run-hook "helm-default-display-buffer" 'helm-window-configuration-hook)))) ;; Shut up byte-compiler in emacs-26 (defvar tab-bar-mode) ;; No warnings in Emacs built --without-x (defvar x-display-name) (defun helm-display-buffer-in-own-frame (buffer &optional resume) "Display Helm buffer BUFFER in a separate frame. Function suitable for `helm-display-function', `helm-completion-in-region-display-function' and/or `helm-show-completion-default-display-function'. See `helm-display-buffer-height' and `helm-display-buffer-width' to configure frame size. Note that this feature is available only with emacs-25+. Note also it is not working properly in helm nested session with emacs version < emacs-28." (cl-assert (and (fboundp 'window-absolute-pixel-edges) (fboundp 'frame-geometry)) nil "Helm buffer in own frame is only available starting at emacs-25+") (if (not (display-graphic-p)) ;; Fallback to default when frames are not usable. (helm-default-display-buffer buffer) (setq helm--buffer-in-new-frame-p t) (let* ((pos (window-absolute-pixel-position)) (px (car pos)) (py (cdr pos)) (half-screen-size (/ (display-pixel-height x-display-name) 2)) (frame-info (frame-geometry)) (screen-width (display-pixel-width x-display-name)) (helm-frame-width (* (frame-char-width) (+ 2 helm-display-buffer-width))) (prmt-size (length helm--prompt)) (prmt-width (* prmt-size (frame-char-width))) (line-height (frame-char-height)) tab-bar-mode (new-frame-alist (if resume (buffer-local-value 'helm--last-frame-parameters (get-buffer buffer)) `((width . ,helm-display-buffer-width) (height . ,helm-display-buffer-height) (tool-bar-lines . 0) ;; lateral constraint to keep the frame inside of the screen (left . ,(cond ((> (+ px helm-frame-width) screen-width) (- screen-width helm-frame-width)) (t (max (- px prmt-width) 0)))) ;; Try to put frame at the best possible place. ;; Frame should be below point if enough ;; place, otherwise above point and ;; current line should not be hidden ;; by helm frame. (top . ,(if (> py half-screen-size) ;; Above point (- py ;; add 2 lines to make sure there is always a gap (* (+ helm-display-buffer-height 2) line-height) ;; account for title bar height too (cddr (assq 'title-bar-size frame-info))) ;; Below point (+ py line-height))) (title . "Helm") (undecorated . ,helm-use-undecorated-frame-option) (background-color . ,(or helm-frame-background-color (face-attribute 'default :background))) (foreground-color . ,(or helm-frame-foreground-color (face-attribute 'default :foreground))) (alpha . ,(or helm-frame-alpha 100)) (font . ,(assoc-default 'font (frame-parameters))) (vertical-scroll-bars . nil) (menu-bar-lines . 0) (fullscreen . nil) (visibility . ,(null helm-display-buffer-reuse-frame)) (minibuffer . t)))) display-buffer-alist) ;; Display minibuffer above or below only in initial session, ;; not on a session triggered by action, this way if user have ;; toggled minibuffer and header-line manually she keeps this ;; setting in next action. (unless (or helm--executing-helm-action resume) ;; Add the hook inconditionally, if ;; helm-echo-input-in-header-line is nil helm-hide-minibuffer-maybe ;; will have anyway no effect so no need to remove the hook. (add-hook 'helm-minibuffer-set-up-hook 'helm-hide-minibuffer-maybe) (with-helm-buffer (setq-local helm-echo-input-in-header-line (not (> py half-screen-size))))) (helm-display-buffer-popup-frame buffer new-frame-alist) ;; When frame size have been modified manually by user restore ;; it to default value unless resuming or not using ;; `helm-display-buffer-reuse-frame'. ;; This have to be done AFTER raising the frame otherwise ;; minibuffer visibility is lost until next session. (unless (or resume (not helm-display-buffer-reuse-frame)) (set-frame-size helm-popup-frame helm-display-buffer-width helm-display-buffer-height))) (helm-log-run-hook "helm-display-buffer-in-own-frame" 'helm-window-configuration-hook))) (defun helm-display-buffer-popup-frame (buffer frame-alist) (if helm-display-buffer-reuse-frame (let* ((x (cdr (assoc 'left frame-alist))) (y (cdr (assoc 'top frame-alist))) (width (cdr (assoc 'width frame-alist))) (height (cdr (assoc 'height frame-alist)))) (unless (and helm-popup-frame (frame-live-p helm-popup-frame)) (setq helm-popup-frame (make-frame frame-alist))) (select-frame helm-popup-frame) (set-frame-position helm-popup-frame x y) (set-frame-width helm-popup-frame width) (set-frame-height helm-popup-frame height) (switch-to-buffer buffer) (select-frame-set-input-focus helm-popup-frame t)) ;; If user have changed `helm-display-buffer-reuse-frame' to nil ;; maybe kill the frame. (when (and helm-popup-frame (frame-live-p helm-popup-frame)) (delete-frame helm-popup-frame)) (display-buffer buffer `(display-buffer-pop-up-frame . ((pop-up-frame-parameters . ,frame-alist)))))) ;; Ensure to quit helm when user delete helm frame manually. ;; If user deletes another frame keep session running. (defun helm--delete-frame-function (frame) (when (and helm-alive-p ;; FRAME is handling helm-buffer (get-buffer-window helm-buffer frame)) (helm-keyboard-quit))) (add-hook 'delete-frame-functions 'helm--delete-frame-function) ;;; Initialize ;; (defun helm-get-sources (sources) "Transform each element of SOURCES in alist. Return the resulting list." (when sources (mapcar (lambda (source) (if (listp source) source (symbol-value source))) (helm-normalize-sources sources)))) (defun helm-initialize (resume input default sources) "Start initialization of Helm session. For RESUME INPUT DEFAULT and SOURCES see `helm'." (helm-log "helm-initialize" "start initialization: resume=%S input=%S" resume input) (helm-frame-or-window-configuration 'save) (let ((sources-list (helm-get-sources sources))) (setq helm--in-fuzzy (cl-loop for s in sources-list for matchfns = (helm-match-functions s) for searchfns = (helm-search-functions s) when (or (memq 'helm-fuzzy-match matchfns) (memq 'helm-fuzzy-search searchfns)) return t)) (helm-log "helm-initialize" "sources-list = %S" sources-list) (helm-set-local-variable 'helm-sources sources-list) ;; Once `helm-buffer' is created `helm-sources' will be a local ;; variable which value is a list of alists. (helm-current-position 'save) (if (helm-resume-p resume) (helm-initialize-overlays (helm-buffer-get)) (helm-initial-setup input default sources-list)) (setq helm-alive-p t) (unless (eq resume 'noresume) (helm--push-and-remove-dups helm-buffer 'helm-buffers) (setq helm-last-buffer helm-buffer)) ;; If a `resume' attribute is present `helm-compute-attr-in-sources' ;; will run its function. (when (helm-resume-p resume) (helm-compute-attr-in-sources 'resume)) (helm-log "helm-initialize" "end initialization"))) (defun helm-current-position (save-or-restore) "Save or restore current position in `helm-current-buffer'. Argument SAVE-OR-RESTORE is either save or restore." (cl-case save-or-restore (save (helm-log "helm-current-position" "Save position at %S" (cons (point) (window-start))) (setq helm-current-position (cons (point) (window-start)))) (restore ;; Maybe `helm-current-buffer' have been deleted ;; during helm session so check if it is here ;; otherwise position in underlying buffer will be lost. (when (get-buffer-window helm-current-buffer 'visible) (helm-log "helm-current-position" "Restore position at %S in buffer %s" helm-current-position (buffer-name (current-buffer))) (goto-char (car helm-current-position)) ;; Fix this position with the NOFORCE arg of `set-window-start' ;; otherwise, if there is some other buffer than `helm-current-buffer' ;; one, position will be lost. (set-window-start (selected-window) (cdr helm-current-position) t))))) (defun helm-initialize-overlays (buffer) "Initialize Helm overlays in BUFFER." (helm-log "helm-initialize-overlays" "overlay setup") (if helm-selection-overlay ;; make sure the overlay belongs to the helm buffer if ;; it's newly created (move-overlay helm-selection-overlay (point-min) (point-min) (get-buffer buffer)) (setq helm-selection-overlay (make-overlay (point-min) (point-min) (get-buffer buffer))) (overlay-put helm-selection-overlay 'face 'helm-selection) (overlay-put helm-selection-overlay 'priority 1))) (defun helm-initial-setup (input default sources) "Initialize Helm settings and set up the Helm buffer." ;; Run global hook. (helm-log-run-hook "helm-initial-setup" 'helm-before-initialize-hook) ;; Run local source hook. (helm--run-init-hooks 'before-init-hook sources) ;; For initialization of helm locals vars that need ;; a value from current buffer, it is here. (helm-set-local-variable 'current-input-method current-input-method) (setq helm-current-prefix-arg nil helm-saved-action nil helm-saved-selection nil helm-suspend-update-flag nil ;; Ensure this is called BEFORE selecting helm-window. helm-current-buffer (helm--current-buffer) helm-buffer-file-name buffer-file-name helm-issued-errors nil helm-saved-current-source nil helm--suspend-update-interactive-flag nil) (when (and (with-helm-current-buffer (and (buffer-narrowed-p) (use-region-p))) (not helm--nested)) (helm-set-local-variable 'helm--current-buffer-narrowed (list (current-buffer) (region-beginning) (region-end)))) (unless (and (or helm-split-window-state helm--window-side-state) helm-reuse-last-window-split-state) ;; `helm-split-window-state' should be the contrary of what we currently ;; have to allow toggling windows with C-t. This was influencing the ;; behavior of `helm-show-action-window-other-window' but we have now ;; removed this limitation, the action buffer beeing displayed 'below' when ;; helm-window is too narrow (vertical split). See bug#2635. (setq helm-split-window-state (if (or (null helm-split-window-default-side) ; same as below. (memq helm-split-window-default-side '(below above)) (null helm-split-width-threshold) (and (integerp helm-split-width-threshold) (>= helm-split-width-threshold (+ (frame-width) 4)))) 'vertical 'horizontal)) (setq helm--window-side-state (or helm-split-window-default-side 'below))) ;; Some sources like helm-mu are using input to init their ;; candidates in init function, so setup initial helm-pattern here. ;; See bug#2530 and https://github.com/emacs-helm/helm-mu/issues/54. ;; Input should have precedence on default. (cond (input (setq helm-input input helm-pattern input)) ((and default helm-maybe-use-default-as-input) (setq helm-pattern (if (listp default) (car default) default) ;; Even if helm-pattern is set we want the ;; prompt to be empty when using default as input, why ;; helm-input is initialized to "". helm-input "")) (helm-maybe-use-default-as-input (setq helm-pattern (or (with-helm-current-buffer (thing-at-point 'symbol)) "") helm-input "")) (t (setq helm-pattern "" helm-input ""))) (helm--fuzzy-match-maybe-set-pattern) ;; Call the init function for sources where appropriate (helm-compute-attr-in-sources 'init sources) (clrhash helm-candidate-cache) (helm-create-helm-buffer) (helm-clear-visible-mark) ;; Run global hook. (helm-log-run-hook "helm-initial-setup" 'helm-after-initialize-hook) ;; Run local source hook. (helm--run-init-hooks 'after-init-hook sources)) (defun helm--run-init-hooks (hook sources) "Run after and before init hooks local to source. See :after-init-hook and :before-init-hook in `helm-source'." ;; We handle here incorrect values of hooks to not break packages using such ;; values i.e. lambda's or lists not bound to a symbol. In the future we may ;; use `helm-log-run-hook' directly which allow using add-hook, remove-hook ;; etc... (dolist (s sources) (helm-acase (assoc-default hook s) ((guard* (and (functionp it) (not (symbolp it)))) (funcall it)) ((guard* (listp it)) (dolist (h it) (funcall h))) (t (helm-log-run-hook "helm--run-init-hooks" it))))) (defun helm-restore-position-on-quit () "Restore position in `helm-current-buffer' when quitting." (helm-current-position 'restore)) (defun helm--push-and-remove-dups (elm sym) "Move ELM of SYM value on top and set SYM to this new value." (set sym (cons elm (delete elm (symbol-value sym))))) (defun helm--current-buffer () "[INTERNAL] Return `current-buffer' BEFORE `helm-buffer' is initialized. Note that it returns the minibuffer in use after Helm has started and is intended for `helm-initial-setup'. To get the buffer where Helm was started, use `helm-current-buffer' instead." (if (minibuffer-window-active-p (minibuffer-window)) ;; If minibuffer is active be sure to use it's buffer ;; as `helm-current-buffer', this allow to use helm ;; from an already active minibuffer (M-: etc...) (window-buffer (active-minibuffer-window)) ;; Fix Bug#456 ;; Use this instead of `current-buffer' to ensure ;; helm session started in helm-mode from a completing-read ;; Use really the buffer where we started and not the one ;; where the completing-read is wrapped. i.e ;; (with-current-buffer SOME-OTHER-BUFFER (completing-read [...]) (window-buffer (with-selected-window (minibuffer-window) (minibuffer-selected-window))))) (define-derived-mode helm-major-mode fundamental-mode "Hmm" "[INTERNAL] Provide major-mode name in Helm buffers. Unuseful when used outside Helm, don't use it.") (put 'helm-major-mode 'mode-class 'special) (put 'helm-major-mode 'helm-only t) (defun helm-create-helm-buffer () "Create and setup `helm-buffer'." (let ((root-dir default-directory) (inhibit-read-only t)) (with-current-buffer (get-buffer-create helm-buffer) (helm-log "helm-create-helm-buffer" "Enabling major-mode %S" major-mode) (helm-log "helm-create-helm-buffer" "kill local variables: %S" (buffer-local-variables)) (kill-all-local-variables) (helm-major-mode) (set (make-local-variable 'buffer-read-only) nil) (buffer-disable-undo) (erase-buffer) ;; Use this instead of setting helm-map local ensure we have all ;; our keys when helm loose minibuffer focus. And the map is ;; made local as well AFAIU. (use-local-map helm-map) (set (make-local-variable 'helm-source-filter) nil) (make-local-variable 'helm-sources) (set (make-local-variable 'helm-display-function) nil) (set (make-local-variable 'helm-selection-point) nil) (set (make-local-variable 'scroll-margin) (if helm-display-source-at-screen-top 0 helm-completion-window-scroll-margin)) (set (make-local-variable 'default-directory) root-dir) (set (make-local-variable 'helm-marked-candidates) nil) (set (make-local-variable 'helm--prompt) helm--prompt) (helm-initialize-persistent-action) (helm-log "helm-create-helm-buffer" "helm-display-function = %S" helm-display-function) (helm-log "helm-create-helm-buffer" "helm--local-variables = %S" helm--local-variables) (helm--set-local-variables-internal) (setq truncate-lines helm-truncate-lines) ; already local. (setq left-margin-width helm-left-margin-width) (setq cursor-type nil)) (helm-initialize-overlays helm-buffer) (get-buffer helm-buffer))) (define-minor-mode helm--minor-mode "[INTERNAL] Enable keymap in Helm minibuffer. Since this mode has no effect when run outside of Helm context, please don't use it outside of Helm. \\{helm-map}" :group 'helm :keymap (and helm-alive-p helm-map) (unless helm-alive-p (setq helm--minor-mode nil))) (put 'helm--minor-mode 'helm-only t) (defun helm--reset-default-pattern () (setq helm-pattern "") (setq helm-maybe-use-default-as-input nil)) (defun helm-read-from-minibuffer (prompt input preselect resume keymap default history) "Read pattern with prompt PROMPT and initial input INPUT. For PRESELECT RESUME KEYMAP DEFAULT HISTORY, see `helm'." (with-helm-buffer (if (and (helm-resume-p resume) ;; When no source, helm-buffer is empty ;; or contain non--candidate lines (e.g grep exit status) (helm-get-current-source)) (helm-mark-current-line t) (helm-update preselect)) (let* ((src (helm-get-current-source)) (src-keymap (assoc-default 'keymap src)) (hist (or (and history (symbolp history) history) ;; Needed for resuming. (assoc-default 'history src))) (timer nil) blink-matching-paren (first-src (car helm-sources)) (source-process-p (or (assq 'candidates-process src) (assq 'candidates-process first-src))) ;; As we are using `helm-keyboard-quit' for `C-g' we have ;; to prevent emacs command loop redefining `C-g' during ;; helm-session. This happen only on async source with ;; large output after a certain delay. The effect is that ;; the minibuffer is exited but the helm async process ;; continue running, and because minibuffer is lost `C-g' ;; have no more effect. By binding `inhibit-quit' here we ;; prevent this and allow `C-g' (the helm one aka ;; `helm-keyboard-quit') to quit immediately. (inhibit-quit source-process-p)) (helm-log "helm-read-from-minibuffer" "helm-get-candidate-number => %S" (helm-get-candidate-number)) (helm-log "helm-read-from-minibuffer" "helm-execute-action-at-once-if-one = %S" helm-execute-action-at-once-if-one) (helm-log "helm-read-from-minibuffer" "helm-quit-if-no-candidate = %S" helm-quit-if-no-candidate) (when (and src (helm-resume-p resume)) (helm-display-mode-line src) (setq helm-pattern input)) ;; Reset `helm-pattern' and update ;; display if no result found with precedent value of `helm-pattern' ;; unless `helm-quit-if-no-candidate' is non-`nil', in this case ;; Don't force update with an empty pattern. ;; Reset also `helm-maybe-use-default-as-input' as this checking ;; happen only on startup. (when helm-maybe-use-default-as-input ;; Store value of `default' temporarily here waiting next update ;; to allow actions like helm-moccur-action matching pattern ;; at the place it jump to. (setq helm-input helm-pattern) (if source-process-p ;; Reset pattern to next update. (with-helm-after-update-hook (helm--reset-default-pattern)) ;; Reset pattern right now. (helm--reset-default-pattern)) ;; Ensure force-update when no candidates ;; when we start with an empty pattern. (and (helm-empty-buffer-p) (null helm-quit-if-no-candidate) (helm-force-update preselect))) ;; Handle `helm-execute-action-at-once-if-one' and ;; `helm-quit-if-no-candidate' now. (cond ((and (if (functionp helm-execute-action-at-once-if-one) (funcall helm-execute-action-at-once-if-one) helm-execute-action-at-once-if-one) (= (helm-get-candidate-number (eq helm-execute-action-at-once-if-one 'current-source)) 1)) (ignore)) ; Don't enter the minibuffer loop. ((and helm-quit-if-no-candidate (= (helm-get-candidate-number) 0)) (setq helm--quit t) (and (functionp helm-quit-if-no-candidate) (funcall helm-quit-if-no-candidate))) (t ; Enter now minibuffer and wait for input. (let ((tap (or default (with-helm-current-buffer (thing-at-point 'symbol))))) (when helm-execute-action-at-once-if-one (helm-display-buffer helm-buffer resume) (select-window (helm-window))) (unwind-protect (minibuffer-with-setup-hook (lambda () ;; Start minor-mode with global value of helm-map. (helm--minor-mode 1) ;; Now override the global value of `helm-map' with ;; the local one which is in this order: ;; - The keymap of current source. ;; - The value passed in KEYMAP ;; - Or fallback to the global value of helm-map. (helm--maybe-update-keymap (or src-keymap keymap helm-map)) (helm-log-run-hook "helm-read-from-minibuffer" 'helm-minibuffer-set-up-hook) (setq timer (run-with-idle-timer (max (with-helm-buffer helm-input-idle-delay) 0.001) 'repeat (lambda () ;; Stop updating in persistent action ;; or when `helm-suspend-update-flag' ;; is non-`nil'. (unless (or helm-in-persistent-action helm-suspend-update-flag) (save-selected-window (helm-check-minibuffer-input) (helm-print-error-messages)))))) ;; minibuffer has already been filled here. (helm--update-header-line)) (read-from-minibuffer (propertize (or prompt "pattern: ") 'face 'helm-minibuffer-prompt) input helm-map nil hist tap helm-inherit-input-method)) (when timer (cancel-timer timer) (setq timer nil))))))))) (defun helm-toggle-suspend-update () "Enable or disable display update in helm. This can be useful for example for quietly writing a complex regexp without Helm constantly updating." (interactive) (helm-suspend-update (not helm-suspend-update-flag) t) (setq helm--suspend-update-interactive-flag (not helm--suspend-update-interactive-flag))) (put 'helm-toggle-suspend-update 'helm-only t) (defun helm-suspend-update (arg &optional verbose) "Enable or disable display update in helm. If ARG is 1 or non nil suspend update, if it is -1 or nil reenable updating. When VERBOSE is specified display a message." (with-helm-buffer (when (setq helm-suspend-update-flag (helm-acase arg (1 t) (-1 nil) (t it))) (helm-kill-async-processes) (setq helm-pattern "")) (when verbose (message (if helm-suspend-update-flag "Helm update suspended!" "Helm update re-enabled!"))) (helm-aif (helm-get-current-source) (helm-display-mode-line it t)))) (defun helm-delete-backward-no-update (arg) "Disable update and delete ARG chars backward. Update is reenabled when idle 1s." (interactive "p") (with-helm-alive-p (unless helm--suspend-update-interactive-flag (helm-suspend-update 1)) (delete-char (- arg)) (run-with-idle-timer 1 nil (lambda () (unless helm--suspend-update-interactive-flag (helm-suspend-update -1) (helm-check-minibuffer-input) (helm-force-update)))))) (put 'helm-delete-backward-no-update 'helm-only t) (defun helm-delete-char-backward (arg) "Delete char backward and update when reaching prompt." (interactive "p") (condition-case _err (cond ((and (use-region-p) delete-active-region (= arg 1)) ;; If a region is active, kill or delete it. (if (eq delete-active-region 'kill) (kill-region (region-beginning) (region-end) 'region) (delete-region (region-beginning) (region-end)))) (t (delete-char (- arg)))) (buffer-read-only (progn (helm-update) (helm-reset-yank-point))))) (put 'helm-delete-char-backward 'helm-only t) (defun helm--suspend-read-passwd (old--fn &rest args) "Suspend Helm while reading password. This is used to advice `tramp-read-passwd', `ange-ftp-get-passwd' and `epa-passphrase-callback-function'." ;; Suspend update when prompting for a tramp password. (setq helm-suspend-update-flag t) (setq overriding-terminal-local-map nil) (setq helm--reading-passwd-or-string t) (unwind-protect ;; No need to suspend timer in emacs-24.4 ;; it is fixed upstream. (apply old--fn args) (setq helm--reading-passwd-or-string nil) (setq helm-suspend-update-flag nil))) (defun helm--maybe-update-keymap (&optional map) "Handle different keymaps in multiples sources. Overrides `helm-map' with the local map of current source. If no map is found in current source, does nothing (keeps previous map)." (with-helm-buffer (helm-aif (or map (assoc-default 'keymap (helm-get-current-source))) ;; We used a timer in the past to leave ;; enough time to helm to setup its keymap ;; when changing source from a recursive minibuffer. ;; e.g C-x C-f M-y C-g ;; => *find-files have now the bindings of *kill-ring. ;; It is no more true now we are using `minor-mode-overriding-map-alist' ;; and `helm--minor-mode' thus it fix Bug#1076 for emacs-24.3 ;; where concurrent timers are not supported. ;; i.e update keymap+check input. (with-current-buffer (window-buffer (minibuffer-window)) (setq minor-mode-overriding-map-alist `((helm--minor-mode . ,it))))))) ;;; Prevent loosing focus when using mouse. ;; (defvar helm--remap-mouse-mode-map (let ((map (make-sparse-keymap))) (cl-loop for k in '([mouse-1] [mouse-2] [mouse-3] [down-mouse-1] [down-mouse-2] [down-mouse-3] [drag-mouse-1] [drag-mouse-2] [drag-mouse-3] [double-mouse-1] [double-mouse-2] [double-mouse-3] [triple-mouse-1] [triple-mouse-2] [triple-mouse-3]) do (define-key map k 'ignore)) map)) (define-minor-mode helm--remap-mouse-mode "[INTERNAL] Prevent escaping helm minibuffer with mouse clicks. Do nothing when used outside of helm context. WARNING: Do not use this mode yourself, it is internal to Helm." :group 'helm :global t :keymap helm--remap-mouse-mode-map (unless helm-alive-p (setq helm--remap-mouse-mode-map nil))) (put 'helm--remap-mouse-mode 'helm-only t) ;; Clean up (defun helm-cleanup () "Clean up the mess when Helm exit or quit." (helm-log "helm-cleanup" "start cleanup") (with-selected-window ;; When exiting with `helm-execute-action-at-once-if-one', ;; `helm-window' may not be created and we endup with an error ;; e.g. in eshell completion when only one candidate to complete ;; so fallback to selected-window in such cases. (or (get-buffer-window helm-buffer) (selected-window)) (let ((frame (selected-frame))) (setq cursor-type (default-value 'cursor-type)) ;; Ensure restoring default-value of mode-line to allow user ;; using the mouse when helm is inactive (Bug#1517,Bug#2377). (setq mode-line-format (default-value 'mode-line-format)) (remove-hook 'post-command-hook 'helm--maybe-update-keymap) (remove-hook 'post-command-hook 'helm--update-header-line) ;; Be sure we call cleanup functions from helm-buffer. (helm-compute-attr-in-sources 'cleanup) ;; Delete or make invisible helm frame. (if (and helm--buffer-in-new-frame-p ;; a helm session running in a frame that runs a nested ;; session share the same frame for both sessions so ;; don't delete the common frame. ;; i.e. helm--nested == t => delete ;; helm--nested == nil => delete ;; helm--nested == share => don't delete (not (eq helm--nested 'share))) (progn (setq-local helm--last-frame-parameters (helm--get-frame-parameters)) (bury-buffer) (if helm-display-buffer-reuse-frame (make-frame-invisible frame) (delete-frame frame))) ;; bury-buffer from this window [1]. ;; Do it at end to make sure buffer is still current. (bury-buffer)))) (helm-kill-async-processes) ;; Remove the temporary hooks added ;; by `with-helm-temp-hook' that ;; may not have been consumed. (when helm--temp-hooks (cl-loop for (fn . hook) in helm--temp-hooks do (remove-hook hook fn)) (setq helm--temp-hooks nil)) ;; When running helm from a dedicated frame ;; with no minibuffer, helm will run in the main frame ;; which have a minibuffer, so be sure to disable ;; the `no-other-window' prop there. (helm-prevent-switching-other-window :enabled nil) (helm-log-run-hook "helm-cleanup" 'helm-cleanup-hook) (helm-frame-or-window-configuration 'restore) ;; [1] now bury-buffer from underlying windows otherwise, ;; if this window is killed the underlying buffer will ;; be a helm buffer. (replace-buffer-in-windows helm-buffer) (setq helm-alive-p nil) ;; Prevent error "No buffer named *helm*" triggered by ;; `helm-set-local-variable'. (setq helm--force-updating-p nil) (setq helm--buffer-in-new-frame-p nil) ;; No need to reinitialize helm-pattern here now it is done only ;; once in init function bug#2530. (setq helm-last-query helm-pattern) ;; This is needed in some cases where last input ;; is yielded infinitely in minibuffer after helm session. (helm-clean-up-minibuffer)) (defun helm-clean-up-minibuffer () "Remove contents of minibuffer." (let ((miniwin (minibuffer-window))) ;; Clean only current minibuffer used by helm. ;; i.e The precedent one is active. (unless (minibuffer-window-active-p miniwin) (with-current-buffer (window-buffer miniwin) (delete-minibuffer-contents))))) ;;; Input handling ;; ;; (defun helm-check-minibuffer-input () "Check minibuffer content." (with-selected-window (or (active-minibuffer-window) (minibuffer-window)) (helm-check-new-input (minibuffer-contents)))) (defun helm-check-new-input (input) "Check INPUT string and update the helm buffer if necessary." (unless (equal input helm-pattern) (setq helm-pattern input) (unless (helm-action-window) (setq helm-input helm-pattern)) (helm-log "helm-check-new-input" "helm-pattern = %S" helm-pattern) (helm-log "helm-check-new-input" "helm-input = %S" helm-input) (helm-log-run-hook "helm-check-new-input" 'helm-before-update-hook) (setq helm--in-update t) (helm-update))) (defun helm--reset-update-flag () (run-with-idle-timer helm-exit-idle-delay nil (lambda () (setq helm--in-update nil)))) ;; (add-hook 'helm-after-update-hook #'helm--reset-update-flag) ;; All candidates (defun helm-get-candidates (source) "Retrieve and return the list of candidates from SOURCE." (let* ((candidate-fn (assoc-default 'candidates source)) (candidate-proc (assoc-default 'candidates-process source)) ;; See comment in helm-get-cached-candidates (Bug#2113). (inhibit-quit candidate-proc) cfn-error (notify-error (lambda (&optional e) (error "In `%s' source: `%s' %s %s" (assoc-default 'name source) (or candidate-fn candidate-proc) (if e "\n" "must be a list, a symbol bound to a list, or a function returning a list") (if e (prin1-to-string e) "")))) (candidates (condition-case-unless-debug err ;; Process candidates-(process) function ;; It may return a process or a list of candidates. (if candidate-proc ;; Calling `helm-interpret-value' with no ;; SOURCE arg force the use of `funcall' ;; and not `helm-apply-functions-from-source'. (helm-interpret-value candidate-proc) (helm-interpret-value candidate-fn source)) (error (helm-log "helm-get-candidates" "Error: %S" (setq cfn-error err)) nil)))) (cond ((and (processp candidates) (not candidate-proc)) (warn "Candidates function `%s' should be called in a `candidates-process' attribute" candidate-fn)) ((and candidate-proc (not (processp candidates))) (error "Candidates function `%s' should run a process" candidate-proc))) (cond ((processp candidates) ;; Candidates will be filtered later in process filter. candidates) ;; An error occured in candidates function. (cfn-error (unless helm--ignore-errors (funcall notify-error cfn-error))) ;; Candidates function returns no candidates. ((or (null candidates) ;; Can happen when the output of a process ;; is empty, and the candidates function call ;; something like (split-string (buffer-string) "\n") ;; which result in a list of one empty string (Bug#938). ;; e.g (completing-read "test: " '("")) (equal candidates '(""))) nil) ((listp candidates) ;; Transform candidates with `candidate-transformer' functions or ;; `real-to-display' functions if those are found, ;; otherwise return candidates unmodified. ;; `filtered-candidate-transformer' is NOT called here. (helm-transform-candidates candidates source)) (t (funcall notify-error))))) (defun helm-get-cached-candidates (source) "Return the cached value of candidates for SOURCE. Cache the candidates if there is no cached value yet." (let* ((name (assoc-default 'name source)) (candidate-cache (gethash name helm-candidate-cache)) ;; Bind inhibit-quit to ensure function terminate in case of ;; quit from `helm-while-no-input' and processes are added to ;; helm-async-processes for further deletion (Bug#2113). ;; FIXME: Is this still needed now `helm-while-no-input' ;; handles quit-flag? (inhibit-quit (assoc-default 'candidates-process source))) (helm-aif candidate-cache (prog1 it (helm-log "helm-get-cached-candidates" "Use cached candidates")) (helm-log "helm-get-cached-candidates" "No cached candidates, calculate candidates") (let ((candidates (helm-get-candidates source))) (cond ((processp candidates) (push (cons candidates (append source (list (cons 'item-count 0) (cons 'incomplete-line "")))) helm-async-processes) (set-process-filter candidates 'helm-output-filter) (setq candidates nil)) ((not (assq 'volatile source)) (puthash name candidates helm-candidate-cache))) candidates)))) ;;; Candidate transformers (defun helm-process-candidate-transformer (candidates source) "Execute `candidate-transformer' function(s) on CANDIDATES in SOURCE." (helm-aif (assoc-default 'candidate-transformer source) (helm-apply-functions-from-source source it candidates) candidates)) (defun helm-process-filtered-candidate-transformer (candidates source) "Execute `filtered-candidate-transformer' function(s) on CANDIDATES in SOURCE." (helm-aif (assoc-default 'filtered-candidate-transformer source) (helm-apply-functions-from-source source it candidates source) candidates)) (defun helm--maybe-process-filter-one-by-one-candidate (candidate source) "Execute `filter-one-by-one' function(s) on real value of CANDIDATE in SOURCE. Return CANDIDATE modified by the function(s)." (helm-aif (assoc-default 'filter-one-by-one source) (let ((real (if (consp candidate) (cdr candidate) candidate))) (when real (if (and (listp it) (not (functionp it))) ;; Don't treat lambda's as list. (cl-loop with cand = candidate for f in it do (setq cand (funcall f real)) finally return cand) (funcall it real)))) candidate)) (defun helm--initialize-one-by-one-candidates (candidates source) "Process CANDIDATES with the `filter-one-by-one' function in SOURCE. Return CANDIDATES unchanged when pattern is not empty." (helm-aif (and (string= helm-pattern "") (assoc-default 'filter-one-by-one source)) (cl-loop for cand in candidates collect (helm--maybe-process-filter-one-by-one-candidate cand source)) candidates)) (defun helm-process-filtered-candidate-transformer-maybe (candidates source process-p) "Execute `filtered-candidate-transformer' function(s) on CANDIDATES in SOURCE. When PROCESS-P is non-nil execute `filtered-candidate-transformer' functions if some, otherwise return CANDIDATES." (if process-p ;; When no filter return CANDIDATES unmodified. (helm-process-filtered-candidate-transformer candidates source) candidates)) (defun helm-process-real-to-display (candidates source) "Execute real-to-display function on all CANDIDATES of SOURCE." (helm-aif (assoc-default 'real-to-display source) (setq candidates (helm-apply-functions-from-source source 'mapcar (lambda (cand) (if (consp cand) ;; override DISPLAY from candidate-transformer (cons (funcall it (cdr cand)) (cdr cand)) (cons (funcall it cand) cand))) candidates)) candidates)) (defun helm-transform-candidates (candidates source &optional process-p) "Transform CANDIDATES from SOURCE according to candidate transformers. When PROCESS-P is non-nil executes the `filtered-candidate-transformer' functions, otherwise processes `candidate-transformer' functions only, `filtered-candidate-transformer' functions being processed later, after the candidates have been narrowed by `helm-candidate-number-limit', see `helm-compute-matches'. When `real-to-display' attribute is present, execute its functions on all maybe filtered CANDIDATES." (helm-process-real-to-display (helm-process-filtered-candidate-transformer-maybe (helm-process-candidate-transformer candidates source) source process-p) source)) ;; Narrowing candidates (defun helm-candidate-number-limit (source) "Apply candidate-number-limit attribute value. This overrides `helm-candidate-number-limit' variable. E.g.: If (candidate-number-limit) is in SOURCE, show all candidates in SOURCE. If (candidate-number-limit . 123) is in SOURCE limit candidate to 123." (helm-aif (assq 'candidate-number-limit source) ;; When assoc value is nil use by default 99999999 otherwise use ;; the assoc value, when it is a symbol interpret its value (bug#1831). (or (helm-aand (cdr it) (helm-interpret-value it)) 99999999) (or helm-candidate-number-limit 99999999))) (defun helm-candidate-get-display (candidate) "Get searched display part from CANDIDATE. CANDIDATE is either a string, a symbol, or a (DISPLAY . REAL) cons cell." (helm-acase candidate ((dst* (disp . real)) disp) ((guard* (symbolp candidate)) (symbol-name candidate)) ((guard* (numberp candidate)) (number-to-string candidate)) (t candidate))) (defun helm-process-pattern-transformer (pattern source) "Execute pattern-transformer attribute function(s) on PATTERN in SOURCE." (helm-aif (assoc-default 'pattern-transformer source) (helm-apply-functions-from-source source it pattern) pattern)) (defun helm-default-match-function (candidate) "Check if `helm-pattern' match CANDIDATE. Default function to match candidates according to `helm-pattern'." (string-match helm-pattern candidate)) ;;; Fuzzy matching ;; ;; (defvar helm--fuzzy-regexp-cache (make-hash-table :test 'eq)) (defun helm--fuzzy-match-maybe-set-pattern () ;; Computing helm-pattern with helm--mapconcat-pattern ;; is costly, so cache it once time for all and reuse it ;; until pattern change. (when helm--in-fuzzy (let ((fun (if (string-match "\\`\\^" helm-pattern) #'identity #'helm--mapconcat-pattern))) (clrhash helm--fuzzy-regexp-cache) ;; FIXME: Splitted part are not handled here, ;; I must compute them in `helm-search-match-part' ;; when negation and in-buffer are used. (if (string-match "\\`!" helm-pattern) (puthash 'helm-pattern (if (> (length helm-pattern) 1) (list (regexp-quote (substring helm-pattern 1 2)) (funcall fun (substring helm-pattern 1))) '("" "")) helm--fuzzy-regexp-cache) (puthash 'helm-pattern (if (> (length helm-pattern) 0) (list (regexp-quote (substring helm-pattern 0 1)) (funcall fun helm-pattern)) '("" "")) helm--fuzzy-regexp-cache))))) (defun helm-fuzzy-match (candidate) "Check if `helm-pattern' fuzzy matches CANDIDATE. This function is used with sources built with `helm-source-sync'." (unless (string-match " " helm-pattern) ;; When pattern have one or more spaces, let ;; multi-match doing the job with no fuzzy matching.[1] (let ((regexp (cadr (gethash 'helm-pattern helm--fuzzy-regexp-cache)))) (if (string-match "\\`!" helm-pattern) (not (string-match regexp candidate)) (string-match regexp candidate))))) (defun helm-fuzzy-search (pattern) "Same as `helm-fuzzy-match' but for sources built with `helm-source-in-buffer'." (unless (string-match " " helm-pattern) ;; Same as in `helm-fuzzy-match' ref[1]. (let* ((regexps (gethash 'helm-pattern helm--fuzzy-regexp-cache)) (partial-regexp (car regexps)) (regexp (cadr regexps))) (if (string-match "\\`!" pattern) ;; Don't try to search here, just return ;; the position of line and go ahead, ;; letting `helm-search-match-part' checking if ;; pattern match against this line. (prog1 (list (pos-bol) (pos-eol)) (forward-line 1)) ;; We could use here directly `re-search-forward' ;; on the regexp produced by `helm--mapconcat-pattern', ;; but it is very slow because emacs have to do an incredible ;; amount of loops to match e.g "[^f]*f[^o]*o..." in the whole buffer, ;; more the regexp is long more the amount of loops grow. ;; (Probably leading to a max-lisp-eval-depth error if both ;; regexp and buffer are too big) ;; So just search the first bit of pattern e.g "[^f]*f", and ;; then search the corresponding line with the whole regexp, ;; which increase dramatically the speed of the search. (cl-loop while (re-search-forward partial-regexp nil t) for bol = (pos-bol) for eol = (pos-eol) if (progn (goto-char bol) (re-search-forward regexp eol t)) do (goto-char eol) and return t else do (goto-char eol) finally return nil))))) (defvar helm-fuzzy-default-score-fn #'helm-fuzzy-flex-style-score) (defun helm-score-candidate-for-pattern (candidate pattern) "Assign score to CANDIDATE according to PATTERN." ;; Unknown candidates always go on top. (if (helm-candidate-prefixed-p candidate) 200.00 (funcall helm-fuzzy-default-score-fn candidate pattern))) ;; The flex scoring needs a regexp whereas the fuzzy scoring works ;; directly with helm-pattern, so cache the needed regexp for flex ;; scoring to not (re)compute it at each candidate. We could reuse ;; the regexp cached in `helm--fuzzy-regexp-cache' but it is not ;; exactly the same as the one needed for flex and also it is always ;; computed against the whole helm-pattern which is not usable for ;; e.g. file completion. (defvar helm--fuzzy-flex-regexp-cache (make-hash-table :test 'equal)) (defun helm-fuzzy-flex-style-score (candidate pattern) "Give a score to CANDIDATE according to PATTERN. A regexp is generated from PATTERN to calculate score. Score is calculated with the emacs-27 flex algorithm using `helm-flex--style-score'." (let ((regexp (helm-aif (gethash pattern helm--fuzzy-flex-regexp-cache) it (clrhash helm--fuzzy-flex-regexp-cache) (puthash pattern (helm--fuzzy-flex-pattern-to-regexp pattern) helm--fuzzy-flex-regexp-cache)))) (helm-flex--style-score candidate regexp t))) (defun helm--fuzzy-flex-pattern-to-regexp (pattern) "Return a regexp from PATTERN compatible with emacs-27 flex algorithm." (completion-pcm--pattern->regex (helm-completion--flex-transform-pattern (list pattern)) 'group)) (defun helm-flex-add-score-as-prop (candidates regexp) (cl-loop with case-fold-search = (helm-set-case-fold-search) for cand in candidates collect (helm-flex--style-score cand regexp))) (defun helm-completion--flex-transform-pattern (pattern) ;; "fob" => '(prefix "f" any "o" any "b" any point) (cl-loop for p in pattern if (stringp p) nconc (cl-loop for str across p nconc (list (string str) 'any)) else nconc (list p))) (defun helm-fuzzy-helm-style-score (candidate pattern) "Give a score to CANDIDATE according to PATTERN. Score is calculated for contiguous matches found with PATTERN. Score is 100 (maximum) if PATTERN is fully matched in CANDIDATE. One point bonus is added to score when PATTERN prefix matches CANDIDATE. Contiguous matches get a coefficient of 2." (let* ((cand (if (stringp candidate) candidate (helm-stringify candidate))) (pat-lookup (helm--collect-pairs-in-string pattern)) (str-lookup (helm--collect-pairs-in-string cand)) (inter (cl-nintersection pat-lookup str-lookup :test 'equal)) ;; Prefix (bonus (cond ((or (equal (car pat-lookup) (car str-lookup)) (equal (caar pat-lookup) (caar str-lookup))) 2) ((and (null pat-lookup) ; length = 1 (string= pattern (substring cand 0 1))) 150) (t 0))) ;; Exact match e.g. foo -> foo == 200 (bonus1 (and (string= cand pattern) 200)) ;; Partial match e.g. foo -> aafooaa == 100 ;; or foo -> fooaaa (bonus2 (and (or (string-match (concat "\\`" (regexp-quote pattern)) cand) (string-match (concat "\\<" (regexp-quote pattern) "\\>") cand)) 100))) (+ bonus (or bonus1 bonus2 ;; Give a coefficient of 2 for contiguous matches. ;; That's mean that "wiaaaki" will not take precedence ;; on "aaawiki" when matching on "wiki" even if "wiaaaki" ;; starts by "wi". (* (length inter) 2))))) (defun helm-fuzzy-matching-default-sort-fn-1 (candidates &optional use-real basename preserve-tie-order) "The transformer for sorting candidates in fuzzy matching. It sorts on the display part by default. It sorts CANDIDATES by their scores as calculated by `helm-score-candidate-for-pattern'. Set USE-REAL to non-nil to sort on the real part. If BASENAME is non-nil assume we are completing filenames and sort on basename of candidates. If PRESERVE-TIE-ORDER is nil, ties in scores are sorted by length of the candidates." (if (string= helm-pattern "") candidates (let ((table-scr (make-hash-table :test 'equal))) (sort candidates (lambda (s1 s2) ;; Score and measure the length on real or display part of candidate ;; according to `use-real'. (let* ((real-or-disp-fn (if use-real #'cdr #'car)) (cand1 (cond ((and basename (consp s1)) (helm-basename (funcall real-or-disp-fn s1))) ((consp s1) (funcall real-or-disp-fn s1)) (basename (helm-basename s1)) (t s1))) (cand2 (cond ((and basename (consp s2)) (helm-basename (funcall real-or-disp-fn s2))) ((consp s2) (funcall real-or-disp-fn s2)) (basename (helm-basename s2)) (t s2))) (data1 (or (gethash cand1 table-scr) (puthash cand1 (list (helm-score-candidate-for-pattern cand1 helm-pattern) (length (helm-stringify cand1))) table-scr))) (data2 (or (gethash cand2 table-scr) (puthash cand2 (list (helm-score-candidate-for-pattern cand2 helm-pattern) (length (helm-stringify cand2))) table-scr))) (len1 (cadr data1)) (len2 (cadr data2)) (scr1 (car data1)) (scr2 (car data2))) (cond ((= scr1 scr2) ;; Comparison by length should not be called here most of ;; the time because we use now flex scoring which does ;; such test, however we still use helm fuzzy scoring ;; with preserve-tie-order, so keep testing length here ;; for it. (unless preserve-tie-order (< len1 len2))) ((> scr1 scr2))))))))) (defun helm-fuzzy-matching-default-sort-fn (candidates _source) "Default `filtered-candidate-transformer' to sort in fuzzy matching." (helm-fuzzy-matching-default-sort-fn-1 candidates)) (defun helm-fuzzy-matching-sort-fn-preserve-ties-order (candidates _source) "Same as `helm-fuzzy-matching-default-sort-fn' but preserving order of ties. The default function, `helm-fuzzy-matching-default-sort-fn', sorts ties by length, shortest first. This function may be more useful when the order of the candidates is meaningful, e.g. with `recentf-list'." ;; Flex scoring is already taking in account length of strings so this ;; function have no effect when flex scoring is in use, force the usage of ;; helm fuzzy scoring to ensure no testing against length is done. (let ((helm-fuzzy-default-score-fn #'helm-fuzzy-helm-style-score)) (helm-fuzzy-matching-default-sort-fn-1 candidates nil nil t))) (defun helm--maybe-get-migemo-pattern (pattern &optional diacritics) (or (and helm-migemo-mode (assoc-default pattern helm-mm--previous-migemo-info)) (if diacritics (char-fold-to-regexp pattern) pattern))) (defun helm-fuzzy-default-highlight-match-1 (candidate &optional pattern diacritics file-comp) (let* ((pair (and (consp candidate) candidate)) (display (helm-stringify (if pair (car pair) candidate))) (real (cdr pair)) (host (and file-comp (get-text-property (max 0 (1- (length display))) 'host display))) (regex (helm--maybe-get-migemo-pattern pattern diacritics)) ;; Match prop at end, because at 0 we might have an icon. (mpart (get-text-property (1- (length display)) 'match-part display)) (mp (cond ((and mpart (string= display mpart)) nil) (mpart) ;; FIXME: This may be wrong when match-on-real ;; is nil, so we should flag match-on-real on ;; top and use it. (file-comp (file-name-nondirectory (or host (and (stringp real) real) display))))) (count 0) beg-str end-str) ;; Happens when matching empty lines (^$), in this case there is nothing to ;; highlight. (if (string= mpart "") candidate (when host (setq pattern (cadr (split-string pattern ":")))) ;; Extract all parts of display keeping original properties. (when (and mp (ignore-errors ;; Avoid error when candidate is a huge line. (string-match (regexp-quote mp) display))) (setq beg-str (substring display 0 (match-beginning 0)) end-str (substring display (match-end 0) (length display)) mp (substring display (match-beginning 0) (match-end 0)))) (with-temp-buffer ;; Insert the whole display part and remove non--match-part ;; to keep their original face properties. (insert (propertize (or mp display) 'read-only nil)) ; Fix (bug#1176) (goto-char (point-min)) (condition-case nil (progn ;; Try first matching against whole pattern. (unless (string= pattern "") (while (helm-re-search-forward regex nil t) (cl-incf count) (helm-add-face-text-properties (match-beginning 0) (match-end 0) 'helm-match))) ;; If no matches start matching against multiples or fuzzy matches. (when (zerop count) (cl-loop with multi-match = (string-match-p " " pattern) with patterns = (if multi-match (cl-loop for pat in (helm-mm-split-pattern pattern) collect (helm--maybe-get-migemo-pattern pat diacritics)) (helm-acase (split-string pattern "" t) ((guard* (string= "!" (car it))) nil) (t it))) for p in patterns ;; Multi matches (regexps patterns). if multi-match do (progn (while (helm-re-search-forward p nil t) (helm-add-face-text-properties (match-beginning 0) (match-end 0) 'helm-match)) (goto-char (point-min))) ;; Fuzzy matches (literal patterns). else do (when (search-forward p nil t) (helm-add-face-text-properties (match-beginning 0) (match-end 0) 'helm-match))))) (invalid-regexp nil)) ;; Now replace the original match-part with the part ;; with face properties added. (setq display (if mp (concat beg-str (buffer-string) end-str) (buffer-string)))) (if real (cons display real) display)))) (cl-defun helm-fuzzy-default-highlight-match (candidate &optional (pattern helm-pattern) diacritics file-comp) "The default function to highlight matches in fuzzy matching. Highlight elements in CANDIDATE matching PATTERN according to the matching method in use. When DIACRITICS is specified, ignore diacritics, see `char-fold-to-regexp' for more infos." (if (string= pattern "") ;; Empty pattern, do nothing. This is needed when this function ;; is used outside of helm-fuzzy-highlight-matches like in *buffers-list. candidate ;; Else start highlighting. (helm-fuzzy-default-highlight-match-1 candidate pattern diacritics file-comp))) (defun helm-fuzzy-highlight-matches (candidates source) "Highlight matches in CANDIDATES for SOURCE. The filtered-candidate-transformer function to highlight fuzzy matches. See `helm-fuzzy-default-highlight-match'." (cl-assert helm-fuzzy-matching-highlight-fn nil "Wrong type argument functionp: nil") (cl-loop with diac = (helm-get-attr 'diacritics source) with file-comp-p = (and (not (helm-action-window)) (or minibuffer-completing-file-name helm--minibuffer-completing-file-name (helm-get-attr 'completing-file-name source))) ;; helm-pattern may have been modified (fuzzy) so ensure to ;; use helm-input which is the raw pattern. with pattern = (if file-comp-p (file-name-nondirectory helm-input) helm-pattern) when (string= pattern "") return candidates for c in candidates collect (funcall helm-fuzzy-matching-highlight-fn c pattern diac file-comp-p))) ;;; helm-flex style ;; ;; Provide the emacs-27 flex style for emacs<27. ;; Reuse the flex scoring algorithm of flex style in emacs-27. (defun helm-flex--style-score (str regexp &optional score) "Score STR candidate according to REGEXP. REGEXP should be generated from a pattern which is a list like \\='(point \"f\" any \"o\" any \"b\" any) for \"fob\" as pattern. Such pattern may be build with `helm-completion--flex-transform-pattern' function, and the regexp with `completion-pcm--pattern->regex'. For commodity, `helm--fuzzy-flex-pattern-to-regexp' is used to build such regexp. Function extracted from `completion-pcm--hilit-commonality' in emacs-27 to provide such scoring in emacs<27." ;; Don't modify the string itself. (setq str (copy-sequence str)) (if (string-match regexp str) (let* ((md (match-data)) (start (pop md)) (len (length str)) (score-numerator 0) (score-denominator 0) (last-b 0) (update-score (lambda (a b) "Update score variables given match range (A B)." (setq score-numerator (+ score-numerator (- b a))) (unless (or (= a last-b) (zerop last-b) (= a (length str))) (setq score-denominator (+ score-denominator 1 (expt (- a last-b 1) (/ 1.0 3))))) (setq last-b b))) result) (funcall update-score start start) (setq md (cdr md)) (while md (funcall update-score start (pop md)) (setq start (pop md))) (funcall update-score len len) (unless (zerop (length str)) (setq result (/ score-numerator (* len (1+ score-denominator)) 1.0)) (put-text-property 0 1 'completion-score result str)) (if (and score result) result str)) (put-text-property 0 1 'completion-score 0.0 str) (if score 0.0 str))) ;;; Matching candidates ;; ;; (defun helm-match-functions (source) (let ((matchfns (or (assoc-default 'match source) (assoc-default 'match-strict source) #'helm-default-match-function))) (if (and (listp matchfns) (not (functionp matchfns))) matchfns (list matchfns)))) (defun helm-search-functions (source) (let ((searchfns (assoc-default 'search source))) (if (and (listp searchfns) (not (functionp searchfns))) searchfns (list searchfns)))) (defun helm-match-from-candidates (cands matchfns match-part-fn limit source) (when cands ; nil in async sources. (condition-case-unless-debug err (cl-loop with hash = (make-hash-table :test 'equal) with allow-dups = (assq 'allow-dups source) with case-fold-search = (helm-set-case-fold-search) with count = 0 for iter from 1 for fn in matchfns when (< count limit) nconc (cl-loop for c in cands for dup = (gethash c hash) for disp = (helm-candidate-get-display c) while (< count limit) for target = (if (helm-get-attr 'match-on-real source) ;; Let's fails on error in ;; case next block returns nil. (or (cdr-safe c) (get-text-property 0 'helm-realvalue disp)) disp) for prop-part = (get-text-property 0 'match-part target) for part = (and match-part-fn (or prop-part (funcall match-part-fn target))) ;; When allowing dups check if DUP ;; have been already found in previous loop ;; by comparing its value with ITER. when (and (or (and allow-dups dup (= dup iter)) (null dup)) (condition-case nil (funcall fn (or part target)) (invalid-regexp nil))) do (progn ;; Give as value the iteration number of ;; inner loop to be able to check if ;; the duplicate have not been found in previous loop. (puthash c iter hash) (setq c (helm--maybe-process-filter-one-by-one-candidate c source)) (cl-incf count)) ;; Filter out nil candidates maybe returned by ;; `helm--maybe-process-filter-one-by-one-candidate'. and when c collect (if (and part (not prop-part)) (if (consp c) (cons (propertize target 'match-part part) (cdr c)) (propertize c 'match-part part)) c))) (error (unless (eq (car err) 'invalid-regexp) ; Always ignore regexps errors. (helm-log-error "helm-match-from-candidates" "helm-match-from-candidates in source `%s': %s %s" (assoc-default 'name source) (car err) (cdr err))) nil)))) (defun helm-compute-matches (source) "Start computing candidates in SOURCE." (save-current-buffer (let ((matchfns (helm-match-functions source)) (matchpartfn (assoc-default 'match-part source)) (helm--source-name (assoc-default 'name source)) (helm-current-source source) (limit (helm-candidate-number-limit source)) (helm-pattern (helm-process-pattern-transformer helm-pattern source))) (helm--fuzzy-match-maybe-set-pattern) ;; If source have a `filtered-candidate-transformer' attr ;; Filter candidates with this func, otherwise just compute ;; candidates. ;; NOTE that this next block of code is returning nil on async sources, ;; the candidates being processed directly in `helm-output-filter' ;; process-filter. (helm-process-filtered-candidate-transformer ;; When using in-buffer method or helm-pattern is empty or ;; using dynamic completion always compute all candidates. (if (or (equal helm-pattern "") (assq 'match-dynamic source) (helm--candidates-in-buffer-p source)) ;; Compute all candidates up to LIMIT. ;; one-by-one are computed here only for sources that ;; display a list of candidates even with an empty ;; pattern. (helm--initialize-one-by-one-candidates (helm-take (helm-get-cached-candidates source) limit) source) ;; Compute candidates according to pattern with their match ;; fns. ;; one-by-one filtered candidates are computed during the ;; execution of next loop in `helm-match-from-candidates'. (helm-match-from-candidates (helm-get-cached-candidates source) matchfns matchpartfn limit source)) source)))) (defun helm--candidates-in-buffer-p (source) (assq 'search source)) (defun helm-render-source (source matches) "Display MATCHES from SOURCE according to its settings." (helm-log "helm-render-source" "Source = %S" (remove (assq 'keymap source) source)) (when matches (helm-insert-header-from-source source) (cl-loop with separate = nil with start = (point) with singleline = (null (assq 'multiline source)) for m in matches for count from 1 if singleline do (helm-insert-match m 'insert count source) else do (progn (if separate (helm-insert-candidate-separator) (setq separate t)) (helm-insert-match m 'insert count source)) finally (and (null singleline) (put-text-property start (point) 'helm-multiline t))))) (defmacro helm-while-no-input (&rest body) "Same as `while-no-input' but returns either BODY or nil. Unlike `while-no-input' this macro ensure to not returns `t'." (declare (debug t) (indent 0)) (let ((catch-sym (make-symbol "input"))) `(with-local-quit (catch ',catch-sym (let ((throw-on-input ',catch-sym) val) (setq val (progn ,@body)) ;; See comments in `while-no-input' about resetting ;; quit-flag. (cond ((eq quit-flag throw-on-input) (setq quit-flag nil)) (quit-flag nil) (t val))))))) (defmacro helm--maybe-use-while-no-input (&rest body) "Wrap BODY in `helm-while-no-input' unless initializing a remote connection." `(progn (if (or (and (file-remote-p helm-pattern) (not (file-remote-p helm-pattern nil t))) helm-update-edebug) ;; Tramp will ask for passwd, don't use `helm-while-no-input'. ,@body (helm-log "helm--maybe-use-while-no-input" "Using here `helm-while-no-input'") ;; Emacs bug , unexpected ;; dbus-event is triggered on dbus init. ;; Ignoring the dbus-event work on emacs28+; for emacs27 or older ;; version, require tramp-archive can workaround the issue. (let ((while-no-input-ignore-events (and (boundp 'while-no-input-ignore-events) (cons 'dbus-event while-no-input-ignore-events)))) (helm-while-no-input ,@body))))) (defun helm--collect-matches (src-list) "Return a list of matches for each source in SRC-LIST. The resulting value is a list of lists, e.g. ((a b c) (c d) (e f)) or (nil nil nil) for three sources when no matches found, however this function can be interrupted by new input and in this case returns a plain nil i.e. not (nil), in this case `helm-update' is not rendering the source, keeping previous candidates in display." (let ((matches (helm--maybe-use-while-no-input (cl-loop for src in src-list collect (helm-compute-matches src))))) (unless (eq matches t) matches))) ;;; Case fold search ;; ;; (cl-defun helm-set-case-fold-search (&optional (pattern helm-pattern)) "Used to set the value of `case-fold-search' in Helm. Return t or nil depending on the value of `helm-case-fold-search' and `helm-pattern'." (if helm-alive-p (let ((helm-case-fold-search (helm-aif (assq 'case-fold-search (helm-get-current-source)) (cdr it) helm-case-fold-search)) ;; Only parse basename for filenames ;; to avoid setting case sensitivity ;; when expanded directories contains upcase ;; characters. (bn-or-pattern (if (string-match "[~/]*" pattern) (helm-basename pattern) pattern))) (helm-set-case-fold-search-1 bn-or-pattern)) case-fold-search)) (defun helm-set-case-fold-search-1 (pattern) (cl-case helm-case-fold-search (smart (let ((case-fold-search nil)) (if (string-match "[[:upper:]]" pattern) nil t))) (t helm-case-fold-search))) ;;; Helm update ;; (defun helm-update (&optional preselect source candidates) "Update candidates list in `helm-buffer' based on `helm-pattern'. Argument PRESELECT is a string or regexp used to move selection to a particular place after finishing update. When SOURCE is provided update mode-line for this source, otherwise the current source will be used. Argument CANDIDATES when provided is used to redisplay these candidates without recomputing them, it should be a list of lists." (helm-log "helm-update" "Start updating") (helm-kill-async-processes) ;; When persistent action have been called ;; we have two windows even with `helm-full-frame'. ;; So go back to one window when updating if `helm-full-frame' ;; is non-`nil'. (when (with-helm-buffer (and helm-onewindow-p ;; We are not displaying helm-buffer in a frame and ;; helm-window is already displayed. (not helm--buffer-in-new-frame-p) (helm-window) (not (helm-action-window)))) (with-helm-window (delete-other-windows))) (with-current-buffer (helm-buffer-get) (set (make-local-variable 'helm-input-local) helm-pattern) (let ((ov '(nil))) (unwind-protect (let (sources matches) ;; Collect sources ready to be updated. (setq sources (cl-loop for src in helm-sources when (helm-update-source-p src) collect src)) ;; When no sources to update erase buffer to avoid duplication of ;; header and candidates when next chunk of update will arrive, ;; otherwise the buffer is erased AFTER [1] the results are ;; computed. This is also needed when pattern is quickly deleted ;; backward and we reach requires-pattern limit, in this case the helm ;; buffer is not updated and stays with the results of the last ;; matching pattern. (unless sources (erase-buffer)) (helm-maybe-dim-prompt-on-update ov) ;; Compute matches without rendering the sources. ;; This prevent the helm-buffer flickering when constantly ;; updating. (helm-log "helm-update" "Matches: %S" (setq matches (or candidates (helm--collect-matches sources)))) ;; If computing matches finished and is not interrupted ;; erase the helm-buffer and render results (Fix #1157). (when matches ;; nil only when interrupted by while-no-input. (erase-buffer) (cl-loop for src in sources for mtc in matches do (helm-render-source src mtc)) ;; Move to first line only when there is matches ;; to avoid cursor moving upside down (Bug#1703). (helm--update-move-first-line))) ;; When there is only one async source, update mode-line and run ;; `helm-after-update-hook' in `helm-output-filter--post-process', ;; when there is more than one source, update mode-line and run ;; `helm-after-update-hook' now even if an async source is ;; present and running in BG. (let ((src (or source (helm-get-current-source)))) (unless (assq 'candidates-process src) (helm-display-mode-line src 'force) (and (car ov) (delete-overlay (car ov))) (helm-log-run-hook "helm-update" 'helm-after-update-hook))) (when preselect (helm-log "helm-update" "Update preselect candidate %s" preselect) (if (helm-window) (with-helm-window (helm-preselect preselect source)) (helm-preselect preselect source))) (setq helm--force-updating-p nil) (helm--reset-update-flag)) (helm-log "helm-update" "end update")))) (defun helm-maybe-dim-prompt-on-update (overlay) "Make minibuffer foreground gray while updating. Argument OVERLAY is a ref-cell." (when (and helm-dim-prompt-on-update ;; Avoid an empty window when coming from bookmarks. (not (helm-empty-buffer-p)) (consp overlay)) ;; Ensure OVERLAY is a ref-cell. (with-selected-window (minibuffer-window) (setcar overlay (make-overlay (minibuffer-prompt-end) (point-max))) (overlay-put (car overlay) 'face 'helm-dim-prompt) (redisplay)))) (defun helm-update-source-p (source) "Whether SOURCE needs updating or not." (let ((len (string-width (if (assq 'multimatch source) ;; Don't count spaces entered when using ;; multi-match. (replace-regexp-in-string " " "" helm-pattern) helm-pattern)))) (and (or (not helm-source-filter) (member (assoc-default 'name source) helm-source-filter)) (>= len (helm-aif (assq 'requires-pattern source) (or (cdr it) 1) 0)) ;; bugs were mentioned here before: Bug#1802, bug#2423 ;; must be fixed already by 'helm-re-search-forward' ;; ;; short patterns that are alone not expected to return any results, ;; ignore in order to not erase Helm buffer on such patterns. (cl-loop for p in (helm-mm-split-pattern helm-pattern) never (member p '("!" "!^" "?" "@!")))))) (defun helm--update-move-first-line () "Goto first line of `helm-buffer'." (goto-char (point-min)) (if (helm-window) (helm-move-selection-common :where 'line :direction 'next :follow t) (forward-line 1) (helm-mark-current-line) ;; If we are here, it is because helm-window is not ready, if one of the ;; functions in `helm-move-selection-after-hook' is called with ;; `with-helm-window' (it shouldn't but never know) we will have an error. (condition-case-unless-debug _err (helm-log-run-hook "helm--update-move-first-line" 'helm-move-selection-after-hook) (error nil)) (helm-follow-execute-persistent-action-maybe))) (cl-defun helm-force-update (&optional (preselect nil spreselect) (recenter t)) "Force recalculation and update of candidates. Unlike `helm-update', this function re-evaluates `init' and `update' attributes when present; also `helm-candidate-cache' is not reinitialized, meaning candidates are not recomputed unless pattern has changed. Selection is preserved to current candidate if it still exists after update or moved to PRESELECT, if specified. If PRESELECT is specified with a nil value no preselection at all is done. The helm-window is re-centered at the end when RECENTER is t which is the default. RECENTER can be also a number in this case it is passed as argument to `recenter'." (with-helm-buffer (let* ((source (helm-get-current-source)) (selection (helm-aif (helm-get-selection nil t source) (regexp-quote it)))) (setq helm--force-updating-p t) (mapc 'helm-force-update--reinit helm-sources) (helm-update (if spreselect preselect selection) source) (when (and (helm-window) recenter) (with-helm-window (recenter (and (numberp recenter) recenter))))))) (defun helm-refresh () "Force recalculation and update of candidates." (interactive) (with-helm-alive-p (helm-force-update))) (put 'helm-refresh 'helm-only t) (defun helm-force-update--reinit (source) "Reinit SOURCE by calling its update and init functions." ;; When using a specific buffer as cache, don't kill it. (helm-aif (and (null (bufferp (assoc-default (helm-get-attr 'name source) helm--candidate-buffer-alist))) (helm-apply-functions-from-source source 'helm-candidate-buffer)) (kill-buffer it)) (dolist (attr '(update init)) (helm-aif (assoc-default attr source) (helm-apply-functions-from-source source it))) (helm-remove-candidate-cache source)) (defun helm-redisplay-buffer () "Redisplay candidates in `helm-buffer'. Candidates are not recomputed, only redisplayed after modifying the whole list of candidates in each source with functions found in `redisplay' attribute of current source. Note that candidates are redisplayed with their display part with all properties included only. This function is used in async sources to transform the whole list of candidates from the sentinel functions (i.e. when all candidates have been computed) because other filters like `candidate-transformer' are modifying only each chunk of candidates from `process-filter' as they come in and not the whole list. Use this for e.g. sorting the whole list of async candidates once computed. Note: To ensure redisplay is done in async sources after Helm reached `candidate-number-limit' you will have also to redisplay your candidates from `helm-async-outer-limit-hook'." (with-helm-buffer (let ((get-cands (lambda (source) (let ((fns (assoc-default 'redisplay source)) candidates helm-move-to-line-cycle-in-source helm-allow-mouse) (helm-goto-source source) (helm-next-line) (helm-awhile (condition-case-unless-debug nil (and (not (helm-pos-header-line-p)) (helm-get-selection nil 'withprop source)) (error nil)) (push it candidates) (when (save-excursion (forward-line 1) (helm-end-of-source-p t)) (cl-return nil)) (helm-next-line)) (helm-apply-functions-from-source source fns (nreverse candidates))))) (get-sources (lambda () (let (sources helm-move-to-line-cycle-in-source) (helm-awhile (helm-get-current-source) (push it sources) (when (save-excursion (helm-move--end-of-source) (forward-line 1) (eobp)) (cl-return nil)) (helm-next-source)) (nreverse sources))))) (goto-char (point-min)) (helm-update nil (helm-get-current-source) (cl-loop with sources = (funcall get-sources) for s in helm-sources for name = (assoc-default 'name s) collect (when (cl-loop for src in sources thereis (string= name (assoc-default 'name src))) (funcall get-cands s))))))) (defun helm-remove-candidate-cache (source) "Remove SOURCE from `helm-candidate-cache'." (remhash (assoc-default 'name source) helm-candidate-cache)) (defvar helm-drag-mouse-1-fn 'ignore) (defun helm-insert-match (match insert-function &optional num source) "Insert MATCH into `helm-buffer' with INSERT-FUNCTION. If MATCH is a cons cell then insert the car as display with the cdr stored as real value in a `helm-realvalue' text property. Args NUM and SOURCE are also stored as text property when specified as respectively `helm-cand-num' and `helm-cur-source'." (let ((start (pos-bol (point))) (dispvalue (helm-candidate-get-display match)) (realvalue (cdr-safe match)) (map (when helm-allow-mouse (make-sparse-keymap))) (inhibit-read-only t) end) (when (and (stringp dispvalue) (not (zerop (length dispvalue)))) (funcall insert-function dispvalue) (setq end (pos-eol)) ;; Some strings may handle another keymap prop. (remove-text-properties start end '(keymap nil)) (put-text-property start end 'read-only nil) ;; Some sources with candidates-in-buffer have already added ;; 'helm-realvalue property when creating candidate buffer. (unless (get-text-property start 'helm-realvalue) (and realvalue (put-text-property start end 'helm-realvalue realvalue))) (when map (define-key map [drag-mouse-1] 'ignore) (define-key map [mouse-1] 'helm-mouse-select-candidate) (define-key map [mouse-2] 'ignore) (define-key map [mouse-3] 'helm-menu-select-action) (add-text-properties start end `(mouse-face highlight keymap ,map ;; At 0 we might have an icon, so match at end. help-echo ,(helm-acase (get-text-property (1- end) 'help-echo) ((guard* (stringp it)) (concat it "\nmouse-1: select candidate\nmouse-3: menu actions")) (t "mouse-1: select candidate\nmouse-3: menu actions"))))) (when num (put-text-property start end 'helm-cand-num num)) (when source (put-text-property start end 'helm-cur-source source)) (funcall insert-function "\n")))) (defun helm--mouse-reset-selection-help-echo () (let* ((inhibit-read-only t) (start (overlay-start helm-selection-overlay)) (end (overlay-end helm-selection-overlay)) (help-echo (get-text-property start 'help-echo))) (when (and (stringp help-echo) (string-match "mouse-2: execute action" help-echo)) (put-text-property start end 'help-echo (replace-match "mouse-1: select candidate" t t help-echo))))) (defun helm--bind-mouse-for-selection (pos) (let ((inhibit-read-only t) (map (get-text-property pos 'keymap))) (when map (define-key map [down-mouse-1] helm-drag-mouse-1-fn) (define-key map [mouse-2] 'helm-maybe-exit-minibuffer) (put-text-property helm-selection-point (overlay-end helm-selection-overlay) 'help-echo (helm-aif (get-text-property pos 'help-echo) (if (and (stringp it) (string-match "mouse-1: select candidate" it)) (replace-match "mouse-2: execute action" t t it) "mouse-2: execute action\nmouse-3: menu actions") "mouse-2: execute action\nmouse-3: menu actions"))))) (defun helm-mouse-select-candidate (event) (interactive "e") (let* ((window (posn-window (event-end event))) (pos (posn-point (event-end event)))) (unwind-protect (with-current-buffer (window-buffer window) (if (and (helm-action-window) (eql window (get-buffer-window helm-buffer))) (user-error "selection in helm-window not available while selecting action") (helm--mouse-reset-selection-help-echo) (goto-char pos) (when (helm-pos-multiline-p) (goto-char (or (helm-get-previous-candidate-separator-pos) (helm-get-previous-header-pos))) (forward-line 1))) (helm-mark-current-line) (when helm-popup-tip-mode (helm-maybe-show-help-echo)) (helm-follow-execute-persistent-action-maybe)) (select-window (minibuffer-window)) (set-buffer (window-buffer window))))) (put 'helm-mouse-select-candidate 'helm-only t) (defun helm-insert-header-from-source (source) "Insert SOURCE name in `helm-buffer' header. Maybe insert, by overlay, additional info after the source name if SOURCE has header-name attribute." (let ((name (assoc-default 'name source))) (helm-insert-header name (helm-aif (assoc-default 'header-name source) (helm-apply-functions-from-source source it name))))) (defun helm-insert-header (name &optional display-string) "Insert header of source NAME into the helm buffer. If DISPLAY-STRING is non-nil and a string value then display this additional info after the source name by overlay." (unless (bobp) (let ((start (point))) (insert (propertize "\n" 'face 'helm-eob-line)) (put-text-property start (point) 'helm-header-separator t))) (let ((start (point))) (insert name) (put-text-property (pos-bol) (pos-eol) 'helm-header t) (when display-string (overlay-put (make-overlay (pos-bol) (pos-eol)) 'display display-string)) (insert "\n") (add-text-properties start (point) '(face helm-source-header ;; Disable line numbers on ;; source headers. display-line-numbers-disable t)))) (defun helm-insert-candidate-separator () "Insert separator of candidates into the Helm buffer." (insert (propertize helm-candidate-separator 'face 'helm-separator)) (put-text-property (pos-bol) (pos-eol) 'helm-candidate-separator t) (insert "\n")) (defun helm-init-relative-display-line-numbers () "Enable `display-line-numbers' for Helm buffers. This is intended to be added to `helm-after-initialize-hook'. This will work only in Emacs-26+, i.e. Emacs versions that have `display-line-numbers-mode'." (when (boundp 'display-line-numbers) (with-helm-buffer (setq display-line-numbers 'relative)))) (define-minor-mode helm-display-line-numbers-mode "Toggle display of line numbers in current Helm buffer." :group 'helm (with-helm-alive-p (cl-assert (boundp 'display-line-numbers) nil "`display-line-numbers' not available") (if helm-display-line-numbers-mode (with-helm-buffer (setq display-line-numbers 'relative)) (with-helm-buffer (setq display-line-numbers nil))))) (put 'helm-display-line-numbers-mode 'helm-only t) ;;; Async process ;; (defun helm-output-filter (process output-string) "The `process-filter' function for Helm async sources." (with-local-quit (helm-output-filter-1 (assoc process helm-async-processes) output-string))) (defun helm-output-filter-1 (process-assoc output-string) (helm-log "helm-output-filter-1" "output-string = %S" output-string) (with-current-buffer helm-buffer (let ((source (cdr process-assoc))) (save-excursion (helm-aif (assoc-default 'insertion-marker source) (goto-char it) (goto-char (point-max)) (helm-insert-header-from-source source) (setcdr process-assoc (append source `((insertion-marker . ,(point-marker)))))) (helm-output-filter--process-source (car process-assoc) output-string source (helm-candidate-number-limit source)))) (helm-output-filter--post-process))) (defun helm-output-filter--process-source (process output-string source limit) (cl-dolist (candidate (helm-transform-candidates (helm-output-filter--collect-candidates (split-string output-string helm-process-output-split-string-separator) (assq 'incomplete-line source)) source t)) (setq candidate (helm--maybe-process-filter-one-by-one-candidate candidate source)) (if (assq 'multiline source) (let ((start (point))) (helm-insert-candidate-separator) (helm-insert-match candidate 'insert-before-markers (1+ (cdr (assq 'item-count source))) source) (put-text-property start (point) 'helm-multiline t)) (helm-insert-match candidate 'insert-before-markers (1+ (cdr (assq 'item-count source))) source)) (cl-incf (cdr (assq 'item-count source))) (when (>= (assoc-default 'item-count source) limit) (helm-kill-async-process process) (helm-log-run-hook "helm-output-filter--process-source" 'helm-async-outer-limit-hook) (cl-return)))) (defun helm-output-filter--collect-candidates (lines incomplete-line-info) "Collect LINES maybe completing the truncated first and last lines." ;; The output of process may come in chunks of any size, so the last ;; line of LINES could be truncated, this truncated line is stored ;; in INCOMPLETE-LINE-INFO to be concatenated with the first ;; incomplete line of the next arriving chunk. INCOMPLETE-LINE-INFO ;; is an attribute of source; it is created with an empty string ;; when the source is computed => (incomplete-line . "") (helm-log "helm-output-filter--collect-candidates" "incomplete-line-info = %S" (cdr incomplete-line-info)) (butlast (cl-loop for line in lines ;; On start `incomplete-line-info' value is empty string. for newline = (helm-aif (cdr incomplete-line-info) (prog1 (concat it line) (setcdr incomplete-line-info nil)) line) collect newline ;; Store last incomplete line (last chunk truncated) until ;; new output arrives. Previously storing 'line' in ;; incomplete-line-info assumed output was truncated in ;; only two chunks. But output could be large and ;; truncated in more than two chunks. Therefore store ;; 'newline' to contain the previous chunks (Bug#1187). finally do (setcdr incomplete-line-info newline)))) (defun helm-output-filter--post-process () (helm-aif (get-buffer-window helm-buffer 'visible) (with-selected-window it (helm-skip-noncandidate-line 'next) (helm-mark-current-line nil 'nomouse) ;; FIXME Don't hardcode follow delay. (helm-follow-execute-persistent-action-maybe 0.5) (helm-display-mode-line (helm-get-current-source)) (helm-log-run-hook "helm-output-filter--post-process" 'helm-after-update-hook) (helm--reset-update-flag)))) (defun helm-process-deferred-sentinel-hook (process event file) "Defer remote processes in sentinels. Meant to be called at the beginning of a sentinel process function." (when (and (not (zerop helm-tramp-connection-min-time-diff)) (string= event "finished\n") (or (file-remote-p file) ;; `helm-suspend-update-flag' ;; is non-`nil' here only during a ;; running process, this will never be called ;; when user set it explicitly with `C-!'. helm-suspend-update-flag)) (setq helm-suspend-update-flag t) ;; Kill the process but don't delete entry in ;; `helm-async-processes'. (helm-kill-async-process process) ;; When tramp opens the same connection twice in less than 5 ;; seconds, it throws 'suppress, which calls the real-handler on ;; the main "Emacs". To avoid this [1] helm waits for 5 seconds ;; before updates yet allows user input during this delay. [1] In ;; recent Emacs versions, this has been fixed so tramp returns nil ;; in such conditions. Note: `tramp-connection-min-time-diff' cannot ;; have values less than 5 seconds otherwise the process dies. (run-at-time helm-tramp-connection-min-time-diff nil (lambda () (when helm-alive-p ; Don't run timer fn after quit. (setq helm-suspend-update-flag nil) (helm-check-minibuffer-input)))))) (defun helm-kill-async-processes () "Kill all asynchronous processes registered in `helm-async-processes'." (while helm-async-processes (helm-kill-async-process (caar helm-async-processes)) (setq helm-async-processes (cdr helm-async-processes)))) (defun helm-kill-async-process (process) "Stop output from `helm-output-filter' and kill associated PROCESS." (set-process-filter process nil) (delete-process process)) ;;; Actions ;; (defun helm-execute-selection-action () "Execute current action." (helm-log-run-hook "helm-execute-selection-action" 'helm-before-action-hook) ;; Position can be change when `helm-current-buffer' ;; is split, so jump to this position before executing action. (helm-current-position 'restore) (unwind-protect (prog1 (helm-execute-selection-action-1) (helm-log-run-hook "helm-execute-selection-action" 'helm-after-action-hook)) (setq helm--executing-helm-action nil))) (defun helm-execute-selection-action-1 (&optional selection action preserve-saved-action) "Execute ACTION on current SELECTION. If PRESERVE-SAVED-ACTION is non-nil, then save the action." (helm-log "helm-execute-selection-action-1" "executing action") (setq action (helm-get-default-action (or action helm-saved-action (if (get-buffer helm-action-buffer) (helm-get-selection helm-action-buffer) (helm-get-actions-from-current-source))))) (helm-aif (and (not helm-in-persistent-action) (get-buffer helm-action-buffer)) (kill-buffer it)) (let ((source (or helm-saved-current-source (helm-get-current-source))) non-essential) (setq selection (helm-coerce-selection (or selection helm-saved-selection (helm-get-selection nil nil source) (and (assq 'accept-empty source) "")) source)) (unless preserve-saved-action (setq helm-saved-action nil)) (when (and selection action) (funcall action selection)))) (defun helm-coerce-selection (selection source) "Apply coerce attribute function to SELECTION in SOURCE. Coerce source with coerce function." (helm-aif (assoc-default 'coerce source) (helm-apply-functions-from-source source it selection) selection)) (defun helm-get-default-action (action) "Get the first ACTION value of action list in source." (if (and (listp action) (not (functionp action))) (cdar action) action)) (defun helm--show-action-window-other-window-p () "Decide if window layout is suitable for showing action buffer. Note that the return value is meaningful only at some point in the code, i.e. before displaying action menu." (when helm-show-action-window-other-window ;; We were previously checking helm-split-window-state (eq vertical) to ;; decide to show action window, we now show it inconditionally in such case ;; but 'below'. (if (< (window-width (helm-window)) (or split-width-threshold 160)) 'below helm-show-action-window-other-window))) (defun helm-select-action () "Select an action for the currently selected candidate. If action buffer is selected, back to the Helm buffer." (interactive) (with-helm-alive-p (let ((src (helm-get-current-source))) (helm-log-run-hook "helm-select-action" 'helm-select-action-hook) (setq helm-saved-selection (helm-get-selection nil nil src)) (with-selected-frame (with-helm-window (selected-frame)) (prog1 (helm-acond ((get-buffer-window helm-action-buffer 'visible) (let ((delta (window-total-height it))) (set-window-buffer it helm-buffer) (helm--set-action-prompt 'restore) ;; If `helm-show-action-window-other-window' is non nil ;; we should have now two windows displaying ;; helm-buffer, delete the one that was handling ;; previously action buffer. (when (helm--show-action-window-other-window-p) (delete-window it)) ;; Resize window on horizontal split, though for some ;; reasons only 'above' needs to be resized. (when (memq helm-show-action-window-other-window '(below above)) (window-resize (get-buffer-window helm-buffer) delta)) (kill-buffer helm-action-buffer) (setq helm-saved-selection nil) (helm-set-pattern helm-input 'noupdate) ;; Maybe hide minibuffer if helm was showing ;; minibuffer in header-line and we are just toggling ;; menu [1]. (helm-hide-minibuffer-maybe))) (helm-saved-selection (setq helm-saved-current-source src) (let ((actions (helm-get-actions-from-current-source src)) helm-onewindow-p) (if (functionp actions) (message "Sole action: %s" ;; lambdas are no more represented as list in ;; Emacs-29+ Bug#2666. (if (or (not (symbolp actions)) (byte-code-function-p actions) (helm-subr-native-elisp-p actions)) "Anonymous" actions)) (helm-show-action-buffer actions) ;; Be sure the minibuffer is entirely deleted ;; (bug#907). Previously this was done from ;; `helm--delete-minibuffer-contents-from' which ;; was itself force updating, now do it explicitely ;; from here. (helm-set-pattern "" t) (helm-force-update nil) ;; Unhide minibuffer to make visible action prompt [1]. (with-selected-window (minibuffer-window) (remove-overlays) (setq cursor-type t)) (helm--set-action-prompt) (helm-check-minibuffer-input)))) (t (message "No Actions available"))) (helm-display-mode-line (helm-get-current-source)) (run-hooks 'helm-window-configuration-hook)))))) (put 'helm-select-action 'helm-only t) (defun helm-menu-select-action (_event) "Popup action menu from mouse-3." (interactive "e") (if (get-buffer-window helm-action-buffer 'visible) (helm-select-action) (let ((src (helm-get-current-source))) (helm-aif (helm-get-actions-from-current-source src) (progn (setq helm-saved-current-source src) (if (functionp it) (message "Sole action: %s" ;; lambdas are no more represented as list in ;; Emacs-29+ Bug#2666. (if (or (not (symbolp it)) (byte-code-function-p it) (helm-subr-native-elisp-p it)) "Anonymous" it)) (setq helm-saved-action (x-popup-menu t (list "Available Actions" (cons "" it)))) (helm-maybe-exit-minibuffer)) (message "No Actions available")))))) (put 'helm-menu-select-action 'helm-only t) (defun helm--set-action-prompt (&optional restore) (with-selected-window (minibuffer-window) (let ((inhibit-read-only t) (props '(face minibuffer-prompt field t read-only t rear-nonsticky t front-sticky t)) (prt (if restore helm--prompt helm--action-prompt))) (erase-buffer) (insert (apply #'propertize prt props)) ;; Restore minibuffer depth indicator if the mode is enabled. (when minibuffer-depth-indicate-mode (minibuffer-depth-setup))))) (defun helm-show-action-buffer (actions) (with-current-buffer (get-buffer-create helm-action-buffer) (erase-buffer) (buffer-disable-undo) (setq cursor-type nil) ;; Maybe display action buffer 'below' if window isn't large enough ;; (bug#2635). (set-window-buffer (helm-aif (helm--show-action-window-other-window-p) (split-window (get-buffer-window helm-buffer) nil it) (get-buffer-window helm-buffer)) helm-action-buffer) (set (make-local-variable 'helm-sources) (list (helm-build-sync-source "Actions" :volatile t :nomark t :persistent-action #'ignore :persistent-help "DoNothing" :keymap 'helm-map :candidates actions :mode-line '("Action(s)" "\\\\[helm-select-action]:BackToCands RET/f1/f2/fn:NthAct") :candidate-transformer (lambda (candidates) (cl-loop for (i . j) in candidates for count from 1 collect (cons (concat (cond ((> count 12) " ") ((< count 10) (format "[f%s] " count)) (t (format "[f%s] " count))) (propertize i 'face 'helm-action)) j))) :candidate-number-limit nil))) (set (make-local-variable 'helm-source-filter) nil) (set (make-local-variable 'helm-selection-overlay) nil) (helm-initialize-overlays helm-action-buffer))) ;; Selection of candidates (defun helm-display-source-at-screen-top-maybe (unit) "Display source at the top of screen when UNIT value is \\='source. Return nil for any other value of UNIT." (when (and helm-display-source-at-screen-top (eq unit 'source)) (set-window-start (selected-window) (save-excursion (forward-line -1) (point))))) (defun helm-skip-noncandidate-line (direction) "Skip source header or candidates separator when going in DIRECTION. DIRECTION is either \\='next or \\='previous. Same as `helm-skip-header-and-separator-line' but ensure point is moved to the right place when at bob or eob." (helm-skip-header-and-separator-line direction) (and (bobp) (forward-line 1)) ; Skip first header. (and (eobp) (forward-line -1))) ; Avoid last empty line. (defun helm-skip-header-and-separator-line (direction) "Skip source header or candidate separator when going to next/previous line. DIRECTION is either \\='next or \\='previous." (let ((fn (cl-ecase direction (next 'eobp) (previous 'bobp)))) (while (and (not (funcall fn)) (or (helm-pos-header-line-p) (helm-pos-candidate-separator-p))) (forward-line (if (and (eq direction 'previous) (not (eq (pos-bol) (point-min)))) -1 1))))) (defun helm-display-mode-line (source &optional force) "Set up mode-line and header-line for `helm-buffer'. SOURCE is a Helm source object. Optional argument FORCE forces redisplay of the Helm buffer's mode and header lines." (set (make-local-variable 'helm-mode-line-string) (helm-interpret-value (or (and (listp source) ; Check if source is empty. (assoc-default 'mode-line source)) (default-value 'helm-mode-line-string)) source)) (let* ((src-name (assoc-default 'name source)) (follow (and (or (helm-follow-mode-p source) (and helm-follow-mode-persistent (member src-name helm-source-names-using-follow))) " (HF)")) (marked (if (assoc-default 'all-marked source) helm-marked-candidates (cl-loop for c in helm-marked-candidates for name = (assoc-default 'name (car c)) when (string= name src-name) collect c)))) ;; Setup mode-line. (if helm-mode-line-string (setq mode-line-format `(:propertize (" " mode-line-buffer-identification " " (:eval (format "L%-3d" (helm-candidate-number-at-point))) ,follow " " (:eval ,(and marked (propertize (format "M%d" (length marked)) 'face 'helm-visible-mark))) (:eval (when ,helm--mode-line-display-prefarg (let ((arg (prefix-numeric-value (or prefix-arg current-prefix-arg)))) (unless (= arg 1) (propertize (format " [prefarg:%s]" arg) 'face 'helm-prefarg))))) " " (:eval (with-helm-buffer (helm-show-candidate-number (car-safe helm-mode-line-string)))) " " helm--mode-line-string-real " " (:eval (make-string (window-width) ? ))) keymap (keymap (mode-line keymap (mouse-1 . ignore) (down-mouse-1 . ignore) (drag-mouse-1 . ignore) (mouse-2 . ignore) (down-mouse-2 . ignore) (drag-mouse-2 . ignore) (mouse-3 . ignore) (down-mouse-3 . ignore) (drag-mouse-3 . ignore)))) helm--mode-line-string-real (substitute-command-keys (if (listp helm-mode-line-string) (cadr helm-mode-line-string) helm-mode-line-string))) (setq mode-line-format (default-value 'mode-line-format))) ;; Setup header-line. (cond (helm-echo-input-in-header-line (setq force t) (helm--set-header-line)) (helm-display-header-line (let ((hlstr (helm-interpret-value (and (listp source) (assoc-default 'header-line source)) source)) (endstr (make-string (window-width) ? ))) (setq header-line-format (propertize (concat " " hlstr endstr) 'face 'helm-header)))))) (when force (force-mode-line-update))) (defun helm--set-header-line (&optional update) (with-selected-window (minibuffer-window) (when helm-display-header-line ;; Prevent cursor movement over the overlay displaying ;; persistent-help in minibuffer (Bug#2108). (setq-local disable-point-adjustment t)) (let* ((beg (save-excursion (vertical-motion 0 (helm-window)) (point))) (end (save-excursion (end-of-visual-line) (point))) ;; The visual line where the cursor is. (cont (buffer-substring beg end)) (pref (propertize " " 'display (if (string-match-p (regexp-opt `(,helm--prompt ,helm--action-prompt)) cont) `(space :width ,helm-header-line-space-before-prompt) (propertize "->" 'face 'helm-header-line-left-margin)))) (pos (- (point) beg))) ;; Increment pos each time we find a "%" up to current-pos (bug#1648). (cl-loop for c across (buffer-substring-no-properties beg (point)) when (eql c ?%) do (cl-incf pos)) ;; Increment pos when cursor is on a "%" to make it visible in header-line ;; i.e "%%|" and not "%|%" (bug#1649). (when (eql (char-after) ?%) (setq pos (1+ pos))) (setq cont (replace-regexp-in-string "%" "%%" cont)) (with-helm-buffer (setq header-line-format (concat pref cont " ")) (funcall helm-default-prompt-display-function pos) (when update (force-mode-line-update)))))) (defun helm-set-default-prompt-display (pos) (put-text-property ;; Increment pos to handle the space before prompt (i.e `pref'). (+ 1 pos) (+ 2 pos) 'face ;; Don't just use cursor face, this can hide the current character. (list :inverse-video t :foreground (face-background 'cursor) :background (face-background 'default)) header-line-format)) (defun helm-exchange-minibuffer-and-header-line () "Display minibuffer in header-line and vice versa for current Helm session. This is a toggle command." (interactive) (cl-assert (not (eq (helm-get-attr 'echo-input-in-header-line) 'never)) nil "echo-input-in-header-line not allowed in this source") (with-helm-window (add-hook 'helm-minibuffer-set-up-hook 'helm-hide-minibuffer-maybe) (setq-local helm-echo-input-in-header-line (not helm-echo-input-in-header-line)) (with-selected-window (minibuffer-window) (if (with-helm-buffer helm-echo-input-in-header-line) (helm-hide-minibuffer-maybe) (remove-overlays) (setq cursor-type t))) (helm-display-mode-line (helm-get-current-source) t))) (put 'helm-exchange-minibuffer-and-header-line 'helm-only t) (defun helm--update-header-line () ;; This should be used in `post-command-hook', ;; nowhere else. (when (with-helm-buffer helm-echo-input-in-header-line) (helm--set-header-line t))) ;; We were previously let-binding `resize-mini-windows' in ;; `helm-read-from-minibuffer', this to prevent minibuffer resizing when ;; inserting multiline string in fake minibuffer (header-line). ;; I seems it had no effect see bug#2638. (defun helm-hide-minibuffer-maybe () "Hide minibuffer contents in a Helm session. This function should normally go to `helm-minibuffer-set-up-hook'. It has no effect if `helm-echo-input-in-header-line' is nil." (when (with-helm-buffer helm-echo-input-in-header-line) (let ((ov (make-overlay (point-min) (point-max) nil nil t))) (overlay-put ov 'window (selected-window)) (helm-aif (and helm-display-header-line (helm-get-attr 'persistent-help)) (progn (overlay-put ov 'display (truncate-string-to-width (substitute-command-keys (concat "\\\\[helm-execute-persistent-action]: " (format "%s (keeping session)" it))) (- (window-width) 1))) (overlay-put ov 'face 'helm-header)) (overlay-put ov 'face (let ((bg-color (face-background 'default nil))) `(:background ,bg-color :foreground ,bg-color)))) (setq cursor-type nil)))) (defun helm-show-candidate-number (&optional name) "Used to display candidate number in mode-line. You can specify NAME of candidates e.g. \"Buffers\" otherwise it is \"Candidate(s)\" by default." (when helm-alive-p (unless (helm-empty-source-p) ;; Build a fixed width string when candidate-number < 1000 (let* ((cand-name (or name "Candidate(s)")) (width (length (format "[999 %s]" cand-name)))) (propertize (format (concat "%-" (number-to-string width) "s") (format "[%s %s]" (helm-get-candidate-number 'in-current-source) cand-name)) 'face (if helm-suspend-update-flag 'helm-candidate-number-suspended 'helm-candidate-number)))))) (cl-defun helm-move-selection-common (&key where direction (follow t)) "Move the selection marker to a new position. Position is determined by WHERE and DIRECTION. Key arg WHERE can be one of: - line - page - edge - source Key arg DIRECTION can be one of: - previous - next - A source or a source name when used with :WHERE \\='source." (let ((move-func (cl-case where (line (cl-ecase direction (previous 'helm-move--previous-line-fn) (next 'helm-move--next-line-fn))) (page (cl-ecase direction (previous 'helm-move--previous-page-fn) (next 'helm-move--next-page-fn))) (edge (cl-ecase direction (previous 'helm-move--beginning-of-buffer-fn) (next 'helm-move--end-of-buffer-fn))) (source (cl-case direction (previous 'helm-move--previous-source-fn) (next 'helm-move--next-source-fn) (t (lambda () ; A source is passed as DIRECTION arg. (helm-move--goto-source-fn direction))))))) source) (unless (or (helm-empty-buffer-p (helm-buffer-get)) (not (helm-window))) (with-helm-window (when helm-allow-mouse (helm--mouse-reset-selection-help-echo)) (helm-log-run-hook "helm-move-selection-common" 'helm-move-selection-before-hook) (funcall move-func) (and (memq direction '(next previous)) (helm-skip-noncandidate-line direction)) (when (helm-pos-multiline-p) (helm-move--beginning-of-multiline-candidate)) (helm-display-source-at-screen-top-maybe where) (helm-mark-current-line) (when follow (helm-follow-execute-persistent-action-maybe)) (helm-display-mode-line (setq source (helm-get-current-source))) (helm-log-run-hook "helm-move-selection-common" 'helm-move-selection-after-hook) (helm--set-minibuffer-completion-confirm source))))) (defun helm-move--beginning-of-multiline-candidate () (let ((header-pos (helm-get-previous-header-pos)) (separator-pos (helm-get-previous-candidate-separator-pos))) (when header-pos (goto-char (if (or (null separator-pos) (< separator-pos header-pos)) header-pos separator-pos)) (forward-line 1)))) (defun helm-move--previous-multi-line-fn () (forward-line -1) (unless (helm-pos-header-line-p) (helm-skip-header-and-separator-line 'previous) (helm-move--beginning-of-multiline-candidate))) (defun helm-move--previous-line-fn () (if (not (helm-pos-multiline-p)) (forward-line -1) (helm-move--previous-multi-line-fn)) (when (and helm-move-to-line-cycle-in-source (helm-pos-header-line-p)) (forward-line 1) (helm-move--end-of-source) ;; We are at end of helm-buffer ;; check if last candidate is a multiline candidate ;; and jump to it (when (and (eobp) (save-excursion (forward-line -1) (helm-pos-multiline-p))) (helm-move--previous-multi-line-fn)))) (defun helm-move--next-multi-line-fn () (let ((header-pos (helm-get-next-header-pos)) (separator-pos (helm-get-next-candidate-separator-pos))) (cond ((and separator-pos (or (null header-pos) (< separator-pos header-pos))) (goto-char separator-pos)) (header-pos (goto-char header-pos))))) (defun helm-move--next-line-fn () (if (not (helm-pos-multiline-p)) (forward-line 1) (helm-move--next-multi-line-fn)) (when (and helm-move-to-line-cycle-in-source (or (save-excursion (and (helm-pos-multiline-p) (goto-char (overlay-end helm-selection-overlay)) (helm-end-of-source-p t))) (helm-end-of-source-p t))) (helm-move--beginning-of-source) (helm-display-source-at-screen-top-maybe 'source))) (defun helm-move--previous-page-fn () (condition-case nil (scroll-down helm-scroll-amount) (beginning-of-buffer (goto-char (point-min))))) (defun helm-move--next-page-fn () (condition-case nil (scroll-up helm-scroll-amount) (end-of-buffer (goto-char (point-max))))) (defun helm-move--beginning-of-buffer-fn () (goto-char (point-min))) (defun helm-move--end-of-buffer-fn () (goto-char (point-max))) (defun helm-move--end-of-source () (helm-aif (helm-get-next-header-pos) (progn (goto-char it) (forward-line -2)) (goto-char (point-max)))) (defun helm-move--beginning-of-source () (helm-aif (helm-get-previous-header-pos) (progn (goto-char it) (forward-line 1)) (goto-char (point-min)))) (defun helm-move--previous-source-fn () (forward-line -1) (if (bobp) (goto-char (point-max)) (helm-skip-header-and-separator-line 'previous)) (goto-char (helm-get-previous-header-pos)) (forward-line 1)) (defun helm-move--next-source-fn () (goto-char (or (and (not (save-excursion (forward-line 1) (eobp))) ;; Empty source at eob are just ;; not displayed unless they are dummy. ;; Bug#1117. (helm-get-next-header-pos)) (point-min)))) (defun helm-move--goto-source-fn (source-or-name) (goto-char (point-min)) (let ((name (if (stringp source-or-name) source-or-name (assoc-default 'name source-or-name)))) (if (or (null name) (string= name "")) (forward-line 1) (condition-case err (while (not (string= name (helm-current-line-contents))) (goto-char (helm-get-next-header-pos))) (error (helm-log "helm-move--goto-source-fn" "%S" err)))))) (defun helm-candidate-number-at-point () (if helm-alive-p (with-helm-buffer (or (get-text-property (point) 'helm-cand-num) 1)) (or (get-text-property (point) 'helm-cand-num) 1))) (defun helm--next-or-previous-line (direction &optional arg) ;; Be sure to not use this in non--interactives calls. (let ((helm-move-to-line-cycle-in-source (and helm-move-to-line-cycle-in-source arg))) (if (and arg (> arg 1)) (cl-loop with pos = (helm-candidate-number-at-point) with cand-num = (helm-get-candidate-number t) with iter = (min arg (if (eq direction 'next) (- cand-num pos) (min arg (1- pos)))) for count from 1 while (<= count iter) do (helm-move-selection-common :where 'line :direction direction)) (helm-move-selection-common :where 'line :direction direction)))) (defun helm-previous-line (&optional arg) "Move selection to the ARG previous line(s). Same behavior as `helm-next-line' when called with a numeric prefix arg." (interactive "p") (with-helm-alive-p (helm--next-or-previous-line 'previous arg))) (put 'helm-previous-line 'helm-only t) (defun helm-next-line (&optional arg) "Move selection to the next ARG line(s). When numeric prefix arg is > than the number of candidates, then move to the last candidate of current source (i.e. don't move to next source)." (interactive "p") (with-helm-alive-p (helm--next-or-previous-line 'next arg))) (put 'helm-next-line 'helm-only t) (defun helm-scroll-up () "Scroll up helm-buffer by `helm-scroll-amount' lines." (interactive) (with-helm-alive-p (helm-move-selection-common :where 'page :direction 'previous))) (put 'helm-scroll-up 'helm-only t) (defun helm-previous-page () "Move selection back with a pageful." (interactive) (with-helm-alive-p (let (helm-scroll-amount) (helm-move-selection-common :where 'page :direction 'previous)))) (put 'helm-previous-page 'helm-only t) (defun helm-scroll-down () "Scroll down helm-buffer by `helm-scroll-amount' lines." (interactive) (with-helm-alive-p (helm-move-selection-common :where 'page :direction 'next))) (put 'helm-scroll-down 'helm-only t) (defun helm-next-page () "Move selection forward with a pageful." (interactive) (with-helm-alive-p (let (helm-scroll-amount) (helm-move-selection-common :where 'page :direction 'next)))) (put 'helm-next-page 'helm-only t) (defun helm-beginning-of-buffer () "Move selection at the top." (interactive) (with-helm-alive-p (helm-move-selection-common :where 'edge :direction 'previous))) (put 'helm-beginning-of-buffer 'helm-only t) (defun helm-end-of-buffer () "Move selection at the bottom." (interactive) (with-helm-alive-p (helm-move-selection-common :where 'edge :direction 'next))) (put 'helm-end-of-buffer 'helm-only t) (defun helm-previous-source () "Move selection to the previous source." (interactive) (with-helm-alive-p (helm-move-selection-common :where 'source :direction 'previous))) (put 'helm-previous-source 'helm-only t) (defun helm-next-source () "Move selection to the next source." (interactive) (with-helm-alive-p (helm-move-selection-common :where 'source :direction 'next))) (put 'helm-next-source 'helm-only t) (defun helm-goto-source (&optional source-or-name) "Move the selection to the source named SOURCE-OR-NAME. If SOURCE-OR-NAME is empty string or nil go to the first candidate of first source." (helm-move-selection-common :where 'source :direction source-or-name)) (defvar helm-follow-action-white-list-commands '(helm-ff-decrease-image-size-persistent helm-ff-increase-image-size-persistent helm-ff-rotate-left-persistent helm-ff-rotate-right-persistent) "Allow `helm-follow-action-forward/backward' switching to next file when one of these commands is the `last-command'. For example when browsing files with `C-` and rotate the current file, hitting `C-` again will not switch to next file but kill its buffer.") (defun helm--follow-action (arg) (let ((helm--temp-follow-flag t) ; Needed in HFF. (in-follow-mode (helm-follow-mode-p))) ;; When follow-mode is already enabled, just go to next or ;; previous line. (when (or (eq last-command 'helm-follow-action-forward) (eq last-command 'helm-follow-action-backward) (eq last-command 'helm-execute-persistent-action) (memq last-command helm-follow-action-white-list-commands) in-follow-mode) (if (> arg 0) (helm-move-selection-common :where 'line :direction 'next :follow nil) (helm-move-selection-common :where 'line :direction 'previous :follow nil))) (unless in-follow-mode (helm-execute-persistent-action)))) (defun helm-follow-action-forward () "Go to next line and execute persistent action." (interactive) (with-helm-alive-p (helm--follow-action 1))) (put 'helm-follow-action-forward 'helm-only t) (defun helm-follow-action-backward () "Go to previous line and execute persistent action." (interactive) (with-helm-alive-p (helm--follow-action -1))) (put 'helm-follow-action-backward 'helm-only t) (defun helm-mark-current-line (&optional resumep nomouse) "Move `helm-selection-overlay' to current line. When RESUMEP is non nil move overlay to `helm-selection-point'. When NOMOUSE is specified do not set mouse bindings. Note that selection is unrelated to visible marks used for marking candidates." (with-helm-buffer (when resumep (goto-char helm-selection-point)) (move-overlay helm-selection-overlay (pos-bol) (if (helm-pos-multiline-p) (let ((header-pos (helm-get-next-header-pos)) (separator-pos (helm-get-next-candidate-separator-pos))) (or (and (null header-pos) separator-pos) (and header-pos separator-pos (< separator-pos header-pos) separator-pos) header-pos (point-max))) (1+ (pos-eol)))) (setq helm-selection-point (overlay-start helm-selection-overlay)) (when (and helm-allow-mouse (null nomouse) (not resumep)) (helm--bind-mouse-for-selection helm-selection-point)))) (defun helm-confirm-and-exit-minibuffer () "Maybe ask for confirmation when exiting helm. It is similar to `minibuffer-complete-and-exit' adapted to Helm. If `minibuffer-completion-confirm' value is \\='confirm, send minibuffer confirm message and exit on next hit. If `minibuffer-completion-confirm' value is t, don't exit and send message \\='no match'." (interactive) (with-helm-alive-p (if (and (helm--updating-p) (null helm--reading-passwd-or-string)) (progn (message "[Display not ready]") (sit-for 0.5) (message nil) (helm-update)) (let* ((src (helm-get-current-source)) (empty-buffer-p (with-current-buffer helm-buffer (eq (point-min) (point-max)))) (unknown (and (not empty-buffer-p) ;; Now such candidates have a helm-new-file or an ;; unknown text property (we were testing if string ;; match [?] previously). (helm-candidate-prefixed-p (helm-get-selection nil 'withprop src))))) (cond ((and (or empty-buffer-p unknown) (memq minibuffer-completion-confirm '(confirm confirm-after-completion))) (setq helm-minibuffer-confirm-state 'confirm) (setq minibuffer-completion-confirm nil) (minibuffer-message " [confirm]")) ;; When require-match is strict (i.e. `t'), buffer ;; should be either empty or in read-file-name have an ;; unknown candidate ([+] prefix), if it's not the case ;; fix it in helm-mode but not here. ;; When `minibuffer-completion-confirm' is set to 'noexit or ;; 'exit, that's mean MUST-MATCH is a function and we use its ;; return value to set `minibuffer-completion-confirm', this is ;; done in `helm--set-minibuffer-completion-confirm'. ((or (eq minibuffer-completion-confirm 'noexit) (and (or empty-buffer-p unknown) (eq minibuffer-completion-confirm t))) (minibuffer-message " [No match]")) (empty-buffer-p ;; This is used when helm-buffer is totally empty, ;; i.e. the [+] have not been added because must-match ;; is used from outside helm-comp-read i.e. from a helm ;; source built with :must-match. (setq helm-saved-selection helm-pattern helm-saved-action (helm-get-default-action (assoc-default 'action (car (with-helm-buffer helm-sources)))) helm-minibuffer-confirm-state nil) (helm-exit-minibuffer)) (t (setq helm-minibuffer-confirm-state nil) (helm-exit-minibuffer))))))) (put 'helm-confirm-and-exit-minibuffer 'helm-only t) (defun helm-confirm-and-exit-hook () "Restore `minibuffer-completion-confirm' when helm update." (unless (or (eq minibuffer-completion-confirm t) (not helm-minibuffer-confirm-state)) (setq minibuffer-completion-confirm helm-minibuffer-confirm-state))) (add-hook 'helm-after-update-hook 'helm-confirm-and-exit-hook) (defun helm--set-minibuffer-completion-confirm (src) "Return the value of a REQUIRE-MATCH arg in a `completing-read'." ;; Set `minibuffer-completion-confirm' to 'noexit or ;; 'exit, according to MUST-MATCH value (possibly a function). (with-helm-buffer (setq minibuffer-completion-confirm (helm-acase (helm-get-attr 'must-match src) ((guard* (and (functionp it) (helm-get-selection nil nil src))) (if (funcall it guard) 'exit 'noexit)) (t it))))) (defun helm-read-string (prompt &optional initial-input history default-value inherit-input-method) "Same as `read-string' but for reading string from a helm session." (let ((helm--reading-passwd-or-string t)) (read-string prompt initial-input history default-value inherit-input-method))) (defun helm--updating-p () ;; helm timer is between two cycles. ;; IOW `helm-check-minibuffer-input' haven't yet compared input ;; and `helm-pattern'. (or (not (equal (minibuffer-contents) helm-pattern)) ;; `helm-check-minibuffer-input' have launched `helm-update'. helm--in-update)) (defun helm-maybe-exit-minibuffer () (interactive) (with-helm-alive-p (if (and (helm--updating-p) (null helm--reading-passwd-or-string)) (progn (message "[Display not ready]") (sit-for 0.5) (message nil) (helm-update)) (helm-exit-minibuffer)))) (put 'helm-maybe-exit-minibuffer 'helm-only t) (defun helm-exit-minibuffer () "Select the current candidate by exiting the minibuffer." (unless helm-current-prefix-arg (setq helm-current-prefix-arg current-prefix-arg)) (setq helm-exit-status 0) (helm-log-run-hook "helm-exit-minibuffer" 'helm-exit-minibuffer-hook) (exit-minibuffer)) (defun helm-keyboard-quit () "Quit minibuffer in helm. If action buffer is displayed, kill it." (interactive) (with-helm-alive-p (when (get-buffer-window helm-action-buffer 'visible) (kill-buffer helm-action-buffer)) (setq helm-exit-status 1) (abort-recursive-edit))) (put 'helm-keyboard-quit 'helm-only t) (defun helm-get-next-header-pos () "Return the position of the next header from point." (next-single-property-change (point) 'helm-header)) (defun helm-get-previous-header-pos () "Return the position of the previous header from point." (previous-single-property-change (point) 'helm-header)) (defun helm-pos-multiline-p () "Return non-`nil' if the current position is in the multiline source region." (get-text-property (point) 'helm-multiline)) (defun helm-get-next-candidate-separator-pos () "Return the position of the next candidate separator from point." (let ((hp (helm-get-next-header-pos))) (helm-aif (next-single-property-change (point) 'helm-candidate-separator) (or ;; Be sure we don't catch ;; the separator of next source. (and hp (< it hp) it) ;; The separator found is in next source ;; we are at last cand, so use the header pos. (and hp (< hp it) hp) ;; A single source, just try next separator. it)))) (defun helm-get-previous-candidate-separator-pos () "Return the position of the previous candidate separator from point." (previous-single-property-change (point) 'helm-candidate-separator)) (defun helm-pos-header-line-p () "Return t if the current line is a header line." (or (get-text-property (pos-bol) 'helm-header) (get-text-property (pos-bol) 'helm-header-separator))) (defun helm-pos-candidate-separator-p () "Return t if the current line is a candidate separator." (get-text-property (pos-bol) 'helm-candidate-separator)) ;;; Debugging ;; ;; (defun helm-debug-output () "Show all Helm locals variables and output of `helm-debug-function'." (interactive) (with-helm-alive-p (helm-help-internal helm-debug-output-buffer 'helm-debug-output-function))) (put 'helm-debug-output 'helm-only t) (defun helm-default-debug-function () "Collect sources of helm current session without their keymap. This is the default function for `helm-debug-function'." (cl-loop for source in (with-helm-buffer helm-sources) collect (remove (assq 'keymap source) source))) (defun helm-debug-output-function () (let ((local-vars (buffer-local-variables (get-buffer helm-buffer))) (count 1)) (insert (format "* Helm debug from `%s' buffer\n\n" helm-buffer)) (insert "** Local variables\n\n#+begin_src elisp\n" (pp-to-string (remove (assq 'helm-sources local-vars) local-vars)) "\n#+end_src\n") (dolist-with-progress-reporter (v (helm-interpret-value helm-debug-function)) "Calculating all helm-related values..." (insert (format "** Value%s\n" count) "#+begin_src elisp\n" (pp-to-string v) "\n#+end_src\n") (cl-incf count)))) (defun helm-enable-or-switch-to-debug () "First hit enable helm debugging, second hit switch to debug buffer." (interactive) (with-helm-alive-p (if helm-debug (helm-run-after-exit #'helm-debug-open-last-log) (setq helm-debug t) (with-helm-buffer (setq truncate-lines nil)) (message "Debugging enabled")))) (put 'helm-enable-or-switch-to-debug 'helm-only t) ;; Misc (defun helm-preselect (candidate-or-regexp &optional source) "Move selection to CANDIDATE-OR-REGEXP. CANDIDATE-OR-REGEXP can be a: - String - Cons cell of two strings - Nullary function, which moves to a candidate When CANDIDATE-OR-REGEXP is a cons cell, tries moving to first element of the cons cell, then the second, and so on. This allows selection of duplicate candidates after the first. When SOURCE is specified, move to it and search CANDIDATE-OR-REGEXP from there." (with-helm-buffer (when candidate-or-regexp (if source (progn (helm-goto-source source) (forward-line 1)) (goto-char (point-min)) (forward-line 1)) (if (functionp candidate-or-regexp) (funcall candidate-or-regexp) (let ((start (point)) mp) (helm-awhile (if (consp candidate-or-regexp) (and (re-search-forward (car candidate-or-regexp) nil t) (re-search-forward (cdr candidate-or-regexp) nil t)) (re-search-forward candidate-or-regexp nil t)) ;; If search fall on an header line continue loop ;; until it match or fail (Bug#1509). (unless (helm-pos-header-line-p) (cl-return (setq mp it)))) (goto-char (or mp start))))) (forward-line 0) ; Avoid scrolling right on long lines. (when (helm-pos-multiline-p) (helm-move--beginning-of-multiline-candidate)) (when (helm-pos-header-line-p) (forward-line 1)) (when helm-allow-mouse (helm--mouse-reset-selection-help-echo)) (helm-mark-current-line) (when helm--deleting-minibuffer-contents-from (recenter)) (helm-display-mode-line (or source (helm-get-current-source))) (helm-log-run-hook "helm-preselect" 'helm-after-preselection-hook))) (defun helm-delete-current-selection () "Delete the currently selected item." (with-helm-window (cond ((helm-pos-multiline-p) (helm-aif (helm-get-next-candidate-separator-pos) (delete-region (pos-bol) (1+ (progn (goto-char it) (pos-eol)))) ;; last candidate (goto-char (helm-get-previous-candidate-separator-pos)) (delete-region (pos-bol) (point-max))) (when (helm-end-of-source-p) (goto-char (or (helm-get-previous-candidate-separator-pos) (point-min))) (forward-line 1))) (t (delete-region (pos-bol) (1+ (pos-eol))) (when (helm-end-of-source-p t) (let ((headp (save-excursion (forward-line -1) (not (helm-pos-header-line-p))))) (and headp (forward-line -1)))))) (unless (helm-end-of-source-p t) (helm-mark-current-line)))) (defun helm-end-of-source-1 (n at-point) (save-excursion (if (and (helm-pos-multiline-p) (null at-point)) (null (helm-get-next-candidate-separator-pos)) (forward-line (if at-point 0 n)) (or (eq (pos-bol) (pos-eol)) (helm-pos-header-line-p) (if (< n 0) (bobp) (eobp)))))) (defun helm-end-of-source-p (&optional at-point) "Return non-nil if we are at EOB or end of source." (helm-end-of-source-1 1 at-point)) (defun helm-beginning-of-source-p (&optional at-point) "Return non-nil if we are at BOB or beginning of source." (helm-end-of-source-1 -1 at-point)) (defun helm--edit-current-selection-internal (func) (with-helm-window (forward-line 0) (let ((realvalue (get-text-property (point) 'helm-realvalue)) (multiline (get-text-property (point) 'helm-multiline))) (funcall func) (forward-line 0) (and realvalue (put-text-property (point) (pos-eol) 'helm-realvalue realvalue)) (and multiline (put-text-property (point) (or (helm-get-next-candidate-separator-pos) (point-max)) 'helm-multiline multiline)) (helm-mark-current-line)))) (defmacro helm-edit-current-selection (&rest forms) "Evaluate FORMS at current selection in the helm buffer. Used generally to modify current selection." (declare (indent 0) (debug t)) `(helm--edit-current-selection-internal (lambda () ,@forms))) (defun helm--delete-minibuffer-contents-from (from-str &optional presel) ;; Giving an empty string value to FROM-STR delete all. (let ((input (minibuffer-contents)) (src (and presel (helm-get-current-source))) (helm--deleting-minibuffer-contents-from presel) helm-move-to-line-cycle-in-source) (helm-reset-yank-point) (unless (zerop (length input)) ;; minibuffer is not empty, delete contents from end ;; of FROM-STR and update. (helm-set-pattern from-str t) (helm-update presel src)))) (defun helm-delete-minibuffer-contents (&optional arg) "Delete minibuffer contents. When `helm-delete-minibuffer-contents-from-point' is non-nil, delete minibuffer contents from point instead of deleting all. With a prefix ARG reverse this behaviour. When at the end of minibuffer, delete all but if a prefix ARG were given also preselect current selection when updating if possible (selection may be beyond candidate-number-limit)." (interactive "P") (with-helm-alive-p (let ((str (if helm-delete-minibuffer-contents-from-point (if (or arg (eobp)) "" (helm-minibuffer-completion-contents)) (if (and arg (not (eobp))) (helm-minibuffer-completion-contents) ""))) (presel (and arg (eobp) (concat "^" (regexp-quote (helm-get-selection nil t)) "$")))) (helm--delete-minibuffer-contents-from str presel)))) (put 'helm-delete-minibuffer-contents 'no-helm-mx t) ;;; helm-source-in-buffer. ;; (defun helm-candidates-in-buffer (&optional source) "The top level function used to store candidates with `helm-source-in-buffer'. Candidates are stored in a buffer generated internally by `helm-candidate-buffer' function. Each candidate must be placed in one line. The buffer is created and fed in the init attribute function of Helm. E.g.: (helm-build-in-buffer-source \"test\" :init (lambda () (helm-init-candidates-in-buffer \\='global \\='(foo foa fob bar baz)))) A shortcut can be used to simplify: (helm-build-in-buffer-source \"test\" :data \\='(foo foa fob bar baz)) By default, Helm makes candidates by evaluating the candidates function, then narrows them by `string-match' for each candidate. But this is slow for large number of candidates. The new way is to store all candidates in a buffer and then narrow with `re-search-forward'. Search function is customizable by search attribute. The important point is that buffer processing is MUCH FASTER than string list processing and is the Emacs way. The init function writes all candidates to a newly-created candidate buffer. The candidates buffer is created or specified by `helm-candidate-buffer'. Candidates are stored in a line. The candidates function narrows all candidates, IOW creates a subset of candidates dynamically. Class `helm-source-in-buffer' is implemented with three attributes: (candidates . helm-candidates-in-buffer) (volatile) (match identity) The volatile attribute is needed because `helm-candidates-in-buffer' creates candidates dynamically and need to be called every time `helm-pattern' changes. Because `helm-candidates-in-buffer' plays the role of `match' attribute function, specifying `(match identity)' makes the source slightly faster. However if source contains `match-part' attribute, match is computed only on part of candidate returned by the call of function provided by this attribute. The function should have one arg, candidate, and return only a specific part of candidate. To customize `helm-candidates-in-buffer' behaviour, use `search', `get-line' and `match-part' attributes." (let ((src (or source (helm-get-current-source)))) (helm-candidates-in-buffer-1 (helm-candidate-buffer) helm-pattern (or (assoc-default 'get-line src) #'buffer-substring-no-properties) (or (assoc-default 'search src) '(helm-candidates-in-buffer-search-default-fn)) ;; When candidate-transformer is specified in source ALL candidates should ;; be computed with the candidate-transformer function (in contrast with ;; filtered-candidate-transformer). This to be consistent with what sync ;; sources do. The car of the cons is used for initial fetching of ;; candidates whereas the cdr is used after when searching (in this case ;; the candidate number limit is used). (if (helm-get-attr 'candidate-transformer src) (cons 99999999 (helm-candidate-number-limit src)) (helm-candidate-number-limit src)) (helm-get-attr 'match-part) src))) (defun helm-candidates-in-buffer-search-default-fn (pattern) "Search PATTERN with `re-search-forward' with bound and noerror args." (condition-case _err ;; Use helm-re-search-forward to avoid infloop (bug#2657). (helm-re-search-forward pattern nil t) (invalid-regexp nil))) (defun helm-candidates-in-buffer-1 (buffer pattern get-line-fn search-fns limit match-part-fn source) "Return the list of candidates inserted in BUFFER matching PATTERN." (with-suppressed-warnings ((obsolete inhibit-point-motion-hooks)) ;; buffer == nil when candidates buffer does not exist. (when buffer (with-current-buffer buffer (let ((inhibit-point-motion-hooks t) (start-point (1- (point-min)))) (goto-char start-point) (if (string= pattern "") (helm-initial-candidates-from-candidate-buffer get-line-fn (if (consp limit) (car limit) limit)) (helm-search-from-candidate-buffer pattern get-line-fn search-fns (if (consp limit) (cdr limit) limit) start-point match-part-fn source))))))) (defun helm-search-from-candidate-buffer (pattern get-line-fn search-fns limit start-point match-part-fn source) (let ((inhibit-read-only t) (diacritics (assoc-default 'diacritics source))) (helm--search-from-candidate-buffer-1 (lambda () (cl-loop with hash = (make-hash-table :test 'equal) with allow-dups = (assq 'allow-dups source) with case-fold-search = (helm-set-case-fold-search) with count = 0 for iter from 1 for searcher in search-fns do (progn (goto-char start-point) ;; The character at start-point is a newline, ;; if pattern match it that's mean we are ;; searching for newline in buffer, in this ;; case skip this false line. ;; See comment >>>[1] in ;; `helm--search-from-candidate-buffer-1'. (and (condition-case nil (looking-at pattern) (invalid-regexp nil)) (forward-line 1))) nconc (cl-loop with pos-lst ;; POS-LST is used as a flag to decide if we ;; run `helm-search-match-part' even if ;; MATCH-PART isn't specified on source. This ;; happen when fuzzy matching or using a ;; negation (!) in one of the patterns, in ;; these case the searcher returns a list ;; '(BEG END) instead of an integer like ;; `re-search-forward'. while (and (setq pos-lst (funcall searcher pattern)) ;; We were previously forwarding line from ;; searcher and checking then for eobp, as a ;; result the last candidate was always ;; skipped. See bug#2650. (if (consp pos-lst) ;; With negation the searcher is not ;; forwarding line, we do it from here. ;; If forward-line doesn't return zero, ;; we are at eob. (zerop (forward-line 1)) (not (eobp))) (< count limit)) for cand = (apply get-line-fn (if (and pos-lst (listp pos-lst)) pos-lst (list (pos-bol) (pos-eol)))) when (and match-part-fn (not (get-text-property 0 'match-part cand))) do (setq cand (propertize cand 'match-part (funcall match-part-fn cand))) for dup = (gethash cand hash) when (and (or (and allow-dups dup (= dup iter)) (null dup)) ;; Happens with (buffer-substring 1 1) when ;; setting cand above (bug#2651) and create a ;; ghost candidate on top. (not (string= cand "")) (or ;; Always collect when cand is matched ;; by searcher funcs and match-part attr ;; is not present. (and (not match-part-fn) (not (consp pos-lst))) ;; If match-part attr is present, or if SEARCHER fn ;; returns a cons cell, collect PATTERN only if it ;; match the part of CAND specified by ;; the match-part func. (helm-search-match-part cand pattern diacritics))) do (progn (puthash cand iter hash) (setq cand (helm--maybe-process-filter-one-by-one-candidate cand source)) (cl-incf count)) and collect cand)))))) (defun helm-search-match-part (candidate pattern diacritics) "Match PATTERN only on match-part property value of CANDIDATE. Because `helm-search-match-part' may be called even if unspecified in source (negation or fuzzy), the part to match falls back to the whole candidate even if match-part hasn't been computed by match-part-fn and stored in the match-part property." (let ((part (or (get-text-property 0 'match-part candidate) candidate)) (fuzzy-regexp (cadr (gethash 'helm-pattern helm--fuzzy-regexp-cache))) (matchfn (cond (helm-migemo-mode 'helm-mm-migemo-string-match) (diacritics 'helm-mm-diacritics-string-match) (t 'string-match)))) (condition-case _err (if (string-match " " pattern) (cl-loop for i in (helm-mm-split-pattern pattern) always (if (string-match "\\`!" i) (not (funcall matchfn (substring i 1) part)) (funcall matchfn i part))) (if (string-match "\\`!" pattern) (if helm--in-fuzzy ;; Fuzzy regexp have already been ;; computed with substring 1. (not (string-match fuzzy-regexp part)) (not (funcall matchfn (substring pattern 1) part))) (funcall matchfn (if helm--in-fuzzy fuzzy-regexp pattern) part))) (invalid-regexp nil)))) (defun helm-initial-candidates-from-candidate-buffer (get-line-fn limit) (cl-loop repeat limit until (eobp) for line = (funcall get-line-fn (pos-bol) (pos-eol)) when line collect line do (forward-line 1))) (defun helm--search-from-candidate-buffer-1 (search-fn) ;; We are adding a newline at bob and at eol ;; and removing these newlines afterward. ;; This is a bad hack that should be removed. ;; To avoid matching the empty line at first line ;; when searching with e.g occur and "^$" just ;; forward-line before searching (See >>>[1] above). (goto-char (point-min)) (insert "\n") (goto-char (point-max)) (insert "\n") (unwind-protect (funcall search-fn) (goto-char (point-min)) (delete-char 1) (goto-char (1- (point-max))) (delete-char 1) (set-buffer-modified-p nil))) (defun helm-candidate-buffer (&optional buffer-spec) "Register and return a buffer storing candidates of current source. This is used to initialize a buffer for storing candidates for a candidates-in-buffer source, candidates will be searched in this buffer and displayed in `helm-buffer'. This should be used only in init functions, don't relay on this in other places unless you know what you are doing. This function is still in public API only for backward compatibility, you should use instead `helm-init-candidates-in-buffer' for initializing your sources. Internally, this function is called without argument and returns the buffer corresponding to current source i.e. `helm--source-name' which is available in only some places. Acceptable values of BUFFER-SPEC: - global (a symbol) Create a new global candidates buffer, named \" *helm candidates:SOURCE*\". This is used by `helm-init-candidates-in-buffer' and it is the most common usage of BUFFER-SPEC. The buffer will be killed and recreated at each new helm-session. - local (a symbol) Create a new local candidates buffer, named \" *helm candidates:SOURCE*HELM-CURRENT-BUFFER\". You may want to use this when you want to have a different buffer each time source is used from a different `helm-current-buffer'. The buffer is erased and refilled at each new session but not killed. You probably don't want to use this value for BUFFER-SPEC. - nil (omit) Only return the candidates buffer of current source if found. - A buffer Register a buffer as a candidates buffer. The buffer needs to exists, it is not created. This allow you to use the buffer as a cache, it is faster because the buffer is already drawn, but be careful when using this as you may mangle your buffer depending on what you write in your init(s) function, IOW don't modify the contents of the buffer in init(s) function but in a transformer. The buffer is not erased nor deleted. Generally it is safer to use a copy of buffer inserted in a global or local buffer. If for some reasons a global buffer and a local buffer exist and are belonging to the same source, the local buffer takes precedence on the global one and is used instead. When forcing update only the global and local buffers are killed before running again the init function." (let ((global-bname (format " *helm candidates:%s*" helm--source-name)) (local-bname (format " *helm candidates:%s*%s" helm--source-name (buffer-name helm-current-buffer)))) (when buffer-spec ;; Register buffer in `helm--candidate-buffer-alist'. ;; This is used only to retrieve buffer associated to current source ;; when using named buffer as value of BUFFER-SPEC. (setq helm--candidate-buffer-alist (cons (cons helm--source-name buffer-spec) (delete (assoc helm--source-name helm--candidate-buffer-alist) helm--candidate-buffer-alist))) ;; When using global or local as value of BUFFER-SPEC ;; create the buffer global-bname or local-bname, otherwise ;; reuse the buffer named BUFFER-SPEC. (unless (bufferp buffer-spec) ;; Global buffers are killed and recreated. (and (eq buffer-spec 'global) (buffer-live-p (get-buffer global-bname)) (kill-buffer global-bname)) ;; Create global or local buffer. ;; Local buffer, once created are reused and a new one ;; is created when `helm-current-buffer' change across sessions. (with-current-buffer (get-buffer-create (helm-acase buffer-spec (global global-bname) (local local-bname) (t (and (stringp buffer-spec) buffer-spec)))) ;; We need a buffer not read-only to perhaps insert later ;; text coming from read-only buffers (Bug#1176). (set (make-local-variable 'buffer-read-only) nil) ;; Undo is automatically disabled in buffer names starting ;; with a space, so no need to disable it. (erase-buffer) (font-lock-mode -1)))) ;; Finally return the candidates buffer. (helm-acond ((get-buffer local-bname)) ((get-buffer global-bname)) ((assoc-default helm--source-name helm--candidate-buffer-alist) (and (or (stringp it) (bufferp it)) (buffer-live-p (get-buffer it)) it))))) (defvar helm-candidate-buffer-longest-len 0 "May store the longest length of candidates in a in-buffer source. It is a local variable set from `helm-init-candidates-in-buffer' in `helm-candidate-buffer'. Allow getting the longest length of initial candidates in transformers without looping again through the whole list. It is useful to align extra informations after candidates in `helm-buffer'.") (defsubst helm-in-buffer-get-longest-candidate () "Return the longest candidate recorded in `helm-candidate-buffer'." (helm-aif (helm-candidate-buffer) (buffer-local-value 'helm-candidate-buffer-longest-len (get-buffer it)) 0)) (defun helm-make-separator (cand &optional longest) "Create a separator to align candidates. Longest candidate should have been calculated at initialization of `helm-source-in-buffer' by `helm-init-candidates-in-buffer' , otherwise LONGEST can be used to specify longest candidate." (let ((lgst (or longest (helm-in-buffer-get-longest-candidate))) (len (length cand))) (if (zerop lgst) " -- " ; For emacs style. (make-string (1+ (if (>= lgst len) (- lgst len) 0)) ? )))) (defun helm-init-candidates-in-buffer (buffer-spec data &optional force-longest) "Register BUFFER-SPEC with DATA for a helm candidates-in-buffer session. Arg BUFFER-SPEC can be a `buffer-name' (stringp), a buffer-spec object (bufferp), or a symbol, either \\='local or \\='global which is passed to `helm-candidate-buffer'. The most common usage of BUFFER-SPEC is \\='global. Arg DATA can be either a list or a plain string. Returns the resulting buffer. Use this in your init function to register a buffer for a `helm-source-in-buffer' session and feed it with DATA. You probably don't want to bother with this and use the :data slot when initializing a source with `helm-source-in-buffer' class. When inserting DATA in `helm-candidate-buffer', if DATA is a list the longest candidate will be recorded in `helm-candidate-buffer-longest-len' local variable. If DATA is a string, it is inserted directly in `helm-candidate-buffer' and `helm-candidate-buffer-longest-len' is not computed unless FORCE-LONGEST is non nil." (declare (indent 1)) (let ((caching (and (or (stringp buffer-spec) (bufferp buffer-spec)) (buffer-live-p (get-buffer buffer-spec)))) (buf (helm-candidate-buffer buffer-spec))) (unless caching (with-current-buffer buf (erase-buffer) (cond ((listp data) (insert (mapconcat (lambda (elm) (let ((cand (helm-acase elm ((guard* (symbolp it)) (symbol-name it)) ((guard* (numberp it)) (number-to-string it)) ((dst* (disp . real)) (propertize disp 'helm-realvalue real)) (t it)))) (setq-local helm-candidate-buffer-longest-len (max helm-candidate-buffer-longest-len (length cand))) cand)) data "\n"))) ((stringp data) (insert data) (when force-longest (setq-local helm-candidate-buffer-longest-len (helm--get-longest-len-in-buffer)))))) buf))) (defun helm--get-longest-len-in-buffer () "Return length of the longest line in buffer." (save-excursion (goto-char (point-min)) (let ((max 0) len) (while (not (eobp)) (setq len (- (pos-eol) (pos-bol))) (when (> len max) (setq max len)) (forward-line 1)) max))) ;;; Resplit helm window ;; ;; (defun helm-toggle-resplit-window () "Toggle resplit helm window, vertically or horizontally." (interactive) (with-helm-alive-p (if (and (= (length (window-list nil 1)) 2) (not (window-dedicated-p (get-buffer-window helm-current-buffer)))) (progn (when helm-prevent-escaping-from-minibuffer (helm-prevent-switching-other-window :enabled nil)) (unwind-protect (with-helm-window (cond ((or helm-full-frame (one-window-p t)) (user-error "Attempt to resplit a single window")) ((helm-action-window) (user-error "Can't resplit while selecting actions")) (t (let ((before-height (window-height))) (delete-window) (set-window-buffer (select-window (if (= (window-height) before-height) ; initial split was horizontal. ;; Split window vertically with `helm-buffer' placed ;; on the good side according to actual value of ;; `helm-split-window-default-side'. (prog1 (cond ((or (eq helm-split-window-default-side 'above) (eq helm-split-window-default-side 'left)) (split-window (selected-window) nil 'above)) (t (split-window-vertically))) (setq helm-split-window-state 'vertical)) ;; Split window vertically, same comment as above. (setq helm-split-window-state 'horizontal) (cond ((or (eq helm-split-window-default-side 'left) (eq helm-split-window-default-side 'above)) (split-window (selected-window) nil 'left)) (t (split-window-horizontally))))) helm-buffer)))) (setq helm--window-side-state (helm--get-window-side-state))) (when helm-prevent-escaping-from-minibuffer (helm-prevent-switching-other-window :enabled t)))) (error "current window configuration not suitable for splitting")))) (put 'helm-toggle-resplit-window 'helm-only t) ;; Utility: Resize helm window. (defun helm-enlarge-window-1 (n) "Enlarge or narrow helm window. If N is positive enlarge, if negative narrow." (unless helm-full-frame (let ((horizontal-p (eq helm-split-window-state 'horizontal))) (with-helm-window (enlarge-window n horizontal-p))))) (defun helm-narrow-window () "Narrow helm window." (interactive) (with-helm-alive-p (helm-enlarge-window-1 -1))) (put 'helm-narrow-window 'helm-only t) (defun helm-enlarge-window () "Enlarge helm window." (interactive) (with-helm-alive-p (helm-enlarge-window-1 1))) (put 'helm-enlarge-window 'helm-only t) (defun helm-toggle-full-frame (&optional arg) "Toggle `helm-buffer' full-frame view." (interactive "p") (cl-assert (null (helm-action-window)) nil "Unable to toggle full frame from action window") (when arg ; Called interactively (cl-assert (null helm--buffer-in-new-frame-p) nil "Can't toggle full frame when using helm own frame")) (if (or helm-onewindow-p (buffer-local-value 'helm-full-frame (get-buffer helm-buffer))) (with-helm-window (setq-local helm-full-frame nil) (setq helm-onewindow-p nil) (let ((split-window-preferred-function helm-split-window-preferred-function)) (switch-to-buffer helm-current-buffer) (helm-display-buffer helm-buffer) (select-window (minibuffer-window)))) (with-helm-window (delete-other-windows) (setq-local helm-full-frame t) (setq helm-onewindow-p t)))) (put 'helm-toggle-full-frame 'helm-only t) (defun helm-swap-windows () "Swap window holding `helm-buffer' with other window." (interactive) (with-helm-alive-p (if (= (length (window-list nil 1)) 2) (cond ((and helm-full-frame (one-window-p t)) (user-error "Can't swap windows in a single window")) ((helm-action-window) (user-error "Can't resplit while selecting actions")) (t (let* ((w1 (helm-window)) (split-state (eq helm-split-window-state 'horizontal)) (w1size (window-total-size w1 split-state)) (b1 (window-buffer w1)) ; helm-buffer (s1 (window-start w1)) (cur-frame (window-frame w1)) (w2 (with-selected-window (helm-window) ;; Don't try to display helm-buffer ;; in a dedicated window. (get-window-with-predicate (lambda (w) (not (window-dedicated-p w))) 1 cur-frame))) (w2size (window-total-size w2 split-state)) (b2 (window-buffer w2)) ; probably helm-current-buffer (s2 (window-start w2)) resize) (with-selected-frame (window-frame w1) (helm-replace-buffer-in-window w1 b1 b2) (helm-replace-buffer-in-window w2 b2 b1) (setq resize (cond ( ;; helm-window is smaller than other window. (< w1size w2size) (- (- (max w2size w1size) (min w2size w1size)))) ( ;; helm-window is larger than other window. (> w1size w2size) (- (max w2size w1size) (min w2size w1size))) ( ;; windows have probably same size. t nil))) ;; Maybe resize the window holding helm-buffer. (and resize (window-resize w2 resize split-state)) (set-window-start w1 s2 t) (set-window-start w2 s1 t)) (setq helm--window-side-state (helm--get-window-side-state))))) (error "current window configuration not suitable for splitting")))) (put 'helm-swap-windows 'helm-only t) (defun helm--get-window-side-state () "Return the position of `helm-window' from `helm-current-buffer'. Possible values are \\='left \\='right \\='below or \\='above." (let ((side-list '(left right below above))) (cl-loop for side in side-list thereis (and (equal (helm-window) (window-in-direction side (get-buffer-window helm-current-buffer t) t)) side)))) (defun helm-replace-buffer-in-window (window buffer1 buffer2) "Replace BUFFER1 by BUFFER2 in WINDOW registering BUFFER1." (when (get-buffer-window buffer1) (unrecord-window-buffer window buffer1) (set-window-buffer window buffer2))) ;; Utility: select another action by key (defun helm-select-nth-action (n) "Select the N nth action for the currently selected candidate." (let ((src (helm-get-current-source))) (setq helm-saved-selection (helm-get-selection nil nil src)) (unless helm-saved-selection (error "Nothing is selected")) (setq helm-saved-action (helm-get-nth-action n (if (get-buffer-window helm-action-buffer 'visible) (assoc-default 'candidates src) (helm-get-actions-from-current-source src)))) (helm-maybe-exit-minibuffer))) (defun helm-get-nth-action (n action) "Return the nth N action from ACTION. Argument ACTION can be a symbol or a list of actions." (cond ((and (zerop n) (functionp action)) action) ((listp action) (or (cdr (elt action n)) (error "No such action"))) ((and (functionp action) (> n 0)) (error "Sole action")) (t (error "Error in `helm-select-nth-action'")))) (defun helm-execute-selection-action-at-nth (linum) "Execute default action on candidate at LINUM lines from selection." (let ((prefarg current-prefix-arg)) (if (>= linum 0) (helm-next-line linum) (helm-previous-line (lognot (1- linum)))) (setq current-prefix-arg prefarg) (helm-exit-minibuffer))) ;;; Persistent Action ;; (defun helm-initialize-persistent-action () (set (make-local-variable 'helm-persistent-action-display-window) nil)) (cl-defun helm-execute-persistent-action (&optional attr split) "Perform the associated action ATTR without quitting helm. Arg ATTR default will be `persistent-action' or `persistent-action-if' if unspecified depending on what's found in source, but it can be anything else. In this case you have to add this new attribute to your source. See `persistent-action' and `persistent-action-if' slot documentation in `helm-source'. When `helm-full-frame' is non-nil, and `helm-buffer' is displayed in only one window, the helm window is split to display `helm-select-persistent-action-window' in other window to maintain visibility. The argument SPLIT can be used to force splitting inconditionally, it is unused currently." (interactive) (with-suppressed-warnings ((obsolete special-display-regexps) (obsolete special-display-buffer-names)) (with-helm-alive-p (let ((source (helm-get-current-source))) (unless attr (setq attr (or (car (assq 'persistent-action source)) (car (assq 'persistent-action-if source))))) (helm-log "helm-execute-persistent-action" "executing persistent-action") (let* ((selection (and source (helm-get-selection nil nil source))) (attr-val (if (eq attr 'persistent-action-if) (funcall (assoc-default attr source) selection) (assoc-default attr source))) ;; If attr value is a cons, use its car as persistent function. (fn (if (and (consp attr-val) ;; maybe a lambda. (not (functionp attr-val))) (car attr-val) attr-val)) ;; And its cdr to decide if helm window should be splitted. (no-split (and (consp attr-val) (not (functionp attr-val)) (cdr attr-val))) ;; Is next-window (from helm-window) a suitable window for PA? (no-suitable-win (helm-aand (not helm--buffer-in-new-frame-p) (get-buffer-window helm-current-buffer) (or (window-dedicated-p it) (window-parameter it 'window-side)))) (cursor-in-echo-area t) mode-line-in-non-selected-windows) (progn (when (and helm-onewindow-p (null no-split) (null helm--buffer-in-new-frame-p)) (helm-toggle-full-frame)) (when (eq fn 'ignore) (cl-return-from helm-execute-persistent-action nil)) (when source (with-helm-window (save-selected-window ;; FIXME: Simplify SPLIT behavior, it is a mess currently. (if no-split (helm-select-persistent-action-window :split 'never) (helm-select-persistent-action-window :split (or split helm-onewindow-p no-suitable-win))) (helm-log "helm-execute-persistent-action" "current-buffer = %S" (current-buffer)) (let ((helm-in-persistent-action t) (display-buffer-alist '((".*" (display-buffer-same-window)))) display-buffer-function pop-up-windows pop-up-frames special-display-regexps special-display-buffer-names) (helm-execute-selection-action-1 selection (or fn (helm-get-actions-from-current-source source)) t) (unless (helm-action-window) (helm-log-run-hook "helm-execute-persistent-action" 'helm-after-persistent-action-hook))) ;; A typical case is when a persistent action delete ;; the buffer already displayed in ;; `helm-persistent-action-display-window' and `helm-full-frame' ;; is enabled, we end up with the `helm-buffer' ;; displayed in two windows. (when (and helm-onewindow-p (> (length (window-list)) 1) (equal (buffer-name (window-buffer helm-persistent-action-display-window)) (helm-buffer-get))) (delete-other-windows))))))))))) (put 'helm-execute-persistent-action 'helm-only t) (cl-defun helm-persistent-action-display-window (&key split) "Return the window that will be used for persistent action. If SPLIT is t window is split in persistent action, if it has the special symbol `never' don't split, if it is nil don't split either. The symbol `never' is kept for backward compatibility." (with-helm-window (setq helm-persistent-action-display-window (cond ((and (window-live-p helm-persistent-action-display-window) (not (member helm-persistent-action-display-window (get-buffer-window-list helm-buffer)))) helm-persistent-action-display-window) ((and helm--buffer-in-new-frame-p helm-initial-frame) (with-selected-frame helm-initial-frame (let ((win (selected-window))) (if (or (window-dedicated-p win) (window-parameter win 'window-side)) (next-window win 1) win)))) ((and split (not (eq split 'never))) (split-window)) ((get-buffer-window helm-current-buffer)) (t (previous-window (selected-window) 1)))))) (cl-defun helm-select-persistent-action-window (&key split) "Select the window that will be used for persistent action. See `helm-persistent-action-display-window' for how to use SPLIT." (select-window (get-buffer-window (helm-buffer-get))) (prog1 (select-window (setq minibuffer-scroll-window (helm-persistent-action-display-window :split split)) 'norecord) (helm-log "helm-select-persistent-action-window" "Selected window is %S" minibuffer-scroll-window))) ;;; Scrolling - recentering ;; ;; (defun helm-other-window-base (command &optional arg) (let ((minibuffer-scroll-window (helm-persistent-action-display-window))) (funcall command (or arg helm-scroll-amount)))) (defun helm-scroll-other-window (&optional arg) "Scroll other window upward ARG many lines. When arg is not provided scroll `helm-scroll-amount' lines. See `scroll-other-window'." (interactive "P") (with-helm-alive-p (helm-other-window-base 'scroll-other-window arg))) (put 'helm-scroll-other-window 'helm-only t) (defun helm-scroll-other-window-down (&optional arg) "Scroll other window downward ARG many lines. When arg is not provided scroll `helm-scroll-amount' lines. See `scroll-other-window-down'." (interactive "P") (with-helm-alive-p (helm-other-window-base 'scroll-other-window-down arg))) (put 'helm-scroll-other-window-down 'helm-only t) (defun helm-recenter-top-bottom-other-window (&optional arg) "Run `recenter-top-bottom' in other window. Meaning of prefix ARG is the same as in `recenter-top-bottom'." (interactive "P") (with-helm-alive-p (with-helm-window (with-selected-window (helm-persistent-action-display-window) (recenter-top-bottom arg))))) (put 'helm-recenter-top-bottom-other-window 'helm-only t) (defun helm-reposition-window-other-window (&optional arg) "Run `reposition-window' in other window. Meaning of prefix ARG is the same as in `reposition-window'." (interactive "P") (with-helm-alive-p (with-helm-window (with-selected-window (helm-persistent-action-display-window) (reposition-window arg))))) (put 'helm-reposition-window-other-window 'helm-only t) ;; Utility: Visible Mark (defun helm-clear-visible-mark () (with-current-buffer (helm-buffer-get) (mapc 'delete-overlay helm-visible-mark-overlays) (set (make-local-variable 'helm-visible-mark-overlays) nil))) (defun helm-this-visible-mark () (cl-loop for o in (overlays-at (point)) when (overlay-get o 'visible-mark) return o)) (defun helm-delete-visible-mark (overlay) (let ((src (helm-get-current-source))) (setq helm-marked-candidates (remove (cons src (helm-get-selection nil nil src)) helm-marked-candidates)) (delete-overlay overlay) (setq helm-visible-mark-overlays (delq overlay helm-visible-mark-overlays)))) (defun helm-make-visible-mark (&optional src selection) (let* ((source (or src (helm-get-current-source))) (sel (or selection (helm-get-selection nil (helm-get-attr 'marked-with-props source) source))) (selection-end (if (helm-pos-multiline-p) ;; Stays within source (or (helm-get-next-candidate-separator-pos) (helm-get-next-header-pos) (point-max)) ;; Not multiline (1+ (pos-eol)))) (o (make-overlay (pos-bol) selection-end))) (overlay-put o 'priority 0) (overlay-put o 'face 'helm-visible-mark) (overlay-put o 'source source) (overlay-put o 'string (buffer-substring (overlay-start o) (overlay-end o))) (overlay-put o 'real sel) (overlay-put o 'before-string (propertize " " 'display `((margin left-margin) ,(propertize helm-visible-mark-prefix 'face 'helm-mark-prefix)))) (overlay-put o 'visible-mark t) (overlay-put o 'evaporate t) (cl-pushnew o helm-visible-mark-overlays) (push (cons source sel) helm-marked-candidates))) (defun helm-toggle-visible-mark (arg) "Toggle Helm visible mark at point ARG times. If ARG is negative toggle backward." (interactive "p") (with-helm-alive-p (with-helm-window (let ((nomark (assq 'nomark (helm-get-current-source))) (next-fns (if (< arg 0) '(helm-beginning-of-source-p . helm-previous-line) '(helm-end-of-source-p . helm-next-line)))) (if nomark (message "Marking not allowed in this source") (cl-loop with n = (if (< arg 0) (* arg -1) arg) repeat n do (progn (helm-aif (helm-this-visible-mark) (helm-delete-visible-mark it) (helm-make-visible-mark)) (if (funcall (car next-fns)) (progn (helm-display-mode-line (helm-get-current-source)) (cl-return nil)) (funcall (cdr next-fns))))) (set-window-margins (selected-window) (if helm-visible-mark-overlays (+ (string-width helm-visible-mark-prefix) helm-left-margin-width) helm-left-margin-width))))))) (put 'helm-toggle-visible-mark 'helm-only t) (defun helm-toggle-visible-mark-forward () (interactive) (helm-toggle-visible-mark 1)) (defun helm-toggle-visible-mark-backward () (interactive) (helm-toggle-visible-mark -1)) (defun helm-file-completion-source-p (&optional source) "Return non-nil if current source is a file completion source." (or helm--completing-file-name ; helm-read-file-name (let ((cur-source (cdr (assq 'name (or source (helm-get-current-source)))))) (cl-loop for i in helm--file-completion-sources thereis (string= cur-source i))))) (defun helm-mark-all (&optional all) "Mark all visible unmarked candidates in current source. With a prefix arg mark all visible unmarked candidates in all sources." (interactive "P") (with-helm-alive-p (with-helm-window ; Using `with-helm-buffer' for some unknow ; reasons infloop. (set-window-margins (selected-window) (+ (string-width helm-visible-mark-prefix) helm-left-margin-width)) (if (null all) (helm-mark-all-1 t) (let ((pos (point))) (goto-char (point-min)) (helm-awhile (helm-get-next-header-pos) (goto-char it) (forward-line 1) (helm-mark-current-line) (helm-mark-all-1)) ;; `save-excursion' seems confused if used in addition of ;; the one used in `helm-mark-all-1', so save POS and back ;; to it when loop is finished. (goto-char pos) (helm-mark-current-line) (helm-display-mode-line (helm-get-current-source) t)))))) (put 'helm-mark-all 'helm-only t) (defun helm-mark-all-1 (&optional ensure-beg-of-source) "Mark all visible unmarked candidates in current source. Need to be wrapped in `with-helm-window'. Arg ENSURE-BEG-OF-SOURCE ensure we are at beginning of source when starting to mark candidates, if handled elsewhere before starting it is not needed." (let* ((src (helm-get-current-source)) (follow (if (helm-follow-mode-p src) 1 -1)) (nomark (assq 'nomark src)) (src-name (assoc-default 'name src)) (filecomp-p (or (helm-file-completion-source-p src) (string= src-name "Files from Current Directory")))) (helm-follow-mode -1) (unwind-protect (if nomark (user-error "Marking not allowed in this source") (save-excursion (when ensure-beg-of-source (goto-char (helm-get-previous-header-pos)) (forward-line 1)) (let* ((next-head (helm-get-next-header-pos)) (end (and next-head (save-excursion (goto-char next-head) (forward-line -1) (point)))) (maxpoint (or end (point-max)))) (while (< (point) maxpoint) (helm-mark-current-line) (let* ((prefix (or (get-text-property (pos-bol) 'helm-new-file) (get-text-property (pos-bol) 'unknown))) (cand (helm-get-selection nil (helm-get-attr 'marked-with-props src) src)) (bn (and filecomp-p (helm-basename cand)))) ;; Don't mark possibles directories ending with . or .. ;; autosave files/links and non--existent files. (unless (or (helm-this-visible-mark) ;; Non existing files in HFF and ;; RFN. Display may be an image. See ;; https://github.com/yyoncho/helm-treemacs-icons/issues/5 ;; and also Bug#2296. prefix (and filecomp-p (member bn '("." "..")))) (helm-make-visible-mark src cand))) (when (helm-pos-multiline-p) (goto-char (or (helm-get-next-candidate-separator-pos) (point-max)))) (forward-line 1)))) (helm-mark-current-line)) (helm-follow-mode follow)))) (defun helm-unmark-all () "Unmark all candidates in all sources of current helm session." (interactive) (with-helm-alive-p (with-helm-window (save-excursion (helm-clear-visible-mark)) (setq helm-marked-candidates nil) (helm-mark-current-line) (helm-display-mode-line (helm-get-current-source)) (set-window-margins (selected-window) helm-left-margin-width)))) (put 'helm-unmark-all 'helm-only t) (defun helm-toggle-all-marks (&optional all) "Toggle all marks. Mark all visible candidates of current source or unmark all candidates visible or invisible in all sources of current Helm session. With a prefix argument mark all candidates in all sources." (interactive "P") (with-helm-alive-p (let ((marked (helm-marked-candidates))) (if (and (>= (length marked) 1) (with-helm-window helm-visible-mark-overlays)) (helm-unmark-all) (helm-mark-all all))))) (put 'helm-toggle-all-marks 'helm-only t) (defun helm--compute-marked (real source &optional wildcard) (let* ((coerced (helm-coerce-selection real source)) (wilds (and wildcard (condition-case nil (helm-file-expand-wildcards coerced t) (error nil))))) ;; Avoid returning a not expanded wildcard fname. ;; e.g assuming "/tmp" doesn't contain "*.el" ;; return nil when coerced is "/tmp/*.el". (unless (or wilds (null wildcard) (string-match-p helm--url-regexp coerced) (file-exists-p coerced) (and (stringp coerced) (null (string-match-p "[[*?]" coerced)))) (setq coerced nil)) (or wilds (and coerced (list coerced))))) (cl-defun helm-marked-candidates (&key with-wildcard all-sources) "Return marked candidates of current source, if any. Otherwise return one element list consisting of the current selection. When key WITH-WILDCARD is specified, expand it. When ALL-SOURCES key value is non-nil returns marked candidates of all sources." (with-current-buffer helm-buffer (let* ((current-src (helm-get-current-source)) (candidates (cl-loop for (source . real) in (reverse helm-marked-candidates) for use-wc = (and with-wildcard (string-match-p "\\*" real) (null (file-exists-p real))) when (or all-sources (and ;; Dummy sources use a unique candidate, two same ;; dummy sources (or more) should share their ;; (unique) marked candidate only when ;; :all-marked is non nil. (not (equal (helm-get-attr 'candidates) '("dummy"))) (equal (assq 'name source) (assq 'name current-src)))) nconc (helm--compute-marked real source use-wc) into mkds finally return (if (and with-wildcard all-sources) (helm-fast-remove-dups mkds :test 'equal) mkds))) sel) (unless candidates (setq sel (helm-get-selection nil (helm-get-attr 'marked-with-props current-src) current-src)) (setq candidates (helm--compute-marked sel current-src (and with-wildcard (null (file-exists-p sel)))))) (helm-log "helm-marked-candidates" "Marked candidates = %S" candidates) candidates))) (defun helm--remove-marked-and-update-mode-line (elm) (with-helm-buffer (setq helm-marked-candidates (delete (rassoc elm helm-marked-candidates) helm-marked-candidates)) (helm-display-mode-line (helm-get-current-source)))) (defun helm-current-source-name= (name) (save-excursion (goto-char (helm-get-previous-header-pos)) (equal name (helm-current-line-contents)))) (defun helm-revive-visible-mark () "Restore marked candidates when helm updates display." (with-current-buffer helm-buffer (save-excursion (dolist (o helm-visible-mark-overlays) (let* ((source (overlay-get o 'source)) (ov-src-name (assoc-default 'name source)) (ov-str (overlay-get o 'string)) (ov-real (overlay-get o 'real)) (ov-ml-str (helm-aif (helm-get-attr 'multiline source) (if (numberp it) ;; Assume display have been computed ;; against real e.g. kill-ring. (helm--multiline-get-truncated-candidate ov-real it) ov-str) ov-str)) beg end) ;; Move point to end of source header line. (goto-char (point-min)) (search-forward ov-src-name nil t) (while (and (search-forward ov-ml-str nil t) (cl-loop for ov in (overlays-at (pos-bol 0)) never (overlay-get ov 'visible-mark)) (helm-current-source-name= ov-src-name)) (setq beg (match-beginning 0) end (if (string= ov-ml-str ov-str) (match-end 0) (1+ (match-end 0)))) ;; Calculate real value of candidate. ;; It can be nil if candidate have only a display value. (let ((real (get-text-property (pos-bol 0) 'helm-realvalue))) (if real ;; Check if real value of current candidate is the same ;; than the one stored in overlay. ;; This is needed when some cands have same display names. ;; Using equal allow testing any type of value for real cand. ;; bug#706. (and (equal ov-real real) (move-overlay o beg end)) (and (equal ov-str (buffer-substring beg end)) (move-overlay o beg end)))))))))) (add-hook 'helm-after-update-hook 'helm-revive-visible-mark) (defun helm-next-point-in-list (curpos points &optional prev) (cond ;; rule out special cases. ((null points) curpos) ((and prev (<= curpos (car points))) (nth (1- (length points)) points)) ((< (car (last points)) curpos) (if prev (car (last points)) (nth 0 points))) ((and (not prev) (>= curpos (car (last points)))) (nth 0 points)) (t (nth (if prev (cl-loop for pt in points for i from 0 if (<= curpos pt) return (1- i)) (cl-loop for pt in points for i from 0 if (< curpos pt) return i)) points)))) (defun helm-next-visible-mark (&optional prev) "Move next Helm visible mark. If PREV is non-nil move to precedent." (interactive) (with-helm-alive-p (with-helm-window (ignore-errors (goto-char (helm-next-point-in-list (point) (sort (mapcar 'overlay-start helm-visible-mark-overlays) '<) prev))) (helm-mark-current-line)))) (put 'helm-next-visible-mark 'helm-only t) (defun helm-prev-visible-mark () "Move previous helm visible mark." (interactive) (with-helm-alive-p (helm-next-visible-mark t))) (put 'helm-prev-visible-mark 'helm-only t) ;;; Kill/yank selection ;; (defun helm-yank-selection (arg) "Set minibuffer contents to current display selection. With a prefix arg set to real value of current selection." (interactive "P") (with-helm-alive-p (let ((str (format "%s" (helm-get-selection nil (not arg))))) (kill-new str) (helm-set-pattern str)))) (put 'helm-yank-selection 'helm-only t) (defun helm-kill-selection-and-quit (arg) "Store display value of current selection to kill ring. With a prefix arg use real value of current selection. Display value is shown in `helm-buffer' and real value is used to perform actions." (interactive "P") (with-helm-alive-p (helm-run-after-exit (lambda (sel) (kill-new sel) ;; Return nil to force `helm-mode--keyboard-quit' ;; in `helm-comp-read' otherwise the value "Saved to kill-ring: foo" ;; is used as exit value for `helm-comp-read'. (prog1 nil (message "Saved to kill-ring: %s" sel) (sit-for 1))) (format "%s" (helm-get-selection nil (not arg)))))) (put 'helm-kill-selection-and-quit 'helm-only t) (defun helm-insert-or-copy (&optional arg) "Insert selection or marked candidates in current buffer. With a prefix arg copy marked candidates to kill-ring. The real value of each candidate is used." (interactive "P") (with-helm-alive-p (helm-run-after-exit (lambda (cands) (with-helm-current-buffer (let ((sels (mapconcat (lambda (c) (format "%s" c)) cands "\n"))) (if arg (kill-new sels) (insert sels))))) (helm-marked-candidates)))) (put 'helm-insert-or-copy 'helm-only t) ;;; Follow-mode: Automatic execution of persistent-action ;; ;; (defvar helm-follow-input-idle-delay nil "`helm-follow-mode' will execute its persistent action after this delay. Note that if the `follow-delay' attr is present in source, it will take precedence over this.") (defun helm-follow-mode (&optional arg) "Execute persistent action every time the cursor is moved. This mode is source local, i.e. It applies on current source only. \\ This mode can be enabled or disabled interactively at anytime during a helm session with \\[helm-follow-mode]. When enabling interactively `helm-follow-mode' in a source, you can keep it enabled for next Emacs sessions by setting `helm-follow-mode-persistent' to a non-nil value. When `helm-follow-mode' is called with a prefix arg and `helm-follow-mode-persistent' is non-nil `helm-follow-mode' will be persistent only for this Emacs session, but not for the next Emacs sessions, i.e. the current source will not be saved to `helm-source-names-using-follow'. A prefix arg with `helm-follow-mode' already enabled will have no effect. Note that you can use instead of this mode the commands `helm-follow-action-forward' and `helm-follow-action-backward' at anytime in all Helm sessions. They are bound by default to \\[helm-follow-action-forward] and \\[helm-follow-action-backward]." (interactive (list (helm-aif (and current-prefix-arg (prefix-numeric-value current-prefix-arg)) (unless (helm-follow-mode-p) it)))) (with-helm-alive-p (with-current-buffer helm-buffer (let* ((src (helm-get-current-source)) (name (assoc-default 'name src)) (fol-attr (assq 'follow src)) (enabled (or (helm-follow-mode-p src) (and helm-follow-mode-persistent (member (assoc-default 'name src) helm-source-names-using-follow)))) ;; No messages when called non interactively. (set-message-function (lambda (msg) (if (and (not current-prefix-arg) arg) 1 msg)))) (if src (progn (if (eq (cdr fol-attr) 'never) (message "helm-follow-mode not allowed in this source") ;; Make follow attr persistent for this emacs session. (helm-follow-mode-set-source (if (or enabled (and (numberp arg) (< arg 0))) -1 1) src) ;; When called from lisp (non--interactive) src-name ;; will never be saved. ;; When arg is nil assume the call is interactive and save ;; src-name to `helm-source-names-using-follow'. ;; However if user call helm-follow-mode with a prefix arg, ;; behave like a non--interactive call and ;; DONT save src-name to `helm-source-names-using-follow'. (when (and helm-follow-mode-persistent (null arg)) (if (null enabled) (unless (member name helm-source-names-using-follow) (push name helm-source-names-using-follow) (customize-save-variable 'helm-source-names-using-follow helm-source-names-using-follow)) (when (member name helm-source-names-using-follow) (setq helm-source-names-using-follow (delete name helm-source-names-using-follow)) (customize-save-variable 'helm-source-names-using-follow helm-source-names-using-follow)))) (message "helm-follow-mode is %s" (if (helm-follow-mode-p src) "enabled" "disabled")) (helm-display-mode-line src t))) (message "Not enough candidates for helm-follow-mode")))))) (put 'helm-follow-mode 'helm-only t) (defun helm-follow-execute-persistent-action-maybe (&optional delay) "Execute persistent action in mode `helm-follow-mode'. This happen after: DELAY or the \\='follow-attr value of current source or `helm-follow-input-idle-delay' or `helm-input-idle-delay' secs." (let* ((src (helm-get-current-source)) (at (or delay (assoc-default 'follow-delay src) helm-follow-input-idle-delay (or (and helm-input-idle-delay (max helm-input-idle-delay 0.01)) 0.01))) (suspend (and helm--in-update ;; Specific to helm-find-files. (assoc-default 'suspend-follow-in-update src)))) (when (and (not suspend) (not (get-buffer-window helm-action-buffer 'visible)) (not (helm-pos-header-line-p)) (or (helm-follow-mode-p src) (and helm-follow-mode-persistent (member (assoc-default 'name src) helm-source-names-using-follow))) (null (eq (assoc-default 'follow src) 'never)) (helm-get-selection nil nil src)) (helm-follow-mode-set-source 1 src) (run-with-idle-timer at nil (lambda () (when helm-alive-p (helm-execute-persistent-action))))))) (defun helm-follow-mode-p (&optional source) (with-helm-buffer (eq (helm-get-attr 'follow (or source (helm-get-current-source))) 1))) (defun helm-follow-mode-set-source (value &optional source) (with-helm-buffer (helm-set-attr 'follow value (or source (helm-get-current-source))))) ;;; Auto-resize mode ;; (defun helm--autoresize-hook (&optional max-height min-height) (when (if (helm--show-action-window-other-window-p) (and (helm-window) (not (get-buffer-window helm-action-buffer 'visible))) (helm-window)) (with-helm-window (fit-window-to-buffer nil (/ (* (frame-height) (or max-height helm-autoresize-max-height)) 100) (/ (* (frame-height) (or min-height helm-autoresize-min-height)) 100))))) (define-minor-mode helm-autoresize-mode "Auto resize helm window when enabled. Helm window is re-sized according to `helm-autoresize-max-height' and `helm-autoresize-min-height'. Note that when this mode is enabled, Helm behaves as if `helm-always-two-windows' is enabled. See `fit-window-to-buffer' for more infos." :group 'helm :global t (if helm-autoresize-mode (progn (add-hook 'helm-after-update-hook 'helm--autoresize-hook) (add-hook 'helm-window-configuration-hook 'helm--autoresize-hook)) (remove-hook 'helm-after-update-hook 'helm--autoresize-hook) (remove-hook 'helm-window-configuration-hook 'helm--autoresize-hook))) (defun helm-help () "Generate Helm's help according to `help-message' attribute. If `helm-buffer' is empty, provide completions on `helm-sources' to choose its local documentation. If source doesn't have any `help-message' attribute, a generic message explaining this is added instead. The global `helm-help-message' is always added after this local help." (interactive) (require 'helm-mode) ; for helm-comp-read. (with-helm-alive-p (let ((source (or (helm-get-current-source) (helm-comp-read "Help for: " (cl-loop for src in (with-helm-buffer helm-sources) collect `(,(assoc-default 'name src) . ,src)) :allow-nest t :exec-when-only-one t)))) (save-selected-window (helm-help-internal helm-help-buffer-name (lambda () (helm-aif (assoc-default 'help-message source) (insert (substitute-command-keys (helm-interpret-value it))) (insert "* No specific help for this source available.")) (insert "\n\n" (substitute-command-keys (helm-interpret-value helm-help-message))))))))) (put 'helm-help 'helm-only t) (defun helm-toggle-truncate-line () "Toggle `truncate-lines' value in `helm-buffer'" (interactive) (with-helm-alive-p (with-helm-buffer (setq truncate-lines (not truncate-lines)) (when (helm-get-previous-header-pos) (helm-update (regexp-quote (helm-get-selection nil t)))) (message "%sisplaying continuation lines" (if truncate-lines "Not D" "D"))))) (put 'helm-toggle-truncate-line 'helm-only t) ;;;###autoload (defun helm-other-buffer (sources buffer) "Simplified Helm interface with other `helm-buffer'. Call `helm' only with SOURCES and BUFFER as args." (helm :sources sources :buffer buffer)) (make-obsolete 'helm-other-buffer 'helm "4.0.1") (provide 'helm-core) ;;; helm-core.el ends here helm-4.0.3/helm-dabbrev.el000066400000000000000000000411621501106761700153250ustar00rootroot00000000000000;;; helm-dabbrev.el --- Helm implementation of dabbrev. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm) (require 'helm-lib) (require 'helm-help) (require 'helm-elisp) ; For show-completion. (defgroup helm-dabbrev nil "Dabbrev related Applications and libraries for Helm." :group 'helm) (defcustom helm-dabbrev-always-search-all t "Always search in all buffers when non--nil. Note that even if nil, a search in all buffers will occur if the length of candidates is <= than `helm-dabbrev-max-length-result'." :type 'boolean) (defcustom helm-dabbrev-candidates-number-limit 1000 "Maximum number of candidates to collect. The higher this number is, the slower the computation of candidates will be. You can use safely a higher value with emacs-26+. Note that this have nothing to do with `helm-candidate-number-limit', this means that computation of candidates stop when this value is reached but only `helm-candidate-number-limit' candidates are displayed in the Helm buffer." :type 'integer) (defcustom helm-dabbrev-ignored-buffers-regexps '("\\*helm" "\\*Messages" "\\*Echo Area" "\\*Buffer List") "List of regexps matching names of buffers that `helm-dabbrev' should not check." :type '(repeat regexp)) (defcustom helm-dabbrev-related-buffer-fn #'helm-dabbrev--same-major-mode-p "A function that decide if a buffer to search in its related to `current-buffer'. This is currently determined by comparing `major-mode' of the buffer to search and the `current-buffer'. The function take one arg, the buffer which is current, look at `helm-dabbrev--same-major-mode-p' for an example. When nil all buffers are considered related to `current-buffer'." :type 'function) (defcustom helm-dabbrev-major-mode-assoc nil "Major mode association alist. This allow helm-dabbrev searching in buffers with the associated `major-mode'. E.g. (emacs-lisp-mode . lisp-interaction-mode) will allow searching in the lisp-interaction-mode buffer when `current-buffer' is an `emacs-lisp-mode' buffer and vice versa i.e. no need to provide (lisp-interaction-mode . emacs-lisp-mode) association. When nil check is the searched buffer has same `major-mode' than the `current-buffer'. This has no effect when `helm-dabbrev-related-buffer-fn' is nil or of course bound to a function that doesn't handle this var." :type '(alist :key-type symbol :value-type symbol)) (defcustom helm-dabbrev-lineno-around 30 "Search first in this number of lines before and after point." :type 'integer) (defcustom helm-dabbrev-cycle-threshold 5 "Number of time helm-dabbrev cycle before displaying helm completion. When nil or 0 disable cycling." :type '(choice (const :tag "Cycling disabled" nil) integer)) (defcustom helm-dabbrev-case-fold-search 'smart "Set `case-fold-search' in `helm-dabbrev'. Same as `helm-case-fold-search' but for `helm-dabbrev'. Note that this is not affecting searching in Helm buffer, but the initial search for all candidates in buffer(s)." :type '(choice (const :tag "Ignore case" t) (const :tag "Respect case" nil) (other :tag "Smart" smart))) (defvaralias 'helm-dabbrev--regexp 'helm-dabbrev-separator-regexp) (make-obsolete-variable 'helm-dabbrev--regexp 'helm-dabbrev-separator-regexp "2.8.3") ;; Check for beginning of line should happen last (^\n\\|^). (defvar helm-dabbrev-separator-regexp "\\s-\\|\t\\|[(\\[\\{\"'`=<>$;,@.#+]\\|\\s\\\\|^\n\\|^" "Regexp matching the start of a dabbrev candidate.") (defvar helm-dabbrev-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "M-/") #'helm-next-line) (define-key map (kbd "M-:") #'helm-previous-line) map)) ;; Internal (defvar helm-dabbrev--cache nil) (defvar helm-dabbrev--data nil) (cl-defstruct helm-dabbrev-info dabbrev limits iterator) (defvar helm-dabbrev--already-tried nil) (defvar helm-dabbrev--computing-cache nil "[INTERNAL] Flag to notify helm-dabbrev is blocked. Do nothing when non nil.") (defun helm-dabbrev--buffer-list () (cl-loop for buf in (buffer-list) unless (cl-loop for r in helm-dabbrev-ignored-buffers-regexps thereis (string-match r (buffer-name buf))) collect buf)) (defun helm-dabbrev--same-major-mode-p (start-buffer) "Decide if current-buffer is related to START-BUFFER." (helm-same-major-mode-p start-buffer helm-dabbrev-major-mode-assoc)) (defun helm-dabbrev--collect (str limit ignore-case all) (let* ((case-fold-search ignore-case) (buffer1 (current-buffer)) ; start buffer. (minibuf (minibufferp buffer1)) results pos-before pos-after) (catch 'break (dolist (buf (if all (helm-dabbrev--buffer-list) (list (current-buffer)))) (with-current-buffer buf (when (or minibuf ; check against all buffers when in minibuffer. (if helm-dabbrev-related-buffer-fn (funcall helm-dabbrev-related-buffer-fn buffer1) t)) (save-excursion ;; Start searching before thing before point. (goto-char (- (point) (length str))) ;; Search the last 30 lines BEFORE point and set POS-BEFORE. (cl-multiple-value-bind (res _pa pb) (helm-dabbrev--search-and-store str -2 limit results) (setq results res ;; No need to set POS-AFTER here. pos-before pb))) (save-excursion ;; Search the next 30 lines AFTER point and set POS-AFTER. (cl-multiple-value-bind (res pa _pb) (helm-dabbrev--search-and-store str 2 limit results) (setq results res ;; No need to set POS-BEFORE, we keep the last ;; value found. pos-after pa))) (save-excursion ;; Search all BEFORE point maybe starting from ;; POS-BEFORE to not search again what previously found. ;; If limit is reached in previous call of ;; `helm-dabbrev--search-and-store' POS-BEFORE is nil and ;; goto-char will fail, so check it. (when pos-before (goto-char pos-before)) (cl-multiple-value-bind (res _pa _pb) (helm-dabbrev--search-and-store str -1 limit results) ;; No need to set POS-BEFORE and POS-AFTER here. (setq results res))) (save-excursion ;; Search all AFTER point maybe starting from POS-AFTER. ;; Same comment as above for POS-AFTER. (when pos-after (goto-char pos-after)) (cl-multiple-value-bind (res _pa _pb) (helm-dabbrev--search-and-store str 1 limit results) ;; No need to set POS-BEFORE and POS-AFTER here. (setq results res))))) (when (>= (length results) limit) (throw 'break nil)))) (nreverse results))) (defun helm-dabbrev--search-and-store (pattern direction limit results) "Search words or symbols matching PATTERN in DIRECTION up to LIMIT. Finally returns all matched candidates appended to RESULTS. Argument DIRECTION can be: - (1): Search forward from point. - (-1): Search backward from point. - (2): Search forward from the `helm-dabbrev-lineno-around' lines after point. - (-2): Search backward from the `helm-dabbrev-lineno-around' lines before point." (let ((res results) after before) (while (and (<= (length res) limit) (cl-case direction (1 (search-forward pattern nil t)) (-1 (search-backward pattern nil t)) (2 (let ((pos (save-excursion (forward-line helm-dabbrev-lineno-around) (point)))) (setq after pos) (search-forward pattern pos t))) (-2 (let ((pos (save-excursion (forward-line (- helm-dabbrev-lineno-around)) (point)))) (setq before pos) (search-backward pattern pos t))))) (let* ((mb (match-beginning 0)) (replace-regexp (concat "\\(" helm-dabbrev-separator-regexp "\\)\\'")) (match-word (helm-dabbrev--search pattern mb replace-regexp))) (when (and match-word (not (member match-word res))) (push match-word res)))) (list res after before))) (defun helm-dabbrev--search (pattern beg sep-regexp) "Search word or symbol at point matching PATTERN. Argument BEG is corresponding to the previous `match-beginning' search. The search starts at (1- BEG) with a regexp starting with `helm-dabbrev-separator-regexp' followed by PATTERN followed by a regexp matching syntactically any word or symbol. The possible false positives matching SEP-REGEXP at end are finally removed." (let ((eol (pos-eol))) (save-excursion (goto-char (1- beg)) (when (re-search-forward (concat "\\(" helm-dabbrev-separator-regexp "\\)" "\\(?99:\\(" (regexp-quote pattern) "\\(\\sw\\|\\s_\\)+\\)\\)") eol t) (replace-regexp-in-string sep-regexp "" (match-string-no-properties 99)))))) (defun helm-dabbrev--get-candidates (dabbrev &optional limit) (cl-assert dabbrev nil "[No Match]") (helm-dabbrev--collect dabbrev (or limit helm-dabbrev-candidates-number-limit) (cl-case helm-dabbrev-case-fold-search (smart (helm-set-case-fold-search-1 dabbrev)) (t helm-dabbrev-case-fold-search)) helm-dabbrev-always-search-all)) (defun helm-dabbrev-default-action (candidate) (with-helm-current-buffer (let* ((limits (helm-bounds-of-thing-before-point helm-dabbrev-separator-regexp)) (beg (car limits)) (end (point))) (run-with-timer 0.01 nil #'helm-insert-completion-at-point beg end candidate)))) ;;;###autoload (cl-defun helm-dabbrev () "Preconfigured helm for dynamic abbreviations." (interactive) (unless helm-dabbrev--computing-cache (let ((dabbrev (helm-thing-before-point nil helm-dabbrev-separator-regexp)) (limits (helm-bounds-of-thing-before-point helm-dabbrev-separator-regexp)) (enable-recursive-minibuffers t) (cycling-disabled-p (or (null helm-dabbrev-cycle-threshold) (zerop helm-dabbrev-cycle-threshold))) (helm-execute-action-at-once-if-one t) (helm-quit-if-no-candidate (lambda () (message "[Helm-dabbrev: No expansion found]")))) (cl-assert (and (stringp dabbrev) (not (string= dabbrev ""))) nil "[Helm-dabbrev: Nothing found before point]") (when (and ;; have been called at least once. (helm-dabbrev-info-p helm-dabbrev--data) ;; But user have moved with some other command ;; in the meaning time. (not (eq last-command 'helm-dabbrev))) (setq helm-dabbrev--data nil)) ;; When candidates are requested in helm directly without cycling, ;; we need them right now before running helm. (when cycling-disabled-p (message "Waiting for helm-dabbrev candidates...") (setq helm-dabbrev--cache (helm-dabbrev--get-candidates dabbrev))) (unless (or cycling-disabled-p (helm-dabbrev-info-p helm-dabbrev--data)) (setq helm-dabbrev--data (make-helm-dabbrev-info :dabbrev dabbrev :limits limits :iterator (helm-iter-list (cl-loop for i in (helm-dabbrev--get-candidates dabbrev helm-dabbrev-cycle-threshold) when (string-match-p (concat "^" (regexp-quote dabbrev)) i) collect i))))) (let ((iter (and (helm-dabbrev-info-p helm-dabbrev--data) (helm-dabbrev-info-iterator helm-dabbrev--data))) deactivate-mark) ;; Cycle until iterator is consumed. (helm-aif (and iter (helm-iter-next iter)) (progn (helm-insert-completion-at-point (car (helm-dabbrev-info-limits helm-dabbrev--data)) ;; END is the end of the previous inserted string, not ;; the end (apart for first insertion) of the initial string. (cdr limits) it) ;; Move already tried candidates to end of list. (push it helm-dabbrev--already-tried)) ;; Iterator is now empty, or cycling was disabled, maybe ;; reset dabbrev to initial value and start helm completion. (let* ((old-dabbrev (if (helm-dabbrev-info-p helm-dabbrev--data) (helm-dabbrev-info-dabbrev helm-dabbrev--data) dabbrev)) (only-one (eq (length helm-dabbrev--already-tried) 1))) (unless helm-dabbrev--cache ; Already computed when ; cycling is disabled. (message "Waiting for helm-dabbrev candidates...") (setq helm-dabbrev--computing-cache t) (setq helm-dabbrev--cache (helm-dabbrev--get-candidates old-dabbrev)) ;; If user continues typing M-/ while display is blocked by ;; helm-dabbrev--get-candidates delete these events. (setq unread-command-events nil)) ;; If the length of candidates is only one when computed ;; that's mean the unique matched item have already been ;; inserted by the iterator, so no need to reinsert the old dabbrev, ;; just let helm exiting with "No expansion found". (unless (or only-one cycling-disabled-p) (setq dabbrev old-dabbrev limits (helm-dabbrev-info-limits helm-dabbrev--data)) (setq helm-dabbrev--data nil) (delete-region (car limits) (point)) (insert dabbrev)) (when (and (null cycling-disabled-p) only-one) (setq helm-dabbrev--cache nil helm-dabbrev--already-tried nil helm-dabbrev--computing-cache nil) (cl-return-from helm-dabbrev (message "[Helm-dabbrev: No expansion found]"))) (with-helm-show-completion (car limits) (cdr limits) (unwind-protect (helm :sources (helm-build-in-buffer-source "Dabbrev Expand" :data (append (cl-loop with lst = helm-dabbrev--cache for cand in helm-dabbrev--already-tried do (setq lst (delete cand lst)) finally return lst) helm-dabbrev--already-tried) :persistent-action 'ignore :persistent-help "DoNothing" :keymap helm-dabbrev-map :action 'helm-dabbrev-default-action :group 'helm-dabbrev) :buffer "*helm dabbrev*" :input (concat "^" dabbrev " ") :resume 'noresume :allow-nest t) (setq helm-dabbrev--computing-cache nil helm-dabbrev--already-tried nil helm-dabbrev--cache nil))))))))) (provide 'helm-dabbrev) ;;; helm-dabbrev.el ends here helm-4.0.3/helm-easymenu.el000066400000000000000000000055531501106761700155520ustar00rootroot00000000000000;;; helm-easymenu.el --- Helm easymenu definitions. -*- lexical-binding: t -*- ;; Copyright (C) 2015 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'easymenu) (easy-menu-add-item nil '("Tools") '("Helm" ["Find any Files/Buffers" helm-multi-files t] ["Helm Everywhere (Toggle)" helm-mode t] ["Helm resume" helm-resume t] "----" ("Files" ["Find files" helm-find-files t] ["Recent Files" helm-recentf t] ["Locate" helm-locate t] ["Search Files with find" helm-find t] ["Bookmarks" helm-filtered-bookmarks t] ["Locate library" helm-locate-library t]) ("Buffers" ["Find buffers" helm-buffers-list t]) ("Projects" ["Browse project" helm-browse-project] ["Projects history" helm-projects-history]) ("Commands" ["Emacs Commands" helm-M-x t] ["Externals Commands" helm-run-external-command t]) ("Help" ["Helm Apropos" helm-apropos t]) ("Info" ["Info at point" helm-info-at-point t] ["Emacs Manual index" helm-info-emacs t] ["Gnus Manual index" helm-info-gnus t] ["Helm documentation" helm-documentation t]) ("Packages" ["Elisp packages" helm-packages t] ["Finder" helm-finder t]) ("Tools" ["Occur" helm-occur t] ["Grep current directory with AG" helm-do-grep-ag t] ["Gid" helm-gid t] ["Etags" helm-etags-select t] ["Lisp complete at point" helm-lisp-completion-at-point t] ["Browse Kill ring" helm-show-kill-ring t] ["Browse register" helm-register t] ["Mark Ring" helm-all-mark-rings t] ["Regexp handler" helm-regexp t] ["Colors & Faces" helm-colors t] ["Show xfonts" helm-select-xfont t] ["Ucs Symbols" helm-ucs t] ["Imenu" helm-imenu t] ["Imenu all" helm-imenu-in-all-buffers t] ["Semantic or Imenu" helm-semantic-or-imenu t] ["Google Suggest" helm-google-suggest t] ["Eval expression" helm-eval-expression-with-eldoc t] ["Calcul expression" helm-calcul-expression t] ["Man pages" helm-man-woman t] ["Top externals process" helm-top t] ["Emacs internals process" helm-list-emacs-process t]) "----" ["Preferred Options" helm-configuration t]) "Spell Checking") (easy-menu-add-item nil '("Tools") '("----") "Spell Checking") (provide 'helm-easymenu) ;;; helm-easymenu.el ends here helm-4.0.3/helm-elisp.el000066400000000000000000001374051501106761700150420ustar00rootroot00000000000000;;; helm-elisp.el --- Elisp symbols completion for helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-lib) (require 'helm-help) (require 'helm-types) (require 'helm-utils) (require 'helm-info) (require 'helm-eval) (require 'helm-files) (declare-function helm-describe-function "helm-lib") (declare-function helm-describe-variable "helm-lib") (declare-function helm-describe-face "helm-lib") (declare-function helm-read-file-name "helm-mode") (declare-function helm-comp-read "helm-mode") (declare-function helm-M-x-transformer-no-sort-no-props "helm-command") (defvar helm-M-x-show-short-doc) (defvar completions-detailed) (defvar helm-completions-detailed) ;;; Customizable values (defgroup helm-elisp nil "Elisp related Applications and libraries for Helm." :group 'helm) (defcustom helm-turn-on-show-completion t "Display candidate in `current-buffer' while moving selection when non--nil." :group 'helm-elisp :type 'boolean) (defcustom helm-show-completion-min-window-height 7 "Minimum completion window height used in show completion. This is used in macro `with-helm-show-completion'." :group 'helm-elisp :type 'integer) (defcustom helm-lisp-quoted-function-list '(funcall apply mapc cl-mapc mapcar cl-mapcar callf callf2 cl-callf cl-callf2 fset fboundp fmakunbound symbol-function) "List of function where quoted function completion happen. E.g. give only function names after (funcall \\='." :group 'helm-elisp :type '(repeat (choice symbol))) (defcustom helm-lisp-unquoted-function-list '(function defadvice) "List of function where unquoted function completion happen. E.g. give only function names after (function ." :group 'helm-elisp :type '(repeat (choice symbol))) (defcustom helm-apropos-fuzzy-match nil "Enable fuzzy matching for `helm-apropos' when non-nil." :group 'helm-elisp :type 'boolean) (defcustom helm-lisp-fuzzy-completion nil "Enable fuzzy matching in emacs-lisp completion when non-nil. NOTE: This enables fuzzy matching in Helm native implementation of elisp completion, but not on helmized elisp completion, i.e. fuzzy completion is not available in `completion-at-point'." :group 'helm-elisp :type 'boolean) (defcustom helm-apropos-function-list '(helm-def-source--emacs-commands helm-def-source--emacs-functions helm-def-source--eieio-classes helm-def-source--eieio-generic helm-def-source--emacs-variables helm-def-source--emacs-faces) "A list of functions that build helm sources to use in `helm-apropos'." :group 'helm-elisp :type '(repeat (choice symbol))) (defcustom helm-apropos-defaut-info-lookup-sources '(helm-source-info-elisp helm-source-info-cl helm-source-info-eieio) "A list of sources to look into when searching info page of a symbol." :group 'helm-elisp :type '(repeat (choice symbol))) (defcustom helm-show-completion-display-function (if (display-graphic-p) #'helm-display-buffer-in-own-frame #'helm-show-completion-default-display-function) "The function used to display helm completion buffer. This function is used by `with-helm-show-completion', when nil fallback to `helm-default-display-buffer'. Default is to use a separate frame on graphic display and `helm-show-completion-default-display-function' on non graphic display." :group 'helm-elisp :type 'function) ;;; Faces ;; ;; (defgroup helm-elisp-faces nil "Customize the appearance of helm-elisp." :prefix "helm-" :group 'helm-elisp :group 'helm-faces) (defface helm-lisp-show-completion `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "DarkSlateGray")) "Face used for showing candidates in `helm-lisp-completion'." :group 'helm-elisp-faces) (defface helm-lisp-completion-info `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "red")) "Face used for showing info in `helm-lisp-completion'." :group 'helm-elisp-faces) (defcustom helm-elisp-help-function 'helm-elisp-show-help "Function for displaying help for Lisp symbols." :group 'helm-elisp :type '(choice (function :tag "Open help for the symbol." helm-elisp-show-help) (function :tag "Show one liner in modeline." helm-elisp-show-doc-modeline))) (defcustom helm-locate-library-fuzzy-match t "Enable fuzzy-matching in `helm-locate-library' when non--nil." :type 'boolean :group 'helm-elisp) ;;; Show completion. ;; ;; Provide show completion with macro `with-helm-show-completion'. (defvar helm-show-completion-overlay nil) ;; Called each time cursor move in helm-buffer. (defun helm-show-completion () (with-helm-current-buffer (helm-aif (helm-get-selection) (overlay-put helm-show-completion-overlay 'display (substring-no-properties it))))) (defun helm-show-completion-init-overlay (beg end) (setq helm-show-completion-overlay (make-overlay beg end)) (overlay-put helm-show-completion-overlay 'face 'helm-lisp-show-completion)) (defun helm-show-completion-default-display-function (buffer &rest _args) "A special resized Helm window is used depending on position in BUFFER." (with-selected-window (selected-window) (if (window-dedicated-p) (helm-default-display-buffer buffer) (let* ((screen-size (+ (count-screen-lines (window-start) (point) t) 1 ; mode-line (if header-line-format 1 0))) ; header-line (def-size (- (window-height) helm-show-completion-min-window-height)) (upper-height (max window-min-height (min screen-size def-size))) split-window-keep-point) (recenter -1) (set-window-buffer (if (active-minibuffer-window) (minibuffer-selected-window) (split-window nil upper-height helm-split-window-default-side)) buffer))))) (defmacro with-helm-show-completion (beg end &rest body) "Show Helm candidate in an overlay at point. BEG and END are the beginning and end position of the current completion in `helm-current-buffer'. BODY is an Helm call where we want to enable show completion. If `helm-turn-on-show-completion' is nil do nothing." (declare (indent 2) (debug t)) `(unwind-protect (if helm-turn-on-show-completion (let ((helm-move-selection-after-hook (append (list 'helm-show-completion) helm-move-selection-after-hook)) (helm-split-window-default-side (if (eq helm-split-window-default-side 'same) 'below helm-split-window-default-side)) helm-split-window-inside-p helm-reuse-last-window-split-state) (helm-set-local-variable 'helm-display-function (or helm-show-completion-display-function 'helm-default-display-buffer)) (with-helm-after-update-hook ;; Show immediately first candidate as soon as helm popup. (helm-show-completion)) (helm-show-completion-init-overlay ,beg ,end) ,@body) ,@body) (when (and helm-show-completion-overlay (overlayp helm-show-completion-overlay)) (delete-overlay helm-show-completion-overlay)))) ;;; Lisp symbol completion. ;; ;; (defun helm-lisp-completion--predicate-at-point (beg) ;; Return a predicate for `all-completions'. (let ((fn-sym-p (lambda () (or (and (eq (char-before) ?\ ) (save-excursion (skip-syntax-backward " " (pos-bol)) (memq (symbol-at-point) helm-lisp-unquoted-function-list))) (and (eq (char-before) ?\') (save-excursion (forward-char -1) (eq (char-before) ?\#))))))) (save-excursion (goto-char beg) (cond ((or ;; Complete on all symbols in non--lisp modes (logs mail etc..) (not (memq major-mode '(emacs-lisp-mode lisp-interaction-mode inferior-emacs-lisp-mode))) (not (or (funcall fn-sym-p) (and (eq (char-before) ?\') (save-excursion (forward-char (if (funcall fn-sym-p) -2 -1)) (skip-syntax-backward " " (pos-bol)) (memq (symbol-at-point) helm-lisp-quoted-function-list))) (eq (char-before) ?\())) ; no paren before str. ;; Looks like we are in a let statement. (condition-case nil (progn (up-list -2) (forward-char 1) (eq (char-after) ?\()) (error nil))) (lambda (sym) (or (boundp sym) (fboundp sym) (symbol-plist sym)))) (t #'fboundp))))) (defun helm-thing-before-point (&optional limits regexp) "Return symbol name before point. If REGEXP is specified return what REGEXP find before point. By default match the beginning of symbol before point. With LIMITS arg specified return the beginning and end position of symbol before point." (save-excursion (let (beg (end (point)) (boundary (field-beginning nil nil (pos-bol)))) (if (re-search-backward (or regexp "\\_<") boundary t) (setq beg (match-end 0)) (setq beg boundary)) (unless (= beg end) (if limits (cons beg end) (buffer-substring-no-properties beg end)))))) (defun helm-bounds-of-thing-before-point (&optional regexp) "Get the beginning and end position of `helm-thing-before-point'. Return a cons (beg . end)." (helm-thing-before-point 'limits regexp)) (defun helm-insert-completion-at-point (beg end str) ;; When there is no space after point ;; we are completing inside a symbol or ;; after a partial symbol with the next arg aside ;; without space, in this case mark the region. ;; deleting it would remove the ;; next arg which is unwanted. (delete-region beg end) (insert str) (let ((pos (cdr (or (bounds-of-thing-at-point 'symbol) ;; needed for helm-dabbrev. (bounds-of-thing-at-point 'filename))))) (when (and pos (< (point) pos)) (push-mark pos t t)))) (defconst helm-lisp-completion-re-chars-classes '(":xdigit:" ":word:" ":upper:" ":unibyte:" ":space:" ":punct:" ":print:" ":nonascii:" ":ascii:" ":multibyte:" ":lower:" ":graph:" ":digit:" ":cntrl:" ":blank:" ":alpha:" ":alnum:") "Table of Char Classes for regexps.") ;;;###autoload (defun helm-lisp-completion-at-point () "Preconfigured Helm for Lisp symbol completion at point." (interactive) (let* ((target (helm-thing-before-point)) (beg (car (helm-bounds-of-thing-before-point))) (end (point)) (pred (and beg (helm-lisp-completion--predicate-at-point beg))) (re-class-p (and (eq 'string (syntax-ppss-context (syntax-ppss (point)))) (save-excursion (re-search-backward "\\[:[[:alpha:]]*" (pos-bol) t)))) (loc-vars (and (fboundp 'elisp--local-variables) (ignore-errors (mapcar #'symbol-name (elisp--local-variables))))) (glob-syms (and target pred (not re-class-p) (all-completions target obarray pred))) (candidates (if re-class-p helm-lisp-completion-re-chars-classes (append loc-vars glob-syms))) (helm-quit-if-no-candidate t) (helm-execute-action-at-once-if-one t) (enable-recursive-minibuffers t)) (if candidates (with-helm-show-completion beg end ;; Overlay is initialized now in helm-current-buffer. (helm :sources (helm-build-in-buffer-source "Lisp completion" :data candidates :persistent-action `(helm-lisp-completion-persistent-action . ,(and (eq helm-elisp-help-function 'helm-elisp-show-doc-modeline) 'never-split)) :nomark t :match-part (lambda (c) (car (split-string c))) :fuzzy-match helm-lisp-fuzzy-completion :popup-info (lambda (c) (helm-get-first-line-documentation (if (string-match "\\`:[[:alpha:]]+:\\'" c) c (intern-soft c)))) :persistent-help (helm-lisp-completion-persistent-help) :filtered-candidate-transformer #'helm-lisp-completion-transformer :action (lambda (candidate) (with-helm-current-buffer (run-with-timer 0.01 nil #'helm-insert-completion-at-point beg end candidate)))) :input target :resume 'noresume :truncate-lines t :buffer "*helm lisp completion*" :allow-nest t)) (message "[No Match]")))) (defun helm-lisp-completion-persistent-action (candidate &optional name) "Show documentation for the function. Documentation is shown briefly in mode-line or completely in other window according to the value of `helm-elisp-help-function'." (funcall helm-elisp-help-function candidate name)) (defun helm-lisp-completion-persistent-help () "Return persistent-help according to the value of `helm-elisp-help-function'" (cl-ecase helm-elisp-help-function (helm-elisp-show-doc-modeline "Show brief doc in mode-line") (helm-elisp-show-help "Toggle show help for the symbol"))) (defun helm-elisp--show-help-1 (candidate &optional name) (helm-acase (if (member candidate helm-lisp-completion-re-chars-classes) candidate (intern-soft candidate)) ((guard* (stringp it)) (helm-describe-re-char-classes it)) ((guard* (and (fboundp it) (boundp it))) (if (member name `(,helm-describe-function-function ,helm-describe-variable-function)) (funcall (intern (format "helm-%s" name)) it) ;; When there is no way to know what to describe ;; prefer describe-function. (helm-describe-function it))) ((guard* (fboundp it)) (helm-describe-function it)) ((guard* (boundp it)) (helm-describe-variable it)) ((guard* (facep it)) (helm-describe-face it)))) (defun helm-elisp-show-help (candidate &optional name) "Show full help for the function CANDIDATE. Arg NAME specifies the name of the top level function calling Helm generic completion (e.g., \"describe-function\") which allows calling the right function when CANDIDATE symbol refers at the same time to variable and a function." (helm-elisp--persistent-help candidate 'helm-elisp--show-help-1 name)) (defun helm-elisp-show-doc-modeline (candidate &optional name) "Show brief documentation for the function in the mode-line." (let ((cursor-in-echo-area t) mode-line-in-non-selected-windows) (helm-show-info-in-mode-line (propertize (helm-get-first-line-documentation (intern candidate) name) 'face 'helm-lisp-completion-info)))) (defun helm-lisp-completion-transformer (candidates _source) "Helm candidates transformer for Lisp completion." (cl-loop for c in candidates for sym = (if (string-match "\\`:[[:alpha:]]+:\\'" c) c (intern-soft c)) for annot = (helm-acase sym ((guard* (stringp it)) (propertize " " 'face 'font-lock-regexp-face)) ((guard* (commandp it)) (propertize " " 'face 'font-lock-function-name-face)) ((guard* (class-p it)) (propertize " " 'face 'font-lock-type-face)) ((guard* (cl-generic-p it)) (propertize " " 'face 'font-lock-function-name-face)) ((guard* (fboundp it)) (propertize " " 'face 'font-lock-function-name-face)) ((guard* (keywordp it)) (propertize " " 'face 'font-lock-keyword-face)) ((guard* (boundp it)) (propertize " " 'face 'font-lock-variable-name-face)) ((guard* (facep it)) (propertize " " 'face 'font-lock-variable-name-face)) ((guard* (helm-group-p it)) (propertize " " 'face 'font-lock-type-face)) (t " ")) collect (cons (concat (propertize " " 'display annot) c) c) into lst finally return (sort lst #'helm-generic-sort-fn))) ;;;###autoload (cl-defun helm-get-first-line-documentation (sym &optional (name "describe-function") (end-column 72)) "Return first line documentation of symbol SYM truncated at END-COLUMN. If SYM is not documented, return \"Not documented\". Argument NAME allows specifiying what function to use to display documentation when SYM name is the same for function and variable." (let ((doc (condition-case _err (helm-acase sym ((guard* (stringp it)) (cadr (split-string (helm-describe-re-char-classes-1 it) "\n"))) ((guard* (class-p it)) (cl--class-docstring (cl--find-class it))) ((guard* (and (fboundp it) (boundp it))) (if (string= name "describe-variable") (documentation-property it 'variable-documentation t) (documentation it t))) ((guard* (custom-theme-p it)) (documentation-property it 'theme-documentation t)) ((guard* (and (helm-group-p it) (not (fboundp it)))) (documentation-property it 'group-documentation t)) ((guard* (fboundp it)) (documentation it t)) ((guard* (boundp it)) (documentation-property it 'variable-documentation t)) ((guard* (facep it)) (face-documentation it))) (void-function "Void function -- Not documented")))) (if (and doc (not (string= doc "")) ;; `documentation' return "\n\n(args...)" ;; for CL-style functions. (not (string-match-p "\\`\n\n" doc))) ;; Some commands specify key bindings or keymap in their first line, ;; e.g.: "\A mode for editing binary [...]. As a result ;; (substitute-command-keys doc) returns a string like "\nUses ;; keymap...\nFirst line docstring. See ;; . (truncate-string-to-width (helm-acase (split-string (substitute-command-keys doc) "\n") ((dst* (l &rest args)) (if (string= l "") (cadr args) l))) end-column nil nil t) (if (or (symbol-function sym) (boundp sym) (facep sym) (helm-group-p sym)) "Not documented" ;; Symbol exist but has no definition yet e.g. ;; (advice-add 'foo-test :override (lambda () (message "invalid ;; function"))) and foo-test is not already defined. "Not already defined or loaded")))) ;;; File completion. ;; ;; Complete file name at point. ;;;###autoload (defun helm-complete-file-name-at-point (&optional force) "Preconfigured Helm to complete file name at point." (interactive) (require 'helm-mode) (let* ((tap (or (thing-at-point 'filename t) "")) beg (init (and tap (or force (save-excursion (end-of-line) (search-backward tap (pos-bol) t) (setq beg (point)) (looking-back "[^'`( ]" (1- (point))))) (expand-file-name tap))) (end (point)) (helm-quit-if-no-candidate t) (helm-execute-action-at-once-if-one t) completion) (with-helm-show-completion beg end (setq completion (helm-read-file-name "FileName: " :initial-input init))) (when (and completion (not (string= completion ""))) (delete-region beg end) (insert (if (string-match "^~" tap) (abbreviate-file-name completion) completion))))) (make-obsolete 'helm-complete-file-name-at-point 'helm-find-files "3.9.6") ;;;###autoload (defun helm-lisp-indent () ;; It is meant to use with `helm-define-multi-key' which ;; does not support args for functions yet, so use `current-prefix-arg' ;; for now instead of (interactive "P"). (interactive) (let ((tab-always-indent (or (eq tab-always-indent 'complete) tab-always-indent))) (indent-for-tab-command current-prefix-arg))) ;;; Apropos ;; ;; (defvar helm-apropos-history nil) (defcustom helm-apropos-show-short-doc nil "Show short docstring of symbols when non nil. NOTE: When displaying helm-apropos in a frame, i.e. when `helm-apropos' is member of `helm-commands-using-frame' setting this to non nil have no effect, you have first to remove `helm-apropos' from `helm-commands-using-frame'." :group 'helm-elisp :type 'boolean) (defvar helm-apropos-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "C-]") #'helm-apropos-toggle-details) map)) (defun helm-apropos-init (test default &optional fn) "Setup `helm-candidate-buffer' for `helm-apropos' sources. A list of symbols fetched with FN is inserted in `helm-candidate-buffer', if FN is not provided symbols are fetched against obarray with predicate TEST. When FN is provided predicate TEST is only used to test DEFAULT." (require 'helm-help) (helm-init-candidates-in-buffer 'global (let ((default-symbol (and (stringp default) (intern-soft default))) (symbols (if fn (funcall fn) (all-completions "" obarray test)))) (if (and default-symbol (funcall test default-symbol)) (cons default-symbol symbols) symbols)))) (defun helm-apropos-short-doc-transformer (candidates _source) (if helm-apropos-show-short-doc (cl-loop for cand in candidates for doc = (helm-get-first-line-documentation (intern-soft cand)) collect (cons (format "%s%s%s" cand (if doc (helm-make-separator cand) "") (if doc (propertize doc 'face 'helm-M-x-short-doc) "")) cand)) candidates)) (defun helm-apropos-default-sort-fn (candidates _source) (if (string= helm-pattern "") candidates (sort candidates #'helm-generic-sort-fn))) (defun helm-apropos-clean-history-variable (candidate) (with-helm-current-buffer ; var is maybe local (let* ((sym (intern-soft candidate)) (cands (symbol-value sym)) (mkds (and (listp cands) (helm-comp-read "Delete entry: " cands :marked-candidates t)))) (cl-assert (listp mkds) nil "Variable value is not a list") (cl-loop for elm in mkds do (if (local-variable-p sym) (set (make-local-variable sym) (setq cands (delete elm cands))) (set sym (setq cands (delete elm cands)))))))) (defun helm-apropos-clean-ring (candidate) (with-helm-current-buffer ; var is maybe local (let* ((sym (intern-soft candidate)) (val (symbol-value sym)) (cands (and (ring-p val) (ring-elements val))) (mkds (and cands (helm-comp-read "Delete entry: " cands :marked-candidates t)))) (when mkds (cl-loop for elm in mkds do (ring-remove val (helm-position elm (ring-elements val) :test 'equal)) and do (if (local-variable-p sym) (set (make-local-variable sym) val) (set sym val))))))) (defun helm-apropos-action-transformer (actions candidate) (let* ((sym (helm-symbolify candidate)) (val (with-helm-current-buffer (symbol-value sym)))) (cond ((custom-variable-p sym) (append actions (let ((standard-value (eval (car (get sym 'standard-value)) t))) (unless (equal standard-value (symbol-value sym)) `(("Reset Variable to default value" . ,(lambda (candidate) (let ((sym (helm-symbolify candidate))) (set sym standard-value))))))) `(("Customize variable" . ,(lambda (candidate) (customize-option (helm-symbolify candidate))))))) ((and val (with-helm-current-buffer (ring-p (symbol-value sym)))) (append actions '(("Clean ring" . helm-apropos-clean-ring)))) ((and (string-match-p "history" candidate) (listp val)) (append actions '(("Clean variable" . helm-apropos-clean-history-variable)))) (t actions)))) (defun helm-def-source--emacs-variables (&optional default) (helm-build-in-buffer-source "Variables" :init (lambda () (helm-apropos-init (lambda (x) (and (boundp x) (not (keywordp x)) (not (class-p x)))) default)) :fuzzy-match helm-apropos-fuzzy-match :filtered-candidate-transformer (delq nil (list (and (null helm-apropos-fuzzy-match) 'helm-apropos-default-sort-fn) (and (null (memq 'helm-apropos helm-commands-using-frame)) #'helm-apropos-short-doc-transformer))) :nomark t :persistent-action (lambda (candidate) (helm-elisp--persistent-help candidate 'helm-describe-variable)) :persistent-help "Toggle describe variable" :keymap helm-apropos-map :action '(("Describe variable" . helm-describe-variable) ("Find variable" . helm-find-variable) ("Info lookup" . helm-info-lookup-symbol) ("Set variable" . helm-set-variable)) :action-transformer 'helm-apropos-action-transformer)) (defun helm-def-source--emacs-faces (&optional default) "Create `helm' source for faces to be displayed with `helm-apropos'." (helm-build-in-buffer-source "Faces" :init (lambda () (helm-apropos-init 'facep default #'face-list)) :fuzzy-match helm-apropos-fuzzy-match :filtered-candidate-transformer (delq nil (list (and (null helm-apropos-fuzzy-match) #'helm-apropos-default-sort-fn) (lambda (candidates _source) (cl-loop for c in candidates collect (propertize c 'face (intern c)))) (and (null (memq 'helm-apropos helm-commands-using-frame)) #'helm-apropos-short-doc-transformer))) :persistent-action (lambda (candidate) (helm-elisp--persistent-help candidate 'helm-describe-face)) :persistent-help "Toggle describe face" :keymap helm-apropos-map :action '(("Describe face" . helm-describe-face) ("Find face" . helm-find-face-definition) ("Customize face" . (lambda (candidate) (customize-face (helm-symbolify candidate))))))) (defun helm-def-source--emacs-commands (&optional default) (require 'helm-command) (helm-build-in-buffer-source "Commands" :init (lambda () (helm-apropos-init 'commandp default)) :fuzzy-match helm-apropos-fuzzy-match :filtered-candidate-transformer (append (list #'helm-M-x-transformer-no-sort-no-props) (and (null helm-apropos-fuzzy-match) '(helm-apropos-default-sort-fn))) :display-to-real 'helm-symbolify :nomark t :persistent-action (lambda (candidate) (helm-elisp--persistent-help candidate 'helm-describe-function)) :persistent-help "Toggle describe command" :keymap helm-apropos-map :action 'helm-type-function-actions)) (defun helm-def-source--emacs-functions (&optional default) (helm-build-in-buffer-source "Functions" :init (lambda () (helm-apropos-init (lambda (x) (and (fboundp x) (not (commandp x)) (not (cl-generic-p x)) (not (class-p x)))) default)) :fuzzy-match helm-apropos-fuzzy-match :filtered-candidate-transformer (delq nil (list (and (null helm-apropos-fuzzy-match) 'helm-apropos-default-sort-fn) (and (null (memq 'helm-apropos helm-commands-using-frame)) #'helm-apropos-short-doc-transformer))) :display-to-real 'helm-symbolify :persistent-action (lambda (candidate) (helm-elisp--persistent-help candidate 'helm-describe-function)) :persistent-help "Toggle describe function" :keymap helm-apropos-map :nomark t :action 'helm-type-function-actions)) (defun helm-def-source--eieio-classes (&optional default) (helm-build-in-buffer-source "Classes" :init (lambda () (helm-apropos-init (lambda (x) (class-p x)) default)) :fuzzy-match helm-apropos-fuzzy-match :filtered-candidate-transformer (delq nil (list (and (null helm-apropos-fuzzy-match) 'helm-apropos-default-sort-fn) (and (null (memq 'helm-apropos helm-commands-using-frame)) #'helm-apropos-short-doc-transformer))) :nomark t :persistent-action (lambda (candidate) (helm-elisp--persistent-help candidate 'helm-describe-class)) :persistent-help "Toggle describe class" :keymap helm-apropos-map :action '(("Describe Class" . helm-describe-class) ("Find Class (C-u for source)" . helm-find-function) ("Info lookup" . helm-info-lookup-symbol)))) (defun helm-def-source--eieio-generic (&optional default) (helm-build-in-buffer-source "Generic functions" :init (lambda () (helm-apropos-init (lambda (x) (cl-generic-p x)) default)) :fuzzy-match helm-apropos-fuzzy-match :filtered-candidate-transformer (delq nil (list (and (null helm-apropos-fuzzy-match) 'helm-apropos-default-sort-fn) (and (null (memq 'helm-apropos helm-commands-using-frame)) #'helm-apropos-short-doc-transformer))) :nomark t :persistent-action (lambda (candidate) (helm-elisp--persistent-help candidate 'helm-describe-function)) :persistent-help "Toggle describe generic function" :keymap helm-apropos-map :action '(("Describe function" . helm-describe-function) ("Find function (C-u for source)" . helm-find-function) ("Info lookup" . helm-info-lookup-symbol)))) (defun helm-info-lookup-fallback-source (candidate) (cl-multiple-value-bind (fn src-name) (helm-acase (helm-symbolify candidate) ((guard* (class-p it)) (list #'helm-describe-function "Describe class")) ((guard* (cl-generic-p it)) (list #'helm-describe-function "Describe generic function")) ((guard* (fboundp it)) (list #'helm-describe-function "Describe function")) ((guard* (facep it)) (list #'helm-describe-face "Describe face")) (t (list #'helm-describe-variable "Describe variable"))) (helm-build-sync-source src-name :candidates (list candidate) :persistent-action (lambda (candidate) (helm-elisp--persistent-help candidate fn)) :persistent-help src-name :nomark t :action fn))) (defun helm-info-lookup-symbol-1 (c) (let ((helm-execute-action-at-once-if-one 'current-source)) (helm :sources (append helm-apropos-defaut-info-lookup-sources (list (helm-info-lookup-fallback-source c))) :resume 'noresume :buffer "*helm lookup*" :input (helm-stringify c)))) (defun helm-info-lookup-symbol (candidate) ;; ???:Running an idle-timer allows not catching RET when exiting ;; with the fallback source. ;; (run-with-idle-timer 0.01 nil #'helm-info-lookup-symbol-1 candidate) (helm-info-lookup-symbol-1 candidate)) (defun helm-apropos-toggle-details () "Toggle details in `helm-apropos'." (interactive) (with-helm-buffer (unless (memq 'helm-apropos helm-commands-using-frame) (setq helm-M-x-show-short-doc (not helm-M-x-show-short-doc) helm-apropos-show-short-doc (not helm-apropos-show-short-doc)) (helm-force-update (concat "^" (helm-stringify (helm-get-selection))) (helm-get-current-source))))) (defun helm-apropos-get-default () (with-syntax-table emacs-lisp-mode-syntax-table (symbol-name (intern-soft (helm-aand (thing-at-point 'symbol t) (replace-regexp-in-string "\\`[~=]" "" it) (replace-regexp-in-string "[~=]\\'" "" it)))))) ;;;###autoload (defun helm-apropos (default) "Preconfigured Helm to describe commands, functions, variables and faces. In non interactives calls DEFAULT argument should be provided as a string, i.e. the `symbol-name' of any existing symbol." (interactive (list (helm-apropos-get-default))) (let ((helm-M-x-show-short-doc (and helm-apropos-show-short-doc (null (memq 'helm-apropos helm-commands-using-frame))))) (helm :sources (mapcar (lambda (func) (funcall func default)) helm-apropos-function-list) :history 'helm-apropos-history :buffer "*helm apropos*" :preselect (and default (concat "^\\_<" (regexp-quote default) "\\_>")) :truncate-lines t))) ;;; Locate elisp library ;; ;; (defvar helm--locate-library-cache nil) (defvar helm--locate-library-doc-cache (make-hash-table :test 'equal)) (defun helm-locate-library-scan-list () (cl-loop for dir in load-path when (file-directory-p dir) nconc (directory-files dir nil (concat (regexp-opt (find-library-suffixes)) "\\'")))) ;;;###autoload (defun helm-locate-library (&optional arg) "Preconfigured helm to locate elisp libraries. When `completions-detailed' or `helm-completions-detailed' is non nil, a description of libraries is provided. The libraries are partially cached in the variables `helm--locate-library-doc-cache' and `helm--locate-library-cache'. TIP: You can make these vars persistent for faster start with the psession package, using M-x psession-make-persistent-variable. NOTE: The caches affect as well `find-libray' and `locate-library' when `helm-mode' is enabled and `completions-detailed' is non nil. There is no need to refresh the caches, they will be updated automatically if some new libraries are found, however when a library update its headers and the description change you can reset the caches with a prefix arg." (interactive "P") (require 'helm-mode) (let (done) (when arg (setq helm--locate-library-cache nil) (clrhash helm--locate-library-doc-cache)) (helm :sources (helm-build-in-buffer-source "Elisp libraries (Scan)" :data #'helm-locate-library-scan-list :fuzzy-match helm-locate-library-fuzzy-match :keymap helm-generic-files-map :candidate-transformer (lambda (candidates) (cl-loop with reporter = (unless done (make-progress-reporter "Scanning libraries..." 0 (length candidates))) with lgst = (helm-in-buffer-get-longest-candidate) for c in candidates for count from 0 for bn = (helm-basename c 2) for sep = (helm-make-separator bn lgst) for path = (or (assoc-default bn helm--locate-library-cache) ;; A lock file in LOAD-PATH (bug#2626). (unless (string-match "\\`\\.#" bn) (let ((p (find-library-name bn))) (push (cons bn p) helm--locate-library-cache) p))) for doc = (and path (or completions-detailed helm-completions-detailed) (or (gethash bn helm--locate-library-doc-cache) (puthash bn (helm-locate-lib-get-summary path) helm--locate-library-doc-cache))) for disp = (and path (if (and doc (or completions-detailed helm-completions-detailed)) (helm-aand (propertize doc 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it)) (concat bn it)) bn)) when (and disp path) collect (cons disp path) when reporter do (progress-reporter-update reporter count) finally do (setq done t))) :action (helm-actions-from-type-file)) :buffer "*helm locate library*"))) ;;; Modify variables from Helm ;; ;; (defun helm-set-variable (var) "Set VAR value interactively." (let* ((sym (helm-symbolify var)) (val (default-value sym)) (strv (prin1-to-string val))) (if (> (length strv) 25) (helm-edit-variable var) (set-default sym (eval-minibuffer (format "Set `%s': " var) (if (or (arrayp val) (memq val '(nil t)) (numberp val)) strv (format "'%s" strv))))))) (defvar helm-edit-variable-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-c") 'helm-set-variable-from-pp-buffer) (define-key map (kbd "C-c C-k") 'helm-edit-variable-quit) (define-key map (kbd "C-c =") 'helm-edit-variable-toggle-diff) map)) (define-derived-mode helm-edit-variable-mode emacs-lisp-mode "helm-edit-variable" "A mode to edit variables values. Special commands: \\{helm-edit-variable-mode-map}") (defvar helm-pretty-print-temp-file-name (expand-file-name "helm-edit-variable.el" temporary-file-directory)) (defvar helm-pretty-print-buffer-name "*pretty print output*") (defvar helm-pretty-print-current-symbol nil) (defun helm-edit-variable (var) (let* ((sym (intern-soft var)) (val (symbol-value sym)) (pp (pp-to-string val)) start) (with-current-buffer (get-buffer-create helm-pretty-print-buffer-name) (erase-buffer) (helm-edit-variable-mode) (setq start (point)) ;; Any number of lines starting with ";;;" + one empty line. (insert (format ";;; Edit variable `%s' and hit C-c C-c when done\n" sym) ";;; Abort with C-c C-k\n\n") (add-text-properties start (1- (point)) '(read-only t)) (add-text-properties (1- (point)) (point) '(read-only t rear-nonsticky t)) (set (make-local-variable 'helm-pretty-print-current-symbol) sym) (add-hook 'kill-buffer-hook (lambda () (when (file-exists-p helm-pretty-print-temp-file-name) (delete-file helm-pretty-print-temp-file-name))) nil t) (save-excursion (insert pp)) (write-region (point-min) (point-max) helm-pretty-print-temp-file-name nil 1) (setq buffer-file-name helm-pretty-print-temp-file-name)) (display-buffer helm-pretty-print-buffer-name))) (defun helm-edit-variable-toggle-diff () "Show diff in edit variable buffer." (interactive) (if (get-buffer-window "*Diff*" 'visible) (kill-buffer "*Diff*") (diff-buffer-with-file))) (defun helm-set-variable-from-pp-buffer () "Set variable associated with buffer to buffer contents. The associated variable is the local variable `helm-pretty-print-current-symbol'." (interactive) (with-current-buffer helm-pretty-print-buffer-name (goto-char (point-min)) (when (re-search-forward "^$" nil t) (forward-line 1)) (let ((val (symbol-value helm-pretty-print-current-symbol))) (save-excursion (if (or (arrayp val) (memq val '(nil t)) (numberp val) (looking-at "[`']")) (set-default helm-pretty-print-current-symbol (read (current-buffer))) (set-default helm-pretty-print-current-symbol `(,@(read (current-buffer)))))) (if (equal val (symbol-value helm-pretty-print-current-symbol)) (message "No changes done") (message "`%s' value modified" helm-pretty-print-current-symbol)) (helm-edit-variable-quit)))) (defun helm-edit-variable-quit () "Quit edit variable buffer." (interactive) (set-buffer-modified-p nil) (quit-window t) (helm-aif (get-buffer-window "*Diff*" 'visible) (quit-window t it))) ;;; Elisp Timers. ;; ;; (defclass helm-absolute-time-timers-class (helm-source-sync helm-type-timers) ((candidates :initform 'timer-list) (allow-dups :initform t) (candidate-transformer :initform (lambda (candidates) (cl-loop for timer in candidates collect (cons (helm-elisp--format-timer timer) timer)))))) (defvar helm-source-absolute-time-timers (helm-make-source "Absolute Time Timers" 'helm-absolute-time-timers-class)) (defclass helm-idle-time-timers-class (helm-source-sync helm-type-timers) ((candidates :initform 'timer-idle-list) (allow-dups :initform t) (candidate-transformer :initform (lambda (candidates) (cl-loop for timer in candidates collect (cons (helm-elisp--format-timer timer) timer)))))) (defvar helm-source-idle-time-timers (helm-make-source "Idle Time Timers" 'helm-idle-time-timers-class)) (defun helm-elisp--format-timer (timer) (format "%s repeat=%s %s(%s)" (let ((time (timer--time timer))) (if (timer--idle-delay timer) (format "idle-for=[%s]" (format-seconds "%dd %hh %mmin %z%,3ss" (time-to-seconds time))) (format-time-string "%m/%d %T" time))) (or (timer--repeat-delay timer) "nil") (mapconcat #'identity (split-string (prin1-to-string (timer--function timer)) "\n") " ") (mapconcat #'prin1-to-string (timer--args timer) " "))) ;;;###autoload (defun helm-timers () "Preconfigured `helm' for timers." (interactive) (helm :sources '(helm-source-absolute-time-timers helm-source-idle-time-timers) :buffer "*helm timers*")) ;;; Complex command history ;; ;; (defvar helm-sexp--last-sexp nil) ;; This wont work compiled. (defun helm-sexp-eval-1 () (interactive) (unwind-protect (progn ;; Trick called-interactively-p into thinking that `cand' is ;; an interactive call, See `repeat-complex-command'. (add-hook 'called-interactively-p-functions #'helm-complex-command-history--called-interactively-skip) (eval (read helm-sexp--last-sexp) t)) (remove-hook 'called-interactively-p-functions #'helm-complex-command-history--called-interactively-skip))) (defun helm-complex-command-history--called-interactively-skip (i _frame1 frame2) (and (eq 'eval (cadr frame2)) (eq 'helm-sexp-eval-1 (cadr (backtrace-frame (+ i 2) #'called-interactively-p))) 1)) (defun helm-sexp-eval (_candidate) (call-interactively #'helm-sexp-eval-1)) (defvar helm-source-complex-command-history (helm-build-sync-source "Complex Command History" :candidates (lambda () ;; Use cdr to avoid adding ;; `helm-complex-command-history' here. (cl-loop for i in command-history unless (equal i '(helm-complex-command-history)) collect (prin1-to-string i))) :action (helm-make-actions "Eval" (lambda (candidate) (and (boundp 'helm-sexp--last-sexp) (setq helm-sexp--last-sexp candidate)) (let ((command (read candidate))) (unless (equal command (car command-history)) (setq command-history (cons command command-history)))) (run-with-timer 0.1 nil #'helm-sexp-eval candidate)) "Edit and eval" (lambda (candidate) (edit-and-eval-command "Eval: " (read candidate)))) :persistent-action #'helm-sexp-eval :multiline t)) ;;;###autoload (defun helm-complex-command-history () "Preconfigured `helm' for complex command history." (interactive) (helm :sources 'helm-source-complex-command-history :buffer "*helm complex commands*")) (provide 'helm-elisp) ;;; helm-elisp.el ends here helm-4.0.3/helm-epa.el000066400000000000000000000252641501106761700144720ustar00rootroot00000000000000;;; helm-epa.el --- helm interface for epa/epg -*- lexical-binding: t; -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm) (eval-when-compile (require 'epg)) (defvar epa-protocol) (defvar epa-last-coding-system-specified) (defvar epg-key-validity-alist) (defvar mail-header-separator) (declare-function epg-list-keys "epg") (declare-function epg-make-context "epg") (declare-function epg-key-sub-key-list "epg") (declare-function epg-sub-key-id "epg") (declare-function epg-key-user-id-list "epg") (declare-function epg-user-id-string "epg") (declare-function epg-user-id-validity "epg") (declare-function epa-sign-region "epa") (declare-function epa--read-signature-type "epa") (declare-function epa-display-error "epa") (declare-function epg-export-keys-to-string "epg") (declare-function epg-context-armor "epg") (declare-function epg-context-set-armor "epg") (declare-function epg-delete-keys "epg") (declare-function helm-read-file-name "helm-mode") (defvar helm-epa--list-only-secrets nil "[INTERNAL] Used to pass MODE argument to `epg-list-keys'.") (defcustom helm-epa-actions '(("Show key" . epa--show-key) ("encrypt file with key" . helm-epa-encrypt-file) ("Copy keys to kill ring" . helm-epa-kill-keys-armor) ("Delete keys" . helm-epa-delete-keys)) "Actions for `helm-epa-list-keys'." :type '(alist :key-type string :value-type symbol) :group 'helm-misc) (defclass helm-epa (helm-source-sync) ((init :initform (lambda () (require 'epg) (require 'epa))) (candidates :initform 'helm-epa-get-key-list) (keymap :initform 'helm-comp-read-map) (mode-line :initform 'helm-comp-read-mode-line)) "Allow building helm sources for GPG keys.") (defun helm-epa-get-key-list (&optional keys) "Build candidate list for `helm-epa-list-keys'." (cl-loop with all-keys = (or keys (epg-list-keys (epg-make-context epa-protocol) nil helm-epa--list-only-secrets)) for key in all-keys for sublist = (car (epg-key-sub-key-list key)) for subkey-id = (epg-sub-key-id sublist) for uid-list = (epg-key-user-id-list key) for uid = (epg-user-id-string (car uid-list)) for validity = (epg-user-id-validity (car uid-list)) collect (cons (format " %s %s %s" (helm-aif (rassq validity epg-key-validity-alist) (string (car it)) "?") (propertize subkey-id 'face (cl-case validity (none 'epa-validity-medium) ((revoked expired) 'epa-validity-disabled) (t 'epa-validity-high))) (propertize uid 'face 'font-lock-warning-face)) key))) (cl-defun helm-epa--select-keys (prompt keys) "A helm replacement for `epa--select-keys'." (let ((result (helm :sources (helm-make-source "Epa select keys" 'helm-epa :candidates (lambda () (helm-epa-get-key-list keys)) :action (lambda (_candidate) (helm-marked-candidates))) :prompt (and prompt (helm-epa--format-prompt prompt)) :buffer "*helm epa*"))) (if (or (equal result "") (null result)) (cl-return-from helm-epa--select-keys (error "No keys selected, aborting")) result))) (defun helm-epa--format-prompt (prompt) (let ((split (split-string prompt "\n"))) (if (cdr split) (format "%s\n(%s): " (replace-regexp-in-string "\\.[\t ]*\\'" "" (car split)) (replace-regexp-in-string "\\.[\t ]*\\'" "" (cadr split))) (format "%s: " (replace-regexp-in-string "\\.[\t ]*\\'" "" (car split)))))) (defun helm-epa--read-signature-type-help () (with-temp-buffer (save-excursion (insert "n: Create a normal signature)\n" "c: Create a cleartext signature)\n" "d: Create a detached signature)")) (while (re-search-forward "^\\(.\\):" nil t) (helm-add-face-text-properties (match-beginning 1) (match-end 1) 'font-lock-variable-name-face)) (buffer-string))) (defun helm-epa--read-signature-type () "A helm replacement for `epa--read-signature-type'." (let ((answer (helm-read-answer "Signature type? [n,c,d,h]" '("n" "c" "d") #'helm-epa--read-signature-type-help))) (helm-acase answer ("n" 'normal) ("c" 'clear) ("d" 'detached)))) (defun helm-epa-collect-keys-from-candidates (candidates) (cl-loop for c in candidates collect (epg-sub-key-id (car (epg-key-sub-key-list c))))) (defun helm-epa-collect-id-from-candidates (candidates) (cl-loop for c in candidates collect (epg-user-id-string (car (epg-key-user-id-list c))))) (defun helm-epa-success-message (str keys ids) (message str (mapconcat (lambda (pair) (concat (car pair) " " (cdr pair))) (cl-loop for k in keys for i in ids collect (cons k i)) "\n"))) ;;;###autoload (define-minor-mode helm-epa-mode "Enable helm completion on gpg keys in epa functions." :group 'helm-misc :global t (require 'epa) (if helm-epa-mode (progn (advice-add 'epa--select-keys :override #'helm-epa--select-keys) (advice-add 'epa--read-signature-type :override #'helm-epa--read-signature-type)) (advice-remove 'epa--select-keys #'helm-epa--select-keys) (advice-remove 'epa--read-signature-type #'helm-epa--read-signature-type))) (defun helm-epa-action-transformer (actions _candidate) "Helm epa action transformer function." (cond ((with-helm-current-buffer (derived-mode-p 'message-mode 'mail-mode)) (helm-append-at-nth actions '(("Sign mail with key" . helm-epa-mail-sign) ("Encrypt mail with key" . helm-epa-mail-encrypt)) 3)) (t actions))) (defun helm-epa-delete-keys (_candidate) "Delete gpg marked keys from helm-epa." (let ((context (epg-make-context epa-protocol)) (keys (helm-marked-candidates))) (message "Deleting gpg keys..") (condition-case error (epg-delete-keys context keys) (error (epa-display-error context) (signal (car error) (cdr error)))) (message "Deleting gpg keys done"))) (defun helm-epa-encrypt-file (_candidate) "Select a file to encrypt with key CANDIDATE." (let* ((file (helm-read-file-name "Encrypt file: ")) (cands (helm-marked-candidates)) (keys (helm-epa-collect-keys-from-candidates cands)) (ids (helm-epa-collect-id-from-candidates cands))) (epa-encrypt-file file cands) (helm-epa-success-message "File encrypted with key(s):\n %s" keys ids))) (defun helm-epa-kill-keys-armor (_candidate) "Copy marked keys to kill ring." (let ((keys (helm-marked-candidates)) (context (epg-make-context epa-protocol))) (with-no-warnings (setf (epg-context-armor context) t)) (condition-case error (kill-new (epg-export-keys-to-string context keys)) (error (epa-display-error context) (signal (car error) (cdr error)))))) (defun helm-epa-mail-sign (candidate) "Sign email with key CANDIDATE." (let ((key (epg-sub-key-id (car (epg-key-sub-key-list candidate)))) (id (epg-user-id-string (car (epg-key-user-id-list candidate)))) start end mode) (save-excursion (goto-char (point-min)) (if (search-forward mail-header-separator nil t) (forward-line)) (setq epa-last-coding-system-specified (or coding-system-for-write (select-safe-coding-system (point) (point-max)))) (let ((verbose current-prefix-arg)) (setq start (point) end (point-max) mode (if verbose (epa--read-signature-type) 'clear)))) ;; TODO Make non-interactive functions to replace epa-sign-region ;; and epa-encrypt-region and inline them. (with-no-warnings (epa-sign-region start end candidate mode)) (message "Mail signed with key `%s %s'" key id))) (defun helm-epa-mail-encrypt (_candidate) "Encrypt email with key CANDIDATE." (let ((cands (helm-marked-candidates)) start end) (save-excursion (goto-char (point-min)) (when (search-forward mail-header-separator nil t) (forward-line)) (setq start (point) end (point-max)) (setq epa-last-coding-system-specified (or coding-system-for-write (select-safe-coding-system start end)))) ;; Don't let some read-only text stop us from encrypting. (let ((inhibit-read-only t) (keys (helm-epa-collect-keys-from-candidates cands)) (ids (helm-epa-collect-id-from-candidates cands))) (with-no-warnings (epa-encrypt-region start end cands nil nil)) (helm-epa-success-message "Mail encrypted with key(s):\n %s" keys ids)))) ;;;###autoload (defun helm-epa-list-keys () "List all gpg keys. This is the helm interface for `epa-list-keys'." (interactive) (helm :sources (helm-make-source "Epg list keys" 'helm-epa :action-transformer 'helm-epa-action-transformer :action 'helm-epa-actions) :buffer "*helm epg list keys*")) (provide 'helm-epa) ;;; helm-epa.el ends here helm-4.0.3/helm-eshell.el000066400000000000000000000477031501106761700152030ustar00rootroot00000000000000;;; helm-eshell.el --- pcomplete and eshell completion for helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Enable like this in .emacs: ;; (add-hook 'eshell-mode-hook ;; (lambda () ;; (eshell-cmpl-initialize) ;; (define-key eshell-mode-map [remap eshell-pcomplete] 'helm-esh-pcomplete) ;; (define-key eshell-mode-map (kbd "M-s f") 'helm-eshell-prompts-all))) ;; (define-key eshell-mode-map (kbd "M-r") 'helm-eshell-history))) ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-lib) (require 'helm-help) (require 'helm-elisp) (declare-function eshell-read-aliases-list "em-alias") (declare-function eshell-send-input "esh-mode" (&optional use-region queue-p no-newline)) (declare-function eshell-bol "esh-mode") (declare-function eshell-parse-arguments "esh-arg" (beg end)) (declare-function eshell-backward-argument "esh-mode" (&optional arg)) (declare-function helm-quote-whitespace "helm-lib") (declare-function eshell-skip-prompt "em-prompt") (defvar eshell-special-chars-outside-quoting) (defgroup helm-eshell nil "Helm completion and history for Eshell." :group 'helm) (defcustom helm-eshell-fuzzy-match nil "Enable fuzzy matching in `helm-esh-pcomplete' when non-nil." :type 'boolean) (defvar helm-eshell-history-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "M-p") #'helm-next-line) map) "Keymap for `helm-eshell-history'.") (defvar helm-esh-completion-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "TAB") #'helm-next-line) map) "Keymap for `helm-esh-pcomplete'.") (defvar helm-eshell--quit-flag nil) ;; Internal. (defvar helm-ec-target "") (defun helm-ec-insert (_candidate) "Replace text at point with CANDIDATE. The function that call this should set `helm-ec-target' to thing at point." (set (make-local-variable 'comint-file-name-quote-list) eshell-special-chars-outside-quoting) (let ((pt (point))) (when (and helm-ec-target (search-backward helm-ec-target nil t) (string= (buffer-substring (point) pt) helm-ec-target)) (delete-region (point) pt))) (when (string-match "\\`\\*" helm-ec-target) (insert "*")) (let ((marked (helm-marked-candidates))) (prog1 t ;; Makes helm returns t on action. (insert (mapconcat (lambda (x) (cond ((string-match "\\`~/" helm-ec-target) ;; Strip out the first escape char added by ;; `comint-quote-filename' before "~" (Bug#1803). (substring (comint-quote-filename (abbreviate-file-name x)) 1)) ((string-match "\\`/" helm-ec-target) (comint-quote-filename x)) (t (concat (and (string-match "\\`[.]/" helm-ec-target) "./") (comint-quote-filename (file-relative-name x)))))) marked " ") (or (helm-aand (car (last marked)) (string-match-p "/\\'" it) "") " "))))) (defun helm-esh-transformer (candidates _sources) (cl-loop for i in candidates collect (cond ((string-match "\\`~/?" helm-ec-target) (abbreviate-file-name i)) ((string-match "\\`/" helm-ec-target) i) (t (file-relative-name i))) into lst finally return (sort lst #'helm-generic-sort-fn))) (defclass helm-esh-source (helm-source-sync) ((init :initform (lambda () (setq pcomplete-current-completions nil pcomplete-last-completion-raw nil) ;; Eshell-command add this hook in all minibuffers ;; Remove it for the helm one. (Fixed in Emacs24) (remove-hook 'minibuffer-setup-hook 'eshell-mode))) (candidates :initform 'helm-esh-get-candidates) ;(nomark :initform t) (persistent-action :initform 'ignore) (nohighlight :initform t) (filtered-candidate-transformer :initform #'helm-esh-transformer) (action :initform 'helm-ec-insert)) "Helm class to define source for Eshell completion.") (defun helm-esh-get-candidates () "Get candidates for Eshell completion using `pcomplete'." (catch 'pcompleted (with-helm-current-buffer (let* ((pcomplete-stub) pcomplete-seen pcomplete-norm-func pcomplete-args pcomplete-last pcomplete-index (pcomplete-autolist pcomplete-autolist) (pcomplete-suffix-list pcomplete-suffix-list) (table (pcomplete-completions)) (entry (or (try-completion helm-pattern (pcomplete-entries)) helm-pattern))) (cl-loop ;; expand entry too to be able to compare it with file-cand. with exp-entry = (and (stringp entry) (not (string= entry "")) (file-name-as-directory (expand-file-name entry default-directory))) with comps = (all-completions pcomplete-stub table) unless comps return (prog1 nil ;; Don't add final space when ;; there is no completion (Bug#1990). (setq helm-eshell--quit-flag t) (message "No completions of %s" pcomplete-stub)) for i in comps ;; Transform the relative names to abs names. for file-cand = (and exp-entry (if (file-remote-p i) i (expand-file-name i (file-name-directory (if (directory-name-p pcomplete-stub) entry (directory-file-name entry)))))) ;; Compare them to avoid dups. for file-entry-p = (and (stringp exp-entry) (stringp file-cand) ;; Fix :/tmp/foo/ $ cd foo (not (file-directory-p file-cand)) (file-equal-p exp-entry file-cand)) if (and file-cand (or (file-remote-p file-cand) (file-exists-p file-cand)) (not file-entry-p)) collect file-cand into ls else ;; Avoid adding entry here. unless file-entry-p collect i into ls finally return (if (and exp-entry (file-directory-p exp-entry) ;; If the car of completion list is ;; an executable, probably we are in ;; command completion, so don't add a ;; possible file related entry here. (and ls (not (executable-find (car ls)))) ;; Don't add entry if already in prompt. (not (file-equal-p exp-entry pcomplete-stub))) (append (list exp-entry) ;; Entry should not be here now but double check. (remove entry ls)) ls)))))) ;;; Eshell history. ;; ;; (defclass helm-eshell-history-source (helm-source-sync) ((init :initform (lambda () ;; Same comment as in `helm-source-esh'. (remove-hook 'minibuffer-setup-hook 'eshell-mode))) (candidates :initform (lambda () (with-helm-current-buffer (cl-loop for c from 0 to (ring-length eshell-history-ring) for elm = (eshell-get-history c) unless (and (member elm lst) eshell-hist-ignoredups) collect elm into lst finally return lst)))) (nomark :initform t) (multiline :initform t) (keymap :initform 'helm-eshell-history-map) (candidate-number-limit :initform 9999) (action :initform (lambda (candidate) (eshell-kill-input) (insert candidate)))) "Helm class to define source for Eshell history.") (defun helm-esh-pcomplete-input (target users-comp last) (if (and (stringp last) (not (string= last "")) (not users-comp) ;; Fix completion on "../" see Bug#1832. (or (file-exists-p last) (helm-aand (file-name-directory last) (file-directory-p it)))) (if (and (file-directory-p last) (string-match "\\`[~.]*.*/[.]\\'" target)) ;; Fix completion on "~/.", "~/[...]/.", and "../." (expand-file-name (concat (helm-basedir (file-name-as-directory last)) (regexp-quote (helm-basename target)))) (expand-file-name last)) ;; Don't add "~" to input to provide completion on all users instead of only ;; on current $HOME (#1832). (unless users-comp last))) (defun helm-esh-pcomplete-default-source () "Make and return the default source for Eshell completion." (helm-make-source "Eshell completions" 'helm-esh-source :fuzzy-match helm-eshell-fuzzy-match :keymap helm-esh-completion-map)) (defvar helm-esh-pcomplete-build-source-fn #'helm-esh-pcomplete-default-source "Function that builds a source or a list of sources.") (defun helm-esh-pcomplete--make-helm (&optional input) (helm :sources (funcall helm-esh-pcomplete-build-source-fn) :buffer "*helm pcomplete*" :resume 'noresume :input input)) ;;;###autoload (defun helm-esh-pcomplete () "Preconfigured `helm' to provide Helm completion in Eshell." (interactive) (let* ((helm-quit-if-no-candidate t) (helm-execute-action-at-once-if-one t) (end (point-marker)) (beg (save-excursion (eshell-bol) (point))) (args (catch 'eshell-incomplete (eshell-parse-arguments beg end))) (target (or (and (looking-back " " (1- (point))) " ") (buffer-substring-no-properties (save-excursion (eshell-backward-argument 1) (point)) end))) (users-comp (string= target "~")) (first (car args)) ; Maybe lisp delimiter "(". last ; Will be the last but parsed by pcomplete. del-space del-dot) (setq helm-ec-target (or target " ") end (point) ;; Reset beg for `with-helm-show-completion'. beg (or (and target (not (string= target " ")) (- end (length target))) ;; Nothing at point. (progn (insert " ") (setq del-space t) (point)))) (when (string-match "\\`[~.]*.*/[.]\\'" target) ;; Fix completion on ;; "~/.", "~/[...]/.", and "../." (delete-char -1) (setq del-dot t) (setq helm-ec-target (substring helm-ec-target 0 (1- (length helm-ec-target))))) (cond ((eq first ?\() (helm-lisp-completion-at-point)) ;; In eshell `pcomplete-parse-arguments' is called ;; with `pcomplete-parse-arguments-function' ;; locally bound to `eshell-complete-parse-arguments' ;; which is calling `lisp-complete-symbol', ;; calling it before would popup the ;; *completions* buffer. (t (setq last (replace-regexp-in-string "\\`\\*" "" (car (last (ignore-errors (pcomplete-parse-arguments)))))) ;; Set helm-eshell--quit-flag to non-nil only on ;; quit, this tells to not add final suffix when quitting ;; helm. (add-hook 'helm-quit-hook #'helm-eshell--quit-hook-fn) (with-helm-show-completion beg end (unwind-protect (or (helm-esh-pcomplete--make-helm (helm-esh-pcomplete-input target users-comp last)) ;; Delete removed dot on quit (and del-dot (prog1 t (insert "."))) ;; A space is needed to have completion, remove ;; it when nothing found. (and del-space (looking-back "\\s-" (1- (point))) (delete-char -1)) (if (and (null helm-eshell--quit-flag) (and (stringp last) (file-directory-p last)) (looking-back "\\([.]\\{1,2\\}\\|[^/]\\)\\'" (1- (point)))) (prog1 t (insert "/")) ;; We need another flag for space here, but ;; global to pass it to `helm-quit-hook', this ;; space is added when point is just after ;; previous completion and there is no ;; more completion, see Bug#1832. (unless (or helm-eshell--quit-flag (looking-back "/\\'" (1- (point)))) (prog1 t (insert " "))) (when (and helm-eshell--quit-flag (string-match-p "[.]\\{2\\}\\'" last)) (insert "/")))) (remove-hook 'helm-quit-hook #'helm-eshell--quit-hook-fn) (setq helm-eshell--quit-flag nil))))))) (defun helm-eshell--quit-hook-fn () (setq helm-eshell--quit-flag t)) ;;;###autoload (defun helm-eshell-history () "Preconfigured Helm for Eshell history." (interactive) (let* ((end (point)) (beg (save-excursion (eshell-bol) (point))) (input (buffer-substring beg end)) flag-empty) (when (eq beg end) (insert " ") (setq flag-empty t) (setq end (point))) (unwind-protect (with-helm-show-completion beg end (helm :sources (helm-make-source "Eshell history" 'helm-eshell-history-source :fuzzy-match helm-eshell-fuzzy-match) :buffer "*helm eshell history*" :resume 'noresume :input input)) (when (and flag-empty (looking-back " " (1- (point)))) (delete-char -1))))) ;;; Eshell prompts ;; (defface helm-eshell-prompts-promptidx `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "cyan")) "Face used to highlight Eshell prompt index.") (defface helm-eshell-prompts-buffer-name `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "green")) "Face used to highlight Eshell buffer name.") (defcustom helm-eshell-prompts-promptidx-p t "Show prompt number." :type 'boolean) (defvar helm-eshell-prompts-keymap (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "C-c o") #'helm-eshell-prompts-other-window) (define-key map (kbd "C-c C-o") #'helm-eshell-prompts-other-frame) map) "Keymap for `helm-eshell-prompt-all'.") (defvar eshell-prompt-regexp) (defvar eshell-highlight-prompt) (defun helm-eshell-prompts-list (&optional buffer) "List the prompts in Eshell BUFFER. Return a list of (\"prompt\" (point) (buffer-name) prompt-index)) E.g. (\"ls\" 162 \"*eshell*\" 3). If BUFFER is nil, use current buffer." (with-current-buffer (or buffer (current-buffer)) (when (eq major-mode 'eshell-mode) (save-excursion (goto-char (point-min)) (let (result (count 1)) (helm-awhile (re-search-forward eshell-prompt-regexp nil t) (when (or (and eshell-highlight-prompt (get-text-property (match-beginning 0) 'read-only)) (null eshell-highlight-prompt)) (push (list (buffer-substring-no-properties it (pos-eol)) it (buffer-name) count) result) (setq count (1+ count)))) (nreverse result)))))) (defun helm-eshell-prompts-list-all () "List the prompts of all Eshell buffers. See `helm-eshell-prompts-list'." (cl-loop for b in (buffer-list) append (helm-eshell-prompts-list b))) (defun helm-eshell-prompts-transformer (candidates &optional all) ;; ("ls" 162 "*eshell*" 3) => ("*eshell*:3:ls" . ("ls" 162 "*eshell*" 3)) (cl-loop for (prt pos buf id) in candidates collect `(,(concat (when all (concat (propertize buf 'face 'helm-eshell-prompts-buffer-name) ":")) (when helm-eshell-prompts-promptidx-p (concat (propertize (number-to-string id) 'face 'helm-eshell-prompts-promptidx) ":")) prt) . ,(list prt pos buf id)))) (defun helm-eshell-prompts-all-transformer (candidates) (helm-eshell-prompts-transformer candidates t)) (cl-defun helm-eshell-prompts-goto (candidate &optional (action 'switch-to-buffer)) ;; Candidate format: ("ls" 162 "*eshell*" 3) (let ((buf (nth 2 candidate))) (unless (and (string= (buffer-name) buf) (eq action 'switch-to-buffer)) (funcall action buf)) (goto-char (nth 1 candidate)) (recenter))) (defun helm-eshell-prompts-goto-other-window (candidate) (helm-eshell-prompts-goto candidate 'switch-to-buffer-other-window)) (defun helm-eshell-prompts-goto-other-frame (candidate) (helm-eshell-prompts-goto candidate 'switch-to-buffer-other-frame)) (helm-make-command-from-action helm-eshell-prompts-other-window "Switch to eshell prompt in other window." 'helm-eshell-prompts-goto-other-window) (helm-make-command-from-action helm-eshell-prompts-other-frame "Switch to eshell prompt in other frame." 'helm-eshell-prompts-goto-other-frame) ;;;###autoload (defun helm-eshell-prompts () "Pre-configured `helm' to browse the prompts of the current Eshell." (interactive) (if (eq major-mode 'eshell-mode) (helm :sources (helm-build-sync-source "Eshell prompts" :candidates (helm-eshell-prompts-list) :candidate-transformer 'helm-eshell-prompts-transformer :action '(("Go to prompt" . helm-eshell-prompts-goto))) :buffer "*helm Eshell prompts*") (message "Current buffer is not an Eshell buffer"))) ;;;###autoload (defun helm-eshell-prompts-all () "Pre-configured `helm' to browse the prompts of all Eshell sessions." (interactive) (helm :sources (helm-build-sync-source "All Eshell prompts" :candidates (helm-eshell-prompts-list-all) :candidate-transformer 'helm-eshell-prompts-all-transformer :action '(("Go to prompt" . helm-eshell-prompts-goto) ("Go to prompt in other window `C-c o`" . helm-eshell-prompts-goto-other-window) ("Go to prompt in other frame `C-c C-o`" . helm-eshell-prompts-goto-other-frame)) :keymap helm-eshell-prompts-keymap) :buffer "*helm Eshell all prompts*")) (provide 'helm-eshell) ;;; helm-eshell ends here helm-4.0.3/helm-eval.el000066400000000000000000000216071501106761700146510ustar00rootroot00000000000000;;; helm-eval.el --- eval expressions from helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'eldoc) (require 'edebug) (declare-function helm-lisp-completion-at-point "helm-elisp.el") (declare-function helm-elisp-show-doc-modeline "helm-elisp.el") (defvar helm-elisp-help-function) (defgroup helm-eval nil "Eval related Applications and libraries for Helm." :group 'helm) (defcustom helm-eldoc-in-minibuffer-show-fn 'helm-show-info-in-mode-line "A function to display eldoc info. Should take one arg: the string to display." :group 'helm-eval :type 'symbol) (defcustom helm-show-info-in-mode-line-delay 12 "Eldoc will show info in mode-line during this delay if user is idle." :type 'integer :group 'helm-eval) ;;; Eldoc compatibility between emacs-24 and emacs-25 ;; (if (require 'elisp-mode nil t) ; emacs-25 ;; Maybe the eldoc functions have been ;; already aliased by eldoc-eval. (cl-loop for (f . a) in '((eldoc-current-symbol . elisp--current-symbol) (eldoc-fnsym-in-current-sexp . elisp--fnsym-in-current-sexp) (eldoc-get-fnsym-args-string . elisp-get-fnsym-args-string) (eldoc-get-var-docstring . elisp-get-var-docstring)) unless (fboundp f) do (defalias f a)) ;; Emacs-24. (declare-function eldoc-current-symbol "eldoc") (declare-function eldoc-get-fnsym-args-string "eldoc" (sym &optional index)) (declare-function eldoc-get-var-docstring "eldoc" (sym)) (declare-function eldoc-fnsym-in-current-sexp "eldoc")) ;;; Evaluation Result ;; ;; ;; Internal (defvar helm-eldoc-active-minibuffers-list nil) (defvar helm-eval-expression-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "") #'helm-eval-new-line-and-indent) (define-key map (kbd "") #'lisp-indent-line) (define-key map (kbd "") #'helm-lisp-completion-at-point) (define-key map (kbd "C-p") #'previous-line) (define-key map (kbd "C-n") #'next-line) (define-key map (kbd "") #'previous-line) (define-key map (kbd "") #'next-line) (define-key map (kbd "") #'forward-char) (define-key map (kbd "") #'backward-char) map)) (defclass helm-evaluation-result-class (helm-source-dummy) ((echo-input-in-header-line :initarg :echo-input-in-header-line :initform 'never))) (defun helm-build-evaluation-result-source () (helm-make-source "Evaluation Result" 'helm-evaluation-result-class :multiline t :mode-line "C-RET: nl-and-indent, M-tab: reindent, C-tab:complete, C-p/n: next/prec-line." :filtered-candidate-transformer (lambda (_candidates _source) (list (condition-case nil (with-helm-current-buffer (pp-to-string (if edebug-active (edebug-eval-expression (read helm-pattern)) (eval (read helm-pattern) t)))) (error "Error")))) :nohighlight t :keymap helm-eval-expression-map :action `(("Copy result to kill-ring" . ,(lambda (candidate) (kill-new (replace-regexp-in-string "\n" "" candidate)) (message "Result copied to kill-ring"))) ("copy sexp to kill-ring" . ,(lambda (_candidate) (kill-new helm-input) (message "Sexp copied to kill-ring")))))) (defun helm-eval-new-line-and-indent () (interactive) (newline) (lisp-indent-line)) (defun helm-eldoc-store-minibuffer () "Store minibuffer buffer name in `helm-eldoc-active-minibuffers-list'." (with-selected-window (minibuffer-window) (push (current-buffer) helm-eldoc-active-minibuffers-list))) ;; From emacs-28.1: As the eldoc API is nowaday a pain to use, try to ;; provide some eldoc in mode-line the best as possible (may break at ;; some point). (defun helm-eldoc-show-in-eval () "Return eldoc in mode-line for current minibuffer input." (let ((buf (window-buffer (active-minibuffer-window)))) (condition-case err (when (member buf helm-eldoc-active-minibuffers-list) (with-current-buffer buf (let* ((info-fn (eldoc-fnsym-in-current-sexp)) (vsym (eldoc-current-symbol)) (sym (car info-fn)) (vardoc (eldoc-get-var-docstring vsym)) (doc (or vardoc (eldoc-get-fnsym-args-string sym (cadr info-fn)))) (all (format "%s: %s" (propertize (symbol-name (if vardoc vsym sym)) 'face (if vardoc 'font-lock-variable-name-face 'font-lock-function-name-face)) doc))) (when doc (funcall helm-eldoc-in-minibuffer-show-fn all))))) (error (message "Eldoc in minibuffer error: %S" err) nil)))) (defun helm-show-info-in-mode-line (str) "Display string STR in mode-line." (save-selected-window (with-helm-window (let ((mode-line-format (concat " " str))) (force-mode-line-update) (sit-for helm-show-info-in-mode-line-delay)) (force-mode-line-update)))) ;;; Calculation Result ;; ;; (defvar helm-source-calculation-result (helm-build-dummy-source "Calculation Result" :filtered-candidate-transformer (lambda (_candidates _source) (list (condition-case err (let ((result (calc-eval helm-pattern))) (if (listp result) (error "At pos %s: %s" (car result) (cadr result)) result)) (error (cdr err))))) :nohighlight t :action `(("Copy result to kill-ring" . ,(lambda (candidate) (kill-new candidate) (message "Result \"%s\" copied to kill-ring" candidate))) ("Copy operation to kill-ring" . ,(lambda (_candidate) (kill-new helm-input) (message "Calculation copied to kill-ring")))))) ;;;###autoload (defun helm-eval-expression (arg) "Preconfigured `helm' for `helm-source-evaluation-result'." (interactive "P") (let ((helm-elisp-help-function #'helm-elisp-show-doc-modeline)) (helm :sources (helm-build-evaluation-result-source) :input (when arg (thing-at-point 'sexp)) :buffer "*helm eval*" :echo-input-in-header-line nil :history 'read-expression-history))) (defvar eldoc-idle-delay) ;;;###autoload (defun helm-eval-expression-with-eldoc () "Preconfigured `helm' for `helm-source-evaluation-result' with `eldoc' support." (interactive) (let ((timer (run-with-idle-timer eldoc-idle-delay 'repeat #'helm-eldoc-show-in-eval))) (unwind-protect (minibuffer-with-setup-hook #'helm-eldoc-store-minibuffer (call-interactively 'helm-eval-expression)) (and timer (cancel-timer timer)) (setq helm-eldoc-active-minibuffers-list (cdr helm-eldoc-active-minibuffers-list))))) ;;;###autoload (defun helm-calcul-expression () "Preconfigured `helm' for `helm-source-calculation-result'." (interactive) (helm :sources 'helm-source-calculation-result :buffer "*helm calcul*")) (provide 'helm-eval) ;;; helm-eval.el ends here helm-4.0.3/helm-external.el000066400000000000000000000274351501106761700155510ustar00rootroot00000000000000;;; helm-external.el --- Run Externals commands within Emacs with helm completion. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'helm-net) (declare-function helm-comp-read "helm-mode") (defgroup helm-external nil "External related Applications and libraries for Helm." :group 'helm) (defcustom helm-raise-command nil "A shell command to jump to a window running specific program. Need external program wmctrl. This will be use with `format', so use something like \"wmctrl -xa %s\"." :type 'string :group 'helm-external) (defcustom helm-external-programs-associations nil "Alist to store externals programs associated with file extension. This variable overhide setting in .mailcap file. E.g.: \\='((\"jpg\" . \"gqview\") (\"pdf\" . \"xpdf\")) " :type '(alist :key-type string :value-type string) :group 'helm-external) (defcustom helm-default-external-file-browser "nautilus" "Default external file browser for your system. Directories will be opened externally with it when opening file externally in `helm-find-files'. Set to nil if you do not have an external file browser or do not want to use it. Windows users should set that to \"explorer.exe\"." :group 'helm-external :type 'string) (defvar helm-open-file-externally-after-hook nil "Hook that run after opening a file with external program.") (defvar helm-open-file-externally-after-finish-hook nil "Hook that run after external program finish.") ;;; Internals (defvar helm-external-command-history nil) (defvar helm-external-commands-list nil "A list of all external commands the user can execute. If this variable is not set by the user, it will be calculated automatically.") (defun helm-external-commands-list-1 (&optional sort) "Return a list of all external commands the user can execute. If `helm-external-commands-list' is non-nil it will return its contents. Else it calculates all external commands and sets `helm-external-commands-list'." (or helm-external-commands-list (setq helm-external-commands-list (cl-loop for dir in (split-string (getenv "PATH") path-separator) when (and (file-exists-p dir) (file-accessible-directory-p dir)) for lsdir = (cl-loop for i in (directory-files dir t) for bn = (file-name-nondirectory i) when (and (not (member bn completions)) (not (file-directory-p i)) (file-executable-p i)) collect bn) append lsdir into completions finally return (if sort (sort completions 'string-lessp) completions))))) (defun helm-run-or-raise (exe &optional files detached) "Run asynchronously EXE or jump to the application window. If EXE is already running just jump to his window if `helm-raise-command' is non-nil. When FILES argument is provided run EXE with FILES. When argument DETACHED is non nil, detach process from Emacs." (let* ((proc-name (replace-regexp-in-string "(" "" (car (split-string exe)))) (fmt-file (lambda (file) (shell-quote-argument (if (eq system-type 'windows-nt) (helm-w32-prepare-filename file) (expand-file-name file))))) (file-arg (and files (mapconcat fmt-file files " "))) process-connection-type proc) (when (and files detached (not (string-match "%s &)\\'" exe))) (setq exe (format "(%s &)" exe))) (when (member proc-name helm-external-commands-list) ;; Allow adding more files to the current process if it is ;; already running (i.e. Don't just raise it without sending ;; files) we assume program doesn't start a new ;; process (like firefox, transmission etc...). (if files (cond ((string-match "%s &)\\'" exe) (message "Starting and detaching `%s' from Emacs" proc-name) (call-process-shell-command (format exe file-arg))) (t (message "Starting %s..." proc-name) (setq proc (start-process-shell-command proc-name nil (if (string-match "%s" exe) (format exe file-arg) (format "%s %s" exe file-arg)))))) ;; Just jump to the already running program instance or start ;; a new process. (if (get-process proc-name) (if helm-raise-command (run-at-time 0.1 nil #'shell-command (format helm-raise-command proc-name)) (error "Error: %s is already running" proc-name)) (if (and detached (not (memq system-type '(windows-nt ms-dos)))) (progn (message "Starting and detaching `%s' from Emacs" proc-name) (call-process-shell-command (format "(%s &)" exe))) (when detached (user-error "Detaching programs not supported on `%s'" system-type)) (setq proc (start-process-shell-command proc-name nil exe))))) (when proc (set-process-sentinel proc (lambda (process event) (when (and (string= event "finished\n") helm-raise-command (not (helm-get-pid-from-process-name proc-name))) (run-hooks 'helm-open-file-externally-after-finish-hook) (shell-command (format helm-raise-command "emacs"))) (message "%s process...Finished." process)))) ;; Move command on top list. (setq helm-external-commands-list (cons proc-name (delete proc-name helm-external-commands-list)))))) (defun helm-get-mailcap-for-file (filename) "Get the command to use for FILENAME from mailcap files." (mailcap-parse-mailcaps) (let* ((ext (file-name-extension filename)) (mime (when ext (mailcap-extension-to-mime ext))) (result (when mime (mailcap-mime-info mime)))) ;; If elisp file have no associations in .mailcap ;; `mailcap-maybe-eval' is returned, in this case just return nil. (when (stringp result) (helm-basename result)))) (defun helm-get-default-program-for-file (filename) "Try to find a default program to open FILENAME. Try first in `helm-external-programs-associations' and then in mailcap file. If nothing found return nil." (let* ((ext (file-name-extension filename)) (def-prog (assoc-default ext helm-external-programs-associations))) (cond ((and def-prog (not (string= def-prog ""))) def-prog) ((and helm-default-external-file-browser (file-directory-p filename)) helm-default-external-file-browser) (t (helm-get-mailcap-for-file filename))))) (defun helm-open-file-externally (_file) "Open FILE with an external program. Try to guess which program to use with `helm-get-default-program-for-file'. If not found or a prefix arg is given query the user which tool to use." (let* ((files (helm-marked-candidates :with-wildcard t)) (fname (expand-file-name (car files))) (collection (helm-external-commands-list-1 'sort)) (def-prog (helm-get-default-program-for-file fname)) (program (if (or helm-current-prefix-arg (not def-prog)) ;; Prefix arg or no default program. (prog1 (helm-comp-read "Program: " collection :must-match t :name "Open file Externally" :history 'helm-external-command-history) ;; Always prompt to set this program as default. (setq def-prog nil)) ;; No prefix arg or default program exists. def-prog))) (unless (or def-prog ; Association exists, no need to record it. ;; Don't try to record non--filenames associations (e.g urls). (not (file-exists-p fname))) (when (y-or-n-p (format "Do you want to make `%s' the default program for this kind of files? " program)) (helm-aif (assoc (file-name-extension fname) helm-external-programs-associations) (setq helm-external-programs-associations (delete it helm-external-programs-associations))) (push (cons (file-name-extension fname) (helm-read-string "Program (Add args maybe and confirm): " program)) helm-external-programs-associations) (customize-save-variable 'helm-external-programs-associations helm-external-programs-associations))) (helm-run-or-raise program files) (run-hooks 'helm-open-file-externally-after-hook) (setq helm-external-command-history (cl-loop for i in helm-external-command-history when (executable-find i) collect i)))) (defun helm-run-external-command-action (candidate &optional detached) (helm-run-or-raise candidate nil detached) (setq helm-external-command-history (cons candidate (delete candidate helm-external-command-history)))) (defclass helm-external-commands (helm-source-in-buffer) ((filtered-candidate-transformer :initform (lambda (candidates _source) (cl-loop for c in candidates if (get-process c) collect (propertize c 'face 'font-lock-type-face) else collect c))) (must-match :initform t) (nomark :initform t) (action :initform (helm-make-actions "Run program" 'helm-run-external-command-action (lambda () (unless (memq system-type '(windows-nt ms-dos)) "Run program detached")) (lambda (candidate) (helm-run-external-command-action candidate 'detached)))))) ;;;###autoload (defun helm-run-external-command () "Preconfigured `helm' to run External PROGRAM asyncronously from Emacs. If program is already running try to run `helm-raise-command' if defined otherwise exit with error. You can set your own list of commands with `helm-external-commands-list'." (interactive) (helm :sources `(,(helm-make-source "External Commands history" 'helm-external-commands :data helm-external-command-history) ,(helm-make-source "External Commands" 'helm-external-commands :data (helm-external-commands-list-1 'sort))) :buffer "*helm externals commands*" :prompt "RunProgram: ") ;; Remove from history no more valid executables. (setq helm-external-command-history (cl-loop for i in helm-external-command-history when (executable-find i) collect i))) (provide 'helm-external) ;;; helm-external.el ends here helm-4.0.3/helm-fd.el000066400000000000000000000135471501106761700143170ustar00rootroot00000000000000;;; helm-fd.el --- helm interface for fd command line tool. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm) (require 'helm-types) (declare-function ansi-color-apply "ansi-color.el") (declare-function split-string-shell-command "shell.el") (defvar helm-fd-executable "fd" "The fd shell command executable.") (defcustom helm-fd-switches '("--no-ignore" "--hidden" "--type" "f" "--type" "d" "--color" "always") "A list of options to pass to fd shell command." :type '(repeat string) :group 'helm-files) (defcustom helm-fd-mode-line-function 'helm-fd-default-mode-line "Function called when `fd' process is finished to format mode-line." :type 'function :group 'helm-files) (defface helm-fd-finish `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Green")) "Face used in mode line when fd process ends." :group 'helm-grep-faces) (defvar helm-fd-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-generic-files-map) (define-key map (kbd "C-]") 'undefined) (define-key map (kbd "DEL") 'helm-delete-backward-no-update) (define-key map (kbd "M-") 'helm-fd-next-directory) (define-key map (kbd "M-") 'helm-fd-previous-directory) map)) (defun helm-fd-next-directory-1 (arg) (with-helm-window (let ((cur-dir (helm-basedir (helm-get-selection)))) (while (equal cur-dir (helm-basedir (helm-get-selection))) (if (> arg 0) (helm-next-line) (helm-previous-line)))))) (defun helm-fd-next-directory () "Move to next directory in a helm-fd source." (interactive) (with-helm-alive-p (helm-fd-next-directory-1 1))) (defun helm-fd-previous-directory () "Move to previous directory in a helm-fd source." (interactive) (with-helm-alive-p (helm-fd-next-directory-1 -1))) (defclass helm-fd-class (helm-source-async) ((candidates-process :initform 'helm-fd-process) (requires-pattern :initform 2) (candidate-number-limit :initform 20000) (nohighlight :initform t) (help-message :initform 'helm-fd-help-message) (filtered-candidate-transformer :initform 'helm-fd-fct) (action :initform 'helm-type-file-actions) (keymap :initform 'helm-fd-map))) (defun helm-fd-process () "Initialize fd process in an helm async source." (let* (process-connection-type (cmd (append helm-fd-switches (or (and (fboundp #'split-string-shell-command) (split-string-shell-command helm-pattern)) (split-string helm-pattern)))) (proc (apply #'start-process "fd" nil helm-fd-executable cmd)) (start-time (float-time)) (fd-version (replace-regexp-in-string "\n" "" (shell-command-to-string (concat helm-fd-executable " --version"))))) (helm-log "helm-fd-process" "Fd command:\nfd %s" (mapconcat 'identity cmd " ")) (helm-log "helm-fd-process" "VERSION: %s" fd-version) (prog1 proc (set-process-sentinel proc (lambda (_process event) (if (string= event "finished\n") (with-helm-window (when helm-fd-mode-line-function (funcall helm-fd-mode-line-function start-time fd-version) (force-mode-line-update))) (helm-log "helm-fd-process sentinel" "Error: Fd %s" (replace-regexp-in-string "\n" "" event)))))))) (defun helm-fd-default-mode-line (start-time fd-version) "Format mode-line with START-TIME and FD-VERSION, as well as `fd' results." (setq mode-line-format `(" " mode-line-buffer-identification " " (:eval (format "L%s" (helm-candidate-number-at-point))) " " (:eval (propertize (format "[%s process finished in %.2fs - (%s results)] " ,fd-version ,(- (float-time) start-time) (helm-get-candidate-number)) 'face 'helm-fd-finish))))) (defun helm-fd-fct (candidates _source) "The filtered-candidate-transformer function for helm-fd." (cl-loop for i in candidates for fname = (ansi-color-apply i) if helm-ff-icon-mode collect (let* ((abs (expand-file-name fname default-directory)) (icon (if (file-directory-p abs) (helm-x-icons-generic "file-directory") (helm-x-icons-icon-for-file (helm-basename fname))))) (cons (concat icon fname) fname)) else collect fname)) (defun helm-fd-1 (directory) "Run fd shell command on DIRECTORY with helm interface." (cl-assert (executable-find helm-fd-executable) nil "Could not find fd executable") (cl-assert (not (file-remote-p directory)) nil "Fd not supported on remote directories") (let ((default-directory directory)) (helm :sources (helm-make-source "Fd" 'helm-fd-class :header-name (lambda (name) (format "%s (%s)" name (abbreviate-file-name default-directory)))) :buffer "*helm fd*"))) (provide 'helm-fd) ;;; helm-fd.el ends here helm-4.0.3/helm-files.el000066400000000000000000012136731501106761700150330ustar00rootroot00000000000000;;; helm-files.el --- helm file browser and related. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-types) (require 'helm-utils) (require 'helm-grep) (require 'helm-help) (require 'helm-locate) (require 'helm-tags) (require 'helm-buffers) (require 'tramp) (eval-when-compile (require 'thingatpt) (require 'ffap) (require 'dired-aux) (require 'dired-x)) (require 'filenotify) (require 'image-mode) (require 'image-dired) (require 'helm-x-icons) (declare-function find-library-name "find-func.el" (library)) (declare-function w32-shell-execute "ext:w32fns.c" (operation document &optional parameters show-flag)) (declare-function gnus-dired-attach "ext:gnus-dired.el" (files-to-attach)) (declare-function eshell-read-aliases-list "em-alias") (declare-function eshell-send-input "esh-mode" (&optional use-region queue-p no-newline)) (declare-function eshell-kill-input "esh-mode") (declare-function eshell-bol "esh-mode") (declare-function eshell-reset "esh-mode.el") (declare-function eshell/cd "em-dirs.el") (declare-function eshell-next-prompt "em-prompt.el") (declare-function eshell-resume-eval "esh-cmd") (declare-function helm-ls-git "ext:helm-ls-git") (declare-function helm-hg-find-files-in-project "ext:helm-ls-hg") (declare-function helm-gid "helm-id-utils.el") (declare-function helm-find-1 "helm-find") (declare-function helm-fd-1 "helm-fd") (declare-function helm-get-default-program-for-file "helm-external") (declare-function helm-open-file-externally "helm-external") (declare-function helm-comp-read "helm-mode") (declare-function helm-read-file-name "helm-mode") (declare-function term-line-mode "term") (declare-function term-char-mode "term") (declare-function term-send-input "term") (declare-function term-next-prompt "term") (declare-function term-process-mark "term") (declare-function bookmark-prop-get "bookmark") (declare-function helm-bookmark-build-source "helm-bookmark") (declare-function comint-next-prompt "comint") (declare-function comint-delete-input "comint") (declare-function comint-send-input "comint") (declare-function comint-goto-process-mark "comint") (declare-function tramp-dissect-file-name "tramp") (declare-function tramp-get-completion-function "tramp") (declare-function seconds-to-time "time-date") (declare-function ffap-fixup-url "ffap") (declare-function ffap-url-at-point "ffap") (declare-function ffap-file-at-point "ffap") (declare-function dired-create-files "dired-aux") (declare-function dired-goto-file "dired") (declare-function dired-move-to-filename "dired") (declare-function dired-move-to-end-of-filename "dired") (declare-function dired-get-filename "dired") (declare-function dired-get-marked-files "dired") (declare-function tramp-list-connections "tramp-cache") (declare-function tramp-get-connection-process "tramp") (declare-function tramp-buffer-name "tramp") (declare-function tramp-make-tramp-file-name "tramp") (declare-function tramp-cleanup-connection "tramp-cmds") (declare-function dired-async-processes "ext:dired-async.el") (declare-function dired-async-mode-line-message "ext:dired-async.el") (declare-function dired-async--modeline-mode "ext:dired-async.el") (declare-function helm-adaptive-sort "ext:helm-adaptive.el") (declare-function wfnames-setup-buffer "ext:wfnames.el") (declare-function svg-lib-progress-bar "ext:svg-lib") (declare-function svg-lib-tag "ext:svg-lib") (declare-function helm-epa-success-message "helm-epa") (declare-function helm-epa-collect-id-from-candidates "helm-epa") (declare-function helm-epa-collect-keys-from-candidates "helm-epa") (declare-function async-byte-compile-file "async-bytecomp.el") (declare-function async-byte-recompile-directory "async-bytecomp.el") (declare-function dnd-begin-drag-files "dnd.el") (defvar term-char-mode-point-at-process-mark) (defvar term-char-mode-buffer-read-only) (defvar recentf-list) (defvar helm-mm-matching-method) (defvar dired-async-mode) (defvar org-directory) (defvar eshell-current-command) (defvar eshell-debug-command) (defvar eshell-current-command) (defvar tramp-archive-enabled) (defvar tramp-tolerate-tilde) (defvar password-cache) (defvar helm-fd-executable) (defvar wfnames-buffer) (defvar Info-current-file) (defvar generated-autoload-file) ;;; Internal vars ;; (defvar helm-ff-last-expanded-candidate-regexp "^[[:multibyte:] \t]*%s" "Regexp that retrieve previous candidate when going up one level. The default value matching a multibyte char at bol allows prefixing candidate with an icon. The format part will be replaced by the display part of the candidate regexp quoted. This should be used for all preselection code for helm-find-files to handle icons.") (defvar helm-find-files-doc-header " (\\\\[helm-find-files-up-one-level]: Go up one level)" "*The doc that is inserted in the Name header of a find-files or dired source.") (defvar helm-ff-auto-update-flag nil "Internal, flag to turn on/off auto-update in `helm-find-files'. Don't set it directly, use instead `helm-ff-auto-update-initial-value'.") (defvar helm-ff-last-expanded nil "Store last expanded directory or file.") (defvar helm-ff-default-directory nil) (defvar helm-ff-history nil) (defvar helm-ff-url-regexp "\\`\\(news\\(post\\)?:\\|nntp:\\|mailto:\\|file:\\|\\(ftp\\|https?\\|telnet\\|gopher\\|www\\|wais\\):/?/?\\).*" "Same as `ffap-url-regexp' but match earlier possible url.") ;; helm-tramp-file-name-regexp is based on old version of ;; tramp-file-name-regexp i.e. "\\`/\\([^[/:]+\\|[^/]+]\\):" but it ;; seems it is wrong and a simpler regexp is enough, let's try it and ;; watch out! (defvar helm-tramp-file-name-regexp "\\`/\\([^/:|]+\\):") (defvar helm-ff-tramp-method-regexp "[/|]:\\([^:]*\\)") (defvar helm-ff--auto-update-state nil) (defvar helm-ff--deleting-char-backward nil) (defvar helm-multi-files--toggle-locate nil) (defvar helm-ff--move-to-first-real-candidate t) (defvar helm-find-files--toggle-bookmark nil) (defvar helm-ff--tramp-methods nil) (defvar helm-ff--directory-files-length (make-hash-table :test 'equal) "Used to count number of candidates in directory. candidate-number-limit is set to this value if this value is bigger than `helm-candidate-number-limit'.") (defvar helm-ff--list-directory-cache (make-hash-table :test 'equal) "Cache for `helm-find-files' candidates.") (defvar helm-ff--file-notify-watchers (make-hash-table :test 'equal) "File-notify watchers for `helm-find-files' are stored here.") (defvar helm-ff-history-buffer-name "*helm-find-files history*") (defvar helm-rsync-command-history nil) (defvar helm-rsync--last-progress-bar-alist nil "Used to store last valid rsync progress bar.") (defvar helm-rsync-process-buffer "*helm-rsync*") (defvar helm-rsync-progress-str-alist nil) (defvar helm-ff--trash-directory-regexp "\\.?Trash[/0-9]+files/?\\'") (defvar helm-ff--show-directories-only nil) (defvar helm-ff--show-files-only nil) (defvar helm-ff--trashed-files nil "[INTERNAL] Files already trashed are stored here during file deletion. This is used only as a let binding.") (defvar helm-ff--show-thumbnails nil) (defvar helm-ff--thumbnailed-directories nil) (defvar helm-source-find-files nil "The main source to browse files. Should not be used among other sources.") (defvar helm-ff-icon-mode nil) ;;; Helm-find-files - The helm file browser. ;; ;; Keymaps (defvar helm-find-files-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "RET") 'helm-ff-RET) (define-key map (kbd "C-]") 'helm-ff-run-toggle-basename) (define-key map (kbd "C-x C-f") 'helm-ff-run-locate) (define-key map (kbd "C-x C-d") 'helm-ff-run-browse-project) (define-key map (kbd "C-x r m") 'helm-ff-bookmark-set) (define-key map (kbd "C-x r b") 'helm-find-files-switch-to-bookmark) (define-key map (kbd "C-x C-q") 'helm-ff-run-edit-marked-files) (define-key map (kbd "C-s") 'helm-ff-run-grep) (define-key map (kbd "M-g s") 'helm-ff-run-grep) (define-key map (kbd "M-g p") 'helm-ff-run-pdfgrep) (define-key map (kbd "M-g z") 'helm-ff-run-zgrep) (define-key map (kbd "M-g a") 'helm-ff-run-grep-ag) (define-key map (kbd "M-g g") 'helm-ff-run-git-grep) (define-key map (kbd "M-g i") 'helm-ff-run-gid) (define-key map (kbd "M-.") 'helm-ff-run-etags) (define-key map (kbd "M-R") 'helm-ff-run-rename-file) (define-key map (kbd "M-C") 'helm-ff-run-copy-file) (define-key map (kbd "M-k") 'helm-ff-run-kill-default-directory) (when (executable-find "rsync") (define-key map (kbd "M-V") 'helm-ff-run-rsync-file)) (define-key map (kbd "C-M-SPC") 'helm-ff-mark-similar-files) (define-key map (kbd "C-M-@") 'helm-ff-mark-similar-files) (define-key map (kbd "C-c C-SPC") 'helm-ff-mark-similar-files) (define-key map (kbd "C-M-c") 'helm-ff-run-mcp) (define-key map (kbd "M-B") 'helm-ff-run-byte-compile-file) (define-key map (kbd "M-L") 'helm-ff-run-load-file) (define-key map (kbd "M-S") 'helm-ff-run-symlink-file) (define-key map (kbd "M-Y") 'helm-ff-run-relsymlink-file) (define-key map (kbd "M-H") 'helm-ff-run-hardlink-file) (define-key map (kbd "M-D") 'helm-ff-run-delete-file) (define-key map (kbd "M-K") 'helm-ff-run-kill-buffer-persistent) (define-key map (kbd "M-T") 'helm-ff-run-touch-files) (define-key map (kbd "M-M") 'helm-ff-run-chmod) (define-key map (kbd "C-c z") 'helm-ff-persistent-compress) (define-key map (kbd "M-Z") 'helm-ff-run-compress-marked-files) (define-key map (kbd "M-c") 'helm-ff-run-compress-to) (define-key map (kbd "C-c d") 'helm-ff-persistent-delete) (define-key map (kbd "M-e") 'helm-ff-run-switch-to-shell) (define-key map (kbd "C-c i") 'helm-ff-run-complete-fn-at-point) (define-key map (kbd "C-c o") 'helm-ff-run-switch-other-window) (define-key map (kbd "C-c C-o") 'helm-ff-run-switch-other-frame) (define-key map (kbd "C-c C-x") 'helm-ff-run-open-file-externally) (define-key map (kbd "C-c C-v") 'helm-ff-run-preview-file-externally) (define-key map (kbd "C-c X") 'helm-ff-run-open-file-with-default-tool) (define-key map (kbd "C-c t") 'helm-ff-toggle-thumbnails) (define-key map (kbd "M-!") 'helm-ff-run-eshell-command-on-file) (define-key map (kbd "M-@") 'helm-ff-run-query-replace-fnames-on-marked) (define-key map (kbd "M-%") 'helm-ff-run-query-replace) (define-key map (kbd "C-M-%") 'helm-ff-run-query-replace-regexp) (define-key map (kbd "C-c =") 'helm-ff-run-ediff-file) (define-key map (kbd "M-=") 'helm-ff-run-ediff-merge-file) (define-key map (kbd "M-p") 'helm-find-files-history) (define-key map (kbd "C-c h") 'helm-ff-file-name-history) (define-key map (kbd "M-i") 'helm-ff-properties-persistent) (define-key map (kbd "C-}") 'helm-narrow-window) (define-key map (kbd "C-{") 'helm-enlarge-window) (define-key map (kbd "C-") 'helm-ff-run-toggle-auto-update) (define-key map (kbd "C-c ") 'helm-ff-run-toggle-auto-update) (define-key map (kbd "C-c C-a") 'helm-ff-run-mail-attach-files) (define-key map (kbd "C-c p") 'helm-ff-run-print-file) (define-key map (kbd "C-c /") 'helm-ff-run-find-sh-command) (define-key map (kbd "C-/") 'helm-ff-run-fd) ;; Next 2 have no effect if candidate is not an image file. (define-key map (kbd "M-l") 'helm-ff-rotate-left-persistent) (define-key map (kbd "M-r") 'helm-ff-rotate-right-persistent) (define-key map (kbd "M-+") 'helm-ff-increase-image-size-persistent) (define-key map (kbd "M--") 'helm-ff-decrease-image-size-persistent) (define-key map (kbd "C-l") 'helm-find-files-up-one-level) (define-key map (kbd "C-:") 'helm-ff-complete-tramp-methods) (define-key map (kbd "C-_") 'helm-ff-undo) (define-key map (kbd "C-r") 'helm-find-files-down-last-level) (define-key map (kbd "C-c r") 'helm-ff-run-find-file-as-root) (define-key map (kbd "C-x C-v") 'helm-ff-run-find-alternate-file) (define-key map (kbd "C-c @") 'helm-ff-run-insert-org-link) (define-key map (kbd "S-") 'helm-ff-sort-alpha) (define-key map (kbd "S-") 'helm-ff-sort-by-newest) (define-key map (kbd "S-") 'helm-ff-sort-by-size) (define-key map (kbd "S-") 'helm-ff-toggle-dirs-only) (define-key map (kbd "S-") 'helm-ff-toggle-files-only) (define-key map (kbd "S-") 'helm-ff-sort-by-ext) (helm-define-key-with-subkeys map (kbd "DEL") ?\d 'helm-ff-delete-char-backward '((C-backspace . helm-ff-run-toggle-auto-update) ([C-c DEL] . helm-ff-run-toggle-auto-update)) nil 'helm-ff-delete-char-backward--exit-fn) (when (fboundp 'tab-bar-mode) (define-key map (kbd "C-c C-t") 'helm-ff-run-find-file-other-tab)) map) "Keymap for `helm-find-files'.") (defvar helm-read-file-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "") 'helm-cr-empty-string) (define-key map (kbd "M-RET") 'helm-cr-empty-string) (define-key map (kbd "C-]") 'helm-ff-run-toggle-basename) (define-key map (kbd "C-.") 'helm-find-files-up-one-level) (define-key map (kbd "C-l") 'helm-find-files-up-one-level) (define-key map (kbd "C-:") 'helm-ff-complete-tramp-methods) (define-key map (kbd "C-_") 'helm-ff-undo) (define-key map (kbd "C-r") 'helm-find-files-down-last-level) (define-key map (kbd "C-c h") 'helm-ff-file-name-history) (define-key map (kbd "C-x r b") 'helm-ff-bookmark-insert-location) (define-key map (kbd "C-") 'helm-ff-run-toggle-auto-update) (define-key map (kbd "C-c ") 'helm-ff-run-toggle-auto-update) (define-key map (kbd "C-c t") 'helm-ff-toggle-thumbnails) (define-key map (kbd "S-") 'helm-ff-sort-alpha) (define-key map (kbd "S-") 'helm-ff-sort-by-newest) (define-key map (kbd "S-") 'helm-ff-sort-by-size) (define-key map (kbd "S-") 'helm-ff-sort-by-ext) (define-key map (kbd "RET") 'helm-ff-RET) (helm-define-key-with-subkeys map (kbd "DEL") ?\d 'helm-ff-delete-char-backward '((C-backspace . helm-ff-run-toggle-auto-update) ([C-c DEL] . helm-ff-run-toggle-auto-update)) nil 'helm-ff-delete-char-backward--exit-fn) map) "Keymap for `helm-read-file-name'.") ;;; User variables ;; (defgroup helm-files nil "Files applications and libraries for Helm." :group 'helm) (defcustom helm-tramp-verbose 0 "Just like `tramp-verbose' but specific to Helm. When set to 0 don't show tramp messages in Helm. If you want to have the default tramp messages set it to 3." :type 'integer) (defcustom helm-ff-auto-update-initial-value nil "Auto update when only one candidate directory is matched. Default value when starting `helm-find-files' is nil to not confuse new users. For a better experience with `helm-find-files' set this to non-nil and use C- to toggle it." :type 'boolean) (defcustom helm-ff-history-max-length 100 "Number of elements shown in `helm-find-files' history." :type 'integer) (defcustom helm-ff-fuzzy-matching t "Enable fuzzy matching for `helm-find-files' when non--nil. See `helm-ff--transform-pattern-for-completion' for more info." :type 'boolean) (defcustom helm-ff-exif-data-program "exiftran" "Program used to extract exif data of an image file." :type 'string) (defcustom helm-ff-exif-data-program-args "-d" "Arguments used for `helm-ff-exif-data-program'." :type 'string) (defcustom helm-ff-newfile-prompt-p t "Whether Prompt or not when creating new file. This set `ffap-newfile-prompt'." :type 'boolean) (defcustom helm-ff-avfs-directory "~/.avfs" "The default avfs directory, usually \\='~/.avfs'. When this is set you will be able to expand archive filenames with `C-j' inside an avfs directory mounted with mountavfs. See ." :type 'string) (defcustom helm-ff-file-compressed-list '("gz" "bz2" "zip" "7z" "xz") "Minimal list of compressed files extension." :type '(repeat (choice string))) (defcustom helm-ff-printer-list nil "A list of available printers on your system. When non-nil let you choose a printer to print file. Otherwise when nil the variable `printer-name' will be used. On Unix based systems (lpstat command needed) you don't need to set this, `helm-ff-find-printers' will find a list of available printers for you." :type '(repeat (choice string))) (defcustom helm-ff-transformer-show-only-basename t "Show only basename of candidates in `helm-find-files'. This can be toggled at anytime from `helm-find-files' with \ \\\\[helm-ff-run-toggle-basename]. Note that even when non nil, the dotted directories on top i.e. \".\" and \"..\" are still displayed as full path when `helm-ff-show-dot-file-path' is non nil." :type 'boolean) (defcustom helm-ff-show-dot-file-path nil "Show full path of dotted directories when non nil." :type 'boolean :set (lambda (var val) (set-default var val) (clrhash helm-ff--list-directory-cache))) (defcustom helm-ff-signal-error-on-dot-files t "Signal error when file is `.' or `..' on file deletion when non-nil. Default is non-nil. WARNING: Setting this to nil is unsafe and can cause deletion of a whole tree." :type 'boolean) (defcustom helm-ff-search-library-in-sexp nil "Search for library in `require' and `declare-function' sexp." :type 'boolean) (defcustom helm-tooltip-hide-delay 25 "Hide tooltips automatically after this many seconds." :type 'integer) (defcustom helm-ff-file-name-history-use-recentf nil "Use `recentf-list' instead of `file-name-history' in `helm-find-files'." :type 'boolean) (defcustom helm-ff-skip-boring-files nil "Non-nil to skip boring files. I.e. the files matching regexps in `helm-boring-file-regexp-list'. This takes effect in `helm-find-files' and file completion used by `helm-mode' i.e. `helm-read-file-name'. Note that when non-nil this will slow down slightly `helm-find-files'." :type 'boolean) (defcustom helm-ff-skip-git-ignored-files nil "Non-nil to skip git ignored files. This take effect only in `helm-find-files'. Check is not done on remote files. Note that when non-nil this will slow down slightly `helm-find-files'." :type 'boolean) (defcustom helm-ff-candidate-number-limit 5000 "The `helm-candidate-number-limit' for `helm-find-files' and friends. Note that when going one level up with `\\\\[helm-find-files-up-one-level]' the length of directory will be used instead if it is higher than this value. This is to avoid failing to preselect the previous directory/file if this one is situated lower than `helm-ff-candidate-number-limit' num candidate." :type 'integer) (defcustom helm-ff-preselect-ignore-large-dirs nil "Preselect directory belonging to current-buffer even if large." :type 'boolean) (defcustom helm-ff-up-one-level-preselect t "Always preselect previous directory when going one level up. When non-nil `candidate-number-limit' source value is modified dynamically when going one level up if the position of previous candidate in its directory is > to `helm-ff-candidate-number-limit'. It can be helpful to disable this and reduce `helm-ff-candidate-number-limit' if you often navigate across very large directories." :type 'boolean) (defcustom helm-files-save-history-extra-sources '("Find" "Fd" "Locate" "Recentf" "Files from Current Directory" "File Cache" "Etags") "Extras source that save candidate to `file-name-history'." :type '(repeat (choice string))) (defcustom helm-find-files-before-init-hook nil "Hook that run before initialization of `helm-find-files'." :type 'hook) (defcustom helm-find-files-after-init-hook nil "Hook that run after initialization of `helm-find-files'." :type 'hook) (defcustom helm-find-files-bookmark-prefix nil "bookmark name prefix of `helm-find-files' sessions." :type 'string) (defcustom helm-ff-guess-ffap-filenames nil "Use ffap to guess local filenames at point in `helm-find-files'. This doesn't disable url or mail at point, see `helm-ff-guess-ffap-urls' for this." :type 'boolean) (defcustom helm-ff-guess-ffap-urls t "Use ffap to guess local urls at point in `helm-find-files'. This doesn't disable guessing filenames at point, see `helm-ff-guess-ffap-filenames' for this. See also `ffap-url-unwrap-remote' that may override this variable." :type 'boolean) (defcustom helm-ff-no-preselect nil "When non-nil `helm-find-files' starts at root of current directory." :type 'boolean) (defcustom helm-ff-allow-non-existing-file-at-point nil "Use non existing file-at-point as initial input in `helm-find-files'." :type 'boolean) (defcustom helm-find-files-ignore-thing-at-point nil "Use only `default-directory' as default input in `helm-find-files'. I.e. text under cursor in `current-buffer' is ignored. Note that when non-nil you will be unable to complete filename at point in `current-buffer'." :type 'boolean) (defcustom helm-substitute-in-filename-stay-on-remote nil "Don't switch back to local filesystem when expanding pattern with / or ~/." :type 'boolean) (defcustom helm-ff-goto-first-real-dired-exceptions '(dired-goto-file) "Dired commands that are allowed moving to first real candidate." :type '(repeat (choice symbol))) (defcustom helm-mounted-network-directories nil "A list of directories used for mounting remotes filesystem. When nil `helm-file-on-mounted-network-p' always return nil otherwise check if a file is in one of these directories. Remote filesystem are generally mounted with sshfs." :type '(repeat string)) (defcustom helm-browse-project-default-find-files-fn (cond ((or (executable-find "fd") (executable-find "fdfind")) #'helm-browse-project-fd-find-files) ((executable-find "rg") #'helm-browse-project-rg-find-files) ((executable-find "ag") #'helm-browse-project-ag-find-files) (t #'helm-browse-project-walk-directory)) "The default function to retrieve files in a non-vc directory. A function that takes a directory name as only arg." :type 'function) (defcustom helm-ff-kill-or-find-buffer-fname-fn #'helm-ff-kill-or-find-buffer-fname "Default function used to expand non-directory filenames in `helm-find-files'. This variable will take effect only in `helm-find-files'. It affects the behavior of persistent-action on filenames and non-existing filenames. The default is to expand filename on first hit on \\\\[helm-execute-persistent-action], pop buffer in other window on second hit and finally kill this buffer on third hit. This is very handy to create several new buffers, or when navigating, show quickly the buffer of file to see its contents briefly before killing it and continue navigating. However some users may not want this, so to disable this behaviour just set this to `ignore' function. Of course you can also write your own function to do something else." :type 'function) (defcustom helm-modes-using-escaped-strings '(eshell-mode shell-mode term-mode) "Modes that requires string's insertion to be escaped." :type '(repeat symbol)) (defcustom helm-ff-allow-recursive-deletes nil "When \\='always don't prompt for recursive deletion of directories. When nil, will ask for recursive deletion. Note that when deleting multiple directories you can answer ! when prompted to avoid being asked for next directories, so it is probably better to not modify this variable." :type '(choice (const :tag "Delete non-empty directories" t) (const :tag "Confirm for each directory" nil))) (defcustom helm-ff-delete-files-function #'helm-delete-marked-files "The function to use by default to delete files. Default is to delete files synchronously, other choice is to delete files asynchronously. BE AWARE that when deleting async you will not be warned about recursive deletion of directories, IOW non-empty directories will be deleted with no warnings in background!!! It is the function that will be used when using `\\\\[helm-ff-run-delete-file]' from `helm-find-files'." :type '(choice (function :tag "Delete files synchronously." helm-delete-marked-files) (function :tag "Delete files asynchronously." helm-delete-marked-files-async))) (defcustom helm-trash-remote-files nil "Allow trashing remote files when non-nil. Trashing remote files with tramp doesn't work out of the box unless the \\='trash-cli' package is installed. This is why trashing remote files from Helm is disabled by default. Tramp is using external \\='trash' command in its `delete-file' and `delete-directory' handlers when using `delete-by-moving-to-trash', which is documented nowhere in Emacs. If you want to enable this you will have to install the \\='trash' command on remote (and/or locally if you want to trash as root). On Ubuntu-based distributions it is \\='trash-cli'." :type 'boolean) (defvaralias 'helm-list-directory-function 'helm-list-remote-directory-fn) (make-obsolete-variable 'helm-list-directory-function 'helm-list-remote-directory-fn "4.0") (defcustom helm-list-remote-directory-fn (cl-case system-type (gnu/linux #'helm-list-dir-external) (berkeley-unix #'helm-list-dir-lisp) (windows-nt #'helm-list-dir-lisp) (t #'helm-list-dir-lisp)) "The function used in `helm-find-files' to list remote directories. Currently Helm provides two functions to do this: `helm-list-dir-lisp' and `helm-list-dir-external'. Using `helm-list-dir-external' will provide a similar display to what is provided with local files i.e. colorized symlinks, executables files etc., whereas using `helm-list-dir-lisp' will allow colorizing only directories but it is more portable. NOTE: `helm-list-dir-external' needs ls and awk as dependencies. Also the ls version installed on the remote side should support the same arguments as the GNU/ls version, which are -A -1 -F -b and -Q. So even if you are using a GNU/ls version locally and you want to connect e.g. on a Freebsd server, you may have failures due to the incompatible ls version installed on remote server. In such case use `helm-list-dir-lisp' which works everywhere but is slower and less featured (only directories colorized)." :type 'function) (defcustom helm-ff-initial-sort-method nil "Sort method to use when initially listing a directory. It is better to keep this nil globally and turn it on only when needed otherwise it may be slightly slower specially with `ext' method which BTW is not provided on remote files (helm will fallback on nil in such case). Note that this have no effect as soon as you start narrowing directory i.e. filtering filenames inside directory." :type '(choice (const :tag "alphabetically" nil) (const :tag "newest" newest) (const :tag "size" size) (const :tag "extensions" ext))) (defcustom helm-ff-rotate-image-program "exiftran" "External program used to rotate images. When nil and `helm-ff-display-image-native' is enabled, fallback to `image-rotate' without modification of exif data i.e. rotation is not persistent otherwise an error is returned when not using `helm-ff-display-image-native' i.e. using image-dired." :type '(choice (const :tag "Mogrify" "mogrify") (const :tag "Exiftran" "exiftran") (const :tag "Jpegtran" "jpegtran"))) (defcustom helm-ff-rotate-image-switch '("-i") "Options used with `helm-ff-rotate-image-program'. If you are using Mogrify or Jpegtran mandatory option is \"-rotate\", with Exiftran mandatory option is \"-i\"." :type '(repeat string)) (defcustom helm-ff-preferred-shell-mode 'eshell-mode "Shell to use to switch to a shell buffer from `helm-find-files'. Possible values are `shell-mode', `eshell-mode' and `term-mode'. This affects `\\\\[helm-ff-run-switch-to-shell]' keybinding." :type '(choice (const :tag "Use Eshell" eshell-mode) (const :tag "Use Shell" shell-mode) (const :tag "Use Term" term-mode))) (defcustom helm-rsync-no-mode-line-update nil "When non nil don't update mode-line when rsync is running. This is useful if you display the progress bar somewhere else, e.g. with minibuffer-line in minibuffer, in this case updating mode-line may create flickering in other frame's mode-line." :type 'boolean) (defcustom helm-rsync-switches '("-a" "-z" "-h" "-s" "--info=all2") "Rsync options to use with HFF Rsync action. Note: Using \"--info=all2\" allows having the name of the file currently transfered in an help-echo in mode-line, if you use \"--info=progress2\" you will not have this information." :type '(repeat string)) (defcustom helm-rsync-percent-sign "%" "Percentage unicode sign to use in Rsync reporter." :type 'string) (defcustom helm-ff-rsync-progress-bar-style (if (display-graphic-p) 'bar 'text) "Style of progress-bar for rsync action. Value can be either bar or text. Progress bar is inaccurate on non graphic displays, use text instead." :type '(choice (const :tag "Progress bar as a bar" bar) (const :tag "Progress bar with text" text))) (defcustom helm-ff-rsync-progress-bar-info '(percent) "Infos shown at end of Rsync progress bar. Valid value is a list containing one or more elements from percent, size, speed and remain. When set to nil show nothing at end of progress bar. This Has no effect when `helm-ff-rsync-progress-bar-style' is text." :type '(set :tag "Check zero or more items to show at end of Rsync progress bar" (const :tag "Show the amount of data copied" size) (const :tag "Show the percentage of data copied" percent) (const :tag "Show the current speed of transfer" speed) (const :tag "Show the time remaining" remain))) (defcustom helm-rsync-progress-bar-function #'helm-rsync-default-progress-bar "Function used to draw a rsync progress bar in mode-line. Function is called with three args: PROCESS, PERCENT, INFO. PROCESS is the rsync process in use, it's name is displayed before the progress bar, it is useful to display it to distinguish the different processes running e.g. rsync1 rsync2 etc... PERCENT is the current percentage of data sent to the progress bar. INFO is what is displayed after the progress bar according to `helm-ff-rsync-progress-bar-info'. Currently Helm provides two functions to draw the progress bar: - The default progress bar which use the whole height of mode-line and print colored spaces to mimic a progress bar. - The SVG based progress bar which use the external library svg-lib (you will have to install it to use this function) and needs emacs to be compiled with svg support." :type '(choice (function :tag "Default progress bar" helm-rsync-default-progress-bar) (function :tag "SVG progress bar" helm-rsync-svg-progress-bar))) (defcustom helm-trash-default-directory nil "The default trash directory. You probably don't need to set this when using a Linux system using standard settings. Should be the directory file name i.e. don't add final slash. When nil helm will compute a default value according to freedesktop specs. It is generally \"~/.local/share/Trash\"." :type 'string) (defcustom helm-ff-lynx-style-map t "Use arrow keys to navigate with `helm-find-files'. Note that if you define this variable with `setq' your change will have no effect, use customize instead." :type 'boolean :set (lambda (var val) (set var val) (if val (progn (define-key helm-find-files-map (kbd "") 'helm-execute-persistent-action) (define-key helm-find-files-map (kbd "") 'helm-find-files-up-one-level) (define-key helm-read-file-map (kbd "") 'helm-execute-persistent-action) (define-key helm-read-file-map (kbd "") 'helm-find-files-up-one-level)) (define-key helm-find-files-map (kbd "") nil) (define-key helm-find-files-map (kbd "") nil) (define-key helm-read-file-map (kbd "") nil) (define-key helm-read-file-map (kbd "") nil)))) (defcustom helm-ff-DEL-up-one-level-maybe nil "Use DEL to maybe go up one level when non nil. Going up one level works only when pattern is a directory endings with \"/\", otherwise this command deletes char backward. When nil always delete char backward." :type 'boolean) (defcustom helm-ff-display-image-native t "Use native `image-mode' when non nil. You should use this only with Emacs>= 27 and `image-auto-resize' enabled to have images resized properly. When this is enabled, you have new commands to zoom in/out images. See `image-transform-resize' and `image-auto-resize'. Otherwise, when nil `image-dired' is used, using imagemagick as backend. NOTE: On Emacs-29 `image-dired' is no more using external program image-magick to display image, so this is used inconditionally even when value is nil." :type 'boolean) (defcustom helm-ff-reset-filters-on-update t "Reset filter variables when changing directory. When filtering directories/files only, switch back to a \"show all\" view when moving out of directory when non nil." :type 'boolean) (defcustom helm-ff-eshell-unwanted-aliases nil "A list of eshell aliases to not display." :type '(repeat string)) (defcustom helm-eshell-on-file-reverse-history t "History source in *eshell-command-on-file appears on top when non nil." :type 'boolean) (defcustom helm-ff-edit-marked-files-fn #'helm-ff-wfnames "A function to edit filenames in a special buffer. By default `wfnames' package is used to avoid wdired which doesn't always work with all emacs versions and also is quite clumsy about default-directory among other things. If you still want to use it, helm is still providing `helm-marked-files-in-dired'." :type '(choice (function :tag "Use Wfnames package to edit filenames." helm-ff-wfnames) (function :tag "Use Wdired package to edit filenames." helm-marked-files-in-dired))) (defcustom helm-find-files-actions (helm-make-actions "Find File" 'helm-find-file-or-marked "Find file in Dired" 'helm-point-file-in-dired "View file" 'view-file "Query replace fnames on marked `M-@'" 'helm-ff-query-replace-fnames-on-marked (lambda () (helm-acase helm-ff-edit-marked-files-fn (helm-marked-files-in-dired "Edit filenames in Wdired `C-x C-q'") (helm-ff-wfnames "Edit filenames in Wfnames `C-x C-q'"))) 'helm-ff-edit-marked-files "Query replace contents on marked `M-%'" 'helm-ff-query-replace "Query replace regexp contents on marked `C-M-%'" 'helm-ff-query-replace-regexp "Attach file(s) to mail buffer `C-c C-a'" 'helm-ff-mail-attach-files "Serial rename files" 'helm-ff-serial-rename "Serial rename by symlinking files" 'helm-ff-serial-rename-by-symlink "Serial rename by copying files" 'helm-ff-serial-rename-by-copying "Open file with default tool" 'helm-open-file-with-default-tool "Find file in hex dump" 'hexl-find-file "Browse project `C-x C-d'" 'helm-ff-browse-project "Complete at point `C-c i'" 'helm-insert-file-name-completion-at-point "Insert as org link `C-c @'" 'helm-files-insert-as-org-link "Find shell command `C-c /'" 'helm-ff-find-sh-command "Fd shell command (C-/)" 'helm-ff-fd "Find files in file" 'helm-find-files-in-file "Add marked files to file-cache" 'helm-ff-cache-add-file "Open file externally `C-c C-x, C-u to choose'" 'helm-open-file-externally "Grep File(s) `C-s, C-u Recurse'" 'helm-find-files-grep "Grep current directory with AG `M-g a, C-u select type'" 'helm-find-files-ag "Git grep `M-g g, C-u from root'" 'helm-ff-git-grep "Zgrep File(s) `M-g z, C-u Recurse'" 'helm-ff-zgrep "Pdf Grep File(s)" 'helm-ff-pdfgrep "Gid `M-g i'" 'helm-ff-gid "Switch to Eshell `M-e'" 'helm-ff-switch-to-shell "Etags `M-., C-u reload tag file'" 'helm-ff-etags-select "Eshell command on file(s) `M-!, C-u take all marked as arguments.'" 'helm-find-files-eshell-command-on-file "Find file as root `C-c r'" 'helm-find-file-as-root "Find alternate file `C-x C-v'" 'find-alternate-file "Ediff File `C-c ='" 'helm-find-files-ediff-files "Ediff Merge File `M-='" 'helm-find-files-ediff-merge-files (lambda () (format "Delete File(s)%s `M-D' (C-u reverse trash)" (if (eq helm-ff-delete-files-function 'helm-delete-marked-files-async) " async" ""))) 'helm-ff-delete-files "Touch File(s) `M-T'" 'helm-ff-touch-files "Encrypt file(s)" 'helm-ff-encrypt-files "Copy file(s) `M-C, C-u to follow'" 'helm-find-files-copy (lambda () (and (executable-find "rsync") "Rsync file(s) `M-V' (C-u edit command)")) 'helm-find-files-rsync "Rename file(s) `M-R, C-u to follow'" 'helm-find-files-rename "Backup files" 'helm-find-files-backup "Copy file to dir(s) `C-M-c'" 'helm-ff-mcp "Symlink files(s) `M-S, C-u to follow'" 'helm-find-files-symlink "Relsymlink file(s) `M-Y, C-u to follow'" 'helm-find-files-relsymlink "Hardlink file(s) `M-H, C-u to follow'" 'helm-find-files-hardlink "Compress file(s) to archive `M-c'" 'helm-find-files-compress-to "Compress or uncompress file(s) `M-Z'" 'helm-ff-compress-marked-files "Change mode on file(s) `M-M'" 'helm-ff-chmod "Find file other window `C-c o'" 'helm-find-files-other-window "Find file other frame `C-c C-o'" 'find-file-other-frame (lambda () (and (fboundp 'tab-bar-mode) "Find file other tab `C-c C-t'")) 'helm-ff-find-file-other-tab "Print File `C-c p, C-u to refresh'" 'helm-ff-print "Locate `C-x C-f, C-u to specify locate db'" 'helm-ff-locate) "Actions for `helm-find-files'." :type '(alist :key-type string :value-type function)) (defcustom helm-dwim-target nil "Default target directory for file actions. Define the directory where you want to start navigating for the target directory when copying, renaming, etc.. You can use the `default-directory' of `next-window', the visited directory, the current `default-directory' or have completion on all the directories belonging to each visible windows." :type '(radio :tag "Define default target directory for file actions." (const :tag "Directory belonging to next window" next-window) (const :tag "Completion on directories belonging to each window" completion) (const :tag "Use initial directory or `default-directory'" default-directory) (const :tag "Use visited directory" nil))) (defcustom helm-ff-use-notify t "Watch directories visited with `helm-find-files' when non nil. If your system have no file notification package available turn this to nil to avoid error messages when using `helm-find-files'." :type 'boolean :set (lambda (var val) (set-default var val) (unless (symbol-value var) (cl-loop for dir being the hash-keys of helm-ff--file-notify-watchers do (remhash dir helm-ff--list-directory-cache))))) (defcustom helm-ff-inotify-unsupported-methods '("adb") "Tramp methods unsupported by file-notify." :type '(repeat string)) (defcustom helm-ff-image-cache-max-len 5 "The last seen image number to keep in cache." :type 'integer) (defcustom helm-ff-image-cache-max-len 5 "The last seen image number to keep in cache." :type 'integer) (defcustom helm-ff-slideshow-default-delay 3 "Delay in seconds between each image in slideshow." :type 'integer) (defcustom helm-file-name-history-hide-deleted nil "Hide deleted files in file-name-history when non nil. This can be toggled at any time from `helm-ff-file-name-history' with \ \\\\[helm-file-name-history-show-or-hide-deleted]." :type 'boolean) (defcustom helm-file-name-history-max-length 72 "Max length of candidates in helm file name history before truncating." :type 'integer) (defcustom helm-ff-follow-blacklist-file-exts '("gpg" "doc" "docx" "mp3" "ogg") "File extensions we don't want to follow when helm-follow-mode is enabled. Note that image files are always followed even if their extensions is present in this list." :type '(repeat string)) (defcustom helm-ff-nohighlight-matches nil "Highlight matches in `helm-find-files' when nil." :type 'boolean :initialize 'custom-initialize-changed :set (lambda (var val) (set var val) ;; Force rebuilding the source to remove the highlight match FCT. (setq helm-source-find-files nil))) (defcustom helm-ff-ignore-following-on-directory nil "In follow mode ignore silently directories when non nil." :type 'boolean) (defcustom helm-ff-dim-prompt-on-update t "When non nil dim prompt while updating." :type 'boolean) (defcustom helm-ff-drag-mouse-1-default-action 'copy "Default action when dragging files. Possible values are `copy', `move' or `link'. Currently supported value is `copy' when dropping to a dired buffer, `move' is working only when dropping on an external application (only thunar tested)." :type '(choice (const :tag "Copy" copy) (const :tag "Move" move) (const :tag "Link" link))) ;;; Faces ;; ;; (defgroup helm-files-faces nil "Customize the appearance of helm-files." :prefix "helm-" :group 'helm-files :group 'helm-faces) (defface helm-ff-prefix `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "yellow" :foreground "black")) "Face used to prefix new file or url paths in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-executable `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "green")) "Face used for executable files in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-suid `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "red" :foreground "white")) "Face used for suid files in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-directory `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "DarkRed" :background "LightGray")) "Face used for directories in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-dotted-directory `((((class color) (min-colors 88) (background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "black" :background "DimGray") (((class color) (min-colors 88) (background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "black" :background "LightGray") (t :inherit default)) "Face used for dotted directories in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-dotted-symlink-directory `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "DarkOrange" :background "DimGray")) "Face used for dotted symlinked directories in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-symlink `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit font-lock-comment-face)) "Face used for symlinks in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-invalid-symlink `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "black" :background "red")) "Face used for invalid symlinks in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-denied `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "red" :background "black")) "Face used for non accessible files in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-file `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit font-lock-builtin-face)) "Face used for file names in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-nofile `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit helm-ff-file)) "Face used for non-files file names in `helm-find-files'. Used when showing tramp host completions." :group 'helm-files-faces) (defface helm-ff-truename `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit font-lock-string-face)) "Face used for symlink truenames in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-dirs `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit font-lock-function-name-face)) "Face used for file names in recursive dirs completion in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-socket `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "DeepPink")) "Face used for socket files in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-pipe `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "yellow" :background "black")) "Face used for named pipes and character device files in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-file-extension `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "magenta")) "Face used for file extensions in `helm-find-files'." :group 'helm-files-faces) (defface helm-ff-backup-file `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "DimGray")) "Face used for backup files in `helm-find-files'." :group 'helm-files-faces) (defface helm-history-deleted `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit helm-ff-invalid-symlink)) "Face used for deleted files in `file-name-history'." :group 'helm-files-faces) (defface helm-history-remote `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Indianred1")) "Face used for remote files in `file-name-history'." :group 'helm-files-faces) (defface helm-delete-async-message `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "yellow")) "Face used for mode-line message." :group 'helm-files-faces) (defface helm-ff-rsync-progress `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit font-lock-warning-face)) "Face used for rsync mode-line indicator." :group 'helm-files-faces) (defface helm-ff-rsync-progress-1 `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "CadetBlue" :foreground "black")) "Face used for rsync progress bar percentage and proc name." :group 'helm-files-faces) (defface helm-ff-rsync-progress-2 `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "orange")) "Face used for rsync progress bar progress." :group 'helm-files-faces) (defface helm-ff-rsync-progress-3 `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "white")) "Face used for rsync progress bar background." :group 'helm-files-faces) (defface helm-ff-rsync-progress-svg `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "black" :foreground "white")) "Face used for rsync svg progress bar." :group 'helm-files-faces) ;;; Helm-find-files ;; ;; (defclass helm-source-ffiles (helm-source-sync) ((header-name :initform (lambda (name) (concat name (substitute-command-keys helm-find-files-doc-header)))) (init :initform (lambda () (setq helm-ff-auto-update-flag helm-ff-auto-update-initial-value) (setq helm-ff--auto-update-state helm-ff-auto-update-flag) (helm-set-local-variable 'bookmark-make-record-function #'helm-ff-make-bookmark-record) (require 'helm-external))) (candidates :initform 'helm-find-files-get-candidates) (update :initform (lambda () (remhash helm-ff-default-directory helm-ff--list-directory-cache))) (match-on-real :initform t) (filtered-candidate-transformer :initform '(helm-ff-fct helm-ff-maybe-show-thumbnails ;; These next two have to be called after ;; `helm-ff-fct' as they use only cons cell candidates. helm-ff-directories-only helm-ff-files-only helm-ff-sort-candidates)) (popup-info :initform (lambda (candidate) (unless (helm-ff-dot-file-p candidate) (helm-file-attributes candidate :dired t :human-size t :octal nil)))) (persistent-action-if :initform 'helm-find-files-persistent-action-if) (persistent-help :initform "Hit1 Expand Candidate, Hit2 or (C-u) Find file") (help-message :initform 'helm-ff-help-message) (mode-line :initform (list "File(s)" helm-mode-line-string)) (volatile :initform t) (cleanup :initform 'helm-find-files-cleanup) (migemo :initform t) (nohighlight :initform (progn helm-ff-nohighlight-matches)) (keymap :initform 'helm-find-files-map) (candidate-number-limit :initform 'helm-ff-candidate-number-limit) (suspend-follow-in-update :initarg :suspend-follow-in-update :initform t :documentation "Prevent following candidate when updating.") (completing-file-name :initarg :completing-file-name :initform t :documentation "Flag to notify `helm-resume' we are completing filenames.") (action-transformer :initform 'helm-find-files-action-transformer) (action :initform 'helm-find-files-actions) (before-init-hook :initform 'helm-find-files-before-init-hook) (after-init-hook :initform 'helm-find-files-after-init-hook) (all-marked :initform t) (group :initform 'helm-files))) ;; Bookmark handlers. ;; (defun helm-ff-make-bookmark-record () "The `bookmark-make-record-function' for `helm-find-files'." (with-helm-buffer `((filename . ,helm-ff-default-directory) (presel . ,(helm-get-selection)) (handler . helm-ff-bookmark-jump)))) (defun helm-ff-bookmark-jump (bookmark) "bookmark handler for `helm-find-files'." (let ((fname (bookmark-prop-get bookmark 'filename)) (presel (bookmark-prop-get bookmark 'presel))) ;; Force tramp connection with `file-directory-p' before lauching ;; hff otherwise the directory name is inserted on top before ;; tramp starts and display candidates. FNAME is here always a ;; directory. (when (file-directory-p fname) (helm-find-files-1 fname (format helm-ff-last-expanded-candidate-regexp (if helm-ff-transformer-show-only-basename (regexp-quote (helm-basename presel)) (regexp-quote presel))))))) (defun helm-ff-bookmark-set () "Record `helm-find-files' session in bookmarks." (interactive) (with-helm-alive-p (with-helm-buffer (bookmark-set (concat helm-find-files-bookmark-prefix (abbreviate-file-name helm-ff-default-directory)))) (message "Helm find files session bookmarked! "))) (put 'helm-ff-bookmark-set 'helm-only t) (defun helm-dwim-target-directory () "Try to return a suitable directory according to `helm-dwim-target'." (with-selected-window (or ;; Try next-window if current-buffer has been ;; killed during this session probably by C-d. (get-buffer-window helm-current-buffer) (next-window (helm-window) 1)) (let ((wins (remove (get-buffer-window helm-marked-buffer-name) (window-list)))) (expand-file-name (cond (;; Provide completion on all the directory belonging to ;; visible windows if some. (and (cdr wins) (eq helm-dwim-target 'completion)) (helm-comp-read "Browse target starting from: " (append (list (or (car-safe helm-ff-history) default-directory) default-directory) (cl-loop for w in wins collect (with-selected-window w default-directory))))) ;; Use default-directory of next-window. ((and (cdr wins) (eq helm-dwim-target 'next-window)) (with-selected-window (next-window) default-directory)) ;; Always use default-directory when only one window. ((and (null (cdr wins)) (eq helm-dwim-target 'default-directory)) default-directory) ;; Use the visited directory. ((or (null (cdr wins)) (null helm-dwim-target)) ;; Using the car of *ff-history allow ;; staying in the directory visited instead of ;; current. (or (car-safe helm-ff-history) default-directory))))))) (defsubst helm-ff--file-directory-p (file) (if (file-remote-p file) (get-text-property 1 'helm-ff-dir file) (file-directory-p file))) (defun helm-ff--count-and-collect-dups (files) (cl-loop with dups = (make-hash-table :test 'equal) for f in files for file = (if (helm-ff--file-directory-p f) (concat (helm-basename f) "/") (helm-basename f)) for count = (gethash file dups) if count do (puthash file (1+ count) dups) else do (puthash file 1 dups) finally return (cl-loop for k being the hash-keys in dups using (hash-value v) if (> v 1) collect (format "%s(%s)" k v) else collect k))) (defun helm-find-files-do-action (action &optional target) "Generic function for creating actions from `helm-source-find-files'. ACTION can be `rsync' or any action supported by `helm-dired-action'." (require 'dired-async) (when (eq action 'rsync) (cl-assert (executable-find "rsync") nil "No command named rsync")) (let* (dired-create-destination-dirs ; We handle dirs creation ourself. (rsync-switches (when (and (eq action 'rsync) helm-current-prefix-arg) (cdr (split-string (read-string "Run rsync like this: " (mapconcat 'identity (cons "rsync" helm-rsync-switches) " ") 'helm-rsync-command-history))))) (ifiles (mapcar 'expand-file-name ; Allow modify '/foo/.' -> '/foo' ;; Use :all-sources to allow actions on wildcards ;; marked in dummy source. FIXME: We should use ;; :all-sources when :all-marked is non nil only, it is ;; true by default but user may modify HFF source by defmethod. (helm-marked-candidates :with-wildcard t :all-sources t))) (cand (unless (cdr ifiles) (helm-get-selection))) ; preselection. (prefarg helm-current-prefix-arg) (prompt (format "%s %s file(s) %s: " (if (and (and (fboundp 'dired-async-mode) dired-async-mode) (not (eq action 'rsync)) (null prefarg)) (concat "Async " (symbol-name action)) (capitalize (symbol-name action))) (length ifiles) (if (memq action '(symlink relsymlink hardlink)) "from" "to"))) (cdir (helm-aand (eq action 'compress) (helm-common-dir ifiles) (if (stringp it) (file-name-as-directory it) (error "Try to compress files not belonging to same drive")))) helm-ff--move-to-first-real-candidate helm-display-source-at-screen-top ; prevent setting window-start. helm-ff-auto-update-initial-value ;; It is not possible to rename a file to a boring name when ;; helm-ff-skip-boring-files is enabled helm-ff-skip-boring-files ;; If HFF is using a frame use a frame as well. (helm-actions-inherit-frame-settings t) helm-use-frame-when-more-than-two-windows (dest (or target (with-helm-display-marked-candidates helm-marked-buffer-name (if cdir (mapcar (lambda (f) (file-relative-name f cdir)) ifiles) (helm-ff--count-and-collect-dups ifiles)) (with-helm-current-buffer (helm-read-file-name prompt :preselect (when cand (format helm-ff-last-expanded-candidate-regexp (regexp-quote (if helm-ff-transformer-show-only-basename (helm-basename cand) cand)))) :default (and cdir (expand-file-name (format "%s.tar.gz" (if cand (helm-basename cand) "new_archive")) cdir)) :must-match (and cdir (lambda (f) (not (file-directory-p f)))) :initial-input (or cdir (helm-dwim-target-directory)) :history (helm-find-files-history nil :comp-read nil)))))) (dest-dir-p (file-directory-p dest)) (dest-dir (if dest-dir-p dest (helm-basedir dest)))) ;; Ignore `dired-create-destination-dirs' and handle directory creation from ;; here like we were doing before. Dired is failing to create directories ;; when e.g. symlinking some files to a not yet existing directory. (unless (or dest-dir-p (file-directory-p dest-dir)) (when (y-or-n-p (format "Create directory `%s'? " dest-dir)) ;; When saying No here with rsync, `helm-rsync-copy-files' will raise an ;; error about dest not existing. (make-directory dest-dir t))) (helm-acase action (rsync (helm-rsync-copy-files ifiles dest rsync-switches)) (compress (helm-do-compress-to ifiles dest)) (t (helm-dired-action dest :files ifiles :action action :follow prefarg))))) ;; Rsync ;; (defun helm-rsync-remote2rsync (file) (if (file-remote-p file) (let ((localname (directory-file-name (expand-file-name (file-remote-p file 'localname)))) (user (file-remote-p file 'user)) ;; Tramp name may contain port e.g. /ssh:host#2222:/foo. (host (replace-regexp-in-string "#[0-9]+" "" (file-remote-p file 'host)))) (if user (format "%s@%s:%s" user host (shell-quote-argument localname)) (format "%s:%s" host (shell-quote-argument localname)))) (shell-quote-argument (directory-file-name (expand-file-name file))))) (defun helm-rsync-format-mode-line-str (proc) (helm-aif (and (process-live-p proc) (assoc-default proc helm-rsync-progress-str-alist)) (progn ;; When rsync progress bar stop for some reason (e.g. rsync ;; takes time to finalize writing file to disk), no output is ;; coming from filter process, as a result the progress bar ;; disapear for a while giving no information to user while ;; the rsync process continues, so keep printing the last valid ;; progress bar (stored in `helm-rsync--last-progress-bar-alist') ;; instead of sending empty string. (unless (equal it "") (push (cons proc it) helm-rsync--last-progress-bar-alist)) (if (eq helm-ff-rsync-progress-bar-style 'text) (format " [%s]" (propertize (or (assoc-default proc helm-rsync--last-progress-bar-alist) ;; Avoid (wrong-type-argument stringp ;; nil) when process is not ready. "") 'face 'helm-ff-rsync-progress)) (format " %s" (or (assoc-default proc helm-rsync--last-progress-bar-alist) ;; Avoid (wrong-type-argument stringp ;; nil) when process is not ready. "")))))) (defun helm-ff--rsync-progress-bar (progbar proc) ;; progbar == " 2,83G 92% 98,65MB/s 0:00:02 " (let ((infos (split-string (replace-regexp-in-string "%" helm-rsync-percent-sign progbar) " " t)) percent info) (if (eq helm-ff-rsync-progress-bar-style 'text) (mapconcat 'identity infos " ") (setq info (mapconcat (lambda (x) (helm-acase x (size (nth 0 infos)) (percent (nth 1 infos)) (speed (nth 2 infos)) (remain (nth 3 infos)))) (helm-mklist helm-ff-rsync-progress-bar-info) ", ")) (when (string-match "\\([0-9]+\\)%" progbar) (setq percent (string-to-number (match-string 1 progbar)))) (if percent (funcall helm-rsync-progress-bar-function proc percent info) "")))) (defun helm-rsync-default-progress-bar (proc percent info) (format "%s%s%s%s" (propertize (capitalize (replace-regexp-in-string "<\\([0-9]+\\)>" "(\\1)" (process-name proc))) 'display '(height 0.9) 'face 'helm-ff-rsync-progress-1) (propertize " " 'display `(space :width ,(list percent)) 'face 'helm-ff-rsync-progress-2) (propertize " " 'display `(space :width ,(list (- 100 percent))) 'face 'helm-ff-rsync-progress-3) (propertize info 'display '(height 0.9) 'face 'helm-ff-rsync-progress-1))) (defun helm-rsync-svg-progress-bar (proc percent info) (cl-assert (image-type-available-p 'svg) nil "svg-lib requires Emacs to be compiled with svg support") (require 'svg-lib) (format "%s%s%s" (propertize " " 'display (svg-lib-tag (process-name proc) 'helm-ff-rsync-progress-svg)) (propertize " " 'display (svg-lib-progress-bar (/ (+ percent 7) 105.0) 'helm-ff-rsync-progress-svg :width 10 :margin 1 :stroke 2 :padding 2)) (if (string= info "") "" (propertize " " 'display (svg-lib-tag info 'helm-ff-rsync-progress-svg))))) (defun helm-rsync-mode-line (proc) "Add Rsync progress to the mode line." (or global-mode-string (setq global-mode-string '(""))) (unless (member `(:eval (helm-rsync-format-mode-line-str ,proc)) global-mode-string) (setq global-mode-string (append global-mode-string `((:eval (helm-rsync-format-mode-line-str ,proc))))))) (defun helm-rsync-restore-mode-line (proc) "Restore the mode line when Rsync finishes." (setq global-mode-string (remove `(:eval (helm-rsync-format-mode-line-str ,proc)) global-mode-string)) (setq helm-rsync--last-progress-bar-alist nil) (force-mode-line-update)) (defun helm-rsync-copy-files (files dest &optional switches) "Send FILES to DEST using Rsync with SWITCHES as arguments. DEST must be a directory. SWITCHES when unspecified default to `helm-rsync-switches'." (cl-assert (file-directory-p dest) t) (setq files (cl-loop for f in files collect (helm-rsync-remote2rsync f)) dest (helm-rsync-remote2rsync dest)) (let* ((buf (generate-new-buffer-name helm-rsync-process-buffer)) (host (file-remote-p dest 'host)) (port (when (and host (string-match "#\\([0-9]+\\)" host)) (match-string 1 host))) (proc (start-process-shell-command "rsync" buf (format "rsync %s" (mapconcat 'identity (append (or switches helm-rsync-switches) (and port ;; Add automatically port ;; specified in tramp name ;; unless user already specified ;; it himself with the -e option ;; by editing command. switches (cl-loop for arg in switches never (string-match-p "\\`-e" arg)) (list (format "-e 'ssh -p %s'" port))) files (list dest)) " "))))) (helm-rsync-mode-line proc) (set-process-sentinel proc (lambda (process event) (cond ((string= event "finished\n") (message "%s copied %s files" (capitalize (process-name process)) (length files))) (t (error "Process %s %s with code %s" (process-name process) (process-status process) (process-exit-status process)))) (setq helm-rsync-progress-str-alist (delete (assoc process helm-rsync-progress-str-alist) helm-rsync-progress-str-alist)) (helm-rsync-restore-mode-line process) (force-mode-line-update))) (set-process-filter proc #'helm-rsync-process-filter))) (defun helm-rsync-process-filter (proc output) "Filter process function used by `helm-rsync-copy-files'." (let ((inhibit-read-only t) fname progbar) (with-current-buffer (process-buffer proc) (when (string-match comint-password-prompt-regexp output) ;; FIXME: Fully not tested and ;; use an agent or auth-source ;; or whatever to get password if ;; available. (process-send-string proc (concat (read-passwd (match-string 0 output)) "\n"))) ;; Extract the progress bar. (with-temp-buffer (insert output) (when (re-search-backward "[[:cntrl:]]" nil t) (setq progbar (buffer-substring-no-properties (match-end 0) (point-max))))) ;; Insert the text, advancing the process marker. (save-excursion (goto-char (process-mark proc)) (insert output) (set-marker (process-mark proc) (point))) (goto-char (process-mark proc)) ;; Extract the file name currently ;; copied (Imply --info=all2 or all1). (save-excursion (when (re-search-backward "^[^[:cntrl:]]" nil t) (setq fname (helm-basename (buffer-substring-no-properties (point) (pos-eol)))))) ;; Now format the string for the mode-line. (let ((ml-str (helm-ff--rsync-progress-bar progbar proc))) (setq ml-str (propertize ml-str 'help-echo (format "%s->%s" (process-name proc) fname))) ;; Now associate the formatted ;; progress-bar string with process. (helm-aif (assoc proc helm-rsync-progress-str-alist) (setcdr it ml-str) (setq helm-rsync-progress-str-alist (push (cons proc ml-str) helm-rsync-progress-str-alist))))) ;; Finally update mode-line. (unless helm-rsync-no-mode-line-update (force-mode-line-update)))) (defun helm-ff-kill-rsync-process (process) "Kill rsync process PROCESS. When called interactively prompt user with completion when more than one process." (interactive (list (get-process (helm-comp-read "Kill rsync process: " (mapcar (lambda (x) (process-name (car x))) helm-rsync-progress-str-alist) :exec-when-only-one t)))) (with-current-buffer (process-buffer process) (delete-process process) (kill-buffer)) (setq helm-rsync-progress-str-alist (delete (assoc process helm-rsync-progress-str-alist) helm-rsync-progress-str-alist))) (defun helm-find-files-rsync (_candidate) "Rsync files from `helm-find-files'." (helm-find-files-do-action 'rsync)) (defun helm-find-files-copy (_candidate) "Copy files from `helm-find-files'." (helm-find-files-do-action 'copy)) (defun helm-find-files-backup (_candidate) "Backup files from `helm-find-files'. This reproduce the behavior of \"cp --backup=numbered from to\"." (cl-assert (and (fboundp 'dired-async-mode) dired-async-mode) nil "Backup only available when `dired-async-mode' is enabled") (helm-find-files-do-action 'backup)) (defun helm-find-files-rename (_candidate) "Rename files from `helm-find-files'." (helm-find-files-do-action 'rename)) (defun helm-find-files-symlink (_candidate) "Symlink files from `helm-find-files'." (helm-find-files-do-action 'symlink)) (defun helm-find-files-relsymlink (_candidate) "Relsymlink files from `helm-find-files'." (helm-find-files-do-action 'relsymlink)) (defun helm-find-files-hardlink (_candidate) "Hardlink files from `helm-find-files'." (helm-find-files-do-action 'hardlink)) (defun helm-find-files-compress-to (_candidate) "Compress to archive from `helm-find-files'." (helm-find-files-do-action 'compress)) (defun helm-ff-compress-marked-files (_candidate) "Compress or uncompress marked files with `dired-compress-file'." (require 'dired-async) (let* ((files (helm-marked-candidates :with-wildcard t))) (if (not (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar #'abbreviate-file-name files) (y-or-n-p (format "Compress or uncompress *%s File(s)" (length files))))) (message "(No (un)compression performed)") (process-put (async-start `(lambda () (require 'dired-aux) (let ((len 0)) (dolist (i ',files) (let ((default-directory (file-name-directory i))) (when (dired-compress-file i) (cl-incf len)))) len)) (lambda (result) (unless (dired-async-processes 'helm-async-compress) (helm-ff--compress-async-modeline-mode -1)) (message "%s File(s) (un)compressed" result) (run-with-timer 0.1 nil (lambda (len flist) (dired-async-mode-line-message "%s %d/%d file(s) done" 'helm-delete-async-message "(Un)compressing" len (length flist))) result files))) 'helm-async-compress t) (helm-ff--compress-async-modeline-mode 1)))) (helm-make-command-from-action helm-ff-run-compress-marked-files "Compress or uncompress marked files." 'helm-ff-compress-marked-files) (defun helm-ff-chmod (_candidate) "Set file mode on marked files. If no mode is specified in prompt, default mode will be the mode of the car of marked files i.e. the first marked file." (let* ((mkd (helm-marked-candidates :with-wildcard t)) (model (car mkd)) (default (helm-file-attributes model :octal t)) (mode (read-file-modes nil model)) (smode (file-modes-number-to-symbolic mode)) (candidates (if (string= default (format "%o" mode)) (cdr mkd) mkd))) (with-helm-display-marked-candidates helm-marked-buffer-name (helm-ff--count-and-collect-dups candidates) (when (y-or-n-p (format "Change file mode to `%s'? " smode)) (dolist (f candidates) (unless (file-symlink-p f) ;; For now don't use the FLAG arg 'nofollow of `set-file-modes' for ;; Emacs-26 compatibility. (set-file-modes f mode))) (message "Changed file mode to `%s' on %s file(s)" smode (length candidates)))))) (defun helm-find-files-other-window (_candidate) "Keep current-buffer and open files in separate windows. When a prefix arg is detected files are opened in a vertical windows layout." (let* ((files (helm-marked-candidates)) (buffers (mapcar 'find-file-noselect files))) (helm-window-show-buffers buffers t))) (defun helm-find-files-byte-compile (_candidate) "Byte compile elisp files from `helm-find-files'." (let ((files (helm-marked-candidates :with-wildcard t)) (parg helm-current-prefix-arg)) (cl-loop for fname in files do (condition-case _err (with-no-warnings (byte-compile-file fname parg)) (wrong-number-of-arguments ;; Emacs-28 accepts only one arg. (byte-compile-file fname) (when parg (load fname))))))) (defun helm-find-files-load-files (_candidate) "Load elisp files from `helm-find-files'." (let ((files (helm-marked-candidates :with-wildcard t))) (cl-loop for fname in files do (load fname)))) (defun helm-find-files-ediff-files-1 (candidate &optional merge) "Generic function to ediff/merge files in `helm-find-files'." (let* ((helm-dwim-target 'next-window) (bname (helm-basename candidate)) (marked (helm-marked-candidates :with-wildcard t)) (prompt (if merge "Ediff Merge `%s' With File: " "Ediff `%s' With File: ")) (fun (if merge 'ediff-merge-files 'ediff-files)) (input (helm-dwim-target-directory)) (presel (if helm-ff-transformer-show-only-basename (helm-basename candidate) (expand-file-name (helm-basename candidate) input)))) (if (= (length marked) 2) (funcall fun (car marked) (cadr marked)) (funcall fun candidate (helm-read-file-name (format prompt bname) :initial-input input :preselect presel))))) (defun helm-find-files-ediff-files (candidate) (helm-find-files-ediff-files-1 candidate)) (defun helm-find-files-ediff-merge-files (candidate) (helm-find-files-ediff-files-1 candidate 'merge)) (defun helm-find-files-grep (_candidate) "Default action to grep files from `helm-find-files'." (helm-do-grep-1 (helm-marked-candidates :with-wildcard t) helm-current-prefix-arg)) (defun helm-ff-git-grep (_candidate) "Default action to git-grep `helm-ff-default-directory'." (helm-grep-git-1 helm-ff-default-directory helm-current-prefix-arg)) (defun helm-find-files-ag (_candidate) (helm-grep-ag helm-ff-default-directory helm-current-prefix-arg)) (defun helm-ff-zgrep (_candidate) "Default action to zgrep files from `helm-find-files'." (helm-ff-zgrep-1 (helm-marked-candidates :with-wildcard t) helm-current-prefix-arg)) (defun helm-ff-pdfgrep (_candidate) "Default action to pdfgrep files from `helm-find-files'." (let* ((recurse nil) (cands (cl-loop for file in (helm-marked-candidates :with-wildcard t) for dir = (file-directory-p file) when dir do (setq recurse t) when (or dir (string= (file-name-extension file) "pdf") (string= (file-name-extension file) "PDF")) collect file))) (when cands (helm-do-pdfgrep-1 cands recurse)))) (defun helm-ff-etags-select (candidate) "Default action to jump to etags from `helm-find-files'." (when (get-buffer helm-action-buffer) (kill-buffer helm-action-buffer)) (let* ((source-name (assoc-default 'name (helm-get-current-source))) (default-directory (if (string= source-name "Find Files") helm-ff-default-directory (file-name-directory candidate)))) (helm-etags-select helm-current-prefix-arg))) ;;; Eshell command on file ;; (defvar eshell-command-aliases-list nil) (defvar helm-eshell-command-on-file-input-history nil) (defvar helm-eshell-command-on-file-history nil) (cl-defun helm-find-files-eshell-command-on-file-1 (&optional map) "Run `eshell-command' on CANDIDATE or marked candidates. This is done possibly with an Eshell alias. If no alias found, you can type in an Eshell command. Only aliases accepting a file as argument at the end of command line are collected, i.e. aliases ending with \"$1\" or \"$*\". Basename of CANDIDATE can be a wild-card. E.g. you can do \"eshell-command command *.el\" Where \"*.el\" is the CANDIDATE. It is possible to do eshell-command command like this: \"command %s some more args\". If MAP is given run `eshell-command' on all marked files at once, Otherwise, run `eshell-command' on each marked files. In other terms, with a prefix arg do on the three marked files \"foo\" \"bar\" \"baz\": \"eshell-command command foo bar baz\" otherwise do \"eshell-command command foo\" \"eshell-command command bar\" \"eshell-command command baz\" Note: You have to setup some aliases in Eshell with the `alias' command or by editing yourself the file `eshell-aliases-file' to make this working." (require 'helm-adaptive) (require 'em-alias) (eshell-read-aliases-list) (unless (> emacs-major-version 27) ;; This advice has been merged in emacs-28. (advice-add 'eshell-eval-command :override #'helm--advice-eshell-eval-command)) (when (or eshell-command-aliases-list (y-or-n-p "No eshell aliases found, run eshell-command without alias anyway? ")) (let* ((cand-list (helm-marked-candidates :with-wildcard t)) (default-directory (or helm-ff-default-directory ;; If candidate is an url *-ff-default-directory is nil ;; so keep value of default-directory. default-directory)) helm-display-source-at-screen-top (helm-actions-inherit-frame-settings t) helm-use-frame-when-more-than-two-windows (command (with-helm-display-marked-candidates helm-marked-buffer-name (helm-ff--count-and-collect-dups (mapcar 'helm-basename cand-list)) (with-helm-current-buffer (helm-comp-read "Command: " (cl-loop with len = 0 with aliases = (cl-loop for (a c) in (eshell-read-aliases-list) for len-key = (length a) when (and (string-match "\\(\\$1\\|\\$\\*\\)" c) (not (member a helm-ff-eshell-unwanted-aliases))) do (when (> len-key len) (setq len len-key)) and collect (list a c)) for (a c) in aliases collect (cons (concat (propertize a 'face 'font-lock-keyword-face) (make-string (1+ (- len (length a))) ? ) c) a)) :fc-transformer #'helm-adaptive-sort :buffer "*helm eshell on file*" :name "Eshell command" :mode-line '("Eshell alias" "C-h m: Help, \\[universal-argument]: Insert output at point") :help-message 'helm-esh-help-message :history 'helm-eshell-command-on-file-history :raw-history t :reverse-history helm-eshell-on-file-reverse-history :input-history 'helm-eshell-command-on-file-input-history ;; Allow quoting when writing in minibuffer i.e. allow usage of ;; \@ and \#. :raw-candidate t)))) (alias-value (car (assoc-default command eshell-command-aliases-list))) cmd-line) (if (or (equal helm-current-prefix-arg '(16)) (equal map '(16))) ;; Two time C-u from `helm-comp-read' mean print to current-buffer. ;; i.e `eshell-command' will use this value. (setq current-prefix-arg '(16)) ;; Else reset the value of `current-prefix-arg' ;; to avoid printing in current-buffer. (setq current-prefix-arg nil)) (if (and (or ;; One prefix-arg have been passed before `helm-comp-read'. ;; If map have been set with C-u C-u (value == '(16)) ;; ignore it. (and map (equal map '(4))) ;; One C-u from `helm-comp-read'. (equal helm-current-prefix-arg '(4)) ;; An alias that finish with $* (and alias-value ;; If command is an alias be sure it accept ;; more than one arg i.e $*. (string-match "\\$\\*" alias-value))) (cdr cand-list) (and alias-value ;; Command is an alias and accept only one arg. (not (string-match "\\$1" alias-value)))) ;; Run eshell-command with ALL marked files as argument. ;; This wont work on remote files, because tramp handlers depend ;; on `default-directory' (limitation). (let ((mapfiles (mapconcat 'shell-quote-argument cand-list " "))) (if (string-match "%s" command) (setq cmd-line (format command mapfiles)) ; See [1] (setq cmd-line (format "%s %s" command mapfiles))) (eshell-command cmd-line)) ;; Run eshell-command sequencially on EACH marked files. ;; To work with tramp handler we have to call ;; COMMAND on basename of each file, using ;; its basedir as `default-directory'. (unwind-protect (progn (cl-loop for f in cand-list for n from 1 for dir = (and (not (string-match helm--url-regexp f)) (helm-basedir f)) ;; We can use basename here as the command will run ;; under default-directory. ;; This allows running e.g. ;; "tar czvf test.tar.gz %s/*" without creating ;; an archive expanding from /home. for file = (shell-quote-argument (if (string-match helm--url-regexp f) f (helm-basename f))) ;; \@ => placeholder for file without extension. ;; \# => placeholder for incremental number. for fcmd = (helm-aand command (replace-regexp-in-string "\\\\#" (format "%03d" n) it t t) (replace-regexp-in-string "\\\\@" (regexp-quote (file-name-sans-extension file)) it t t)) for com = (if (string-match "%s" fcmd) ;; [1] This allows to enter other args AFTER filename ;; i.e (format fcmd file) (format "%s %s" fcmd file)) do (let ((default-directory (or dir default-directory))) (eshell-command com)))) ;; Async process continues running but doesn't need anymore ;; the advice at this point (see the `eshell-eval-command' ;; call in `eshell-command'). (unless (> emacs-major-version 27) (advice-remove 'eshell-eval-command #'helm--advice-eshell-eval-command))))))) (defun helm--advice-eshell-eval-command (command &optional input) "Fix return value when command ends with \"&\"." ;; Fix this emacs commit which is plain wrong as it returns ;; either nil or an error (double because format spec doesn't ;; always match specifier) whereas it should return either a ;; single element (CAR DELIM) or DELIM itself if the car of ;; DELIM is a process. ;; This prevent running eshell-command async when needed i.e. when ;; command ends with "&". ;; ;; UPDATE: This have now been merged in Emacs-28. ;; ;; 6b6f91b357f6fe2f1e0d72f046a1b8d8a2d6d8c3 ;; Author: John Wiegley ;; AuthorDate: Fri May 27 02:57:18 2005 +0000 ;; Commit: John Wiegley ;; CommitDate: Fri May 27 02:57:18 2005 +0000 (if eshell-current-command ;; we can just stick the new command at the end of the current ;; one, and everything will happen as it should (setcdr (last (cdr eshell-current-command)) (list `(let ((here (and (eobp) (point)))) ,(and input `(insert-and-inherit ,(concat input "\n"))) (if here (eshell-update-markers here)) (eshell-do-eval ',command)))) (and eshell-debug-command (with-current-buffer (get-buffer-create "*eshell last cmd*") (erase-buffer) (insert "command: \"" input "\"\n"))) (setq eshell-current-command command) (let* ((delim (catch 'eshell-incomplete (eshell-resume-eval))) (val (car-safe delim))) ;; If the return value of `eshell-resume-eval' is wrapped in a ;; list, it indicates that the command was run asynchronously. ;; In that case, unwrap the value before checking the delimiter ;; value. (if (and val (not (processp val)) (not (eq val t))) (error "Unmatched delimiter: %S" val) ;; Eshell-command expect a list like () to know if the ;; command should be async or not. (or (and (processp val) delim) val))))) (defun helm-find-files-eshell-command-on-file (_candidate) "Run `eshell-command' on CANDIDATE or marked candidates. See `helm-find-files-eshell-command-on-file-1' for more info." (helm-find-files-eshell-command-on-file-1 helm-current-prefix-arg)) (defun helm-ff--shell-interactive-buffer-p (buffer &optional mode) (with-current-buffer buffer (when (eq major-mode (or mode 'eshell-mode)) (let ((next-prompt-fn (cl-case major-mode (shell-mode #'comint-next-prompt) (eshell-mode #'eshell-next-prompt) (term-mode #'term-next-prompt)))) (save-excursion (goto-char (point-min)) (funcall next-prompt-fn 1) (null (eql (point) (point-min)))))))) (defun helm-ff-switch-to-shell (_candidate) "Switch to a shell buffer and cd to `helm-ff-default-directory'. Set your preferred shell mode in `helm-ff-preferred-shell-mode'. With a numeric prefix arg switch to numbered shell buffer, if no prefix arg provided and more than one shell buffer exists, provide completions on those buffers. If only one shell buffer exists, switch to this one, if no shell buffer exists or if the numeric prefix arg shell buffer doesn't exists, create it and switch to it." ;; Reproduce the Emacs-25 behavior to be able to edit and send ;; command in term buffer. (let (term-char-mode-buffer-read-only ; Emacs-25 behavior. term-char-mode-point-at-process-mark ; Emacs-25 behavior. (cd-eshell (lambda () (eshell/cd helm-ff-default-directory) (eshell-reset))) (cd-shell (lambda () (goto-char (point-max)) (when (eq helm-ff-preferred-shell-mode 'shell-mode) (comint-delete-input)) (insert (format "cd %s" (shell-quote-argument (or (file-remote-p helm-ff-default-directory 'localname) helm-ff-default-directory)))) (cl-case helm-ff-preferred-shell-mode (shell-mode (comint-send-input)) (term-mode (progn (term-char-mode) (term-send-input)))))) (bufs (cl-loop for b in (mapcar 'buffer-name (buffer-list)) when (helm-ff--shell-interactive-buffer-p b helm-ff-preferred-shell-mode) collect b))) ;; Jump to a shell buffer or open a new session. (helm-aif (and (not helm-current-prefix-arg) (if (cdr bufs) (helm-comp-read "Switch to shell buffer: " bufs :must-match t) (car bufs))) ;; Display in same window by default to preserve the ;; historical behaviour (pop-to-buffer it '(display-buffer-same-window)) (cl-case helm-ff-preferred-shell-mode (eshell-mode (eshell helm-current-prefix-arg)) (shell-mode (shell (helm-aif (and helm-current-prefix-arg (prefix-numeric-value helm-current-prefix-arg)) (format "*shell<%s>*" it)))) (term-mode (progn (ansi-term (getenv "SHELL") (helm-aif (and helm-current-prefix-arg (prefix-numeric-value helm-current-prefix-arg)) (format "*ansi-term<%s>*" it))) (term-line-mode))))) ;; Now cd into directory. (helm-aif (and (memq major-mode '(shell-mode term-mode)) (get-buffer-process (current-buffer))) (accept-process-output it 0.1)) (unless (helm-ff-shell-alive-p major-mode) (funcall (if (eq major-mode 'eshell-mode) cd-eshell cd-shell))))) (defun helm-ff-shell-alive-p (mode) "Returns non nil when a process is running inside `shell-mode' buffer." (cl-ecase mode (shell-mode (save-excursion (comint-goto-process-mark) (or (null comint-last-prompt) (not (eql (point) (marker-position (cdr comint-last-prompt))))))) (eshell-mode (get-buffer-process (current-buffer))) (term-mode (save-excursion (goto-char (term-process-mark)) (not (looking-back "\\$ " (- (point) 2))))))) (defun helm-ff-touch-files (_candidate) "The touch files action for helm-find-files." (let* ((files (helm-marked-candidates)) (split (cl-loop for f in files for spt = (unless helm-current-prefix-arg (cons (helm-basedir f) (split-string f ", ?"))) if spt append (cl-loop with dir = (car spt) for ff in (cdr spt) collect (expand-file-name ff dir)) else collect f)) (timestamp (helm-comp-read "Timestamp (default Now): " (cl-loop for f in split for time = (file-attributes f) for date = (and time (format-time-string "%Y-%m-%d %H:%M:%S" (nth 5 time))) when date collect (cons (format "%s: %s" (helm-basename f) date) date)) :default (format-time-string "%Y-%m-%d %H:%M:%S" (current-time)))) (failures (cl-loop with default-directory = helm-ff-default-directory for f in split for file = (or (file-remote-p f 'localname) f) when (> (process-file "touch" nil nil nil "-d" timestamp file) 0) collect f))) (when failures (message "Failed to touch *%s files:\n%s" (length failures) (mapconcat (lambda (f) (format "- %s\n" f)) failures ""))))) (defun helm-ff-encrypt-files (_candidate) "Encrypt marked files." (require 'epg) (require 'epa) (require 'helm-epa) (let* ((mkds (helm-marked-candidates :with-wildcard t)) (recipients (helm :sources (helm-build-sync-source "Select recipient for encryption: " :persistent-action 'ignore :candidates 'helm-epa-get-key-list :action (lambda (_candidate) (helm-marked-candidates)) :must-match t) :buffer "*helm-ff epg keys*")) (keys (and recipients (helm-epa-collect-keys-from-candidates recipients))) (ids (and recipients (helm-epa-collect-id-from-candidates recipients)))) (when recipients ; may be aborted by quit. (cl-loop for f in mkds do (epa-encrypt-file f recipients)) (helm-epa-success-message (concat (format "%s File(s) encrypted" (length mkds)) " with key(s):\n %s") keys ids)))) (helm-make-command-from-action helm-ff-run-touch-files "Used to interactively run touch file action from keyboard." 'helm-ff-touch-files) (defun helm-ff-sort-by-size () (interactive) (let ((helm-ff-initial-sort-method 'size)) (helm-force-update (concat (regexp-quote (helm-get-selection nil helm-ff-transformer-show-only-basename)) "$")) (message "Sorting by size"))) (put 'helm-ff-sort-by-size 'helm-only t) (defun helm-ff-sort-by-newest () (interactive) (let ((helm-ff-initial-sort-method 'newest)) (helm-force-update (concat (regexp-quote (helm-get-selection nil helm-ff-transformer-show-only-basename)) "$")) (message "Sorting by newest"))) (put 'helm-ff-sort-by-newest 'helm-only t) (defun helm-ff-sort-by-ext () (interactive) (let ((helm-ff-initial-sort-method 'ext)) (helm-force-update (concat (regexp-quote (helm-get-selection nil helm-ff-transformer-show-only-basename)) "$")) (message "Sorting by extensions"))) (put 'helm-ff-sort-by-ext 'no-helm-mx t) (defun helm-ff-sort-alpha () (interactive) (let ((helm-ff-initial-sort-method nil)) (helm-force-update (concat (regexp-quote (helm-get-selection nil helm-ff-transformer-show-only-basename)) "$")) (message "Sorting alphabetically"))) (put 'helm-ff-sort-alpha 'helm-only t) (defun helm-ff-directories-only (candidates _source) (if helm-ff--show-directories-only (cl-loop for (d . r) in candidates when (file-directory-p r) ;; We can use this as long as this filtering function ;; is called after `helm-ff-fct' otherwise candidates ;; may not be cons cell at first call [1]. collect (cons d r)) candidates)) (defun helm-ff-files-only (candidates _source) (if helm-ff--show-files-only (cl-loop for (d . r) in candidates unless (file-directory-p r) ;; Same comment as in [1] above. collect (cons d r)) candidates)) (defun helm-ff-toggle-dirs-only () "Show only directories in helm-find-files." (interactive) (with-helm-alive-p (setq helm-ff--show-directories-only (not helm-ff--show-directories-only)) (setq helm-ff--show-files-only nil) (helm-update (helm-get-selection nil t)))) (put 'helm-ff-toggle-dirs-only 'helm-only t) (defun helm-ff-toggle-files-only () "Show only files in helm-find-files." (interactive) (with-helm-alive-p (setq helm-ff--show-files-only (not helm-ff--show-files-only)) (setq helm-ff--show-directories-only nil) (helm-update (helm-get-selection nil t)))) (put 'helm-ff-toggle-files-only 'helm-only t) (defun helm-ff-after-persistent-show-all () (when helm-ff-reset-filters-on-update (setq helm-ff--show-directories-only nil helm-ff--show-files-only nil))) (defun helm-ff-serial-rename-action (method) "Rename all marked files in `helm-ff-default-directory' with METHOD. See `helm-ff-serial-rename-1'." (let* ((helm--reading-passwd-or-string t) (cands (helm-marked-candidates :with-wildcard t)) (def-name (car cands)) (name (helm-read-string "NewName: " (replace-regexp-in-string "[0-9]+$" "" (helm-basename def-name (file-name-extension def-name))))) (start (read-number "StartAtNumber: ")) (extension (helm-read-string "Extension: " (file-name-extension (car cands)))) (dir (expand-file-name (helm-read-file-name "Serial Rename to directory: " :initial-input (expand-file-name helm-ff-default-directory) :test 'file-directory-p :must-match t))) done) (with-helm-display-marked-candidates helm-marked-buffer-name (helm-ff--count-and-collect-dups cands) (if (y-or-n-p (format "Rename %s file(s) to <%s> like this ?\n%s " (length cands) dir (format "%s <-> %s%s.%s" (helm-basename (car cands)) name start extension))) (progn (helm-ff-serial-rename-1 dir cands name start extension :method method) (setq done t) (message nil)))) (if done (with-helm-current-buffer (helm-find-files-1 dir)) (message "Operation aborted")))) (defun helm-ff-member-directory-p (file directory) (let ((dir-file (expand-file-name (file-name-as-directory (file-name-directory file)))) (cur-dir (expand-file-name (file-name-as-directory directory)))) (string= dir-file cur-dir))) (cl-defun helm-ff-serial-rename-1 (directory collection new-name start-at-num extension &key (method 'rename)) "Rename files in COLLECTION to DIRECTORY with the prefix name NEW-NAME. Rename start at number START-AT-NUM - ex: prefixname-01.jpg. EXTENSION is the file extension to use. In empty prompt, reuse the original extension of file. METHOD can be one of rename, copy or symlink. Files will be renamed if they are files of current directory, otherwise they will be treated with METHOD. Default METHOD is rename." ;; Maybe remove directories selected by error in collection. (setq collection (cl-remove-if 'file-directory-p collection)) (let* ((tmp-dir (file-name-as-directory (concat (file-name-as-directory directory) (symbol-name (cl-gensym "tmp"))))) (fn (cl-case method (copy 'copy-file) (symlink 'make-symbolic-link) (rename 'rename-file) (t (error "Error: Unknown method %s" method))))) (make-directory tmp-dir) (unwind-protect (progn ;; Rename all files to tmp-dir with new-name. ;; If files are not from start directory, use method ;; to move files to tmp-dir. (cl-loop for i in collection for count from start-at-num for fnum = (if (< count 10) "0%s" "%s") for nname = (concat tmp-dir new-name (format fnum count) (if (not (string= extension "")) (format ".%s" (replace-regexp-in-string "[.]" "" extension)) (file-name-extension i 'dot))) do (if (helm-ff-member-directory-p i directory) (rename-file i nname) (funcall fn i nname))) ;; Now move all from tmp-dir to destination. (cl-loop with dirlist = (directory-files tmp-dir t directory-files-no-dot-files-regexp) for f in dirlist do (if (file-symlink-p f) (make-symbolic-link (file-truename f) (concat (file-name-as-directory directory) (helm-basename f))) (rename-file f directory)))) (delete-directory tmp-dir t)))) (defun helm-ff-serial-rename (_candidate) "Serial rename all marked files to `helm-ff-default-directory'. Rename only file of current directory, and symlink files coming from other directories. See `helm-ff-serial-rename-1'." (helm-ff-serial-rename-action 'rename)) (defun helm-ff-serial-rename-by-symlink (_candidate) "Serial rename all marked files to `helm-ff-default-directory'. Rename only file of current directory, and symlink files coming from other directories. See `helm-ff-serial-rename-1'." (helm-ff-serial-rename-action 'symlink)) (defun helm-ff-serial-rename-by-copying (_candidate) "Serial rename all marked files to `helm-ff-default-directory'. Rename only file of current directory, and copy files coming from other directories. See `helm-ff-serial-rename-1'." (helm-ff-serial-rename-action 'copy)) (defvar helm-ff-query-replace-fnames-history-from nil) (defvar helm-ff-query-replace-fnames-history-to nil) (defun helm-ff-query-replace-on-filenames (candidates) "Query replace on filenames of CANDIDATES. This doesn't replace inside the files, only modify filenames." (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar 'helm-basename candidates) (let* ((regexp (read-string "Replace regexp on filename(s): " nil 'helm-ff-query-replace-history-from (helm-basename (car candidates)))) (rep (read-string (format "Replace regexp `%s' with: " regexp) nil 'helm-ff-query-replace-history-to))) (cl-loop with query = "y" with count = 0 for old in candidates for new = (helm-ff--query-replace-in-fname-set-new-name old regexp rep count) ;; If `regexp' is not matched in `old' ;; `replace-regexp-in-string' will ;; return `old' unmodified. unless (string= old new) do (progn (when (file-exists-p new) (setq new (concat (file-name-sans-extension new) (format "(%s)" count) (file-name-extension new t)))) (unless (string= query "!") (setq query (helm-read-answer (format "Replace `%s' by `%s' [!,y,n,q,h]" (helm-basename old) (helm-basename new)) '("y" "n" "!" "q") #'helm-read-answer-default-help-fn))) (when (string= query "q") (cl-return (message "Operation aborted"))) (unless (string= query "n") (rename-file old new) (cl-incf count))) finally (message "%d Files renamed" count)))) ;; This fix the emacs bug where "Emacs-Lisp:" is sent ;; in minibuffer (not the echo area). (sit-for 0.1) (with-current-buffer (window-buffer (minibuffer-window)) (delete-minibuffer-contents))) (defun helm-ff--query-replace-in-fname-set-new-name (old regexp rep count) "Setup a new name for OLD replacing part matching REGEXP with REP. COUNT is used for incrementing new name if needed." (let (subexp target) (concat (helm-basedir old) (helm--replace-regexp-in-buffer-string (save-match-data (cond ((string= regexp "%.") (setq subexp 1) (helm-ff--prepare-str-with-regexp (setq target (helm-basename old t)))) ((string= regexp ".%") (setq subexp 1) (helm-ff--prepare-str-with-regexp (setq target (file-name-extension old)))) ((string= regexp "%") (regexp-quote (setq target (helm-basename old)))) ((string-match "%:\\([0-9]+\\):\\([0-9]+\\)" regexp) (setq subexp 1) (let ((beg (match-string 1 regexp)) (end (match-string 2 regexp)) (str (helm-basename old))) (setq target (substring str (string-to-number beg) (string-to-number end))) (helm-ff--prepare-str-with-regexp str beg end))) (t regexp))) (save-match-data (cond (;; Handle incremental ;; replacement with \# in ;; search and replace ;; feature in placeholder \@. (string-match "\\\\@/\\(.*\\)/\\(.*\\)/" rep) (helm-aand (save-match-data (replace-regexp-in-string "\\\\#" (format "%03d" (1+ count)) (match-string 2 rep))) (replace-regexp-in-string (match-string 1 rep) it target))) ;; Incremental replacement ;; before or after \@. ((or (string-match "\\`\\\\#\\\\@\\'" rep) (string-match "\\`\\\\@\\\\#\\'" rep)) (replace-regexp-in-string "\\\\#" (format "%03d" (1+ count)) (replace-match target t t rep))) ;; Simple incremental replacement. ((or (string-match "\\`\\\\#" rep) (string-match "\\\\#\\'" rep)) (replace-match (format "%03d" (1+ count)) t t rep)) ;; Substring replacement in placeholder. ((string-match "\\\\@:\\([0-9]*\\):\\([0-9]*\\)" rep) (replace-match (substring target (string-to-number (match-string 1 rep)) (helm-acase (match-string 2 rep) ((guard* (string= it "")) (length target)) (t (string-to-number it)))) t t rep)) ;; Simple replacement by placeholder. ((string-match "\\\\@" rep) (replace-match target t t rep)) ;; Replacement with ;; upcase, downcase or ;; capitalized text. ((string= rep "%u") #'upcase) ((string= rep "%d") #'downcase) ((string= rep "%c") #'capitalize) ;; Simple replacement with ;; whole replacement regexp. (t rep))) (helm-basename old) t nil subexp)))) (defun helm-ff--prepare-str-with-regexp (str &optional rep1 rep2) ;; This is used in `helm-ff-query-replace-on-filenames' to prepare ;; STR when REGEXP is specified as substring e.g %:1:3 in this case ;; substring from 1 to 3 in STR will be enclosed with parenthesis to ;; match this substring as a subexp e.g %:1:3 on string "emacs" will ;; be replaced by "e\\(ma\\)cs" using subexp 1 like this: ;; (helm--replace-regexp-in-buffer-string "e\\(ma\\)cs" "fo" "emacs" nil t 1) ;; => "efocs" ;; ^^ ;; Where "1" and "3" will be strings extracted with match-string ;; from regexp and refered respectively in this function as REP1 and ;; REP2. (let* ((from (or (and rep1 (string-to-number rep1)) 0)) (to (or (and rep2 (string-to-number rep2)) (length str))) (subexp (concat "\\(" (regexp-quote (substring str from to)) "\\)")) (before-str (unless (zerop from) (regexp-quote (substring str 0 from)))) (after-str (unless (= to (length str)) (regexp-quote (substring str to (length str)))))) (concat before-str subexp after-str))) ;; The action. (defun helm-ff-query-replace-fnames-on-marked (_candidate) (let ((marked (helm-marked-candidates :with-wildcard t))) (helm-ff-query-replace-on-filenames marked))) ;; The command for `helm-find-files-map'. (helm-make-command-from-action helm-ff-run-query-replace-fnames-on-marked "Run query-replace on filenames from HFF." 'helm-ff-query-replace-fnames-on-marked) (defun helm-ff-query-replace (_candidate) (let ((bufs (cl-loop for f in (helm-marked-candidates :with-wildcard t) collect (buffer-name (find-file-noselect f))))) (helm-buffer-query-replace-1 nil bufs))) (helm-make-command-from-action helm-ff-run-query-replace "Run query-replace from HFF." 'helm-ff-query-replace) (defun helm-ff-query-replace-regexp (_candidate) (let ((bufs (cl-loop for f in (helm-marked-candidates :with-wildcard t) collect (buffer-name (find-file-noselect f))))) (helm-buffer-query-replace-1 'regexp bufs))) (helm-make-command-from-action helm-ff-run-query-replace-regexp "Run query-replace regexp from HFF." 'helm-ff-query-replace-regexp) (defun helm-ff-toggle-auto-update () (if helm-ff--deleting-char-backward (progn (message "[Auto expansion disabled]") (sit-for 1) (message nil) (setq helm-ff--auto-update-state nil)) (setq helm-ff-auto-update-flag (not helm-ff-auto-update-flag)) (setq helm-ff--auto-update-state helm-ff-auto-update-flag) (message "[Auto expansion %s]" (if helm-ff-auto-update-flag "enabled" "disabled")))) (defun helm-ff-run-toggle-auto-update () (interactive) (with-helm-alive-p (helm-ff-toggle-auto-update))) (put 'helm-ff-run-toggle-auto-update 'helm-only t) (defun helm-ff-delete-char-backward () "Go up one level or disable HFF auto update and delete char backward. Going up one level works only when pattern is a directory endings with \"/\", otherwise this command deletes char backward. Going up one level can be disabled if necessary by deleting \"/\" at end of pattern using \\\\[backward-char] and \\[helm-delete-minibuffer-contents]." (interactive) (with-helm-alive-p (if (and helm-ff-DEL-up-one-level-maybe (string-match "/\\'" helm-pattern) (file-directory-p helm-pattern)) (call-interactively 'helm-find-files-up-one-level) (setq helm-ff-auto-update-flag nil) (setq helm-ff--deleting-char-backward t) (call-interactively (lookup-key (current-global-map) (read-kbd-macro "DEL"))) (helm--update-header-line)))) (put 'helm-ff-delete-char-backward 'helm-only t) (defun helm-ff-delete-char-backward--exit-fn () (setq helm-ff-auto-update-flag helm-ff--auto-update-state) (setq helm-ff--deleting-char-backward nil)) (defvar helm-ff--RET-disabled nil) (defun helm-ff-RET-1 (&optional must-match) "Used for RET action in `helm-find-files'. See `helm-ff-RET' for details. If MUST-MATCH is specified exit with `helm-confirm-and-exit-minibuffer' which handle must-match mechanism." (let ((sel (helm-get-selection)) ;; Ensure `file-directory-p' works on remote files. non-essential) (cl-assert sel nil "Trying to exit with no candidates") (if (and (or (file-directory-p sel) (helm-ff--invalid-tramp-name-p sel)) ;; Allows exiting with default action when a prefix arg ;; is specified. (null current-prefix-arg) (null helm-ff--RET-disabled) (or (and (file-remote-p sel) (string= "." (helm-basename sel)) (string-match-p "\\`[/].*:.*:\\'" helm-pattern)) (not (string= "." (helm-basename sel))))) (helm-execute-persistent-action) (if must-match (helm-confirm-and-exit-minibuffer) (helm-maybe-exit-minibuffer))))) (defun helm-ff-RET () "Default action for RET in `helm-find-files'. Behave differently depending on `helm-selection': - candidate basename is \".\" => open it in dired. - candidate is a directory => expand it. - candidate is a file => open it." (interactive) (helm-ff-RET-1)) (put 'helm-ff-RET 'helm-only t) (defun helm-ff-TAB-1 (&optional force-menu) "Used for TAB action in `helm-find-files'." (let ((sel (helm-get-selection))) (if (and (null force-menu) (file-directory-p sel) (not (string= "." (helm-basename sel)))) (helm-execute-persistent-action) (helm-select-action)))) (defun helm-ff-TAB (arg) "Default action for TAB in `helm-find-files'. Behave differently depending on `helm-selection': - candidate basename is \".\" => open the action menu. - candidate is a directory => expand it. - candidate is a file => open action menu. Called with a prefix arg open menu unconditionally." (interactive "P") (helm-ff-TAB-1 arg)) (put 'helm-ff-TAB 'helm-only t) (defun helm-ff-RET-must-match () "Same as `helm-ff-RET' but used in must-match map." (interactive) (helm-ff-RET-1 t)) (helm-make-command-from-action helm-ff-run-grep "Run Grep action from `helm-source-find-files'." 'helm-find-files-grep) (helm-make-command-from-action helm-ff-run-git-grep "Run git-grep action from `helm-source-find-files'." 'helm-ff-git-grep) (helm-make-command-from-action helm-ff-run-grep-ag "Run grep AG action from `helm-source-find-files'." 'helm-find-files-ag) (helm-make-command-from-action helm-ff-run-pdfgrep "Run Pdfgrep action from `helm-source-find-files'." 'helm-ff-pdfgrep) (helm-make-command-from-action helm-ff-run-zgrep "Run Grep action from `helm-source-find-files'." 'helm-ff-zgrep) (helm-make-command-from-action helm-ff-run-copy-file "Run Copy file action from `helm-source-find-files'." 'helm-find-files-copy) (helm-make-command-from-action helm-ff-run-rsync-file "Run Rsync file action from `helm-source-find-files'." 'helm-find-files-rsync) (helm-make-command-from-action helm-ff-run-rename-file "Run Rename file action from `helm-source-find-files'." 'helm-find-files-rename) (helm-make-command-from-action helm-ff-run-byte-compile-file "Run Byte compile file action from `helm-source-find-files'." 'helm-find-files-byte-compile) (helm-make-command-from-action helm-ff-run-load-file "Run Load file action from `helm-source-find-files'." 'helm-find-files-load-files) (helm-make-command-from-action helm-ff-run-eshell-command-on-file "Run eshell command on file action from `helm-source-find-files'." 'helm-find-files-eshell-command-on-file) (helm-make-command-from-action helm-ff-run-ediff-file "Run Ediff file action from `helm-source-find-files'." 'helm-find-files-ediff-files) (helm-make-command-from-action helm-ff-run-ediff-merge-file "Run Ediff merge file action from `helm-source-find-files'." 'helm-find-files-ediff-merge-files) (helm-make-command-from-action helm-ff-run-symlink-file "Run Symlink file action from `helm-source-find-files'." 'helm-find-files-symlink) (helm-make-command-from-action helm-ff-run-relsymlink-file "Run Symlink file action from `helm-source-find-files'." 'helm-find-files-relsymlink) (helm-make-command-from-action helm-ff-run-hardlink-file "Run Hardlink file action from `helm-source-find-files'." 'helm-find-files-hardlink) (helm-make-command-from-action helm-ff-run-compress-to "Run Compress to archive action from `helm-source-find-files'." 'helm-find-files-compress-to) (helm-make-command-from-action helm-ff-run-chmod "Run chmod action from `helm-source-find-files'." 'helm-ff-chmod) (defun helm-ff-delete-files (candidate) "Delete files default action." (funcall helm-ff-delete-files-function candidate)) (helm-make-command-from-action helm-ff-run-delete-file "Run Delete file action from `helm-source-find-files'." 'helm-ff-delete-files) (helm-make-command-from-action helm-ff-run-complete-fn-at-point "Run complete file name action from `helm-source-find-files'." 'helm-insert-file-name-completion-at-point) (helm-make-command-from-action helm-ff-run-switch-to-shell "Run switch to eshell action from `helm-source-find-files'." 'helm-ff-switch-to-shell) (helm-make-command-from-action helm-ff-run-switch-other-window "Run switch to other window action from `helm-source-find-files'. When a prefix arg is provided, split is done vertically." 'helm-find-files-other-window) (helm-make-command-from-action helm-ff-run-switch-other-frame "Run switch to other frame action from `helm-source-find-files'." 'find-file-other-frame) (helm-make-command-from-action helm-ff-run-open-file-externally "Run open file externally command action from `helm-source-find-files'." 'helm-open-file-externally) (helm-make-command-from-action helm-ff-run-open-file-with-default-tool "Run open file externally command action from `helm-source-find-files'." 'helm-open-file-with-default-tool) (defun helm-ff-locate (candidate) "Locate action function for `helm-find-files'." (helm-locate-set-command) (let ((default (concat (helm-basename (expand-file-name candidate helm-ff-default-directory)) (unless (or ;; "-b" is already added when fuzzy matching. helm-locate-fuzzy-match ;; The locate '-b' option doesn't exists ;; in everything (es). (and (eq system-type 'windows-nt) (string-match "^es" helm-locate-command))) " -b")))) (helm-locate-1 helm-current-prefix-arg nil 'from-ff default))) (helm-make-command-from-action helm-ff-run-locate "Run locate action from `helm-source-find-files'." 'helm-ff-locate) (defun helm-files-insert-as-org-link (candidate) (insert (format "[[%s][]]" candidate)) (goto-char (- (point) 2))) (helm-make-command-from-action helm-ff-run-insert-org-link "Run insert org link from HFF." 'helm-files-insert-as-org-link) (helm-make-command-from-action helm-ff-run-find-file-as-root "Run find file as root from HFF." 'helm-find-file-as-root) (helm-make-command-from-action helm-ff-run-find-alternate-file "Run `find-alternate-file' from HFF." 'find-alternate-file) (helm-make-command-from-action helm-ff-run-mail-attach-files "Run mail attach files command action from `helm-source-find-files'." 'helm-ff-mail-attach-files) (helm-make-command-from-action helm-ff-run-etags "Run Etags command action from `helm-source-find-files'." 'helm-ff-etags-select) (defvar lpr-printer-switch) (defun helm-ff-print (_candidate) "Print marked files. You may to set in order variables `lpr-command',`lpr-switches' and/or `printer-name', but with no settings Helm should detect your printer(s) and print with the default `lpr' settings. NOTE: DO NOT set the \"-P\" flag in `lpr-switches'. If you really have to modify this, do it in `lpr-printer-switch'. Same as `dired-do-print' but for Helm." (require 'lpr) (when (or helm-current-prefix-arg (not helm-ff-printer-list)) (setq helm-ff-printer-list (helm-ff-find-printers))) (let* ((file-list (helm-marked-candidates :with-wildcard t)) (len (length file-list)) (printer-name (if helm-ff-printer-list (helm-comp-read "Printer: " helm-ff-printer-list) printer-name)) (lpr-switches (if (and (stringp printer-name) (string< "" printer-name)) (cons (concat lpr-printer-switch " " printer-name) lpr-switches) lpr-switches)) (command (helm-read-string (format "Print *%s File(s):\n%s with: " len (mapconcat (lambda (f) (format "- %s\n" f)) file-list "")) (when (and lpr-command lpr-switches) (mapconcat 'identity (cons lpr-command (if (stringp lpr-switches) (list lpr-switches) lpr-switches)) " ")))) (file-args (mapconcat #'shell-quote-argument file-list " ")) (cmd-line (concat command " " file-args))) (if command (start-process-shell-command "helm-print" nil cmd-line) (error "Error: Please verify your printer settings in Emacs.")))) (helm-make-command-from-action helm-ff-run-print-file "Run Print file action from `helm-source-find-files'." 'helm-ff-print) (defun helm-ff-checksum (file) "Calculate the checksum of FILE. The checksum is copied to `kill-ring'. Checksum is calculated with the md5sum, sha1sum, sha224sum, sha256sum, sha384sum and sha512sum when available, otherwise the Emacs function `secure-hash' is used but it is slow and may crash Emacs and even the whole system as it eats all memory." (cl-assert (file-regular-p file) nil "`%s' is not a regular file" file) (let* ((algo (intern (helm-comp-read "Algorithm: " '(md5 sha1 sha224 sha256 sha384 sha512)))) (cmd (concat (symbol-name algo) "sum")) (bn (helm-basename file)) proc) (message "Calculating %s checksum for %s..." algo bn) (if (executable-find cmd) (progn (set-process-filter (setq proc (start-file-process cmd nil cmd "-b" file)) (lambda (_process output) (when output (kill-new output)))) (set-process-sentinel proc `(lambda (_process event) (when (string= event "finished\n") (message "Calculating %s checksum for `%s' done and copied to kill-ring" ,(symbol-name algo) ,bn))))) (async-let ((sum (with-temp-buffer (insert-file-contents-literally file) (secure-hash algo (current-buffer))))) (kill-new sum) (message "Calculating %s checksum for `%s' done and copied to kill-ring" algo bn))))) (defun helm-ff-toggle-basename () (with-helm-buffer (setq helm-ff-transformer-show-only-basename (not helm-ff-transformer-show-only-basename)) (let* ((cand (helm-get-selection)) (target (if helm-ff-transformer-show-only-basename (helm-basename cand) cand))) (helm-force-update (format helm-ff-last-expanded-candidate-regexp (regexp-quote target)))))) (defun helm-ff-run-toggle-basename () (interactive) (with-helm-alive-p (unless (helm-empty-source-p) (helm-ff-toggle-basename)))) (put 'helm-ff-run-toggle-basename 'helm-only t) (defun helm-ff-mark-similar-files-1 () "Mark similar files. Files are considered similar if they have the same face and same extension." (with-helm-window (let* ((src (helm-get-current-source)) (file (helm-get-selection nil 'withprop src)) (face (get-text-property (min 2 (length file)) 'face file)) (ext (file-name-extension file))) (helm-map-candidates-in-source src (lambda (_cand) (helm-make-visible-mark)) (lambda (cand) (and (not (helm-this-visible-mark)) (eq (get-text-property (min 2 (length cand)) 'face cand) face) (equal ext (file-name-extension cand))))) (helm-mark-current-line) (helm-display-mode-line src t) (when helm-marked-candidates (message "%s candidates marked" (length helm-marked-candidates)) (set-window-margins (selected-window) 1))))) (defun helm-ff-mark-similar-files () "Mark all files similar to selection." (interactive) (with-helm-alive-p (let ((marked (helm-marked-candidates))) (if (and (>= (length marked) 1) (with-helm-window helm-visible-mark-overlays)) (helm-unmark-all) (helm-ff-mark-similar-files-1))))) (put 'helm-ff-mark-similar-files 'helm-only t) (defun helm-reduce-file-name-1 (fname level) ;; This is the old version of helm-reduce-file-name, we still use it ;; with ftp fnames as expand-file-name is not working as expected ;; with ftp fnames (emacs bug). (cl-loop with result with iter = (helm-iter-reduce-fname (expand-file-name fname)) repeat level do (setq result (helm-iter-next iter)) finally return (or result (expand-file-name "/")))) (defun helm-reduce-file-name-2 (fname level) ;; This version comes from Bug#2004 (UNC paths) and should fix ;; it. It works with local files and remote files as well but not ;; with ftp, see helm-reduce-file-name-1. (while (> level 0) (unless (or (string= fname "/") (string= (file-remote-p fname 'localname) "/")) (setq fname (expand-file-name (concat (expand-file-name fname) "/../")))) (setq level (1- level))) fname) (defun helm-reduce-file-name (fname level) "Reduce FNAME by number LEVEL from end." (if (helm-aand (file-remote-p fname 'method) (string= it "ftp")) (helm-reduce-file-name-1 fname level) (helm-reduce-file-name-2 fname level))) (defun helm-iter-reduce-fname (fname) "Yield FNAME reduced by one level at each call." (let ((split (split-string fname "/" t))) (unless (or (null split) (string-match "\\`\\(~\\|[[:alpha:]]:\\)" (car split))) (setq split (cons "/" split))) (lambda () (when (and split (cdr split)) (cl-loop for i in (setq split (butlast split)) concat (if (string= i "/") i (concat i "/"))))))) (defvar helm-find-files--level-tree nil) (defvar helm-find-files--level-tree-iterator nil) (defun helm-find-files-up-one-level (arg) "Go up one level like unix command `cd ..'. If prefix numeric arg is given go ARG level up." (interactive "p") (with-helm-alive-p (helm-ff-after-persistent-show-all) (let ((src (helm-get-current-source))) (when (and (helm-file-completion-source-p src) (not (helm-ff--invalid-tramp-name-p))) (with-helm-window (when (helm-follow-mode-p) (helm-follow-mode -1) (message nil))) ;; When going up one level we want to be at the line ;; corresponding to actual directory, so store this info ;; in `helm-ff-last-expanded'. (let ((cur-cand (helm-get-selection nil nil src)) (new-pattern (helm-reduce-file-name helm-pattern arg))) ;; Ensure visibility on all candidates for preselection. (unless (helm-empty-source-p) ;; We may have an empty source in read-file-name when a ;; predicate is used e.g. images and the default is a non ;; file image. (helm-set-attr 'candidate-number-limit (if helm-ff-up-one-level-preselect (max (gethash new-pattern helm-ff--directory-files-length helm-ff-candidate-number-limit) helm-ff-candidate-number-limit) helm-ff-candidate-number-limit))) (cond ((file-directory-p helm-pattern) (setq helm-ff-last-expanded helm-ff-default-directory)) ((file-exists-p helm-pattern) (setq helm-ff-last-expanded helm-pattern)) ((and cur-cand (file-exists-p cur-cand)) (setq helm-ff-last-expanded cur-cand))) (unless helm-find-files--level-tree (setq helm-find-files--level-tree (cons helm-ff-default-directory helm-find-files--level-tree))) (setq helm-find-files--level-tree-iterator nil) (push new-pattern helm-find-files--level-tree) (setq helm-ff--show-thumbnails (member new-pattern helm-ff--thumbnailed-directories)) (helm-set-pattern new-pattern helm-suspend-update-flag) (with-helm-after-update-hook (helm-ff-retrieve-last-expanded))))))) (put 'helm-find-files-up-one-level 'helm-only t) (defun helm-find-files-down-last-level () "Retrieve previous paths reached by `C-l' in helm-find-files." (interactive) (with-helm-alive-p (when (and (helm-file-completion-source-p) (not (helm-ff--invalid-tramp-name-p))) (unless helm-find-files--level-tree-iterator (setq helm-find-files--level-tree-iterator (helm-iter-list (cdr helm-find-files--level-tree)))) (setq helm-find-files--level-tree nil) (helm-aif (helm-iter-next helm-find-files--level-tree-iterator) (progn (setq helm-ff--show-thumbnails (member it helm-ff--thumbnailed-directories)) (helm-set-pattern it)) (setq helm-find-files--level-tree-iterator nil))))) (put 'helm-find-files-down-last-level 'helm-only t) (defun helm-find-files--reset-level-tree () (setq helm-find-files--level-tree-iterator nil helm-find-files--level-tree nil)) (add-hook 'helm-cleanup-hook 'helm-find-files--reset-level-tree) (add-hook 'post-self-insert-hook 'helm-find-files--reset-level-tree) (add-hook 'helm-after-persistent-action-hook 'helm-find-files--reset-level-tree) (defun helm-ff-retrieve-last-expanded () "Move overlay to last visited directory `helm-ff-last-expanded'. This happen after using `helm-find-files-up-one-level', or hitting C-j on \"..\"." (when helm-ff-last-expanded (let ((presel (if helm-ff-transformer-show-only-basename (helm-basename (directory-file-name helm-ff-last-expanded)) (directory-file-name helm-ff-last-expanded)))) (with-helm-window (when (re-search-forward (format helm-ff-last-expanded-candidate-regexp ;; The space at eol may contain a display property ;; e.g. "-> symlink truename". (concat (regexp-quote presel) " ?$")) nil t) (forward-line 0) (helm-mark-current-line))) (setq helm-ff-last-expanded nil)))) (defun helm-ff-undo () "Undo minibuffer in `helm-find-files'. Ensure disabling `helm-ff-auto-update-flag' before undoing." (interactive) (let ((old--flag helm-ff-auto-update-flag)) (setq helm-ff-auto-update-flag nil) (setq helm-ff--auto-update-state nil) (unwind-protect (progn (undo) (helm-check-minibuffer-input)) (setq helm-ff-auto-update-flag old--flag) (setq helm-ff--auto-update-state helm-ff-auto-update-flag)))) (put 'helm-ff-undo 'helm-only t) ;;; Auto-update - helm-find-files auto expansion of directories. ;; ;; (defun helm-ff-update-when-only-one-matched () "Expand to directory when sole completion. When only one candidate is remaining and it is a directory, expand to this directory. This happen only when `helm-ff-auto-update-flag' is non-nil or when `helm-pattern' is equal to \"~/\"." (let ((src (helm-get-current-source))) (when (and (helm-file-completion-source-p src) (not (get-buffer-window helm-action-buffer 'visible)) (not (helm-ff--invalid-tramp-name-p)) (not (string-match-p "\\`[.]\\{2\\}[^/]+" (helm-basename helm-pattern)))) (with-helm-buffer (let* ((history-p (string= (assoc-default 'name src) "Read File Name History")) (pat (helm-ff-set-pattern helm-pattern)) ;; Try to shut up persistent tramp error with adb method when ;; adding tilde to path. (tramp-tolerate-tilde (equal (file-remote-p pat 'method) tramp-adb-method)) (completed-p (helm-aand (expand-file-name (substitute-in-file-name pat)) (string= (file-name-as-directory it) helm-ff-default-directory))) (candnum (helm-get-candidate-number)) (lt2-p (and (<= candnum 2) (>= (string-width (helm-basename helm-pattern)) 2))) (cur-cand (helm-get-selection nil nil src)) expand-to) (when (and (or (and helm-ff-auto-update-flag (null helm-ff--deleting-char-backward) ;; Bug#295 ;; File predicates are returning t ;; with paths like //home/foo. ;; So check it is not the case by regexp ;; to allow user to do C-a / to start e.g ;; entering a tramp method e.g /sudo::. (not (string-match "\\`//" helm-pattern)) (not (eq last-command 'helm-yank-text-at-point))) ;; Fix Bug#542. (string= helm-pattern "~/") ;; Only one remaining directory, expand it. (and (= candnum 1) helm-ff--auto-update-state (file-accessible-directory-p pat) (null helm-ff--deleting-char-backward))) (or ;; Only one candidate remaining ;; and at least 2 char in basename. lt2-p ;; Already completed. completed-p) (not history-p) ; Don't try to auto complete in history. (stringp cur-cand) (file-accessible-directory-p cur-cand)) (if (and (not (helm-ff-dot-file-p cur-cand)) ; [1] ;; Maybe we are here because completed-p is true ;; but check this again to be sure. (Windows fix) (<= candnum 2)) ; [2] ;; If after going to next line the candidate ;; is not one of "." or ".." [1] ;; and only one candidate is remaining [2], ;; assume candidate is a new directory to expand, and do it. (progn (setq expand-to (file-name-as-directory (substring-no-properties cur-cand))) (setq helm-ff--show-thumbnails (member expand-to helm-ff--thumbnailed-directories)) (helm-set-pattern expand-to) ;; Reset flags to show all when changing dir. (helm-ff-after-persistent-show-all)) ;; The candidate is one of "." or ".." ;; that mean we have entered the last letter of the directory name ;; in prompt, so expansion is already done, just add the "/" at end ;; of name unless helm-pattern ends with "." ;; (i.e we are writing something starting with ".") (unless (string-match "\\`.*[.]\\{1\\}\\'" helm-pattern) ;; Need to expand-file-name to avoid e.g /ssh:host:./ in prompt. (setq expand-to (expand-file-name (file-name-as-directory helm-pattern))) (setq helm-ff--show-thumbnails (member expand-to helm-ff--thumbnailed-directories)) (helm-set-pattern expand-to))) ;; When typing pattern in minibuffer, helm ;; expand very fast to a directory matching pattern and ;; don't let undo the time to set a boundary, the result ;; is when e.g. going to root with "//" and undoing, undo ;; doesn't undo to previous input. One fix for this is to ;; advice `undo-auto--boundary-ensure-timer' so that it is ;; possible to modify its delay (use a value of 1s for ;; helm), a second fix is to run directly here `undo-boundary' ;; inside a timer. (run-at-time helm-input-idle-delay nil #'undo-boundary) (helm-check-minibuffer-input))))))) (cl-defun helm-ff-auto-expand-to-home-or-root (&optional (pattern helm-pattern spattern)) "Allow expanding to $HOME or \"/\" or text yanked after pattern. Argument PATTERN default to `helm-pattern' and should _not_ be used for other purpose than debugging the second cond clause of this function. When PATTERN is specified, specific helm functions are not called to avoid errors when called outside helm for debugging purpose." (when (or spattern (and (helm-file-completion-source-p) (with-current-buffer (window-buffer (minibuffer-window)) (eolp)) (not (string-match helm-ff-url-regexp pattern)))) (cond ((and (not (file-remote-p pattern)) (null (file-exists-p pattern)) (string-match-p "\\`\\([.]\\)\\{2\\}[^/]+" (helm-basename pattern)) (string-match-p "/\\'" pattern) (null spattern)) (helm-ff-recursive-dirs pattern) (helm-ff--maybe-set-pattern-and-update)) ((string-match "\\(?:\\`~/\\)\\|/?\\$.*/\\|/\\./\\|/\\.\\./\\|/~.*/\\|//\\|\\(/[[:alpha:]]:/\\)" pattern) (let* ((match (match-string 0 pattern)) (input (cond ((string= match "/./") (expand-file-name default-directory)) ((string= pattern "/../") "/") ((string-match-p "\\`/\\$" match) (let ((sub (substitute-in-file-name match))) (if (file-directory-p sub) sub (replace-regexp-in-string "/\\'" "" sub)))) (t (helm-ff--expand-substitued-pattern pattern))))) ;; `file-directory-p' returns t on "/home/me/." (Bug#1844). (if (and (file-directory-p input) (not (string-match-p "[^.]\\.\\'" input))) (progn (setq helm-ff-default-directory (setq input (file-name-as-directory input))) ;; When changing directory ensure to show all. (helm-ff-after-persistent-show-all)) (setq helm-ff-default-directory (file-name-as-directory (file-name-directory input)))) (if spattern input (helm-ff--maybe-set-pattern-and-update input)))) ((and (string-match "\\`/\\(-\\):.*" pattern) (null spattern)) (helm-ff--maybe-set-pattern-and-update (replace-match tramp-default-method t t pattern 1)))))) (defun helm-ff--maybe-set-pattern-and-update (&optional str) (with-helm-window (when str (helm-set-pattern str)) (helm-check-minibuffer-input))) (defun helm-ff--expand-file-name-no-dot (name &optional directory) "Prevent expanding \"/home/user/.\" to \"/home/user\"." ;; Bug#1844 - If user enter "~/." to type an hidden filename ;; don't expand to /home/him e.g. ;; (expand-file-name "~/.") =>"/home/thierry" ;; (helm-ff--expand-substitued-pattern "~/.") =>"/home/thierry/." (concat (expand-file-name name directory) (and (string-match "[^.]\\.\\'" name) "/."))) (defun helm-ff--expand-substitued-pattern (pattern) ;; [Windows] On UNC paths "/" expand to current machine, ;; so use the root of current Drive. (i.e "C:/") (let* ((directory (and (memq system-type '(windows-nt ms-dos)) (getenv "SystemDrive"))) (subst (helm-substitute-in-filename pattern)) ;; On Windows use a simple call to `expand-file-name' to ;; avoid Bug#2004. (expand-fn (if directory #'expand-file-name #'helm-ff--expand-file-name-no-dot))) ;; Fix Bug#2223 with tilde in directory names e.g. "~/tmp/~test/". (funcall expand-fn (if (string-match-p "\\`~[^/]" subst) pattern subst) ;; directory is nil on Nix. directory))) (defun helm-substitute-in-filename (fname) "Substitute all parts of FNAME from start up to \"~/\" or \"/\". On windows system substitute from start up to \"/[[:lower:]]:/\". This function is needed for `helm-ff-auto-expand-to-home-or-root' and should be used carefully elsewhere, or not at all, using `substitute-in-file-name' instead." (cond ((and helm--url-regexp (string-match-p helm--url-regexp fname)) fname) ((and (file-remote-p fname) helm-substitute-in-filename-stay-on-remote) (let ((sub (substitute-in-file-name fname))) (if (file-directory-p sub) sub (replace-regexp-in-string "/\\'" "" sub)))) (t (with-temp-buffer (insert fname) (goto-char (point-min)) (when (memq system-type '(windows-nt ms-dos)) (skip-chars-forward "/")) ;; Avoid infloop in UNC paths Bug#424 (if (re-search-forward "~.*/?\\|//\\|/[[:alpha:]]:/" nil t) (let ((match (match-string 0))) (goto-char (if (or (string= match "//") (string-match-p "/[[:alpha:]]:/" match)) (1+ (match-beginning 0)) (match-beginning 0))) (buffer-substring-no-properties (point) (pos-eol))) fname))))) (defun helm-point-file-in-dired (file) "Put point on filename FILE in dired buffer." (unless (and helm--url-regexp (string-match-p helm--url-regexp file)) (let ((target (expand-file-name (helm-substitute-in-filename file)))) (dired (file-name-directory target)) (dired-goto-file target)))) (defun helm-marked-files-in-dired (_candidate) "Open a dired buffer with only marked files. With a prefix arg toggle dired buffer to wdired mode. Note: This function works only in Emacs-29+ because Wdired doesn't support editing absolute fnames in previous Emacs versions." (let* ((marked (helm-marked-candidates :with-wildcard t)) (current (car marked))) (unless (and helm--url-regexp (string-match-p helm--url-regexp current)) (let ((target (expand-file-name (helm-substitute-in-filename current)))) (dired (cons helm-ff-default-directory marked)) (dired-goto-file target) (when (or helm-current-prefix-arg current-prefix-arg) (call-interactively 'wdired-change-to-wdired-mode)))))) (defun helm-ff-wfnames (_candidate) "Edit marked fnames with `Wfnames' package." (cl-assert (require 'wfnames nil t) nil "Wfnames package not found") (let ((marked (helm-marked-candidates :with-wildcard t))) (wfnames-setup-buffer marked #'switch-to-buffer (buffer-live-p (get-buffer wfnames-buffer))))) (defun helm-ff-edit-marked-files (candidate) "Edit marked files with `helm-ff-edit-marked-files-fn' fn." (funcall helm-ff-edit-marked-files-fn candidate)) (helm-make-command-from-action helm-ff-run-edit-marked-files "Execute `helm-ff-edit-marked-files' interactively." 'helm-ff-edit-marked-files) (defun helm-ff--create-tramp-name (fname) "Build filename from `helm-pattern' like /su:: or /sudo::." ;; `tramp-make-tramp-file-name' takes 7 args on emacs-26 whereas it ;; takes only 5 args in emacs-24/25. (apply #'tramp-make-tramp-file-name ;; `tramp-dissect-file-name' returns a list in emacs-26 ;; whereas in 24.5 it returns a vector, thus the car is a ;; symbol (`tramp-file-name') which is not needed as argument ;; for `tramp-make-tramp-file-name' so transform the cdr in ;; vector, and for 24.5 use directly the returned value. (cl-loop with v = (helm-ff--tramp-cons-or-vector (tramp-dissect-file-name fname)) for i across v collect i))) (defun helm-ff--tramp-cons-or-vector (vector-or-cons) "Return VECTOR-OR-CONS as a vector." (helm-acase vector-or-cons ((guard* (and (consp it) (cdr it))) (vconcat guard)) ((guard* (vectorp it)) it) (t (error "Wrong type argument: %s" it)))) (defun helm-ff--get-tramp-methods () "Return a list of the car of `tramp-methods'." (or helm-ff--tramp-methods (setq helm-ff--tramp-methods (mapcar 'car tramp-methods)))) (defun helm-ff--previous-mh-tramp-method (str) (save-match-data (with-temp-buffer (insert str) (when (re-search-backward (concat "\\([|]\\)\\(" (mapconcat 'identity (helm-ff--get-tramp-methods) "\\|") "\\):") nil t) (list (buffer-substring-no-properties (pos-bol) (match-beginning 2)) (buffer-substring-no-properties (match-beginning 2) (match-end 2))))))) (defun helm-ff--get-host-from-tramp-invalid-fname (fname) "Extract hostname from an incomplete tramp file name. Return nil on valid file name remote or not." ;; Check first if whole file is remote (file-remote-p is inefficient ;; in this case) otherwise we are matching e.g. /home/you/ssh:foo/ ;; which is not a remote name. ;; FIXME this will not work with a directory or a file named like ;; "ssh:foo" and located at root (/) but it seems there is no real ;; solution apart disabling tramp-mode when a file/dir located at / ;; is matching helm-tramp-file-name-regexp; This would prevent usage ;; of tramp if one have such a directory at / (who would want to ;; have such a dir at / ???) See emacs-bug#31489. (when (string-match-p helm-tramp-file-name-regexp fname) (let* ((bn (helm-basename fname)) (bd (replace-regexp-in-string (regexp-quote bn) "" fname)) (split (split-string bn ":" t)) (meth (car (member (car split) (helm-ff--get-tramp-methods))))) (and meth (string= bd "/") (car (last split)))))) (cl-defun helm-ff--tramp-hostnames (&optional (pattern helm-pattern)) "Get a list of hosts for tramp method found in `helm-pattern'. Argument PATTERN default to `helm-pattern'. It is here only for debugging purpose." (when (string-match helm-tramp-file-name-regexp pattern) (let* ((mh-method (helm-ff--previous-mh-tramp-method pattern)) (method (or (cadr mh-method) (match-string 1 pattern)))) (cl-loop with all-methods = (helm-ff--get-tramp-methods) for (f . h) in (tramp-get-completion-function method) append (cl-loop for e in (funcall f (car h)) for host = (and (consp e) (cadr e)) ;; On emacs-27 host may be ;; ("root" t) in sudo method. when (and (stringp host) (not (member host all-methods))) collect (helm-ff-filter-candidate-one-by-one (concat (or (car mh-method) "/") method ":" host))) into comps finally return (helm-fast-remove-dups comps :test 'equal))))) (defun helm-ff-before-action-hook-fn () "Exit Helm when user try to execute action on an invalid tramp fname." (let* ((src (helm-get-current-source)) (cand (helm-get-selection nil nil src))) (when (and (helm-file-completion-source-p src) (stringp cand) (helm-ff--invalid-tramp-name-p cand) ; Check candidate. (helm-ff--invalid-tramp-name-p)) ; check helm-pattern. (error "Error: Unknown file or directory `%s'" cand)))) (add-hook 'helm-before-action-hook 'helm-ff-before-action-hook-fn) (cl-defun helm-ff--invalid-tramp-name-p (&optional (pattern helm-pattern)) "Return non-nil when PATTERN is an invalid tramp filename." (or (string= (helm-ff-set-pattern pattern) "@@TRAMP@@") ;; Tramp methods completion. (string-match helm-ff-tramp-method-regexp pattern))) (defun helm-ff--tramp-multihops-p (name) (cl-loop for m in (helm-ff--get-tramp-methods) thereis (string-match (format "\\`\\(/%s:.*[|]\\).*" m) name))) (defun helm-ff-complete-tramp-methods () "Completion on tramp methods in a nested helm session." (interactive) (with-helm-alive-p (let* (initial-input (str helm-pattern) (pattern (with-temp-buffer (insert str) (let ((end (point)) beg) (when (re-search-backward "[/|]" nil t) (setq beg (1+ (point))) (unless (= beg end) (setq initial-input (buffer-substring beg end)) (delete-region beg end)) (buffer-string))))) (collection (helm-ff--get-tramp-methods)) (method (helm-comp-read "Tramp methods: " (sort collection #'string<) :initial-input initial-input :fc-transformer (lambda (candidates _source) (cl-loop for c in candidates collect (propertize c 'face 'helm-ff-file))) :allow-nest t :must-match t))) (helm-set-pattern (concat pattern method ":"))))) (put 'helm-ff-complete-tramp-methods 'no-helm-mx t) (defun helm-ff-set-pattern (pattern) "Handle tramp filenames in `helm-pattern'." (let* ((methods (helm-ff--get-tramp-methods)) ;; Returns the position of last ":" entered. (postfixed (file-remote-p pattern)) (reg "\\`/\\([^[/:]+\\|[^/]+]\\):.*:") cur-method tramp-name) (when (string-match "\\`/\\(-\\):" pattern) (setq pattern (replace-match tramp-default-method t t pattern 1))) ;; In some rare cases tramp can return a nil input, ;; so be sure pattern is a string for safety (Bug#476). (unless pattern (setq pattern "")) (cond ((string-match helm-ff-url-regexp pattern) pattern) ((string-match "\\`\\$" pattern) (substitute-in-file-name pattern)) ((string= pattern "") "") ((string-match "\\`[.]\\{1,2\\}/\\'" pattern) (expand-file-name pattern)) ;; Directories ending by a dot (Bug#1940) ((string-match "[^/][.]/\\'" pattern) (expand-file-name pattern)) ((string-match ".*\\(~?/?[.]\\{1\\}/\\)\\'" pattern) (expand-file-name default-directory)) ((string-match ".*\\(~//\\|//\\)\\'" pattern) (expand-file-name "/")) ; Expand to "/" or "c:/" ((string-match "\\`\\(~/\\|.*/~/\\)\\'" pattern) (expand-file-name "~/")) ((string-match "\\`~/" pattern) (expand-file-name pattern)) ((string-match helm-ff-tramp-method-regexp pattern) pattern) ;; Match "/method:maybe_hostname:~" ((and (string-match (concat reg "~") pattern) postfixed (setq cur-method (match-string 1 pattern)) (member cur-method methods)) (setq tramp-name (expand-file-name (helm-ff--create-tramp-name (match-string 0 pattern)))) (replace-match tramp-name nil t pattern)) ;; Match "/method:maybe_hostname:" ((and (string-match reg pattern) postfixed (setq cur-method (match-string 1 pattern)) (member cur-method methods)) (setq tramp-name (helm-ff--create-tramp-name (match-string 0 pattern))) (replace-match tramp-name nil t pattern)) ;; Match "/hostname:" ((and (string-match helm-tramp-file-name-regexp pattern) postfixed (setq cur-method (match-string 1 pattern)) (and cur-method (not (member cur-method methods)))) (setq tramp-name (helm-ff--create-tramp-name (match-string 0 pattern))) (replace-match tramp-name nil t pattern)) ;; Match "/method:" in this case don't try to connect. ((and (null postfixed) (string-match helm-tramp-file-name-regexp pattern) (member (match-string 1 pattern) methods)) ;; A flag to notify tramp name is incomplete. "@@TRAMP@@") ;; Return PATTERN unchanged. (t pattern)))) (defun helm-find-files-get-candidates () "Create candidate list for `helm-source-find-files'." (let* ((path (helm-ff-set-pattern helm-pattern)) (dir-p (file-accessible-directory-p path)) basedir invalid-basedir non-essential (tramp-verbose helm-tramp-verbose)) ; No tramp message when 0. ;; Tramp check if path is valid without waiting a valid ;; connection and may send a file-error. (setq helm--ignore-errors (file-remote-p path)) (set-text-properties 0 (length path) nil path) ;; Bug#118 allow creation of newdir+newfile. (unless (or ;; A tramp file name not completed. (string= path "@@TRAMP@@") ;; An empty pattern (string= path "") (file-remote-p path) ;; Check if base directory of PATH is valid. (helm-aif (file-name-directory path) ;; If PATH is a valid directory IT=PATH, ;; else IT=basedir of PATH. (file-directory-p it))) ;; BASEDIR is invalid, that's mean user is starting ;; to write a non--existing path in minibuffer ;; probably to create a 'new_dir' or a 'new_dir+new_file'. (setq invalid-basedir t)) ;; Don't set now `helm-pattern' if `path' == "@@TRAMP@@" ;; like that the actual value (e.g /ssh:) is passed to ;; `helm-ff--tramp-hostnames'. (unless (or (string= path "@@TRAMP@@") invalid-basedir) ; Leave helm-pattern unchanged. (setq helm-ff-auto-update-flag ; [1] ;; Unless auto update is disabled start auto updating only ;; at third char. (unless (or (null helm-ff--auto-update-state) ;; But don't enable auto update when ;; deleting backward. helm-ff--deleting-char-backward (and dir-p (not (string-match-p "/\\'" path)))) (or (>= (length (helm-basename path)) 3) dir-p))) ;; At this point the tramp connection is triggered. (helm-log "helm-find-files-get-candidates" "Pattern=%S" (setq helm-pattern (if (string-match helm-ff-tramp-method-regexp path) ;; A tramp method, don't modify pattern. helm-pattern (helm-ff--transform-pattern-for-completion path)))) ;; This have to be set after [1] to allow deleting char backward. (setq basedir (or (helm-aand (if (and dir-p helm-ff-auto-update-flag) ;; Add the final "/" to path ;; when `helm-ff-auto-update-flag' is enabled. (file-name-as-directory path) (if (string= path "") "/" (file-name-directory path))) (expand-file-name it)) default-directory)) (setq helm-ff-default-directory (if (string= helm-pattern "") (expand-file-name "/") ; Expand to "/" or "c:/" ;; If path is an url *default-directory have to be nil. (unless (or (string-match helm-ff-url-regexp path) (and helm--url-regexp (string-match helm--url-regexp path))) basedir)))) (when (and (string-match ":\\'" path) (file-remote-p basedir nil t)) (setq helm-pattern basedir)) (cond (invalid-basedir nil) ((string-match helm-ff-tramp-method-regexp path) ; Tramp methods (mapcar (lambda (method) (helm-ff-filter-candidate-one-by-one (concat "/" ":" method))) (helm-ff--get-tramp-methods))) ((string= path "@@TRAMP@@") (helm-ff--tramp-hostnames)) ; Hostnames completion. ((or (and (file-regular-p path) (eq last-repeatable-command 'helm-execute-persistent-action)) ;; `ffap-url-regexp' don't match until url is complete. (string-match helm-ff-url-regexp path) (and helm--url-regexp (string-match helm--url-regexp path))) ;; Do NOT filter boring files here (Bug#2330). (list (helm-ff-filter-candidate-one-by-one path nil t))) ((string= path "") (helm-ff-directory-files "/")) ;; Check here if directory is accessible (not working on Windows). ((and (file-directory-p path) (not (file-readable-p path))) ;; Prefix error message with @@@@ for safety ;; (some files may match file-error See bug#2400) (list (cons (format "@@@@file-error: Opening directory permission denied `%s'" path) path))) ;; A fast expansion of PATH is made only if `helm-ff-auto-update-flag' ;; is enabled. ((and dir-p helm-ff-auto-update-flag) (helm-ff-directory-files path)) (t (helm-ff-directory-files basedir))))) (defun helm-list-directory (directory &optional sel) "List directory DIRECTORY. If DIRECTORY is remote use `helm-list-remote-directory-fn', otherwise use `directory-files'. SEL argument is only here for debugging purpose, it default to `helm-get-selection'." (let* ((remote (file-remote-p directory 'method)) (helm-list-remote-directory-fn (helm-acase remote ("ftp" #'helm-list-dir-lisp) ("adb" #'helm-list-dir-adb) (t helm-list-remote-directory-fn))) (use-ext-remote-fn (and remote (eq helm-list-remote-directory-fn 'helm-list-dir-external))) (sort-method (helm-acase helm-ff-initial-sort-method (newest (if use-ext-remote-fn "-t" #'file-newer-than-file-p)) (size (if use-ext-remote-fn "-S" #'helm-ff-file-larger-that-file-p)) (ext (if use-ext-remote-fn #'identity #'helm-group-candidates-by))))) (if remote (ignore-errors (funcall helm-list-remote-directory-fn directory sort-method)) (helm-acase helm-ff-initial-sort-method ((newest size) (sort (helm-local-directory-files directory t directory-files-no-dot-files-regexp) sort-method)) (ext (funcall sort-method (helm-local-directory-files directory t directory-files-no-dot-files-regexp) #'file-name-extension (or sel (helm-get-selection) ""))) (t (helm-local-directory-files directory t directory-files-no-dot-files-regexp)))))) (defsubst helm-ff-file-larger-that-file-p (f1 f2) (let ((attr1 (file-attributes f1)) (attr2 (file-attributes f2))) (> (nth 7 attr1) (nth 7 attr2)))) (defun helm-list-dir-lisp (directory &optional sort-method) "List DIRECTORY with `file-name-all-completions' as backend. Add a `helm-ff-dir' property on each fname ending with \"/\"." ;; NOTE: `file-name-all-completions' and `directory-files' and most ;; tramp file handlers don't handle cntrl characters in fnames, so ;; the displayed files will be plain wrong in this case, even worst ;; the filenames will be splitted in two or more filenames. (cl-loop for f in (sort (file-name-all-completions "" directory) (or sort-method 'string-lessp)) unless (or (string= f "") (member f '("./" "../" "." ".."))) if (and (helm--dir-name-p f) (helm--dir-file-name f directory)) collect (propertize it 'helm-ff-dir t) else collect (propertize (expand-file-name f directory) 'helm-ff-file t))) (defun helm-file-name-all-completions-internal (directory) (let ((switches "-1F")) (with-temp-buffer (insert-directory (format "%s*" (file-name-as-directory directory)) switches t) (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n" t)))) (defun helm-list-dir-adb (directory &optional sort-method) "List DIRECTORY with `helm-file-name-all-completions-internal' as backend. This is used for tramp adb backend. Add a `helm-ff-dir' property on each fname ending with \"/\"." (let ((inhibit-message t)) (cl-loop with files = (helm-file-name-all-completions-internal directory) for f in (sort files (or sort-method 'string-lessp)) for split = (split-string f "->" t) for fname = (replace-regexp-in-string " $" "" (car split)) for truename = (cadr split) collect (cond ((string-match "/\\'" fname) (propertize (helm--dir-file-name fname directory) 'helm-ff-dir t)) (truename (propertize (expand-file-name (substring fname 0 (1- (length fname))) directory) 'helm-ff-sym truename)) (t (propertize (expand-file-name fname directory) 'helm-ff-file t)))))) (defun helm-list-dir-external (dir &optional sort-method) "List directory DIR with external shell command as backend. This function is fast enough to be used for remote files and save the type of files at the same time in a property for using it later in the transformer." (let ((default-directory (file-name-as-directory (expand-file-name dir)))) (with-temp-buffer (when (eq (process-file-shell-command (format ;; -A remove dot files, -F append [*=@|/>] at eof ;; and -Q quote the real filename. If not using -Q, ;; there is no way to distinguish if foo* is a real ;; file or if it is foo the executable file so with ;; -Q we have "foo"* for the executable file foo and ;; "foo*" for the real file foo. The downside is ;; that we need an extra step to remove the quotes ;; at the end which impact performances. "ls -A -1 -F -b -Q %s | awk -v dir=%s '{print dir $0}'" (or sort-method "") (shell-quote-argument default-directory)) nil t nil) 0) (goto-char (point-min)) (save-excursion (while (re-search-forward "[*=@|/>]$" nil t) ;; A line looks like /home/you/"foo"@ (helm-acase (match-string 0) ("*" (replace-match "") (put-text-property (pos-bol) (pos-eol) 'helm-ff-exe t)) ("@" (replace-match "") (put-text-property (pos-bol) (pos-eol) 'helm-ff-sym t)) ("/" (replace-match "") (put-text-property (pos-bol) (pos-eol) 'helm-ff-dir t)) (("=" "|" ">") (replace-match ""))))) (while (re-search-forward "[\"]" nil t) (replace-match "")) (add-text-properties (point-min) (point-max) '(helm-ff-file t)) (split-string (buffer-string) "\n" t))))) ;; This is to fix issue with file-notify.el no more following symlinks ;; on emacs-29 (regression) with inotify backend at least. Also it ;; seems inotify is not configured to follow symlinks on other systems ;; (MacOS) so this should fix as well this issue on such systems see ;; bug#2542. ;; Store here associations of (truename . symlink) when opening a ;; symlinked directory, then add the watch to the truename; When the ;; watcher ring on the truename remove the symlinked directory from cache. (defvar helm-ff--list-directory-links nil) (defun helm-ff-directory-files (directory &optional force-update) "List contents of DIRECTORY. Argument FULL mean absolute path. It is same as `directory-files' but always returns the dotted filename \\='.' and \\='..' even on root directories in Windows systems. When FORCE-UPDATE is non nil recompute candidates even if DIRECTORY is in cache." (let* ((method (file-remote-p directory 'method)) (dfn (directory-file-name directory)) (truename (and (file-symlink-p dfn) (file-truename dfn)))) (setq directory (file-name-as-directory (expand-file-name directory))) (when truename (cl-pushnew (cons truename directory) helm-ff--list-directory-links :test 'equal)) (or (and (not force-update) (not (member method helm-ff-inotify-unsupported-methods)) (gethash directory helm-ff--list-directory-cache)) (let* (file-error (ls (condition-case err (helm-list-directory directory) ;; Handle file-error from here for Windows ;; because predicates like `file-readable-p' and friends ;; seem broken on emacs for Windows systems (always returns t). ;; This should never be called on GNU/Linux/Unix ;; as the error is properly intercepted in ;; `helm-find-files-get-candidates' by `file-readable-p'. (file-error (prog1 ;; Prefix error message with @@@@ for safety ;; (some files may match file-error See bug#2400) (list (format "@@@@%s:%s" (car err) (mapconcat 'identity (cdr err) " "))) (setq file-error t))))) (dot (concat directory ".")) (dot2 (concat directory "..")) (candidates (append (and (not file-error) (list dot dot2)) ls)) watcher) (puthash directory (+ (length ls) 2) helm-ff--directory-files-length) (prog1 (puthash directory (cl-loop for f in candidates when (helm-ff-filter-candidate-one-by-one f) collect it) helm-ff--list-directory-cache) ;; Put an inotify watcher to check directory modifications. (unless (or (null helm-ff-use-notify) (member method helm-ff-inotify-unsupported-methods) (helm-aand (setq watcher (gethash directory helm-ff--file-notify-watchers)) ;; [1] If watcher is invalid enter ;; next and delete it. (file-notify-valid-p it))) (condition-case-unless-debug err (let ((to-watch (or truename directory))) (when watcher ;; If watcher is still in cache and we are here, ;; that's mean test [1] above fails and watcher ;; is invalid, so delete it. (file-notify-rm-watch watcher) (remhash directory helm-ff--file-notify-watchers)) ;; Keep adding DIRECTORY to ;; helm-ff--file-notify-watchers but watch on the ;; truename and not the symlink as before bug#2542. (puthash directory (file-notify-add-watch to-watch '(change attribute-change) (helm-ff--inotify-make-callback to-watch)) helm-ff--file-notify-watchers)) (file-notify-error (user-error "Error: %S %S" (car err) (cdr err)))))))))) (defun helm-ff--inotify-make-callback (directory) "Return a callback for `file-notify-add-watch'." (lambda (event) (let ((desc (cadr event)) (target directory)) ; Either truename or directory. (helm-log "Inotify callback" "Event %S called on %S" event directory) ;; `attribute-changed' means permissions have changed, not ;; file modifications like file changes, visit ;; etc... AFAIU the desc for this is `changed' and for our ;; use case we don't care of this. Elemnts of ;; `helm-ff--list-directory-links' are of the form ;; (truename . visited-symlink-directory) (when (memq desc '(created deleted renamed attribute-changed)) ;; Watched directory is the truename which is not in the ;; cache, so remove its associated directory (the symlink) ;; from the cache bug#2542. (helm-aif (assoc directory helm-ff--list-directory-links) (progn (setq target (cdr it)) (setq helm-ff--list-directory-links (delete it helm-ff--list-directory-links)))) ;; When TARGET is modified remove it from cache. (helm-log "Inotify callback" "Removing %S from `helm-ff--list-directory-cache'" target) (remhash target helm-ff--list-directory-cache))))) (defun helm-ff-tramp-cleanup-hook (vec) "Remove remote directories related to VEC in helm-ff* caches. Remove as well all related file-notify watchers. This is meant to run in `tramp-cleanup-connection-hook'." (cl-loop for key being the hash-keys in helm-ff--list-directory-cache when (equal (file-remote-p key 'method) (cadr vec)) do (remhash key helm-ff--list-directory-cache)) (cl-loop for key being the hash-keys in helm-ff--file-notify-watchers when (equal (file-remote-p key 'method) (cadr vec)) do (progn (file-notify-rm-watch (gethash key helm-ff--file-notify-watchers)) (remhash key helm-ff--file-notify-watchers)))) (add-hook 'tramp-cleanup-connection-hook #'helm-ff-tramp-cleanup-hook) (defun helm-ff-handle-backslash (fname) ;; Allow creation of filenames containing a backslash. (cl-loop with bad = '((92 . "")) for i across fname if (assq i bad) concat (cdr it) else concat (string i))) (defun helm-ff-fuzzy-matching-p () (and helm-ff-fuzzy-matching (not (memq helm-mm-matching-method '(multi1 multi3p))))) (defun helm-ff--transform-pattern-for-completion (pattern) "Maybe return PATTERN with it's basename modified as a regexp. This happens only when `helm-ff-fuzzy-matching' is enabled. This provides a similar behavior as `ido-enable-flex-matching'. See also `helm--mapconcat-pattern'. If PATTERN is an url return it unmodified. When PATTERN contains a space fallback to multi-match. If basename contains one or more space fallback to multi-match. If PATTERN is a valid directory name, return PATTERN unchanged." ;; handle bad filenames containing a backslash (no more needed in ;; emacs-26, also prevent regexp matching with e.g. "\|"). ;; (setq pattern (helm-ff-handle-backslash pattern)) (let ((bn (helm-basename pattern)) (bd (or (helm-basedir pattern) "")) ;; Trigger tramp connection with file-directory-p. (dir-p (file-directory-p pattern)) (tramp-p (cl-loop for (m . f) in tramp-methods thereis (string-match m pattern)))) ;; Always regexp-quote base directory name to handle ;; crap dirnames such e.g bookmark+ (cond ((or (and dir-p tramp-p (string-match ":\\'" pattern)) (string= pattern "") (and dir-p (<= (length bn) 2)) ;; Fix Bug#541 when BD have a subdir similar ;; to BN, don't switch to match plugin ;; which will match both. (and dir-p (string-match (regexp-quote bn) bd))) ;; Use full PATTERN on e.g "/ssh:host:". (regexp-quote pattern)) ;; Prefixing BN with a space call multi-match completion. ;; This allow showing all files/dirs matching BN (Bug#518). ;; FIXME: some multi-match methods may not work here. (dir-p (concat (regexp-quote bd) " " (regexp-quote bn))) ((or (not (helm-ff-fuzzy-matching-p)) (string-match "[ !]" bn)) ; Fall back to multi-match. (concat (regexp-quote bd) " " bn)) ((or (string-match "[*][.]?.*" bn) ; Allow entering wildcard. (string-match "/\\'" pattern) ; Allow mkdir. (string-match helm-ff-url-regexp pattern) (and (string= helm-ff-default-directory "/") tramp-p)) ;; Don't treat wildcards ("*") as regexp char. ;; (e.g ./foo/*.el => ./foo/\\*\\.el) or ./foo/*.[ch] => ;; ./foo/\\*\\.\\[ch] (concat (regexp-quote bd) ;; We were previously using ;; (replace-regexp-in-string "[*]" "[*]" bn) but this ;; doesn't handle wilcards like *.[ch], so regexp-quote ;; bn as well. (regexp-quote bn))) (t (concat (regexp-quote bd) (if (>= (length bn) 2) ; wait 2nd char before concating. (helm--mapconcat-pattern bn) (concat ".*" (regexp-quote bn)))))))) (defalias 'helm-dir-is-dot 'helm-ff-dot-file-p) (make-obsolete 'helm-dir-is-dot 'helm-ff-dot-file-p "3.8.8") (defun helm-ff-save-history () "Store the last value of `helm-ff-default-directory' in `helm-ff-history'. Note that only existing directories are saved here." (when (and helm-ff-default-directory (helm-file-completion-source-p) (file-directory-p helm-ff-default-directory)) (set-text-properties 0 (length helm-ff-default-directory) nil helm-ff-default-directory) (push helm-ff-default-directory helm-ff-history))) (add-hook 'helm-cleanup-hook 'helm-ff-save-history) (defun helm-ff-valid-symlink-p (file &optional link) "Returns the truename of FILE if it exists. If we already know the truename of FILE we can pass it with LINK arg to avoid an unnecessary call to `file-truename'." (helm-aif (condition-case-unless-debug nil ;; `file-truename' send error ;; on cyclic symlinks (Bug#692). (or link (file-truename file)) (error nil)) (and (file-exists-p it) it))) (defun helm-get-default-mode-for-file (filename) "Return the default mode to open FILENAME." (let ((mode (cl-loop for (r . m) in auto-mode-alist thereis (and (string-match r filename) m)))) (or (and (symbolp mode) mode) "Fundamental"))) (defun helm-ff-properties (candidate) "Show file properties of CANDIDATE in a tooltip or message." (require 'helm-external) ; For `helm-get-default-program-for-file'. (helm-aif (helm-file-attributes candidate) (let* ((dired-line (helm-file-attributes-dired-line it t t)) (type (cl-getf it :type)) (mode-type (cl-getf it :mode-type)) (owner (cl-getf it :uid)) (owner-right (cl-getf it :user t)) (group (cl-getf it :gid)) (group-right (cl-getf it :group)) (other-right (cl-getf it :other)) (octal (cl-getf it :octal)) (trash (and (helm-ff-trash-file-p candidate) (helm-ff--get-dest-file-from-trash (helm-ff-trash-list) (replace-regexp-in-string "\\.trashinfo\\'" "" candidate)))) (size (helm-file-human-size (cl-getf it :size))) (modif (cl-getf it :modif-time)) (access (cl-getf it :access-time)) (ext (helm-get-default-program-for-file candidate)) (tooltip-hide-delay (or helm-tooltip-hide-delay tooltip-hide-delay)) (posn (with-helm-window (posn-at-point (save-excursion (end-of-visual-line) (point))))) (tooltip-frame-parameters (append tooltip-frame-parameters `((left . ,(car (posn-x-y posn))) (top . ,(cdr (posn-x-y posn))))))) (if (and (display-graphic-p) tooltip-mode) (tooltip-show (concat (helm-basename candidate) "\n" dired-line "\n" (format "Mode: %s\n" (helm-get-default-mode-for-file candidate)) (format "Ext prog: %s\n" (or (and ext (replace-regexp-in-string " %s" "" ext)) "Not defined")) (format "Type: %s: %s\n" type mode-type) (when (string= type "symlink") (format "True name: '%s'\n" (cond ((string-match "^\\.#" (helm-basename candidate)) "Autosave symlink") ((helm-ff-valid-symlink-p candidate)) (t "Invalid Symlink")))) (format "Owner: %s: %s\n" owner owner-right) (format "Group: %s: %s\n" group group-right) (format "Others: %s\n" other-right) (format "NumMode: %s\n" octal) (format "Size: %s\n" size) (when (string= type "directory") (format "Size used in directory: %s\n" (helm-directory-size candidate current-prefix-arg t))) (format "Modified: %s\n" modif) (format "Accessed: %s\n" access) (and (stringp trash) (format "Trash: %s\n" (abbreviate-file-name trash))))) (message dired-line) (sit-for 5))) (message "Permission denied, file not readable"))) (helm-make-persistent-command-from-action helm-ff-properties-persistent "Show properties without quitting helm." 'properties-action 'helm-ff-properties) (helm-make-persistent-command-from-action helm-ff-persistent-delete "Delete current candidate without quitting." 'quick-delete 'helm-ff-quick-delete) (defun helm-ff-kill-default-directory (_candidate) (with-helm-window (kill-new helm-ff-default-directory) (message "`%s' copied to kill-ring" helm-ff-default-directory))) (helm-make-persistent-command-from-action helm-ff-run-kill-default-directory "Kill `helm-ff-default-directory'." 'kill-default-directory 'helm-ff-kill-default-directory) (defun helm-ff-dot-file-p (file) "Check if FILE is `.' or `..'." (member (helm-basename file) '("." ".."))) (defun helm-ff-kill-buffer-fname (candidate) (let* ((buf (get-file-buffer candidate)) (buf-name (buffer-name buf))) (cond ((and buf (eq buf (get-buffer helm-current-buffer))) (user-error "Can't kill `helm-current-buffer' without quitting session")) (buf (kill-buffer buf) (message "Buffer `%s' killed" buf-name)) (t (message "No buffer to kill"))))) (defun helm-ff-kill-or-find-buffer-fname (candidate) "Find file CANDIDATE or kill its buffer if it is visible. Never kill `helm-current-buffer'. Never kill buffer modified. This is called normally on third hit of \ \\\\[helm-execute-persistent-action] in `helm-find-files-persistent-action-if'." (let* ((buf (get-file-buffer candidate)) (buf-name (buffer-name buf)) (win (get-buffer-window buf)) (helm--reading-passwd-or-string t) ;; Prevent tramp from asking yes-or-no-p for ;; `tramp-allow-unsafe-temporary-files'. auto-save-default) (cond ((and buf win (eql buf (get-buffer helm-current-buffer))) (user-error "Can't kill `helm-current-buffer' without quitting session")) ((and buf win (buffer-modified-p buf)) (message "Can't kill modified buffer, please save it before")) ((and buf win) (kill-buffer buf) (if (and helm-persistent-action-display-window (window-dedicated-p (next-window win 1))) (delete-window helm-persistent-action-display-window) (set-window-buffer win helm-current-buffer)) (message "Buffer `%s' killed" buf-name)) (t (find-file candidate))))) (helm-make-persistent-command-from-action helm-ff-run-kill-buffer-persistent "Execute `helm-ff-kill-buffer-fname' without quitting." 'kill-buffer-fname 'helm-ff-kill-buffer-fname) ;; Preview with external tool (defun helm-ff-persistent-open-file-externally (file) (require 'helm-external) (if (helm-get-default-program-for-file file) (helm-open-file-externally file) (message "Please configure an external program for `*%s' file in `helm-external-programs-associations'" (file-name-extension file t)))) (helm-make-persistent-command-from-action helm-ff-run-preview-file-externally "Run open file externally without quitting helm." 'open-file-externally 'helm-ff-persistent-open-file-externally) (defun helm-ff-prefix-filename (disp fname &optional new-file) "Return DISP maybe prefixed with a string or an icon. Arg FNAME is the real filename whereas DISP is the display part of candidate. Icons are used when `helm-ff-icon-mode' is enabled. When NEW-FILE is non nil, returns a string prefixed with [+] or [@] or a special icon, otherwise DISP is returned prefixed with its icon or unchanged." (let (prefix-new prefix-url) (cond ((and new-file (or (string-match helm-ff-url-regexp disp) (and helm--url-regexp (string-match helm--url-regexp disp)))) (setq prefix-url (if helm-ff-icon-mode (helm-acase (match-string 1 disp) ("mailto:" (helm-x-icons-generic "mail")) (t (helm-x-icons-generic "link-external"))) (propertize " " 'display (propertize "[@]" 'face 'helm-ff-prefix)))) (add-text-properties 0 1 '(helm-url t) prefix-url) (concat prefix-url " " disp)) (new-file (setq prefix-new (if helm-ff-icon-mode (if (string-match "/\\'" disp) (helm-x-icons-generic "create_new_folder") (helm-x-icons-generic "note_add")) (propertize " " 'display (propertize "[+]" 'face 'helm-ff-prefix)))) (add-text-properties 0 1 '(helm-new-file t) prefix-new) (concat prefix-new " " disp)) (t (concat (helm-ff-get-icon disp fname) disp))))) (defun helm-ff-score-candidate-for-pattern (real disp pattern) (cond ((member real '("." "..")) 900000) ((and (string-match-p "\\`\\s-\\{2\\}" disp) (string= real (substring-no-properties disp 2))) ;; Incomplete filenames are prefixed with two spaces, the ;; first one beeing propertized with a 'display prop ;; i.e. "[+] foo". 900001) (t (helm-score-candidate-for-pattern real pattern)))) (defun helm-ff-sort-candidates-1 (candidates input) "Sort function for `helm-source-find-files'. Return candidates prefixed with basename of INPUT first." (if (or (and (file-directory-p input) (string-match "/\\'" input)) (string-match "\\`\\$" input) (null candidates)) candidates (let* ((memo-src (make-hash-table :test 'equal)) (all (sort candidates (lambda (s1 s2) (let* ((score (lambda (disp real) (helm-ff-score-candidate-for-pattern disp real (helm-basename input)))) ;; Reals (r1 (helm-basename (if (consp s1) (cdr s1) s1))) (r2 (helm-basename (if (consp s2) (cdr s2) s2))) ;; Displays (d1 (helm-basename (if (consp s1) (car s1) s1))) (d2 (helm-basename (if (consp s2) (car s2) s2))) (sc1 (or (gethash r1 memo-src) (puthash r1 (funcall score r1 d1) memo-src))) (sc2 (or (gethash r2 memo-src) (puthash r2 (funcall score r2 d2) memo-src)))) ;; The score fn may return a string (happens with "/adb:"). (cond ((or (stringp sc1) (stringp sc2))) ((= sc1 sc2) (< (string-width r1) (string-width r2))) ((> sc1 sc2)))))))) all))) (defun helm-ff-sort-candidates (candidates _source) "Sort function for `helm-source-find-files'. Return candidates prefixed with basename of `helm-input' first." (helm-ff-sort-candidates-1 candidates helm-input)) (defun helm-ff-boring-file-p (file) "Returns non nil when FILE is matching boring regexps." ;; Prevent user doing silly thing like ;; adding the dotted files to boring regexps (#924). (and helm-ff-skip-boring-files (not (string-match "\\.$" file)) (string-match helm-ff--boring-regexp file))) (defvar helm-ff--git-found-p nil) (defun helm-ff-git-ignored-p (file) "Returns non nil when FILE is matched in \".gitignore\" file." (and helm-ff-skip-git-ignored-files (not (file-remote-p file)) (or helm-ff--git-found-p (setq helm-ff--git-found-p (executable-find "git"))) (zerop (call-process "git" nil nil nil "check-ignore" "-q" file)))) (defun helm-ff-fct (candidates _source) "Filter in charge of displaying basename or full path in HFF. Because CANDIDATES are directly stored as (basename . full_path), when `helm-ff-transformer-show-only-basename' is non nil do nothing and return directly CANDIDATES." (if (null helm-ff-transformer-show-only-basename) (cl-loop for (_disp . real) in candidates for fc = (helm-ff-filter-candidate-one-by-one real 'reverse) when fc collect fc) candidates)) (defun helm-ff-filter-candidate-one-by-one (file &optional reverse skip-boring-check) "Transform file in a cons cell like (DISPLAY . REAL). DISPLAY is shown as basename of FILE and REAL as full path of FILE. If REVERSE is non nil DISPLAY is shown as full path. If SKIP-BORING-CHECK is non nil don't filter boring files." (let* ((basename (helm-basename file)) (dot (helm-ff-dot-file-p file)) (urlp (string-match-p helm-ff-url-regexp file)) (tramp-invalid-fname (helm-ff--get-host-from-tramp-invalid-fname file)) (disp (or tramp-invalid-fname ;; Filename with cntrl chars e.g. foo^J (replace-regexp-in-string "[[:cntrl:]]" "?" (if (or reverse urlp (and dot helm-ff-show-dot-file-path)) file basename)))) (len (length disp)) (backup (backup-file-name-p disp))) (when (string-match "/\\'" file) (setq disp (concat disp "/") len (1+ len))) ;; We want to filter boring files only on the files coming ;; from the output of helm-ff-directory-files not on single ;; candidate (Bug#2330). (unless (and (not skip-boring-check) (or (helm-ff-boring-file-p basename) (helm-ff-git-ignored-p file))) ;; Highlight extensions. (helm-aif (and (not backup) (not urlp) (helm-file-name-extension disp)) (when (condition-case _err (string-match (format "\\.\\(%s\\)\\'" it) disp) (invalid-regexp nil)) (add-face-text-property (match-beginning 1) (match-end 1) 'helm-ff-file-extension t disp))) ;; Handle tramp files with minimal highlighting. (if (and (or (string-match-p helm-tramp-file-name-regexp helm-pattern) (helm-file-on-mounted-network-p helm-pattern))) (helm-acond (;; Dot directories . and .. dot (cons (helm-ff-prefix-filename (propertize disp 'face 'helm-ff-dotted-directory) file) file)) ;; Directories. ((get-text-property 1 'helm-ff-dir file) (cons (helm-ff-prefix-filename (propertize disp 'face 'helm-ff-directory) file) file)) ;; Backup files. (backup (cons (helm-ff-prefix-filename (propertize disp 'face 'helm-ff-backup-file) file) file)) ;; Executable files. ((get-text-property 1 'helm-ff-exe file) (add-face-text-property 0 len 'helm-ff-executable t disp) (cons (helm-ff-prefix-filename disp file) file)) ;; Symlinks. ((get-text-property 1 'helm-ff-sym file) (add-face-text-property 0 len 'helm-ff-symlink t disp) (if (stringp it) ; adb method. (progn (add-face-text-property 0 (length it) 'helm-ff-truename nil it) (cons (propertize disp 'display (concat disp " ->" it)) file)) (cons (helm-ff-prefix-filename disp file) file))) ;; Regular files. ((get-text-property 1 'helm-ff-file file) (add-face-text-property 0 len 'helm-ff-file t disp) (cons (helm-ff-prefix-filename disp file) file)) ;; Tramp methods. ((string-match helm-ff-tramp-method-regexp file) (let ((method (match-string 1 file)) (mh (helm-ff--tramp-multihops-p helm-pattern))) (cons (propertize (concat (if mh "" "/") method) 'face 'helm-ff-file) (if mh (concat (match-string 1 helm-pattern) ":" method) (concat "/:" method))))) ;; non existing files. (t (add-face-text-property 0 len 'helm-ff-file t disp) (when tramp-invalid-fname (add-text-properties 0 len `(host ,tramp-invalid-fname) disp)) (cons (helm-ff-prefix-filename disp file (unless tramp-invalid-fname 'new-file)) file))) ;; Highlight local files showing everything, symlinks, exe, ;; dirs etc... (let* ((attr (condition-case err (file-attributes file) (file-error ;; Possible error not happening during listing ;; but when calling file-attributes see error ;; with sshfs bug#2405 (message "%s:%s" (car err) (cdr err)) nil))) (type (car attr)) x-bit) (cond (;; Not a file but the message error printed in ;; helm-buffer. Such a message should not have a ;; subdir so matching on bol should suffice, but to ;; be sure use @@@@ as prefix in file-error message ;; to be safe bug#2400. (string-match "\\`@@@@file-error:" file) file) (;; A dead symlink. (and (stringp type) (not (helm-ff-valid-symlink-p file)) (not (string-match "^\\.#" basename))) (add-face-text-property 0 len 'helm-ff-invalid-symlink t disp) (cons (helm-ff-prefix-filename disp file) file)) ;; A dotted directory symlinked. ((and dot (stringp type)) (cons (helm-ff-prefix-filename (propertize disp 'face 'helm-ff-dotted-symlink-directory) file) file)) ;; A dotted directory. (dot (cons (helm-ff-prefix-filename (propertize disp 'face 'helm-ff-dotted-directory) file) file)) ;; Backup files. (backup (cons (helm-ff-prefix-filename (propertize disp 'face 'helm-ff-backup-file) file) file)) ;; A symlink. ((stringp type) (let* ((abbrev (abbreviate-file-name type)) (len-abbrev (length abbrev))) (helm-aif (helm-file-name-extension abbrev) (when (string-match (format "\\.\\(%s\\)\\'" it) abbrev) (add-face-text-property (match-beginning 1) (match-end 1) 'helm-ff-file-extension t abbrev))) (add-face-text-property 0 len-abbrev 'helm-ff-truename t abbrev) ;; Colorize extension only on truename. (add-face-text-property 0 len 'helm-ff-symlink nil disp) (cons (concat (helm-ff-prefix-filename disp file) ;; Displaying this in a space with display prop ;; allows retrieving the candidate with ;; `helm-ff-retrieve-last-expanded'. If we put ;; the display prop on the whole candidate ;; `helm-fuzzy-highlight-matches' don't match ;; properly. (propertize " " 'display (concat " -> " abbrev))) file))) ;; A directory. ((eq t type) (cons (helm-ff-prefix-filename (propertize disp 'face 'helm-ff-directory) file) file)) ;; A character device file. ((and attr (string-match "\\`[cp]" (setq x-bit (substring (nth 8 attr) 0 4)))) (add-face-text-property 0 len 'helm-ff-pipe t disp) (cons (helm-ff-prefix-filename disp file) file)) ;; A socket file. ((and attr (string-match "\\`[s]" x-bit)) (add-face-text-property 0 len 'helm-ff-socket t disp) (cons (helm-ff-prefix-filename disp file) file)) ;; An executable file. ((and attr (string-match "x\\'" x-bit)) (add-face-text-property 0 len 'helm-ff-executable t disp) (cons (helm-ff-prefix-filename disp file) file)) ;; An executable file with suid ((and attr (string-match "s\\'" x-bit)) (add-face-text-property 0 len 'helm-ff-suid t disp) (cons (helm-ff-prefix-filename disp file) file)) ;; A file. ((and attr (null type)) (add-face-text-property 0 len 'helm-ff-file t disp) (cons (helm-ff-prefix-filename disp file) file)) ;; A tramp method ;; At this point no need to handle multi hops syntax ;; which is considered remote and handled in first ;; cond before. ((string-match helm-ff-tramp-method-regexp file) (cons (propertize (concat "/" (match-string 1 file)) 'face 'helm-ff-nofile) (concat "/:" (match-string 1 file)))) ;; A non--existing file. (t (add-face-text-property 0 len 'helm-ff-nofile t disp) (cons (helm-ff-prefix-filename disp file 'new-file) file)))))))) (defun helm-ff-get-icon (disp file) "Get icon from `helm-x-icons-provider' for FILE. Arg DISP is the display part of the candidate. Arg FILE is the real part of candidate, a filename with no props." (when helm-ff-icon-mode (let ((icon (helm-acond (;; Non symlink directories. (helm-ff--is-dir-from-disp disp) (helm-aif (helm-x-icons-match-to-alist (helm-basename file) 'dir) (apply (car it) (cdr it)) (helm-x-icons-generic "file-directory"))) (;; All files, symlinks may be symlink directories. (helm-ff--is-file-from-disp disp) ;; Detect symlink directories. We must call ;; `file-directory-p' here but it is ;; limited to symlinks, so it should not ;; degrade too much performances. (if (and (memq it '(helm-ff-symlink helm-ff-dotted-symlink-directory)) (file-directory-p file)) (helm-x-icons-generic "file-symlink-directory") (helm-x-icons-icon-for-file (helm-basename file))))))) (when icon (concat icon " "))))) (defun helm-ff--is-dir-from-disp (disp) "Return the face used for candidate when candidate is a directory." (helm-aand (get-text-property 0 'face disp) (memq it '(helm-ff-directory helm-ff-dotted-directory)) (car it))) (defun helm-ff--is-file-from-disp (disp) "Return the face used for file's candidate or dotted-symlink dirs." (helm-aand (get-text-property 0 'face disp) (memq it '(helm-ff-file helm-ff-suid helm-ff-executable helm-ff-socket helm-ff-pipe helm-ff-symlink helm-ff-dotted-symlink-directory helm-ff-backup-file helm-ls-git-untracked-face)) (car it))) ;;;###autoload (define-minor-mode helm-ff-icon-mode "Display icons from `helm-x-icons-provider' package in HFF when enabled." :global t :group 'helm-files (when helm-ff-icon-mode (unless (require helm-x-icons-provider nil t) (setq helm-ff-icon-mode nil) (message "No suitable Icons package found"))) (clrhash helm-ff--list-directory-cache)) ;;; Action transformer ;; (defun helm-ff--in-backup-directory () (when backup-directory-alist (cl-loop for (_p . f) in backup-directory-alist thereis (file-equal-p f helm-ff-default-directory)))) (defun helm-ff-restore-backups (_candidate) (let ((mkd (helm-marked-candidates)) (copied 0) ovw) (cl-dolist (file mkd) (let (dest) (when (string-match "\\(?:\\`\\([!]\\)[^!]*\\1.*\\)\\|\\(?:~\\'\\)" (helm-basename file)) (setq dest (helm-aand (replace-regexp-in-string "\\.~[[:digit:]]*~?" "" (helm-basename file)) (helm-ff--normalize-backup-name it) (if (string-match "\\`/" it) it ;; If basename doesn't contain now ;; "/", that's mean it was a backup file ;; stored in current directory, just ;; expand it to this directory. (expand-file-name it helm-ff-default-directory)))) (if (and (file-exists-p dest) (null ovw)) (helm-acase (helm-read-answer (format "Overwrite `%s' (answer [y,n,!,q])? " dest) '("y" "n" "!" "q")) ("y" (cl-incf copied) (copy-file file dest t t t t)) ("n" (ignore)) ("!" (setq ovw t) (cl-incf copied) (copy-file file dest t t t t)) ("q" (setq copied nil) (cl-return (message "Abort restoring files")))) (cl-incf copied) (copy-file file dest t t t t))))) (when (numberp copied) (message "(%s/%s) files copied" copied (length mkd))))) (defun helm-ff--normalize-backup-name (fname) "Normalize backup FNAME to its original name." ;; When Emacs build a backup filename for the backup directory it ;; replace "/" by "!" in the basedir of file and double the "!" in ;; the basename, this is done by `make-backup-file-name-1'. We want ;; to replace only the "!" in the basedir part of FNAME. (with-temp-buffer (insert fname) (goto-char (point-min)) (save-excursion (when (looking-at "!") (replace-match "/")) (while (re-search-forward "[^!]\\([!]\\)[^!]" nil t) (replace-match "/" nil nil nil 1))) (let ((count 0) rep) (while (re-search-forward "!" nil t) (cl-incf count)) (setq rep (make-string (/ count 2) ?!)) (replace-regexp-in-string "[!]+" rep (buffer-string))))) (defun helm-ff-update-directory-autoloads (_candidate) "Action to update or create autoloads file in current directory." (let ((default-directory helm-ff-default-directory) (file (read-file-name "Write autoload definitions to file: " helm-ff-default-directory nil nil nil (lambda (f) (string-match "autoloads\\|loaddefs" f))))) (if (fboundp 'loaddefs-generate) (loaddefs-generate default-directory file) (let ((generated-autoload-file file)) (update-directory-autoloads default-directory))))) (defun helm-find-files-action-transformer (actions candidate) "Action transformer for `helm-source-find-files'." (let ((str-at-point (with-helm-current-buffer (buffer-substring-no-properties (pos-bol) (pos-eol))))) (when (file-regular-p candidate) (setq actions (helm-append-at-nth actions '(("Checksum File" . helm-ff-checksum)) 4))) (when (file-exists-p (expand-file-name (format "#%s#" (helm-basename candidate)) (helm-basedir candidate))) (setq actions (helm-append-at-nth actions '(("Recover file" . recover-file)) 4))) (cond ((and (file-exists-p candidate) (string-match helm-ff--trash-directory-regexp (helm-basedir (expand-file-name candidate))) (not (member (helm-basename candidate) '("." "..")))) (helm-append-at-nth actions '(("Restore file(s) from trash" . helm-restore-file-from-trash) ("Delete file(s) from trash" . helm-ff-trash-rm)) 1)) ((and helm--url-regexp (not (string-match-p helm--url-regexp str-at-point)) (not (with-helm-current-buffer (eq major-mode 'dired-mode))) (string-match-p ":\\([0-9]+:?\\)" str-at-point)) (append '(("Find file to line number" . helm-ff-goto-linum)) actions)) ((string-match (image-file-name-regexp) candidate) (helm-append-at-nth actions (helm-make-actions "Rotate image right `M-r'" 'helm-ff-rotate-image-right "Rotate image left `M-l'" 'helm-ff-rotate-image-left "Start slideshow with marked" 'helm-ff-start-slideshow-on-marked (lambda () (and (fboundp 'image-dired-wallpaper-set) (display-graphic-p) "Change wall paper")) 'image-dired-wallpaper-set) 3)) ((string-match "\\.el\\'" candidate) (helm-append-at-nth actions `(("Byte compile lisp file(s) `M-B, C-u to load'" . helm-find-files-byte-compile) ("Byte compile file(s) async" . ,(lambda (_candidate) (cl-loop for file in (helm-marked-candidates) do (async-byte-compile-file file)))) ("Byte recompile directory async" . ,(lambda (_) (async-byte-recompile-directory helm-ff-default-directory))) ("Load File(s) `M-L'" . helm-find-files-load-files) ("Update directory autoloads" . helm-ff-update-directory-autoloads)) 2)) ((string-match (concat (regexp-opt load-suffixes) "\\'") candidate) (helm-append-at-nth actions '(("Load File(s) `M-L'" . helm-find-files-load-files)) 2)) ((and (string-match "\\.html?$" candidate) (file-exists-p candidate)) (helm-append-at-nth actions '(("Browse url file" . browse-url-of-file)) 2)) ((and (helm-ff--in-backup-directory) (cl-loop for file in (helm-marked-candidates) always (string-match "\\(?:\\`\\([!]\\)[^!]*\\1.*\\)\\|\\(?:~\\'\\)" (helm-basename file)))) (helm-append-at-nth actions '(("Restore backup file(s)" . helm-ff-restore-backups)) 1)) (t actions)))) ;;; Trashing files ;; (defun helm-ff-trash-action (fn names &rest args) "Execute a trash action FN on marked files. Arg NAMES is a list of strings to pass to messages. E.g. \\='(\"delete\" \"deleting\") ARGS are other arguments to be passed to FN." (let ((mkd (helm-marked-candidates)) errors aborted) (with-helm-display-marked-candidates helm-marked-buffer-name (if (and args (string= (car names) "restore")) (cl-loop for f in mkd for bd = (helm-basename f) for assoc = (assoc bd (car args)) when assoc collect (concat (truncate-string-to-width (car assoc) 40 nil nil t) " -> " (truncate-string-to-width (helm-basedir (cdr assoc)) 40 nil nil t))) (helm-ff--count-and-collect-dups (mapcar 'helm-basename mkd))) (if (y-or-n-p (format "%s %s files from trash? " (capitalize (car names)) (length mkd))) (progn (message "%s files from trash..." (capitalize (cadr names))) (cl-loop for f in mkd do (condition-case err (apply fn f args) (error (push (format "%s" (cadr err)) errors) nil)))) (message "%s files from trash aborted" (capitalize (cadr names))) (setq aborted t))) ;; Handle errors from outside the ;; with-helm-display-marked-candidates block otherwise warning is ;; never displayed. (if errors (progn (display-warning 'helm (with-temp-buffer (insert (format-time-string "%Y-%m-%d %H:%M:%S\n" (current-time))) (insert (format "Failed to %s %s/%s files from trash\n" (car names) (length errors) (length mkd))) (insert (mapconcat 'identity errors "\n") "\n ") (buffer-string)) :error "*helm restore warnings*") (message "%s files from trash aborted" (capitalize (cadr names)))) (unless aborted (message "%s %s files from trash done" (capitalize (cadr names)) (length mkd)))))) (defun helm-ff-trash-rm (_candidate) "Delete marked-files from a Trash directory. The Trash directory should be a directory compliant with and each file should have its \\='*.trashinfo' correspondent file in Trash/info directory." (helm-ff-trash-action 'helm-ff-trash-rm-1 '("delete" "deleting"))) (defun helm-ff-trash-rm-1 (file) (let ((info-file (concat (helm-reduce-file-name file 2) "info/" (helm-basename file "trashinfo") ".trashinfo"))) (cl-assert (file-exists-p file) nil (format "No such file or directory `%s'" file)) (cl-assert (file-exists-p info-file) nil (format "No such file or directory `%s'" info-file)) (if (file-directory-p file) (delete-directory file t) (delete-file file)) (delete-file info-file))) (defun helm-restore-file-from-trash (_candidate) "Restore marked-files from a Trash directory. The Trash directory should be a directory compliant with and each file should have its \\='*.trashinfo' corresponding file in Trash/info directory." (let* ((default-directory (file-name-as-directory helm-ff-default-directory)) (trashed-files (helm-ff-trash-list))) (helm-ff-trash-action 'helm-restore-file-from-trash-1 '("restore" "restoring") trashed-files))) (defun helm-restore-file-from-trash-1 (file trashed-files) "Restore FILE from a trash directory. Arg TRASHED-FILES is an alist of (fname_in_trash . dest) obtained with `helm-ff-trash-list'." ;; Emacs trash duplicate files with a unique name + .trashinfo in ;; the filename which is wrong, only files in info directory should ;; end with .trashinfo, so fix the filename before looking for dest name. (let* ((fname (replace-regexp-in-string "\\.trashinfo\\'" "" file)) (info-file (concat (helm-reduce-file-name fname 2) "info/" (helm-basename fname) ".trashinfo")) (dest-file (helm-ff--get-dest-file-from-trash trashed-files fname))) (cl-assert (not (file-exists-p dest-file)) nil (format "File `%s' already exists" dest-file)) (cl-assert dest-file nil "No such file in trash") (message "Restoring %s to %s..." (helm-basename file) (helm-basedir dest-file)) (rename-file file dest-file) (message "Restoring %s to %s done" (helm-basename file) (helm-basedir dest-file)) (delete-file info-file))) (defun helm-ff-trash-file-p (file) "Return t when FILE is a trashed file." (and (file-exists-p file) (string-match helm-ff--trash-directory-regexp (helm-basedir file)) (not (member (helm-basename file) '("." ".."))))) (defun helm-ff--get-dest-file-from-trash (trashed-files file) (assoc-default (helm-basename file) trashed-files)) (cl-defun helm-ff-trash-list (&optional (trash-dir nil strash-dir)) "Return an alist of trashed files basename and dest name. Assume the trash system in use is freedesktop compatible, see This function is intended to be used from a trash directory i.e. it use `helm-ff-default-directory', but it may be used elsewhere by specifying the trash directory with TRASH-DIR arg." (unless (or (fboundp 'system-move-file-to-trash) (and strash-dir (null trash-dir))) ;; Files owned by root are trashed in /root/.local/share/Trash. ;; Files owned by user and trashed by root are trashed in ;; /home/.Trash. ;; Files owned by user and trashed by user are trashed in ;; ~/.local/share/Trash. (cl-loop for f in (directory-files (expand-file-name ;; helm-ff-default-directory is currently the ;; trash directory. "info" (helm-basedir (directory-file-name (or trash-dir helm-ff-default-directory)))) t directory-files-no-dot-files-regexp) collect (cons (helm-basename (replace-regexp-in-string "\\.trashinfo\\'" "" f)) (with-temp-buffer (save-excursion (insert-file-contents f)) (when (re-search-forward "^path=" nil t) (let ((path (helm-url-unhex-string (buffer-substring-no-properties (point) (pos-eol))))) (if (string-match "\\`/" path) ;; path is absolute path ;; When path is relative, assume the ;; trash directory is located at ;; /home/.Trash and path is the ;; relative name of file from /home. (expand-file-name path "/home"))))))))) (defun helm-ff-goto-linum (candidate) "Find file CANDIDATE and maybe jump to line number found in fname at point. Line number should be added at end of fname preceded with \":\". E.g. \"foo:12\"." (let ((linum (with-helm-current-buffer (let ((str (buffer-substring-no-properties (pos-bol) (pos-eol)))) (when (string-match ":\\([0-9]+:?\\)" str) (match-string 1 str)))))) (find-file candidate) (and linum (not (string= linum "")) (helm-goto-line (string-to-number linum) t)))) (defun helm-ff-mail-attach-files (_candidate) "Run `mml-attach-file' on `helm-marked-candidates'." (require 'mml) (let ((flist (helm-marked-candidates :with-wildcard t)) (dest-buf (and (derived-mode-p 'message-mode 'mail-mode) (current-buffer))) bufs) (unless dest-buf (setq bufs (cl-loop for b in (buffer-list) when (with-current-buffer b (derived-mode-p 'message-mode 'mail-mode)) collect (buffer-name b))) (if (and bufs (y-or-n-p "Attach files to existing mail composition buffer? ")) (setq dest-buf (if (cdr bufs) (helm-comp-read "Attach to buffer: " bufs :nomark t) (car bufs))) (compose-mail) (setq dest-buf (current-buffer)))) (switch-to-buffer dest-buf) (save-restriction (widen) (save-excursion (goto-char (point-max)) (cl-loop for f in flist do (mml-attach-file f (or (mm-default-file-encoding f) "application/octet-stream"))))))) (defvar image-dired-display-image-buffer) (defun helm-ff-rotate-current-image-1 (file angle) "Rotate current image at ANGLE degrees." (cl-assert (and (file-exists-p file) (string-match (image-file-name-regexp) file)) nil "Can't rotate non image file") (setq file (file-truename file)) ; For symlinked images. (let ((default-directory (file-name-directory file)) (basename (helm-basename file)) ;; convert ANGLE to a suitable value for exiftran. (num-arg (if (string= helm-ff-rotate-image-program "exiftran") (cl-case angle (90 "-9") ; 90 clockwise (270 "-2")) ; 270 clockwise == -90 (number-to-string angle))) rotation-failed) ;; Try to rotate image with exiftran even with helm-ff-display-image-native. (if (and helm-ff-rotate-image-program (executable-find helm-ff-rotate-image-program)) (apply #'process-file helm-ff-rotate-image-program nil nil nil (append helm-ff-rotate-image-switch (list num-arg basename))) (setq rotation-failed t)) ;; Display image in image-mode. (if (helm-ff-display-image-native-p) (if rotation-failed ;; When rotation fails fallback to `image-rotate' with no ;; transformation of file. (with-selected-window (helm-persistent-action-display-window) (condition-case _err (with-no-warnings (image-rotate angle)) (wrong-number-of-arguments (image-rotate)))) (helm-ff--display-image-native file)) ;; Use image-dired to display image. (when rotation-failed (error "%s not found" (or helm-ff-rotate-image-program "`helm-ff-rotate-image-program'"))) (when (buffer-live-p image-dired-display-image-buffer) (kill-buffer image-dired-display-image-buffer)) (image-dired-display-image basename) (message nil) (display-buffer (get-buffer image-dired-display-image-buffer))))) (defun helm-ff-rotate-image-left (candidate) "Rotate image file CANDIDATE left. This affects directly file CANDIDATE." (helm-ff-rotate-current-image-1 candidate 270)) (defun helm-ff-rotate-image-right (candidate) "Rotate image file CANDIDATE right. This affects directly file CANDIDATE." (helm-ff-rotate-current-image-1 candidate 90)) (defun helm-ff-rotate-left-persistent () "Rotate image left without quitting helm." (interactive) (with-helm-alive-p (helm-set-attr 'image-action1 'helm-ff-rotate-image-left) (helm-execute-persistent-action 'image-action1))) (put 'helm-ff-rotate-left-persistent 'helm-only t) (defun helm-ff-rotate-right-persistent () "Rotate image right without quitting helm." (interactive) (with-helm-alive-p (helm-set-attr 'image-action2 'helm-ff-rotate-image-right) (helm-execute-persistent-action 'image-action2))) (put 'helm-ff-rotate-right-persistent 'helm-only t) (defun helm-ff-resize-image-1 (arg) ;; `image-decrease-size' and `image-increase-size' are not usable ;; because they run directly `image--change-size' in a timer without ;; taking care of the selected-window. (cl-assert (and (fboundp 'image--change-size) (helm-ff-display-image-native-p)) nil "Resizing image not available") (if (> arg 0) (run-with-idle-timer 0.3 nil (lambda () (with-selected-window (helm-persistent-action-display-window) (image--change-size 1.2)))) (run-with-idle-timer 0.3 nil (lambda () (with-selected-window (helm-persistent-action-display-window) (image--change-size 0.8)))))) (defun helm-ff-increase-image-size (_candidate) (helm-ff-resize-image-1 1)) (defun helm-ff-decrease-image-size (_candidate) (helm-ff-resize-image-1 -1)) (defun helm-ff-increase-image-size-persistent () "Increase image size without quitting helm." (interactive) (with-helm-alive-p (helm-set-attr 'image-action3 'helm-ff-increase-image-size) (helm-execute-persistent-action 'image-action3))) (put 'helm-ff-increase-image-size-persistent 'helm-only t) (defun helm-ff-decrease-image-size-persistent () "Decrease image size without quitting helm." (interactive) (with-helm-alive-p (helm-set-attr 'image-action4 'helm-ff-decrease-image-size) (helm-execute-persistent-action 'image-action4))) (put 'helm-ff-decrease-image-size-persistent 'helm-only t) (defun helm-ff-exif-data (candidate) "Extract exif data from file CANDIDATE using `helm-ff-exif-data-program'." (if (and helm-ff-exif-data-program (executable-find helm-ff-exif-data-program)) (shell-command-to-string (format "%s %s %s" helm-ff-exif-data-program helm-ff-exif-data-program-args candidate)) (format "No program %s found to extract exif" helm-ff-exif-data-program))) (defvar helm-ff-image-native-buffer "*image-native-display*") (defvar helm-ff-sound-file-extensions '("wav" "au")) (defun helm-ff--maybe-follow (candidate) (let ((file (if helm-ff-ignore-following-on-directory (file-exists-p candidate) (file-regular-p candidate))) (image (string-match-p (image-file-name-regexp) candidate)) (ext (file-name-extension candidate))) (and file (or image (not (member ext helm-ff-follow-blacklist-file-exts)))))) (cl-defun helm-find-files-persistent-action-if (candidate) "Open subtree CANDIDATE without quitting helm. If CANDIDATE is not a directory expand CANDIDATE filename. If CANDIDATE is alone, open file CANDIDATE filename. That means: First hit on C-j expands CANDIDATE, second hit opens file. If a prefix arg is given or `helm-follow-mode' is on, then open file." (let* ((follow (or (helm-follow-mode-p) helm--temp-follow-flag)) (image-cand (string-match-p (image-file-name-regexp) candidate)) (sound-cand (member (file-name-extension candidate) helm-ff-sound-file-extensions)) (selection (helm-get-selection)) (insert-in-minibuffer (lambda (fname) (with-selected-window (or (active-minibuffer-window) (minibuffer-window)) (unless follow (delete-minibuffer-contents) (set-text-properties 0 (length fname) nil fname) (insert fname)))))) (helm-set-attr 'candidate-number-limit helm-ff-candidate-number-limit) (unless (helm-ff--maybe-follow candidate) (when follow (helm-follow-mode -1) (message nil) (cl-return-from helm-find-files-persistent-action-if (prog1 #'ignore (user-error "Can't follow this kind of file"))))) (cond (;; Tramp methods completion. (string-match helm-ff-tramp-method-regexp candidate) (let ((method (match-string 1 candidate))) (cons (lambda (candidate) (funcall insert-in-minibuffer (if (helm-ff--tramp-multihops-p candidate) (concat (match-string 1 candidate) method ":") (concat "/" method ":")))) 'never-split))) ((and (helm-ff--invalid-tramp-name-p) (string-match helm-tramp-file-name-regexp candidate)) (cons (lambda (_candidate) ;; First hit insert hostname and ;; second hit insert ":" and expand. (if (string= candidate helm-pattern) (funcall insert-in-minibuffer (concat candidate ":")) (funcall insert-in-minibuffer candidate))) 'never-split)) (;; A symlink directory, expand it but not to its truename ;; unless a prefix arg is given. (and (file-directory-p candidate) (file-symlink-p candidate)) (cons (lambda (_candidate) (helm-ff-after-persistent-show-all) (let ((new-dir (file-name-as-directory (if current-prefix-arg (file-truename (expand-file-name candidate)) (expand-file-name candidate))))) (setq helm-ff--show-thumbnails (member new-dir helm-ff--thumbnailed-directories)) (funcall insert-in-minibuffer new-dir))) 'never-split)) ;; A directory, open it. ((file-directory-p candidate) (cons (lambda (_candidate) (helm-ff-after-persistent-show-all) (when (string= (helm-basename candidate) "..") (setq helm-ff-last-expanded helm-ff-default-directory)) (let ((new-dir (file-name-as-directory (expand-file-name candidate)))) (setq helm-ff--show-thumbnails (member new-dir helm-ff--thumbnailed-directories)) (funcall insert-in-minibuffer new-dir)) (with-helm-after-update-hook (helm-ff-retrieve-last-expanded))) 'never-split)) ;; A symlink file, expand to it's true name. (first hit) ((and (file-symlink-p candidate) (not current-prefix-arg) (not follow)) (cons (lambda (_candidate) (funcall insert-in-minibuffer (file-truename candidate)) (helm-check-minibuffer-input)) ; Force update. 'never-split)) ;; A regular file, expand it, (first hit) ((and (not (file-equal-p selection helm-pattern)) (not current-prefix-arg) (not follow)) (cons (lambda (_candidate) (funcall insert-in-minibuffer selection) (helm-check-minibuffer-input)) ; Force update. 'never-split)) (sound-cand (lambda (candidate) (play-sound-file candidate))) ;; An image file and it is the second hit on C-j, display it. (image-cand (if (helm-ff-display-image-native-p) #'helm-ff--display-or-kill-image-native (lambda (_candidate) (require 'image-dired) (let* ((win (get-buffer-window image-dired-display-image-buffer 'visible)) (fname (and win (with-selected-window win (get-text-property (point-min) 'original-file-name)))) (remove-buf-only (and win fname (with-helm-buffer (file-equal-p candidate fname))))) (when remove-buf-only (with-helm-window (if (and helm-persistent-action-display-window (window-dedicated-p (next-window win 1))) (delete-window helm-persistent-action-display-window) (set-window-buffer win helm-current-buffer)))) (when (buffer-live-p (get-buffer image-dired-display-image-buffer)) (kill-buffer image-dired-display-image-buffer)) (unless remove-buf-only ;; Fix emacs bug never fixed upstream. (unless (file-directory-p image-dired-dir) (make-directory image-dired-dir)) (switch-to-buffer image-dired-display-image-buffer) (message "Resizing image...") (cl-letf (((symbol-function 'message) #'ignore)) (image-dired-display-image candidate)) (message "Resizing image done") (with-current-buffer image-dired-display-image-buffer (let ((exif-data (helm-ff-exif-data candidate))) (setq default-directory helm-ff-default-directory) (image-dired-update-property 'help-echo exif-data)))))))) ;; Allow browsing archive on avfs fs. ;; Assume volume is already mounted with mountavfs. ((helm-aand helm-ff-avfs-directory (file-name-directory candidate) (string-match (regexp-quote (expand-file-name helm-ff-avfs-directory)) it) (helm-ff-file-compressed-p candidate)) (cons (lambda (_candidate) (funcall insert-in-minibuffer (concat candidate "#/"))) 'never-split)) ;; File is not existing and have no basedir, typically when ;; user hit C-k (minibuffer is empty) and then write foo and ;; hit C-j. This make clear that when no basedir, helm will ;; create the file in default-directory. ((and (not (file-exists-p candidate)) (not (helm-basedir candidate))) (cons (lambda (_candidate) (funcall insert-in-minibuffer (expand-file-name candidate default-directory))) 'never-split)) ;; On second hit we open file. ;; On Third hit we kill it's buffer maybe. (t (lambda (candidate) (funcall helm-ff-kill-or-find-buffer-fname-fn candidate)))))) ;; Native image display (with image-mode). ;; (defvar helm-ff--image-cache nil) (defun helm-ff-display-image-native-p () "Use `helm-ff-display-image-native' when returns `t'." (or helm-ff-display-image-native ;; Image-dired in emacs-29 uses image-mode but ;; display is no more working with our old ;; image-dired code, so force usage of ;; helm-ff-display-image-native. (fboundp 'image-dired-display-image-mode))) (defun helm-ff--display-or-kill-image-native (candidate) ;; Display images in same buffer ;; `helm-ff-image-native-buffer'. (if (and (buffer-live-p (get-buffer helm-ff-image-native-buffer)) (file-equal-p (buffer-file-name (get-buffer helm-ff-image-native-buffer)) candidate) ;; Allow redisplaying ;; `helm-ff-image-native-buffer' when it ;; already exists and display same image as candidate. (get-buffer-window helm-ff-image-native-buffer 'visible)) (progn (set-window-buffer helm-persistent-action-display-window helm-current-buffer) (kill-buffer helm-ff-image-native-buffer)) (helm-ff--display-image-native candidate))) (defun helm-ff-clean-image-cache () (when helm-ff--image-cache (cl-loop for img in helm-ff--image-cache do (clear-image-cache img) finally do (setq helm-ff--image-cache nil)))) (defun helm-ff--display-image-native (candidate) (when (string-match-p (image-file-name-regexp) candidate) (when (buffer-live-p (get-buffer helm-ff-image-native-buffer)) (kill-buffer helm-ff-image-native-buffer)) ;; Avoid hight memory consumption see ;; https://lists.gnu.org/archive/html/bug-gnu-emacs/2021-11/msg00879.html. (when (> (length helm-ff--image-cache) (* helm-ff-image-cache-max-len 2)) ;; Only keep the last `helm-ff-image-cache-max-len' images in cache. (cl-loop for img in (butlast helm-ff--image-cache (1+ helm-ff-image-cache-max-len)) do (clear-image-cache img) (setq helm-ff--image-cache (delete img helm-ff--image-cache)))) (cl-letf* (((symbol-function 'message) #'ignore) (buf (find-file-noselect candidate t))) ;; When going back reuse the cached images. (unless (member candidate helm-ff--image-cache) (setq helm-ff--image-cache (append helm-ff--image-cache (list (expand-file-name candidate))))) (with-current-buffer buf (rename-buffer helm-ff-image-native-buffer)) (display-buffer buf)))) ;;; Slideshow action ;; (defvar helm-ff--slideshow-iterator nil) (defvar helm-ff--slideshow-sequence nil) (defvar helm-ff--slideshow-in-pause nil) (defvar helm-ff-slideshow-helper "Type `\\[helm-ff-slideshow-pause-or-restart]' to %s, \ `\\[helm-ff-slideshow-next]' for next, `\\[helm-ff-slideshow-previous]' for previous, \ `\\[helm-ff-slideshow-quit]' to quit") (defvar helm-slideshow-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map image-mode-map) (define-key map (kbd "SPC") 'helm-ff-slideshow-pause-or-restart) (define-key map (kbd "q") 'helm-ff-slideshow-quit) (define-key map (kbd "n") 'helm-ff-slideshow-next) (define-key map (kbd "p") 'helm-ff-slideshow-previous) map)) (define-derived-mode helm-slideshow-mode image-mode "helm-image-mode" "Mode to display images from helm-find-files. Special commands: \\{helm-slideshow-mode-map} ") (put 'helm-slideshow-mode 'no-helm-mx t) (defun helm-ff-slideshow-help-string (counter-string state) (concat counter-string (substitute-command-keys (format helm-ff-slideshow-helper state)))) (defun helm-ff-start-slideshow-on-marked (_candidate) "Start a slideshow on marked files." (let ((marked (helm-remove-if-not-match (image-file-name-regexp) (helm-marked-candidates :with-wildcard t)))) (cl-assert (cdr marked) nil "Can't start a slideshow on a single file") (setq helm-ff--slideshow-sequence marked) (setq helm-ff--slideshow-iterator (helm-iter-circular marked)) (helm-ff--display-image-native (helm-iter-next helm-ff--slideshow-iterator)) (delete-other-windows (get-buffer-window helm-ff-image-native-buffer)) (cl-letf (((symbol-function 'message) #'ignore)) (helm-slideshow-mode)) (setq mode-line-format (helm-ff-slideshow-help-string (format "(1/%s) " (length marked)) "pause")) (helm-ff-slideshow-loop helm-ff--slideshow-iterator))) (defun helm-ff-slideshow-state () (format "(%s/%s) " (1+ (helm-position (buffer-file-name) helm-ff--slideshow-sequence :test 'equal)) (length helm-ff--slideshow-sequence))) (defun helm-ff-slideshow-sequence-from-current (&optional reverse) (helm-reorganize-sequence-from-elm helm-ff--slideshow-sequence (buffer-file-name) reverse)) (defun helm-ff-slideshow-loop (iterator &optional restart) (while (sit-for helm-ff-slideshow-default-delay) (helm-ff--display-image-native (helm-iter-next iterator)) (delete-other-windows (get-buffer-window helm-ff-image-native-buffer)) (cl-letf (((symbol-function 'message) #'ignore)) (helm-slideshow-mode)) (when restart (message "Helm Slideshow started") (sit-for 1) (message nil) (setq restart nil)) (setq mode-line-format (helm-ff-slideshow-help-string (helm-ff-slideshow-state) "pause")))) (defun helm-ff-slideshow-pause-or-restart () (interactive) (setq helm-ff--slideshow-in-pause (not helm-ff--slideshow-in-pause)) (if helm-ff--slideshow-in-pause (setq mode-line-format (helm-ff-slideshow-help-string nil "restart")) (message "Helm Slideshow restarting...") (setq helm-ff--slideshow-iterator (helm-iter-circular (helm-ff-slideshow-sequence-from-current))) (helm-ff-slideshow-loop helm-ff--slideshow-iterator 'restart))) (put 'helm-ff-slideshow-pause-or-restart 'no-helm-mx t) (defun helm-ff-slideshow-next () (interactive) (setq helm-ff--slideshow-in-pause t) (setq helm-ff--slideshow-iterator nil) (helm-ff--display-image-native (car (helm-ff-slideshow-sequence-from-current))) (delete-other-windows (get-buffer-window helm-ff-image-native-buffer)) (cl-letf (((symbol-function 'message) #'ignore)) (helm-slideshow-mode)) (message (concat (helm-ff-slideshow-state) (substitute-command-keys (format helm-ff-slideshow-helper "restart"))))) (put 'helm-ff-slideshow-next 'no-helm-mx t) (defun helm-ff-slideshow-previous () (interactive) (setq helm-ff--slideshow-in-pause t) (setq helm-ff--slideshow-iterator nil) (helm-ff--display-image-native (car (helm-ff-slideshow-sequence-from-current 'reverse))) (delete-other-windows (get-buffer-window helm-ff-image-native-buffer)) (cl-letf (((symbol-function 'message) #'ignore)) (helm-slideshow-mode)) (message (concat (helm-ff-slideshow-state) (substitute-command-keys (format helm-ff-slideshow-helper "restart"))))) (put 'helm-ff-slideshow-previous 'no-helm-mx t) (defun helm-ff-slideshow-quit () (interactive) (setq helm-ff--slideshow-iterator nil) (setq helm-ff--slideshow-in-pause nil) (helm-ff-clean-image-cache) (quit-window)) (put 'helm-ff-slideshow-quit 'no-helm-mx t) ;;; Thumbnails view ;; (defun helm-ff-maybe-show-thumbnails (candidates _source) (require 'image-dired) (if (and helm-ff--show-thumbnails (null (file-remote-p helm-ff-default-directory))) (prog1 (cl-loop with scale = (image-compute-scaling-factor nil) for (disp . img) in candidates for type = (helm-acase (file-name-extension img) ((guard* (and (member it '("png" "jpg" "jpeg")) (memq image-dired-thumbnail-storage '(standard standard-large)))) 'png) (("jpg" "jpeg") 'jpeg) ("png" 'png)) if type collect (let ((thumbnail (plist-get (cdr (helm-ff--image-dired-get-thumbnail-image img type scale)) :file))) ;; When icons are displayed the leading space handling disp ;; prop is already here, just replace icon with the thumbnail. (unless helm-ff-icon-mode (setq disp (concat " " disp))) (add-text-properties 0 1 `(display (image :type ,type :margin 5 :file ,thumbnail) rear-nonsticky '(display)) disp) (cons disp img)) else collect (cons disp img)) ;; Ensure this is done AFTER previous clause otherwise thumb files will ;; never be created if they don't already exist. (cl-pushnew helm-ff-default-directory helm-ff--thumbnailed-directories :test 'equal)) candidates)) ;; Same as `image-dired-get-thumbnail-image' but use ;; `helm-ff--image-dired-thumb-name' which cache thumbnails for further use. (defun helm-ff--image-dired-get-thumbnail-image (file &optional type scale) "Return the image descriptor for a thumbnail of image file FILE." (unless (string-match-p (image-file-name-regexp) file) (error "%s is not a valid image file" file)) (let* ((thumb-file (helm-ff--image-dired-thumb-name file)) thumb-attr) ;; Don't check status of files with `file-attributes' if it has already been ;; done in this session. (when (and (not (member helm-ff-default-directory helm-ff--thumbnailed-directories)) (or (not (setq thumb-attr (file-attributes thumb-file))) (time-less-p (file-attribute-modification-time thumb-attr) (file-attribute-modification-time (file-attributes file))))) (image-dired-create-thumb file thumb-file)) (create-image thumb-file type nil :scale scale))) (defvar helm-ff-image-dired-thumbnails-cache (make-hash-table :test 'equal) "Store associations of image_file/thumbnail_file.") (defun helm-ff--image-dired-thumb-name (file) (or (gethash file helm-ff-image-dired-thumbnails-cache) (let ((thumb-name (image-dired-thumb-name file))) (puthash file thumb-name helm-ff-image-dired-thumbnails-cache) thumb-name))) (defun helm-ff-toggle-thumbnails () (interactive) (cl-assert (null (file-remote-p helm-ff-default-directory)) nil "Thumbnails show not supported on remote files") (setq helm-ff--show-thumbnails (not helm-ff--show-thumbnails)) (when helm-ff--show-thumbnails (message "Loading thumbnails...") (with-helm-after-update-hook (message "Loading thumbnails done"))) (when (and (null helm-ff--show-thumbnails) (member helm-ff-default-directory helm-ff--thumbnailed-directories)) (setq helm-ff--thumbnailed-directories (delete helm-ff-default-directory helm-ff--thumbnailed-directories))) (helm-force-update (regexp-quote (replace-regexp-in-string "\\`[[:multibyte:] ]*" "" (helm-get-selection nil t))))) (put 'helm-ff-toggle-thumbnails 'no-helm-mx t) ;;;###autoload (defun helm-ff-clear-image-dired-thumbnails-cache () "Clear `helm-ff-image-dired-thumbnails-cache'. You may want to do this after customizing `image-dired-thumbnail-storage' which may change the place where thumbnail files are stored." (interactive) (clrhash helm-ff-image-dired-thumbnails-cache)) ;;;###autoload (defun helm-ff-cleanup-image-dired-dir-and-cache () "Cleanup `image-dired-dir' directory. Delete all thumb files that are no more associated with an existing image file in `helm-ff-image-dired-thumbnails-cache'." (interactive) (cl-loop for key being the hash-keys in helm-ff-image-dired-thumbnails-cache using (hash-value val) unless (file-exists-p key) do (progn (message "Deleting %s" val) (delete-file val) (remhash key helm-ff-image-dired-thumbnails-cache)))) ;;; Recursive dirs completion ;; (defun helm-find-files-recursive-dirs (directory &optional input) (when (string-match "\\([.]\\)\\{2\\}" input) (setq input (replace-match "" nil t input))) (message "Recursively searching %s from %s ..." input (abbreviate-file-name directory)) ;; Ensure to not create a new frame (let (helm-actions-inherit-frame-settings) (helm :sources (helm-make-source "Recursive directories" 'helm-locate-subdirs-source :header-name (lambda (name) (format "%s from `%s'" name (abbreviate-file-name helm-ff-default-directory))) :basedir (if (string-match-p "\\`es" helm-locate-recursive-dirs-command) directory (shell-quote-argument directory)) :subdir (shell-quote-argument input) :candidate-transformer `(,(lambda (candidates) (cl-loop for c in candidates when (and (file-directory-p c) (null (helm-boring-directory-p c helm-boring-file-regexp-list)) (string-match-p (regexp-quote input) (helm-basename c))) collect (propertize c 'face 'helm-ff-dirs))) helm-w32-pathname-transformer ,(lambda (candidates) (helm-ff-sort-candidates-1 candidates input))) :persistent-action 'ignore :action (lambda (c) (helm-set-pattern (file-name-as-directory (expand-file-name c))))) :candidate-number-limit 999999 :allow-nest t :resume 'noresume :ff-transformer-show-only-basename nil :buffer "*helm recursive dirs*"))) (defun helm-ff-recursive-dirs (_candidate) "Launch a recursive search in `helm-ff-default-directory'." (with-helm-default-directory helm-ff-default-directory (helm-find-files-recursive-dirs default-directory (helm-basename (helm-get-selection))))) (defun helm-ff-file-compressed-p (candidate) "Whether CANDIDATE is a compressed file or not." (member (file-name-extension candidate) helm-ff-file-compressed-list)) (defun helm-ff--fname-at-point () "Try to guess fname at point." (let ((end (point)) (limit (helm-aif (bounds-of-thing-at-point 'filename) (car it) (point)))) (save-excursion (while (re-search-backward "\\(~\\|/\\|[[:lower:][:upper:]]:/\\)" limit t)) (buffer-substring-no-properties (point) end)))) (defun helm-insert-file-name-completion-at-point (_candidate) "Insert file name completion at point. When completing i.e. there is already something at point, insert filename abbreviated, relative or full according to initial input, whereas when inserting i.e. there is nothing at point, insert filename full, abbreviated or relative according to prefix arg, respectively no prefix arg, one prefix arg or two prefix arg." (with-helm-current-buffer (if buffer-read-only (error "Error: Buffer `%s' is read-only" (buffer-name)) (let* ((mkds (helm-marked-candidates :with-wildcard t)) (candidate (car mkds)) (end (point)) (tap (helm-ffap-guesser)) (guess (and (stringp tap) (substring-no-properties tap))) (beg (helm-aif (and guess (save-excursion (when (re-search-backward (regexp-quote guess) (pos-bol) t) (point)))) it (point))) (full-path-p (and (stringp guess) (or (string-match-p (concat "^" (getenv "HOME")) guess) (string-match-p "\\`\\(/\\|[[:lower:][:upper:]]:/\\)" guess)))) (escape-fn (if (memq major-mode helm-modes-using-escaped-strings) #'shell-quote-argument #'identity))) (when (and beg end) (delete-region beg end)) (insert (funcall escape-fn (helm-ff--format-fname-to-insert candidate beg end full-path-p guess helm-current-prefix-arg)) (if (cdr mkds) " " "") (mapconcat escape-fn (cl-loop for f in (cdr mkds) collect (helm-ff--format-fname-to-insert f nil nil nil nil helm-current-prefix-arg)) " ")))))) (defun helm-ff--format-fname-to-insert (candidate &optional beg end full-path guess prefarg) (set-text-properties 0 (length candidate) nil candidate) (if (and beg end guess (not (string= guess "")) (null prefarg) (or (string-match "^\\(~/\\|/\\|[[:lower:][:upper:]]:/\\)" guess) (file-exists-p candidate))) (cond (full-path (expand-file-name candidate)) ((string= (match-string 1 guess) "~/") (abbreviate-file-name candidate)) (t (file-relative-name candidate))) (helm-acase prefarg ((4) (abbreviate-file-name candidate)) ((16) (file-relative-name candidate)) ((64) (helm-basename candidate)) (t candidate)))) (cl-defun helm-find-files-history (arg &key (comp-read t)) "The `helm-find-files' history. Show the first `helm-ff-history-max-length' elements of `helm-ff-history' in an `helm-comp-read'." (interactive "p") (let ((history (when helm-ff-history (helm-fast-remove-dups helm-ff-history :test 'equal)))) (when history (setq helm-ff-history (if (>= (length history) helm-ff-history-max-length) (helm-take history helm-ff-history-max-length) history)) (if comp-read (let ((src (helm-build-sync-source "Helm Find Files History" :candidates helm-ff-history :fuzzy-match (helm-ff-fuzzy-matching-p) :persistent-action 'ignore :migemo t :action (lambda (candidate) (if arg (helm-set-pattern (expand-file-name candidate)) (identity candidate)))))) (helm :sources src :resume 'noresume :buffer helm-ff-history-buffer-name :allow-nest t)) helm-ff-history)))) (put 'helm-find-files-history 'helm-only t) ;; The `helm-drag-mouse-1-fn' for helm-find-files, it is bound to ;; in `helm--bind-mouse-for-selection'. ;; See https://freedesktop.org/wiki/Specifications/XDND/ ;; and (info "(elisp) Drag and Drop") for more infos. (defun helm-ff-mouse-drag (event) "Drag-and-drop marked files at EVENT. It is the drag-an-drop function of dired adapted for helm-find-files." (interactive "e") (when mark-active (deactivate-mark)) (let ((action helm-ff-drag-mouse-1-default-action)) (save-excursion (with-selected-window (posn-window (event-end event)) (goto-char (posn-point (event-end event)))) (track-mouse (let ((beginning-position (mouse-pixel-position)) new-event) (catch 'track-again (setq new-event (read-event)) (if (not (eq (event-basic-type new-event) 'mouse-movement)) (when (eq (event-basic-type new-event) 'mouse-1) (push new-event unread-command-events)) (let ((current-position (mouse-pixel-position))) ;; If the mouse didn't move far enough, don't ;; inadvertently trigger a drag. (when (and (eq (car current-position) (car beginning-position)) (ignore-errors (and (> 3 (abs (- (cadr beginning-position) (cadr current-position)))) (> 3 (abs (- (caddr beginning-position) (caddr current-position))))))) (throw 'track-again nil))) ;; We can get an error if there's by some chance no file ;; name at point. (condition-case error (let ((files (with-helm-window (helm-marked-candidates)))) (dnd-begin-drag-files files nil action t)) (error (when (eq (event-basic-type new-event) 'mouse-1) (push new-event unread-command-events)) ;; Errors from `dnd-begin-drag-files' should be ;; treated as user errors, since they should ;; only occur when the user performs an invalid ;; action, such as trying to create a link to ;; a remote file. (user-error (cadr error))))))))))) (put 'helm-ff-mouse-drag 'helm-only t) (defvar helm-dnd-protocol-alist '(("^file:///" . helm-dnd-handle-local-file) ("^file://" . helm-dnd-handle-file) ("^file:" . helm-dnd-handle-local-file)) "The functions to call when dropping to helm-buffer. Prevent dropping to helm buffer when user starts a drag-and-drop action and release the mouse in this same buffer.") (defun helm-dnd-handle-local-file (_uri _action) "Prevent dropping files to helm buffer." (user-error "Can't drop files in helm buffer")) (defun helm-dnd-handle-file (_uri _action) "Prevent dropping files to helm buffer." (user-error "Can't drop files in helm buffer")) ;; This is used to advice `x-dnd-handle-drag-n-drop-event'. (defun helm-ff--restore-frame (&rest _args) (let ((hframe (window-frame (helm-window)))) (if (eql (selected-frame) hframe) (select-window (active-minibuffer-window)) (select-frame hframe)))) (defun helm-find-files-1 (fname &optional preselect) "Find FNAME filename with PRESELECT filename preselected. Use it for non-interactive calls of `helm-find-files'." (require 'tramp) (require 'dnd) ;; Resolve FNAME now outside of helm. ;; [FIXME] When `helm-find-files-1' is used directly from lisp ;; and FNAME is an abbreviated path, for some reasons ;; `helm-update' is called many times before resolving ;; the abbreviated path (Bug#1939) so be sure to pass a ;; full path to helm-find-files-1. (unless (string-match-p helm-ff-url-regexp fname) (setq fname (expand-file-name (substitute-in-file-name fname)))) (when (get-buffer helm-action-buffer) (kill-buffer helm-action-buffer)) (setq helm-find-files--toggle-bookmark nil) (let* ( ;; Be sure we don't erase the precedent minibuffer if some. (helm-ff-auto-update-initial-value (and helm-ff-auto-update-initial-value (not (minibuffer-window-active-p (minibuffer-window))))) (tap (thing-at-point 'filename)) (def (and tap (or (file-remote-p tap) (expand-file-name tap)))) ;; Ensure not being prompted for password each time we ;; navigate to a directory. (password-cache t)) (helm-set-local-variable 'helm-follow-mode-persistent nil) (when (fboundp 'dnd-begin-drag-files) (helm-set-local-variable 'helm-drag-mouse-1-fn 'helm-ff-mouse-drag 'dnd-protocol-alist (append helm-dnd-protocol-alist dnd-protocol-alist)) (advice-add 'x-dnd-handle-drag-n-drop-event :after #'helm-ff--restore-frame)) (unless helm-source-find-files (setq helm-source-find-files (helm-make-source "Find Files" 'helm-source-ffiles))) (when (helm-get-attr 'follow helm-source-find-files) (helm-set-attr 'follow -1 helm-source-find-files)) ;; If preselected candidate is further than `helm-ff-candidate-number-limit' ;; in the directory file list, we have to increase `candidate-number-limit' ;; attr to have this candidate visible for preselection. NOTE: ;; When HFF has yet not been launched in this directory the maximum length ;; of this directory is unknown and candidate will NOT be selected until ;; next time we call HFF on this same buffer. (helm-aif (and preselect (not helm-ff-preselect-ignore-large-dirs) (gethash fname helm-ff--directory-files-length helm-ff-candidate-number-limit)) (helm-set-attr 'candidate-number-limit (max it helm-ff-candidate-number-limit) helm-source-find-files)) (helm-ff-setup-update-hook) (add-hook 'helm-resume-after-hook 'helm-ff--update-resume-after-hook) (unwind-protect (helm :sources '(helm-source-find-files helm-find-files-dummy-source) :input fname :case-fold-search helm-file-name-case-fold-search :preselect preselect :ff-transformer-show-only-basename helm-ff-transformer-show-only-basename :dim-prompt-on-update helm-ff-dim-prompt-on-update :default def :prompt "Find files or url: " :buffer "*helm find files*") (helm-ff--update-resume-after-hook nil t) (setq helm-ff-default-directory nil) (advice-remove 'x-dnd-handle-drag-n-drop-event 'helm-ff--restore-frame)))) (defvar helm-find-files-dummy-source (helm-build-dummy-source "New file or directory" :filtered-candidate-transformer (lambda (_candidates _source) (unless (file-exists-p helm-pattern) (list (helm-ff-filter-candidate-one-by-one helm-pattern nil t)))) :all-marked t :keymap 'helm-find-files-map :action 'helm-find-files-actions :action-transformer 'helm-find-files-dummy-action-transformer)) (defun helm-find-files-dummy-action-transformer (actions candidate) "Action transformer for `helm-find-files-dummy-source'." (let (cand-no-linum linum) (cond ((and helm--url-regexp (not (string-match-p helm--url-regexp candidate)) (string-match "\\(:[[:digit:]]+:?\\)\\'" candidate) (file-exists-p (setq cand-no-linum (replace-match "" t t candidate 1)))) (setq linum (replace-regexp-in-string ":" "" (match-string 1 candidate))) (helm-append-at-nth actions `(("Find file to line number" . ,(lambda (_candidate) (find-file cand-no-linum) (helm-goto-line (string-to-number linum) t)))) 1)) (t actions)))) (defun helm-ff--update-resume-after-hook (sources &optional nohook) "Meant to be used in `helm-resume-after-hook'. When NOHOOK is non-nil run inconditionally, otherwise only when source is `helm-source-find-files'." ;; When using the action `helm-find-files-load-files' on helm-files.el, ;; `helm-source-find-files' is reseted to nil hence error when calling ;; `helm-set-attr' outside helm => "no buffer named *helm*". (unless helm-source-find-files (setq helm-source-find-files (helm-make-source "Find Files" 'helm-source-ffiles))) (when (or nohook (string= "Find Files" (assoc-default 'name (car sources)))) (helm-set-attr 'resume `(lambda () (helm-ff-setup-update-hook) (setq helm-ff-default-directory ,helm-ff-default-directory helm-ff-last-expanded ,helm-ff-last-expanded)) helm-source-find-files))) (defun helm-ff-clean-initial-input () ;; When using hff in an external frame initial input is printed in ;; the minibuffer of initial-frame, delete it. (with-selected-frame helm-initial-frame (helm-clean-up-minibuffer))) (defun helm-ff-setup-update-hook () (dolist (hook '(helm-ff-clean-initial-input ; Add to be called first. helm-ff-update-when-only-one-matched helm-ff-auto-expand-to-home-or-root)) (add-hook 'helm-after-update-hook hook))) (defun helm-find-files-cleanup () (mapc (lambda (hook) (remove-hook 'helm-after-update-hook hook)) '(helm-ff-auto-expand-to-home-or-root helm-ff-update-when-only-one-matched helm-ff-clean-initial-input)) (maphash (lambda (k _v) (when (member k helm-ff--thumbnailed-directories) (remhash k helm-ff--list-directory-cache))) helm-ff--list-directory-cache) (setq helm-ff--show-directories-only nil helm-ff--show-files-only nil helm-ff--show-thumbnails nil helm-ff--thumbnailed-directories nil) (helm-ff-clean-image-cache)) (defun helm-ff-bookmark () (helm :sources 'helm-source-bookmark-helm-find-files :buffer "*helm ff bookmarks*")) (defun helm-find-files-switch-to-bookmark () "Switch to helm-bookmark for `helm-find-files' from `helm-find-files.'" (interactive) (require 'helm-bookmark) (with-helm-alive-p (helm-run-after-exit 'helm-ff-bookmark))) (put 'helm-find-files-switch-to-bookmark 'helm-only t) (defun helm-ff-bookmark-insert-location () "Insert helm-find-files bookmark in minibuffer." (interactive) (require 'helm-bookmark) (with-helm-alive-p (helm :sources (helm-bookmark-build-source "bookmark insert location" 'helm-bookmark-helm-find-files-setup-alist 'helm-source-in-buffer :filtered-candidate-transformer '(helm-adaptive-sort helm-highlight-bookmark) :action (lambda (candidate) (with-selected-window (minibuffer-window) (delete-minibuffer-contents) (bookmark-insert-location candidate)))) :buffer "*helm bookmark insert*" :allow-nest t))) (put 'helm-ff-bookmark-insert-location 'helm-only t) (defun helm-find-files-initial-input (&optional input) "Return INPUT if present, otherwise try to guess it." (let ((guesser (helm-acase (helm-ffap-guesser) ("" nil) (t it)))) (unless (eq major-mode 'image-mode) (if input (if (or (file-remote-p input) (string-match helm-ff-url-regexp input)) input (expand-file-name input)) (helm-find-files-input (if (and helm-ff-allow-non-existing-file-at-point guesser (not (string-match ffap-url-regexp guesser))) ;; Keep the ability of jumping to numbered lines even ;; when allowing non existing filenames at point. (helm-aand guesser (thing-at-point 'filename) (replace-regexp-in-string ":[0-9]+\\'" "" it)) guesser) (thing-at-point 'filename)))))) (defun helm-ffap-guesser () "Same as `ffap-guesser' but without gopher and machine support." (require 'ffap) ;; Avoid "Stack overflow in regexp matcher" error ;; in evil `ffap-guesser' by removing crap `ffap-gopher-at-point' ;; (bug fixed in emacs-26 http://debbugs.gnu.org/cgi/bugreport.cgi?bug=25391) . ;; `ffap-machine-at-point' have been removed too as it was anyway ;; disabled with `ffap-machine-p-known' bound to 'reject. ;; `ffap-file-at-point' can be neutralized with ;; `helm-ff-guess-ffap-filenames' and `ffap-url-at-point' with ;; `helm-ff-guess-ffap-urls' ;; Note also that `ffap-url-unwrap-remote' can override these ;; variables. (let ((ffap-alist (and helm-ff-guess-ffap-filenames ffap-alist)) (ffap-url-regexp helm--url-regexp)) (if (eq major-mode 'dired-mode) (let ((beg (save-excursion (dired-move-to-filename))) (end (save-excursion (dired-move-to-end-of-filename t)))) (helm-aif (and beg end (member (buffer-substring beg end) '("." ".."))) (concat (file-name-as-directory (expand-file-name dired-directory)) (car it)) (dired-get-filename 'no-dir t))) (let* ((beg (and (use-region-p) (region-beginning))) (end (and (use-region-p) (region-end))) (str (and beg end (buffer-substring-no-properties beg end))) (ffap (or (helm-aand helm-ff-guess-ffap-urls ffap-url-regexp (ffap-url-at-point) (ffap-fixup-url it) (and (string-match ffap-url-regexp it) it)) (and helm-ff-guess-ffap-urls ffap-url-regexp (fboundp 'ffap-fixup-email) ; Emacs-30 (ffap-fixup-email (thing-at-point 'email))) (ffap-file-at-point)))) ;; Workaround emacs bugs: ;; When the region is active and a file is detected ;; `ffap-string-at-point' returns the region prefixed with ;; "/", e.g. at a beginning of a patch (first bug) and make ;; `file-remote-p' returning an error (second bug), so in such ;; case returns the region itself instead of the region ;; corrupted by ffap. (if (and str ffap) str ffap))))) (defun helm-find-files-input (file-at-pt thing-at-pt) "Try to guess a default input for `helm-find-files'." (let* ((non-essential t) (remp (or (and file-at-pt (file-remote-p file-at-pt)) (and thing-at-pt (file-remote-p thing-at-pt)))) (def-dir (helm-current-directory)) (urlp (and file-at-pt helm--url-regexp (string-match helm--url-regexp file-at-pt))) (lib (when helm-ff-search-library-in-sexp (helm-find-library-at-point))) (hlink (helm-ff-find-url-at-point)) (file-p (and file-at-pt (not (string= file-at-pt "")) (not remp) (or (file-exists-p file-at-pt) helm-ff-allow-non-existing-file-at-point) (not urlp) thing-at-pt (not (string= thing-at-pt "")) (file-exists-p (file-name-directory (expand-file-name thing-at-pt def-dir)))))) (cond (lib) ; e.g we are inside a require sexp. (hlink) ; String at point is an hyperlink. (file-p ; a regular file (and file-at-pt (if (not (member (helm-basename file-at-pt) '("." ".."))) (expand-file-name file-at-pt) file-at-pt))) (urlp (helm-html-decode-entities-string file-at-pt)) ; possibly an url or email. ((and file-at-pt (not remp) (or helm-ff-allow-non-existing-file-at-point (file-exists-p file-at-pt))) (expand-file-name file-at-pt))))) (defun helm-ff-find-url-at-point () "Try to find link to an url in text-property at point." (let* ((he (get-text-property (point) 'help-echo)) (ov (overlays-at (point))) (ov-he (and ov (overlay-get (car (overlays-at (point))) 'help-echo))) (w3m-l (get-text-property (point) 'w3m-href-anchor)) (nt-prop (get-text-property (point) 'nt-link))) ;; Org link. (when (and (stringp he) (string-match "^LINK: " he)) (setq he (replace-match "" t t he))) (cl-loop for i in (list he ov-he w3m-l nt-prop) thereis (and (stringp i) helm--url-regexp (string-match helm--url-regexp i) i)))) (defun helm-find-library-at-point () "Try to find library path at point. Find inside `require' and `declare-function' sexp." (require 'find-func) (let* ((beg-sexp (save-excursion (search-backward "(" (pos-bol) t))) (end-sexp (save-excursion (ignore-errors (end-of-defun)) (point))) (sexp (and beg-sexp end-sexp (buffer-substring-no-properties (1+ beg-sexp) (1- end-sexp))))) (ignore-errors (cond (;; Should work only when point is on the use-package line ;; i.e. first line of sexp otherwise it prevents matching ;; urls with helm-find-files (bug #2469). (and sexp (string-match "use-package +\\([^ )\n]+\\)" sexp)) (find-library-name (match-string 1 sexp))) ((and sexp (string-match "require +[']\\([^ )]+\\)" sexp)) ;; If require use third arg, ignore it, ;; always use library path found in `load-path'. (find-library-name (match-string 1 sexp))) ;; Assume declare-function sexps are on one line. ((and sexp (string-match "declare-function .+? \"\\(?:ext:\\)?\\([^ )]+\\)\"" sexp)) (find-library-name (match-string 1 sexp))) (t nil))))) ;;; Handle copy, rename, symlink, relsymlink and hardlink from helm. ;; ;; (defun helm-ff--valid-default-directory () (with-helm-current-buffer (cl-loop for b in (buffer-list) for cd = (with-current-buffer b default-directory) when (eq (car (file-attributes cd)) t) return cd))) (cl-defun helm-dired-action (destination &key action follow (files (dired-get-marked-files))) "Execute ACTION on FILES to DESTINATION. Where ACTION is a symbol that can be one of: \\='copy', \\='rename', \\='symlink', \\='relsymlink', \\='hardlink' or \\='backup'. Argument FOLLOW when non-nil specifies to follow FILES to DESTINATION for the actions copy and rename." (require 'dired-async) (require 'dired-x) ; For dired-keep-marker-relsymlink (when (get-buffer dired-log-buffer) (kill-buffer dired-log-buffer)) ;; When default-directory in current-buffer is an invalid directory, ;; (e.g buffer-file directory have been renamed somewhere else) ;; be sure to use a valid value to give to dired-create-file. ;; i.e start-process is creating a process buffer based on default-directory. (let ((default-directory (helm-ff--valid-default-directory)) (fn (cl-case action (copy 'dired-copy-file) (rename 'dired-rename-file) (symlink 'make-symbolic-link) (relsymlink 'dired-make-relative-symlink) (hardlink 'dired-hardlink) (backup 'backup-file))) (marker (cl-case action ((copy rename backup) dired-keep-marker-copy) (symlink dired-keep-marker-symlink) (relsymlink dired-keep-marker-relsymlink) (hardlink dired-keep-marker-hardlink))) (dirflag (and (= (length files) 1) (file-directory-p (car files)) (not (file-directory-p destination)))) (dired-async-state (if (and (boundp 'dired-async-mode) dired-async-mode) 1 -1))) (and follow (fboundp 'dired-async-mode) (dired-async-mode -1)) ;; When dired-create-destination-dirs is available (emacs-27.1+) such error ;; is handled later in dired-create-files. (when (and (null (boundp 'dired-create-destination-dirs)) (cdr files) (not (file-directory-p destination))) (error "%s: target `%s' is not a directory" action destination)) (unwind-protect (dired-create-files fn (symbol-name action) files (if (file-directory-p destination) ;; When DESTINATION is a directory, build file-name in this directory. ;; Else we use DESTINATION. (lambda (from) (expand-file-name (file-name-nondirectory from) destination)) (lambda (_from) destination)) marker) (and (fboundp 'dired-async-mode) (dired-async-mode dired-async-state))) (push (file-name-as-directory (if (file-directory-p destination) (expand-file-name destination) (file-name-directory destination))) helm-ff-history) ;; If follow is non--nil we should not be in async mode. (when (and follow (not (memq action '(symlink relsymlink hardlink))) (not (get-buffer dired-log-buffer))) (let ((target (directory-file-name destination)) (cands-to-mark (helm-get-dest-fnames-from-list files destination dirflag))) (with-helm-after-update-hook (helm-ff-maybe-mark-candidates cands-to-mark)) ;; Wait for the notify callback ends before calling HFF. (run-at-time 0.1 nil (lambda () (if (and dirflag (eq action 'rename)) (helm-find-files-1 (file-name-directory target) (format helm-ff-last-expanded-candidate-regexp (if helm-ff-transformer-show-only-basename (helm-basename target) target))) (helm-find-files-1 (if (file-directory-p destination) (file-name-as-directory (expand-file-name destination)) (expand-file-name (helm-basedir destination))) (format helm-ff-last-expanded-candidate-regexp (if helm-ff-transformer-show-only-basename (helm-basename (car files)) (car files))))))))))) (defun helm-get-dest-fnames-from-list (flist dest-cand rename-dir-flag) "Transform filenames of FLIST to abs of DEST-CAND. If RENAME-DIR-FLAG is non-nil collect the `directory-file-name' of transformed members of FLIST." ;; At this point files have been renamed/copied at destination. ;; That's mean DEST-CAND exists. (cl-loop with dest = (expand-file-name dest-cand) for src in flist for basename-src = (helm-basename src) for fname = (cond (rename-dir-flag (directory-file-name dest)) ((file-directory-p dest) (concat (file-name-as-directory dest) basename-src)) (t dest)) when (file-exists-p fname) collect fname into tmp-list finally return (sort tmp-list 'string<))) (defun helm-ff-maybe-mark-candidates (seq) "Add visible mark to all candidates in SEQ. This is used when copying/renaming/symlinking etc. and following files to destination." (when (and (string= (assoc-default 'name (helm-get-current-source)) (assoc-default 'name helm-source-find-files)) seq) (with-helm-window (while seq (if (string= (car seq) (helm-get-selection)) (progn (helm-make-visible-mark) (helm-next-line) (setq seq (cdr seq))) (helm-next-line))) (unless (helm-this-visible-mark) (helm-prev-visible-mark))))) ;;; Compress/uncompress files ;; ;; (define-minor-mode helm-ff--compress-async-modeline-mode "Notify mode-line that an async process run." :group 'dired-async :global t :lighter (:eval (propertize (format " [%s async job (Un)compressing file(s)]" (length (dired-async-processes 'helm-async-compress))) 'face 'helm-delete-async-message)) (unless helm-ff--compress-async-modeline-mode (let ((visible-bell t)) (ding)))) (defun helm-do-compress-to (ifiles ofile) "Compress IFILES files/directories to the OFILE archive. Choose the archiving command based on the OFILE extension and `dired-compress-files-alist'." (let ((cmd (cl-loop for (r . c) in dired-compress-files-alist when (string-match r ofile) return c)) (error-file (expand-file-name "dired-shell-command-output" temporary-file-directory))) (cl-assert cmd nil "No compression rule found for %s, see `dired-compress-files-alist'" ofile) (when (and (file-exists-p ofile) (not (y-or-n-p (format "%s exists, overwrite?" (abbreviate-file-name ofile))))) (message "Compression aborted")) (message "Compressing %d file(s) to `%s'..." (length ifiles) (helm-basename ofile)) (process-put (async-start `(lambda () (require 'cl-lib) (require 'dired-aux) (let* ((default-directory (cl-loop with base = (car ',ifiles) for file in ',ifiles do (setq base (fill-common-string-prefix base file)) finally return (file-name-directory base))) (local-name (shell-quote-argument (file-local-name ,ofile))) (input (mapconcat (lambda (in-file) (shell-quote-argument (file-relative-name in-file))) ',ifiles " ")) process-status) (when (not (zerop (setq process-status (dired-shell-command (format-spec ,cmd `((?o . ,local-name) (?i . ,input))))))) (let ((error-output (with-current-buffer " *dired-check-process output*" (buffer-string)))) (with-temp-file ,error-file (insert error-output)))) process-status)) (lambda (result) (unless (dired-async-processes 'helm-async-compress) (helm-ff--compress-async-modeline-mode -1)) (if (zerop result) ; dired-shell-command succeed. (progn (message "Compressed %d file(s) to `%s' done" (length ifiles) (file-name-nondirectory ofile)) (run-with-timer 0.1 nil (lambda (flist dest) (dired-async-mode-line-message "%s %d file(s) to %s done" 'helm-delete-async-message "Compressing" (length flist) (helm-basename dest))) ifiles ofile)) (when (file-exists-p error-file) (pop-to-buffer (find-file-noselect error-file)))))) 'helm-async-compress t) (helm-ff--compress-async-modeline-mode 1))) (defun helm-ff--dired-compress-file (file) ;; `dired-compress-file' doesn't take care of binding `default-directory' when ;; uncompressing FILE, as a result FILE is uncompressed in the directory where ;; helm was started i.e. the current value of `default-directory'. (with-helm-default-directory helm-ff-default-directory (dired-compress-file file))) (defun helm-ff-quick-compress (_candidate) "Compress or uncompress marked files without quitting." (with-helm-window (let (cfile) (unwind-protect (helm-read-answer-dolist-with-action "Compress or uncompress file `%s'? " (helm-marked-candidates) (lambda (c) (setq cfile (save-selected-window (helm-ff--dired-compress-file c))) (message nil) (helm--remove-marked-and-update-mode-line c)) #'abbreviate-file-name) (setq helm-marked-candidates nil helm-visible-mark-overlays nil) (helm-force-update (let ((presel (helm-get-selection))) ;; FIXME: Probably this would never happen, I see no cases here where ;; helm-get-selection doesn't exist. (unless (file-exists-p presel) (setq presel cfile)) (when presel (format helm-ff-last-expanded-candidate-regexp (regexp-quote (if (and helm-ff-transformer-show-only-basename (not (helm-ff-dot-file-p presel))) (helm-basename presel) presel)))))))))) (helm-make-persistent-command-from-action helm-ff-persistent-compress "Compress or uncompress marked candidates without quitting." 'quick-compress 'helm-ff-quick-compress) ;;; Delete and trash files ;; ;; (defun helm-file-buffers (filename) "Return a list of buffer names corresponding to FILENAME." (cl-loop with name = (expand-file-name filename) for buf in (buffer-list) for bfn = (buffer-file-name buf) when (and bfn (string= name bfn)) collect (buffer-name buf))) (defun helm-ff--delete-by-moving-to-trash (file) "Decide to trash or delete FILE. Return non-nil when FILE needs to be trashed." (let ((remote (file-remote-p file))) (or (and delete-by-moving-to-trash (null helm-current-prefix-arg) (null current-prefix-arg) (or (and remote helm-trash-remote-files) (null remote))) (and (null delete-by-moving-to-trash) (or helm-current-prefix-arg current-prefix-arg) (or (and remote helm-trash-remote-files) (null remote)))))) (defun helm-trash-directory () "Try to find a trash directory. Return the \"files\" subdirectory of trash directory. When `helm-trash-default-directory' is set use it as trash directory." (let* ((xdg-data-dir (or helm-trash-default-directory (directory-file-name (expand-file-name "Trash" (or (getenv "XDG_DATA_HOME") "~/.local/share"))))) (trash-files-dir (expand-file-name "files" xdg-data-dir))) ;; Just return nil if the Trash directory is not yet created. It will be ;; created later by `delete-directory'. (and (file-exists-p trash-files-dir) trash-files-dir))) (cl-defun helm-ff-file-already-trashed (file &optional (trash-alist nil strash-alist)) "Return FILE when it is already in trash. Optional arg TRASH-ALIST should be an alist as what `helm-ff-trash-list' returns." (unless (or (fboundp 'system-move-file-to-trash) (and strash-alist (null trash-alist))) (let ((trash-files-dir (helm-trash-directory))) (cl-loop for (_bn . fn) in (or trash-alist (helm-ff-trash-list trash-files-dir)) thereis (and (file-equal-p file fn) file))))) (defun helm-ff-quick-delete (_candidate) "Delete file CANDIDATE without quitting. When a prefix arg is given, meaning of `delete-by-moving-to-trash' is the opposite." (with-helm-window (let* ((marked (helm-marked-candidates)) (trash (helm-ff--delete-by-moving-to-trash (car marked))) (helm-ff--trashed-files (and trash (helm-ff-trash-list (helm-trash-directory)))) (old--allow-recursive-deletes helm-ff-allow-recursive-deletes) (buffers (cl-loop for f in marked append (helm-file-buffers f)))) (unwind-protect (progn (helm-read-answer-dolist-with-action "Really %s file `%s'" marked (lambda (file) (helm-ff--quick-delete-action file trash)) (list (if trash "Trash" "Delete") #'abbreviate-file-name)) (when buffers (helm-read-answer-dolist-with-action "Kill buffer `%s', too? " buffers #'kill-buffer))) (setq helm-marked-candidates nil helm-visible-mark-overlays nil) (helm-force-update (let ((presel (helm-get-selection))) (when presel (format helm-ff-last-expanded-candidate-regexp (regexp-quote (if (and helm-ff-transformer-show-only-basename (not (helm-ff-dot-file-p presel))) (helm-basename presel) presel)))))) (setq helm-ff-allow-recursive-deletes old--allow-recursive-deletes))))) (defun helm-ff--quick-delete-action (candidate trash) "Delete or trash CANDIDATE and remove it from display." (helm-preselect (format helm-ff-last-expanded-candidate-regexp (regexp-quote (if (and helm-ff-transformer-show-only-basename (not (helm-ff-dot-file-p candidate))) (helm-basename candidate) candidate)))) (helm-acase (helm-delete-file candidate helm-ff-signal-error-on-dot-files trash) (skip ;; This happens only when trying to ;; trash a file already trashed. (helm-delete-visible-mark (helm-this-visible-mark)) (if (helm-end-of-source-p) (helm-previous-line) (helm-next-line))) (t (helm-delete-current-selection))) (message nil) (helm--remove-marked-and-update-mode-line candidate)) (defun helm-delete-file (file &optional error-if-dot-file-p trash) "Delete FILE after querying the user. When a prefix arg is given, meaning of `delete-by-moving-to-trash' is the opposite. Return error when ERROR-IF-DOT-FILE-P is non-nil and user tries to delete a dotted file i.e. \".\" or \"..\". Ask user when directory are not empty to allow recursive deletion unless `helm-ff-allow-recursive-deletes' is non nil. When user is asked and reply with \"!\" don't ask for remaining directories. Ask to kill buffers associated with that file, too. When TRASH is non nil, trash FILE even if `delete-by-moving-to-trash' is nil." (require 'dired) (cl-block nil (when (and error-if-dot-file-p (helm-ff-dot-file-p file)) (error "Error: Cannot operate on `.' or `..'")) (let ((helm--reading-passwd-or-string t) (file-attrs (file-attributes file)) (trash (or trash (helm-ff--delete-by-moving-to-trash file))) (delete-by-moving-to-trash trash)) (cond ((and (eq (nth 0 file-attrs) t) ; a not empty directory. (directory-files file t directory-files-no-dot-files-regexp)) (if (or helm-ff-allow-recursive-deletes trash) (delete-directory file 'recursive trash) (helm-acase (helm-read-answer (format "Recursive delete of `%s'? [y,n,!,q,h]" (abbreviate-file-name file)) '("y" "n" "!" "q") #'helm-read-answer-default-help-fn) ("y" (delete-directory file 'recursive trash)) ("!" (setq helm-ff-allow-recursive-deletes t) (delete-directory file 'recursive trash)) ("n" (cl-return 'skip)) ("q" (throw 'helm-abort-delete-file (progn (message "Abort file deletion") (sleep-for 1))))))) ((eq (nth 0 file-attrs) t) ; a directory. (delete-directory file nil trash)) (t (delete-file file trash)))))) (defun helm-delete-marked-files (_ignore) "Delete marked files with `helm-delete-file'. When a prefix arg is given, meaning of `delete-by-moving-to-trash' is the opposite." (let* ((files (helm-marked-candidates :with-wildcard t)) (len 0) (trash (helm-ff--delete-by-moving-to-trash (car files))) (helm-ff--trashed-files (and trash (helm-ff-trash-list (helm-trash-directory)))) (prmt (if trash "Trash" "Delete")) (old--allow-recursive-deletes helm-ff-allow-recursive-deletes) (buffers (cl-loop for f in files append (helm-file-buffers f)))) (with-helm-display-marked-candidates helm-marked-buffer-name (helm-ff--count-and-collect-dups files) (if (not (y-or-n-p (format "%s *%s File(s)" prmt (length files)))) (message "(No deletions performed)") (catch 'helm-abort-delete-file (unwind-protect (progn (dolist (i files) (set-text-properties 0 (length i) nil i) (let ((res (helm-delete-file i helm-ff-signal-error-on-dot-files trash))) (if (eq res 'skip) (progn (message "Directory is not empty, skipping") (sleep-for 1)) (cl-incf len)))) (when buffers (helm-read-answer-dolist-with-action "Kill buffer `%s', too? " buffers #'kill-buffer))) (setq helm-ff-allow-recursive-deletes old--allow-recursive-deletes))) (message "%s File(s) %s" len (if trash "trashed" "deleted")))))) ;;; Delete files async ;; ;; (defvar helm-ff-delete-log-file (locate-user-emacs-file "helm-delete-file.log") "The file use to communicate with Emacs child when deleting files async.") (defvar helm-ff--trash-flag nil) (define-minor-mode helm-ff--delete-async-modeline-mode "Notify mode-line that an async process run." :group 'dired-async :global t :lighter (:eval (propertize (format " %s file(s) async [%s job]..." (if helm-ff--trash-flag "Trashing" "Deleting") (length (dired-async-processes 'helm-delete-async))) 'face 'helm-delete-async-message)) (unless helm-ff--delete-async-modeline-mode (let ((visible-bell t)) (ding)) (setq helm-ff--trash-flag nil))) (defalias 'helm-delete-async-mode-line-message 'dired-async-mode-line-message) (make-obsolete 'helm-delete-async-mode-line-message 'dired-async-mode-line-message "3.9.8") (defun helm-delete-async-kill-process () "Kill async process created by helm delete files async." (interactive) (let* ((processes (dired-async-processes)) (proc (car (last processes)))) (and proc (delete-process proc)) (unless (> (length processes) 1) (helm-ff--delete-async-modeline-mode -1)))) (defun helm-delete-marked-files-async (_ignore) "Same as `helm-delete-marked-files' but async. When a prefix arg is given, meaning of `delete-by-moving-to-trash' is the opposite. This function is not using `helm-delete-file' and BTW not asking user for recursive deletion of directory, be warned that directories are always deleted with no warnings." (let* ((files (helm-marked-candidates :with-wildcard t)) (trash (helm-ff--delete-by-moving-to-trash (car files))) (prmt (if trash "Trash" "Delete")) buffers callback ;; Workaround emacs-26 bug with tramp see ;; https://github.com/jwiegley/emacs-async/issues/80. (async-quiet-switch "-q")) (cl-loop for f in files for buf = (helm-file-buffers f) for dot-file-p = (helm-ff-dot-file-p f) when (and helm-ff-signal-error-on-dot-files dot-file-p) do (cl-return (error "Error: Cannot operate on `.' or `..'")) when buf do (setq buffers (nconc buf buffers))) (setq callback (lambda (result) (unless (dired-async-processes 'helm-delete-async) (helm-ff--delete-async-modeline-mode -1)) (when (file-exists-p helm-ff-delete-log-file) (display-warning 'helm (with-temp-buffer (insert-file-contents helm-ff-delete-log-file) (buffer-string)) :error "*helm delete files*") (fit-window-to-buffer (get-buffer-window "*helm delete files*")) (delete-file helm-ff-delete-log-file)) (when buffers (helm-read-answer-dolist-with-action "Kill buffer `%s', too? " buffers #'kill-buffer)) (run-with-timer 0.1 nil (lambda () (dired-async-mode-line-message "%s (%s/%s) file(s) async done" 'helm-delete-async-message (if trash "Trashing" "Deleting") result (length files)))))) (setq helm-ff--trash-flag trash) (with-helm-display-marked-candidates helm-marked-buffer-name (helm-ff--count-and-collect-dups files) (if (not (y-or-n-p (format "%s *%s File(s)" prmt (length files)))) (message "(No deletions performed)") (process-put (async-start `(lambda () (require 'cl-lib) ;; `delete-by-moving-to-trash' have to be set globally, ;; using the TRASH argument of delete-file or ;; delete-directory is not enough. (setq delete-by-moving-to-trash ,trash) (let ((result 0)) (dolist (file ',files result) (condition-case err (cond ((eq (nth 0 (file-attributes file)) t) (delete-directory file 'recursive ,trash) (setq result (1+ result))) (t (delete-file file ,trash) (setq result (1+ result)))) (error (with-temp-file ,helm-ff-delete-log-file (insert (format-time-string "%x:%H:%M:%S\n")) (insert (format "%S\n " err)))))))) callback) 'helm-delete-async t) (helm-ff--delete-async-modeline-mode 1))))) (defun helm-find-file-or-marked (candidate) "Open file CANDIDATE or open helm marked files in separate windows. Called with one prefix arg open files in separate windows in a vertical split. Called with two prefix arg open files in background without selecting them." (let ((marked (helm-marked-candidates :with-wildcard t)) (url-p (and helm--url-regexp ; we should have only one candidate. (string-match helm--url-regexp candidate))) (ffap-newfile-prompt helm-ff-newfile-prompt-p) (find-file-wildcards nil) (helm--reading-passwd-or-string t)) (if (cdr marked) (if (equal helm-current-prefix-arg '(16)) (mapc 'find-file-noselect marked) ;; If helm-current-prefix-arg is detected split is done ;; vertically. (helm-window-show-buffers (mapcar 'find-file-noselect marked))) (let ((dir (and (not url-p) (helm-basedir candidate)))) (cond ((and dir (file-directory-p dir)) (find-file (substitute-in-file-name candidate))) (url-p (find-file-at-point candidate)) ;; A a non--existing filename ending with / ;; Create a directory and jump to it. ((and (not (file-exists-p candidate)) (string-match "/$" candidate)) (helm-ff--mkdir candidate 'helm-ff)) ;; A non--existing filename NOT ending with / or ;; an existing filename, create or jump to it. ;; If the basedir of candidate doesn't exists, ;; ask for creating it. (dir (helm-ff--mkdir dir) (find-file candidate)) ;; Find file at `default-directory' when basedir is ;; unspecified e.g user hit C-k foo RET. (t (find-file candidate))))))) (defun helm-ff-find-file-other-tab (_candidate) "Display marked files in a new tab. See `helm-buffers-switch-buffers-in-tab-1' for more infos." (helm-buffers-switch-buffers-in-tab-1 (mapcar 'find-file-noselect (helm-marked-candidates)))) (helm-make-command-from-action helm-ff-run-find-file-other-tab "Run find file in other tab action from `helm-find-files'." 'helm-ff-find-file-other-tab (cl-assert (fboundp 'tab-bar-mode) nil "Tab-bar-mode not available")) (defun helm-ff--new-dirs-to-update (path) "Collect directories to update when creating new directory PATH." (let ((result (list path))) (helm-awhile (helm-reduce-file-name path 1) (if (not (file-directory-p it)) (progn (push it result) (setq path it)) (push it result) (cl-return))) result)) (defun helm-ff--mkdir (dir &optional helm-ff) (when (or (not confirm-nonexistent-file-or-buffer) (y-or-n-p (format "Create directory `%s'? " (abbreviate-file-name (expand-file-name dir))))) (let ((dirfname (directory-file-name dir)) (to-update (and helm-ff (helm-ff--new-dirs-to-update dir)))) (if (file-exists-p dirfname) (error "Mkdir: Unable to create directory `%s': file exists." (helm-basename dirfname)) (make-directory dir 'parent)) (when helm-ff ;; Refresh cache. (mapc (lambda (x) (helm-ff-directory-files x t)) to-update) ;; Allow having this new dir in history ;; to be able to retrieve it immediately ;; if we want to e.g copy a file from somewhere in it. (setq helm-ff-default-directory (file-name-as-directory (expand-file-name dir))) (push helm-ff-default-directory helm-ff-history)) (or (and helm-ff (helm-find-files-1 dir)) t)))) (defun helm-transform-file-load-el (actions candidate) "Add action to load the file CANDIDATE if it is an Emacs Lisp file. Else return ACTIONS unmodified." (if (member (file-name-extension candidate) '("el" "elc")) (append actions '(("Load Emacs Lisp File" . load-file))) actions)) (defun helm-transform-file-browse-url (actions candidate) "Add an action to browse the file CANDIDATE if it is a HTML file or URL. Else return ACTIONS unmodified." (let ((browse-action '("Browse with Browser" . browse-url))) (cond ((string-match "^http\\|^ftp" candidate) (cons browse-action actions)) ((string-match "\\.html?$" candidate) (append actions (list browse-action))) (t actions)))) (defun helm-file-on-mounted-network-p (file) "Return non-nil when FILE is part of a mounted remote directory. This function is checking `helm-mounted-network-directories' list." (when helm-mounted-network-directories (cl-loop for dir in helm-mounted-network-directories thereis (file-in-directory-p file dir)))) ;; helm-find-files bindings for filecache (defvar file-cache-alist) (defun helm-ff-cache-add-file (_candidate) (require 'filecache) (let ((mkd (helm-marked-candidates :with-wildcard t))) (mapc 'file-cache-add-file mkd))) (defun helm-ff-file-cache-remove-file-1 (file) "Remove FILE from `file-cache-alist'." (let ((entry (assoc (helm-basename file) file-cache-alist)) (dir (helm-basedir file)) new-entry) (setq new-entry (remove dir entry)) (when (= (length entry) 1) (setq new-entry nil)) (setq file-cache-alist (cons new-entry (remove entry file-cache-alist))))) (defun helm-ff-file-cache-remove-file (_file) "Remove marked files from `file-cache-alist.'" (let ((mkd (helm-marked-candidates))) (mapc 'helm-ff-file-cache-remove-file-1 mkd))) ;;; Find files in file ;; ;; (defclass helm-find-files-in-file-class (helm-source-in-file helm-type-file) ()) (cl-defmethod helm--setup-source ((source helm-find-files-in-file-class)) (helm-aif (slot-value source 'candidate-transformer) (setf (slot-value source 'candidate-transformer) (append (helm-mklist it) `(,(lambda (candidates) (cl-loop for c in candidates when (and (not (string= c "")) (file-exists-p c)) collect c))))))) (defun helm-find-files-in-file-build-source (file) (helm-make-source (format "Find files in `%s'" (helm-basename file)) 'helm-find-files-in-file-class :candidates-file file)) (defun helm-find-files-in-file (_file) "Helm action for listing filenames listed in marked files." (require 'helm-for-files) (let ((sources (cl-loop for f in (helm-marked-candidates) collect (helm-find-files-in-file-build-source f)))) (helm :sources sources :quit-if-no-candidate (lambda () (message "No files found in file(s)")) :buffer "*helm find files in files*"))) (cl-defun helm-ff-mcp (_candidate) "Copy the car of marked candidates to the remaining marked candidates. The car of marked should be a regular file and the rest of marked (cdr) should be existing directories." (let* ((mkd (helm-marked-candidates)) (file (car mkd)) (targets (cdr mkd)) (skipped 0) operations) (cl-assert (file-regular-p file) nil (format "ERROR: Not a regular file `%s'" file)) (cl-assert targets nil (format "ERROR: No destination specified for file `%s'" file)) (cl-assert (cl-loop for f in targets always (file-directory-p f)) nil "ERROR: Destinations must be existing directories") (when targets (cl-loop with yes-for-all for dest in targets for dest-file = (expand-file-name (helm-basename file) dest) for dir-ok = (file-accessible-directory-p dest) for exists = (and dir-ok (file-exists-p (expand-file-name (helm-basename file) dest))) for overwrite = (or (null exists) yes-for-all (helm-acase (helm-read-answer (format "File `%s' already-exists, overwrite (y,n,!,q) ? " dest-file) '("y" "n" "!" "q")) ("y" t) ("n" nil) ("!" (prog1 t (setq yes-for-all t))) ("q" (cl-return-from helm-ff-mcp (message "Operation aborted"))))) if dir-ok do (if overwrite (push (list file (file-name-as-directory dest) overwrite) operations) (cl-incf skipped)) else do (cl-incf skipped)) (when operations (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar #'abbreviate-file-name targets) (if (y-or-n-p (format "Copy `%s' to directories?" (helm-basename file))) (progn (process-put (async-start `(lambda () (require 'cl-lib) (cl-loop with copies = 0 with skipped = ,skipped for (file dest overwrite) in ',operations do (condition-case _err (progn (copy-file file dest overwrite) (cl-incf copies)) (file-error (cl-incf skipped))) finally return (list file copies skipped))) (lambda (result) (let ((copied (nth 1 result))) (unless (dired-async-processes) (dired-async--modeline-mode -1)) (run-with-idle-timer 0.1 nil (lambda () (dired-async-mode-line-message "Mcp done, %s %s of %s done, %s files skipped" 'dired-async-message copied (if (> copied 1) "copies" "copy") (helm-basename (nth 0 result)) (nth 2 result))))))) 'dired-async-process t) (dired-async--modeline-mode 1)) (message "Operation aborted"))))))) (helm-make-command-from-action helm-ff-run-mcp "Copy the car of marked candidates to the remaining marked candidates." 'helm-ff-mcp) ;;; File name history ;; ;; (defun helm-files-save-file-name-history (&optional force) "Save marked files to `file-name-history'." (let* ((src (helm-get-current-source)) (src-name (assoc-default 'name src))) (when (or force (helm-file-completion-source-p src) (member src-name helm-files-save-history-extra-sources)) (let ((mkd (helm-marked-candidates :with-wildcard t)) (history-delete-duplicates t)) (cl-loop for sel in mkd when (and sel (stringp sel) ;; If file was one of HFF candidates assume it ;; is an existing file, so no need to call ;; file-exists-p which is costly on remote candidates. ;; (file-exists-p sel) (not (helm-ff--file-directory-p sel)) ;; When creating a new directory previous test ;; check for file-directory-p BEFORE its ;; creation, so check for ending slash as ;; well to know if it is a future directory. (not (string-match "/\\'" sel))) do ;; we use `abbreviate-file-name' here because ;; other parts of Emacs seems to, ;; and we don't want to introduce duplicates. (add-to-history 'file-name-history (abbreviate-file-name (expand-file-name ;; See comments in `helm-recentf-source' ;; about bug#2709. (substring-no-properties sel))))))))) (add-hook 'helm-exit-minibuffer-hook 'helm-files-save-file-name-history) (defvar helm-source-file-name-history (helm-build-sync-source "File Name History" :candidates 'file-name-history :persistent-action #'ignore ;; See comments in `helm-recentf-source' about bug#2709. :coerce 'substring-no-properties :filtered-candidate-transformer #'helm-file-name-history-transformer :action 'helm-type-file-actions)) (defun helm-file-name-history-show-or-hide-deleted () (interactive) (setq helm-file-name-history-hide-deleted (not helm-file-name-history-hide-deleted)) (helm-update)) (put 'helm-file-name-history-show-or-hide-deleted 'helm-only t) (defvar helm-file-name-history-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "C-c d") 'helm-file-name-history-show-or-hide-deleted) (define-key map (kbd "C-x C-f") 'helm-ff-file-name-history-run-ff) map)) (defun helm-file-name-history-transformer (candidates _source) (cl-loop with lgst = helm-file-name-history-max-length for elm in candidates for c = (truncate-string-to-width elm helm-file-name-history-max-length nil nil t) for disp = (cond ((or (file-remote-p elm) (and (fboundp 'tramp-archive-file-name-p) (tramp-archive-file-name-p c))) (propertize c 'face 'helm-history-remote)) ((file-exists-p elm) (let ((last-access (format-time-string "%d/%m/%Y %X" (nth 4 (file-attributes elm))))) (propertize elm 'display (concat (propertize c 'face 'helm-ff-file) (make-string (1+ (- lgst (length c))) ? ) last-access)))) (t (unless helm-file-name-history-hide-deleted (propertize c 'face 'helm-history-deleted)))) when disp collect (cons (if helm-ff-icon-mode (concat (helm-x-icons-icon-for-file (helm-basename elm)) " " disp) disp) elm))) (defun helm-ff-file-name-history-ff (candidate) (helm-set-pattern (expand-file-name candidate))) (helm-make-command-from-action helm-ff-file-name-history-run-ff "Switch back to current HFF session with selection as preselect." 'helm-ff-file-name-history-ff) (defun helm-ff-file-name-history-delete-item (_candidate) (let ((files (helm-marked-candidates))) (with-helm-display-marked-candidates helm-marked-buffer-name (helm-ff--count-and-collect-dups files) (when (y-or-n-p "Delete file(s) from history? ") (cl-loop for f in files do (setq file-name-history (delete f file-name-history)))) (message nil)))) (defun helm-ff-file-name-history () "Switch to `file-name-history' without quitting `helm-find-files'." (interactive) (let ((src (helm-build-sync-source "File name history" :init (lambda () (with-helm-alive-p (when helm-ff-file-name-history-use-recentf (require 'recentf) (or recentf-mode (recentf-mode 1))))) :candidate-number-limit (helm-aand (or (get 'file-name-history 'history-length) history-length) (and (numberp it) it)) :candidates (lambda () (if helm-ff-file-name-history-use-recentf recentf-list file-name-history)) :help-message 'helm-file-name-history-help-message :fuzzy-match t :persistent-action 'ignore :migemo t :filtered-candidate-transformer 'helm-file-name-history-transformer :action (helm-make-actions "Find file" (lambda (candidate) (helm-set-pattern (expand-file-name candidate)) (with-helm-after-update-hook (helm-exit-minibuffer))) "Find file in helm" 'helm-ff-file-name-history-ff "Delete candidate(s)" 'helm-ff-file-name-history-delete-item) :keymap helm-file-name-history-map))) (with-helm-alive-p (helm :sources src :buffer "*helm-file-name-history*" :allow-nest t :resume 'noresume)))) (put 'helm-ff-file-name-history 'helm-only t) ;;; Browse project ;; Need dependencies: ;; ;; ;; Only hg and git are supported for now. (defvar helm--browse-project-cache (make-hash-table :test 'equal)) (defvar helm-buffers-in-project-p) (defvar helm-browse-project-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-generic-files-map) (define-key map (kbd "M-g a") 'helm-browse-project-run-ag) map)) (defun helm-browse-project-get-buffers (root-directory) (cl-loop for b in (helm-buffer-list) ;; FIXME: Why default-directory is root-directory ;; for current-buffer when coming from helm-quit-and-find-file. for cd = (with-current-buffer b default-directory) for bn = (buffer-file-name (get-buffer b)) if (or (and bn (file-in-directory-p bn root-directory)) (and (null bn) (not (file-remote-p cd)) (file-in-directory-p cd root-directory))) collect b)) (defun helm-browse-project-build-buffers-source (directory) (helm-make-source "Buffers in project" 'helm-source-buffers :header-name (lambda (name) (format "%s (%s)" name (abbreviate-file-name directory))) :buffer-list (lambda () (helm-browse-project-get-buffers directory)))) (defun helm-browse-project-walk-directory (directory) "Default function for `helm-browse-project-default-find-files-fn'." (helm-walk-directory directory :directories nil :path 'full :skip-subdirs t)) (defun helm-browse-project-find-files-1 (directory program) "List files in DIRECTORY recursively with external PROGRAM." (require 'helm-fd) (let* ((fd-exe (or helm-fd-executable (executable-find "fdfind") (executable-find "fd"))) (cmd (cl-ecase program (ag "ag --hidden -g '.*' %s") (rg "rg --files --hidden -g '*' %s") (fd (concat fd-exe " --hidden --type f --glob '*' %s"))))) (with-temp-buffer (call-process-shell-command (format cmd directory) nil t nil) (mapcar (lambda (f) (expand-file-name f directory)) (split-string (buffer-string) "\n"))))) (defun helm-browse-project-ag-find-files (directory) "A suitable function for `helm-browse-project-default-find-files-fn'. Use AG as backend." (helm-browse-project-find-files-1 directory 'ag)) (defun helm-browse-project-rg-find-files (directory) "A suitable function for `helm-browse-project-default-find-files-fn'. Use RG as backend." (helm-browse-project-find-files-1 directory 'rg)) (defun helm-browse-project-fd-find-files (directory) "A suitable function for `helm-browse-project-default-find-files-fn'. Use FD as backend." (helm-browse-project-find-files-1 directory 'fd)) (defun helm-browse-project-ag (_candidate) "A `helm-grep' AG action for `helm-browse-project'." (let ((dir (with-helm-buffer (helm-get-attr 'root-dir)))) (helm-grep-ag dir helm-current-prefix-arg))) (helm-make-command-from-action helm-browse-project-run-ag "Run `helm-grep' AG from `helm-browse-project'." 'helm-browse-project-ag) (defclass helm-browse-project-source (helm-source-in-buffer helm-type-file) ((root-dir :initarg :root-dir :initform nil :custom 'file) (match-part :initform (lambda (c) (if (with-helm-buffer helm-ff-transformer-show-only-basename) (helm-basename c) c))) (filter-one-by-one :initform (lambda (c) (if (with-helm-buffer helm-ff-transformer-show-only-basename) (cons (propertize (helm-basename c) 'face 'helm-ff-file) c) (propertize c 'face 'helm-ff-file))))) "Class to define a source in `helm-browse-project' handling non VC handled directories.") (cl-defmethod helm--setup-source :after ((source helm-browse-project-source)) (let ((actions (slot-value source 'action))) (setf (slot-value source 'action) (helm-append-at-nth (symbol-value actions) '(("Grep project with AG `M-g a, C-u select type'" . helm-browse-project-ag)) 7)) (setf (slot-value source 'keymap) helm-browse-project-map))) (defun helm-browse-project-find-files (directory &optional refresh) "Browse non VC handled directory DIRECTORY." (when refresh (remhash directory helm--browse-project-cache)) (unless (gethash directory helm--browse-project-cache) (puthash directory (funcall helm-browse-project-default-find-files-fn directory) helm--browse-project-cache)) (helm :sources `(,(helm-browse-project-build-buffers-source directory) ,(helm-make-source "Browse project" 'helm-browse-project-source :root-dir directory :data (gethash directory helm--browse-project-cache) :header-name (lambda (name) (format "%s (%s)" name (abbreviate-file-name directory))))) :ff-transformer-show-only-basename nil :buffer "*helm browse project*")) (defvar helm-browse-project-history nil) ;;;###autoload (defun helm-projects-history (&optional arg) "Jump to project already visisted with `helm-browse-project'. Prefix arg allows browsing files recursively under a project not handled by git or hg, otherwise it has no effect." (interactive "P") (setq helm-browse-project-history (cl-loop for p in helm-browse-project-history when (file-directory-p p) collect p)) (helm :sources (helm-build-sync-source "Project history" :candidates helm-browse-project-history :action (helm-make-actions "Browse project" (lambda (candidate) (with-helm-default-directory candidate (helm-browse-project (or arg helm-current-prefix-arg)))) "Remove project(s)" (lambda (_candidate) (let ((mkd (helm-marked-candidates))) (cl-loop for c in mkd do (setq helm-browse-project-history (delete c helm-browse-project-history))) (message "%s projects removed" (length mkd)))))) :buffer "*helm browse project history*")) ;;;###autoload (defun helm-browse-project (arg) "Preconfigured helm to browse projects. Browse files and see status of project with its VCS. Only HG and GIT are supported for now. Fall back to `helm-browse-project-find-files' if current directory is not under control of one of those VCS. With a prefix ARG browse files recursively, with two prefix ARG rebuild the cache. If the current directory is found in the cache, start `helm-browse-project-find-files' even with no prefix ARG. NOTE: The prefix ARG have no effect on the VCS controlled directories. Needed dependencies for VCS: and ." (interactive "P") (require 'helm-x-files) (let ((helm-type-buffer-actions (remove (assoc "Browse project from buffer" helm-type-buffer-actions) helm-type-buffer-actions)) (helm-buffers-in-project-p t)) (cl-flet ((push-to-hist (root) (setq helm-browse-project-history (cons root (delete root helm-browse-project-history))))) (helm-acond ((and (require 'helm-ls-git nil t) (fboundp 'helm-ls-git-root-dir) (helm-ls-git-root-dir)) (push-to-hist it) (helm-ls-git)) ((and (require 'helm-ls-hg nil t) (fboundp 'helm-hg-root) (helm-hg-root)) (push-to-hist it) (helm-hg-find-files-in-project)) ((helm-browse-project-get--root-dir (helm-current-directory)) (if (or arg (gethash it helm--browse-project-cache)) (progn (push-to-hist it) (helm-browse-project-find-files it (equal arg '(16)))) (helm :sources (helm-browse-project-build-buffers-source it) :buffer "*helm browse project*"))))))) (defun helm-browse-project-get--root-dir (directory) (cl-loop with dname = (file-name-as-directory directory) while (and dname (not (gethash dname helm--browse-project-cache))) if (file-remote-p dname) do (setq dname nil) else do (setq dname (helm-basedir (substring dname 0 (1- (length dname))))) finally return (or dname (file-name-as-directory directory)))) (defun helm-ff-browse-project (_candidate) "Browse project in current directory. See `helm-browse-project'." (with-helm-default-directory helm-ff-default-directory (helm-browse-project helm-current-prefix-arg))) (helm-make-command-from-action helm-ff-run-browse-project "Run browse project." 'helm-ff-browse-project) ;;; Actions calling helm and main interactive functions. ;; ;; (defun helm-ff-gid (_candidate) (with-helm-default-directory helm-ff-default-directory (helm-gid))) (helm-make-command-from-action helm-ff-run-gid "Run gid from HFF." 'helm-ff-gid) ;; helm-find bindings for helm-find-files. (defun helm-ff-find-sh-command (_candidate) "Run `helm-find' from `helm-find-files'." (require 'helm-find) (helm-find-1 helm-ff-default-directory)) (helm-make-command-from-action helm-ff-run-find-sh-command "Run find shell command action with key from `helm-find-files'." 'helm-ff-find-sh-command) ;; helm-hd bindings for hff (defun helm-ff-fd (_candidate) "Run fd shell command from `helm-find-files'." (require 'helm-fd) (helm-fd-1 helm-ff-default-directory)) (helm-make-command-from-action helm-ff-run-fd "Run fd shell command action with key from `helm-find-files'." 'helm-ff-fd) ;;;###autoload (defun helm-find-files (arg) "Preconfigured `helm' for helm implementation of `find-file'. Called with a prefix arg show history if some. Don't call it from programs, use `helm-find-files-1' instead. This is the starting point for nearly all actions you can do on files." (interactive "P") (let* (tramp-archive-enabled ; Disable tramp-archive which is ; kicking in unexpectedly. (hist (and arg helm-ff-history (helm-find-files-history nil))) (smart-input (or hist (helm-find-files-initial-input))) (default-input (expand-file-name (helm-current-directory))) (input (cond ((and (null hist) helm-find-files-ignore-thing-at-point) default-input) ((and (eq major-mode 'org-agenda-mode) org-directory (not smart-input)) (file-name-as-directory (expand-file-name org-directory))) ((and (eq major-mode 'dired-mode) smart-input) (file-name-directory smart-input)) ((and (not (string= smart-input "")) smart-input)) (t default-input))) (input-as-presel (null (file-directory-p input))) (presel (helm-aif (or hist (and input-as-presel input) (buffer-file-name (current-buffer)) (and (eq major-mode 'dired-mode) smart-input) (and (eq major-mode 'Info-mode) Info-current-file)) (if (and helm-ff-transformer-show-only-basename (null hist) (not (string-match-p "[.]\\{1,2\\}\\'" it))) (helm-basename it) it)))) ;; Continue using the same display function as history which used ;; probably itself the same display function as inner HFF call, ;; i.e. if history was using frame use a frame otherwise use a window. (when (and hist (buffer-live-p (get-buffer helm-ff-history-buffer-name))) (helm-set-local-variable 'helm-display-function (with-current-buffer helm-ff-history-buffer-name helm-display-function) 'helm--last-frame-parameters (with-current-buffer helm-ff-history-buffer-name helm--last-frame-parameters))) (set-text-properties 0 (length input) nil input) (setq current-prefix-arg nil) ;; Allow next helm session to reuse helm--last-frame-parameters as ;; resume would do. (let ((helm--executing-helm-action (not (null hist)))) (helm-find-files-1 input (and presel (null helm-ff-no-preselect) (format helm-ff-last-expanded-candidate-regexp (regexp-quote presel))))))) (provide 'helm-files) ;;; helm-files.el ends here helm-4.0.3/helm-find.el000066400000000000000000000164241501106761700146430ustar00rootroot00000000000000;;; helm-find.el --- helm interface for find command. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm-files) (require 'helm-external) (defcustom helm-findutils-skip-boring-files t "Ignore boring files in find command results." :group 'helm-files :type 'boolean) (defcustom helm-findutils-search-full-path nil "Search in full path with shell command find when non-nil. I.e. use the -path/ipath arguments of find instead of -name/iname." :group 'helm-files :type 'boolean) (defcustom helm-find-noerrors nil "Prevent showing error messages in helm buffer when non nil." :group 'helm-files :type 'boolean) (defcustom helm-find-show-full-path-fn #'identity "Function used in transformer to show the full path of candidate. You may want to show the relative path or the abbreviated path instead of the full path. The basename is accessible with \\\\[helm-ff-run-toggle-basename], so no need to use a function that display the basename of candidate here." :group 'helm-files :type '(choice (const :tag "Display absolute path" identity) (const :tag "Display relative path" file-relative-name) (const :tag "Display abbreviated path" abbreviate-file-name))) (defvar helm-find-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-generic-files-map) (define-key map (kbd "DEL") 'helm-delete-backward-no-update) map)) (defvar helm-source-findutils (helm-build-async-source "Find" :header-name (lambda (name) (concat name " in [" (helm-default-directory) "]")) :candidates-process 'helm-find-shell-command-fn :filtered-candidate-transformer 'helm-findutils-transformer :action-transformer 'helm-transform-file-load-el :persistent-action 'helm-ff-kill-or-find-buffer-fname :action 'helm-type-file-actions :help-message 'helm-generic-file-help-message :keymap helm-find-map :candidate-number-limit 9999 :requires-pattern 3)) (defun helm-findutils-transformer (candidates _source) (let (non-essential (default-directory (helm-default-directory))) (cl-loop for i in candidates for abs = (expand-file-name (helm-aif (file-remote-p default-directory) (concat it i) i)) for type = (car (file-attributes abs)) for fname = (if (and helm-ff-transformer-show-only-basename (not (string-match "[.]\\{1,2\\}$" i))) (helm-basename abs) (funcall helm-find-show-full-path-fn abs)) for disp = (helm-acase type ('t (propertize fname 'face 'helm-ff-directory)) ((guard* (stringp it)) (propertize fname 'face 'helm-ff-symlink)) (otherwise (propertize fname 'face 'helm-ff-file))) collect (cons (helm-ff-prefix-filename disp abs) abs)))) (defun helm-find--build-cmd-line () (require 'find-cmd) (let* ((default-directory (or (file-remote-p default-directory 'localname) default-directory)) (patterns+options (split-string helm-pattern "\\(\\`\\| +\\)\\* +")) (fold-case (helm-set-case-fold-search (car patterns+options))) (patterns (split-string (car patterns+options))) (additional-options (and (cdr patterns+options) (list (concat (cadr patterns+options) " ")))) (ignored-dirs ()) (ignored-files (when helm-findutils-skip-boring-files (cl-loop for f in completion-ignored-extensions if (string-match "/$" f) do (push (replace-match "" nil t f) ignored-dirs) else collect (concat "*" f)))) (path-or-name (if helm-findutils-search-full-path '(ipath path) '(iname name))) (name-or-iname (if fold-case (car path-or-name) (cadr path-or-name)))) (find-cmd (and ignored-dirs `(prune (name ,@ignored-dirs))) (and ignored-files `(not (name ,@ignored-files))) `(and ,@(mapcar (lambda (pattern) `(,name-or-iname ,(concat "*" pattern "*"))) patterns) ,@additional-options)))) (defun helm-find-shell-command-fn () "Asynchronously fetch candidates for `helm-find'. Additional find options can be specified after a \"*\" separator." (let* (process-connection-type non-essential (cmd (concat (helm-find--build-cmd-line) (if helm-find-noerrors "2> /dev/null" ""))) (proc (start-file-process-shell-command "hfind" helm-buffer cmd))) (helm-log "helm-find-shell-command-fn" "Find command:\n%s" cmd) (prog1 proc (set-process-sentinel proc (lambda (process event) (helm-process-deferred-sentinel-hook process event (helm-default-directory)) (if (string= event "finished\n") (helm-locate-update-mode-line "Find") (helm-log "helm-find-shell-command-fn sentinel" "Error: Find %s" (replace-regexp-in-string "\n" "" event)))))))) (defun helm-find-1 (dir) (let ((default-directory (file-name-as-directory dir))) (helm :sources 'helm-source-findutils :buffer "*helm find*" :ff-transformer-show-only-basename nil :case-fold-search helm-file-name-case-fold-search))) ;;; Preconfigured commands ;; ;; ;;;###autoload (defun helm-find (arg) "Preconfigured `helm' for the find shell command. Recursively find files whose names are matched by all specified globbing PATTERNs under the current directory using the external program specified in `find-program' (usually \"find\"). Every input PATTERN is silently wrapped into two stars: *PATTERN*. With prefix argument, prompt for a directory to search. When user option `helm-findutils-search-full-path' is non-nil, match against complete paths, otherwise, against file names without directory part. The (possibly empty) list of globbing PATTERNs can be followed by the separator \"*\" plus any number of additional arguments that are passed to \"find\" literally." (interactive "P") (let ((directory (if arg (file-name-as-directory (read-directory-name "DefaultDirectory: ")) default-directory))) (helm-find-1 directory))) (provide 'helm-find) ;;; helm-find.el ends here helm-4.0.3/helm-font.el000066400000000000000000000274111501106761700146670ustar00rootroot00000000000000;;; helm-font --- Font and ucs selection for Helm -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) ;; No warnings in Emacs built --without-x (declare-function x-list-fonts "xfaces.c") (declare-function helm-generic-sort-fn "helm-utils") (defgroup helm-font nil "Related applications to display fonts in Helm." :group 'helm) (defcustom helm-ucs-recent-size 10 "Number of recent chars to keep." :type 'integer :group 'helm-font) (defcustom helm-ucs-actions '(("Insert character" . helm-ucs-insert-char) ("Insert character name" . helm-ucs-insert-name) ("Insert character code in hex" . helm-ucs-insert-code) ("Kill marked characters" . helm-ucs-kill-char) ("Kill name" . helm-ucs-kill-name) ("Kill code" . helm-ucs-kill-code) ("Describe char" . helm-ucs-describe-char)) "Actions for `helm-source-ucs'." :group 'helm-font :type '(alist :key-type string :value-type function)) (defvar helm-ucs-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "") 'helm-ucs-persistent-delete) (define-key map (kbd "") 'helm-ucs-persistent-backward) (define-key map (kbd "") 'helm-ucs-persistent-forward) (define-key map (kbd "C-c SPC") 'helm-ucs-persistent-insert-space) map) "Keymap for `helm-ucs'.") (defface helm-ucs-char `((((class color) (background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Gold")) "Face used to display ucs characters." :group 'helm-font) ;;; Xfont selection ;; ;; (defvar helm-xfonts-cache nil) (defvar helm-previous-font nil) (defvar helm-source-xfonts (helm-build-sync-source "X Fonts" :init (lambda () (unless helm-xfonts-cache (setq helm-xfonts-cache (x-list-fonts "*"))) ;; Save current font so it can be restored in cleanup (setq helm-previous-font (cdr (assq 'font (frame-parameters))))) :candidates 'helm-xfonts-cache :action `(("Copy font to kill ring" . ,(lambda (elm) (kill-new elm))) ("Set font" . ,(lambda (elm) (kill-new elm) (set-frame-font elm 'keep-size) (message "Font copied to kill ring")))) :cleanup (lambda () ;; Restore previous font (set-frame-font helm-previous-font 'keep-size)) :persistent-action (lambda (new-font) (set-frame-font new-font 'keep-size) (kill-new new-font)) :persistent-help "Preview font and copy to kill-ring")) ;;; 𝕌𝕔𝕤 𝕊𝕪𝕞𝕓𝕠𝕝 𝕔𝕠𝕞𝕡𝕝𝕖𝕥𝕚𝕠𝕟 ;; ;; (defvar helm-ucs--max-len nil) (defvar helm-ucs--names nil) (defvar helm-ucs-history nil) (defvar helm-ucs-recent nil "Ring of recent `helm-ucs' selections.") (defun helm-calculate-ucs-alist-max-len (names) "Calculate the length of the longest NAMES list candidate." (cl-loop for (_n . v) in names maximize (length (format "#x%x:" v)) into code maximize (max 1 (string-width (format "%c" v))) into char finally return (cons code char))) (defun helm-calculate-ucs-hash-table-max-len (names) "Calculate the length of the longest NAMES hash table candidate." (cl-loop for _n being the hash-keys of names using (hash-values v) maximize (length (format "#x%x:" v)) into code maximize (max 1 (string-width (format "%c" v))) into char finally return (cons code char))) (defun helm-calculate-ucs-max-len () "Calculate the length of the longest `ucs-names' candidate." (let ((ucs-struct (ucs-names))) (if (hash-table-p ucs-struct) (helm-calculate-ucs-hash-table-max-len ucs-struct) (helm-calculate-ucs-alist-max-len ucs-struct)))) (defun helm-ucs-collect-symbols-alist (names) "Collect ucs symbols from the NAMES list." (cl-loop with pr = (make-progress-reporter "collecting ucs names" 0 (length names)) for (n . v) in names for count from 1 for xcode = (format "#x%x:" v) for len = (length xcode) for diff = (- (car helm-ucs--max-len) len) for code = (format "(#x%x): " v) for char = (propertize (format "%c" v) 'face 'helm-ucs-char) unless (or (string= "" n) ;; `char-displayable-p' return a font object or ;; t for some char that are displayable but have ;; no special font (e.g 10) so filter out char ;; with no font. (not (fontp (char-displayable-p (read xcode))))) collect (concat code (make-string diff ? ) char " " n) and do (progress-reporter-update pr count))) (defun helm-ucs-collect-symbols-hash-table (names) "Collect ucs symbols from the NAMES hash-table." (cl-loop with pr = (make-progress-reporter "collecting ucs names" 0 (hash-table-count names)) for n being the hash-keys of names using (hash-values v) for count from 1 for xcode = (format "#x%x:" v) for len = (length xcode) for diff = (- (car helm-ucs--max-len) len) for code = (format "(#x%x): " v) for char = (propertize (format "%c" v) 'face 'helm-ucs-char) unless (or (string= "" n) (not (fontp (char-displayable-p (read xcode))))) collect (concat code (make-string diff ? ) char " " n) and do (progress-reporter-update pr count))) (defun helm-ucs-collect-symbols (ucs-struct) "Collect ucs symbols from UCS-STRUCT. Depending on the Emacs version, the variable `ucs-names' can either be an alist or a hash-table." (if (hash-table-p ucs-struct) (helm-ucs-collect-symbols-hash-table ucs-struct) (helm-ucs-collect-symbols-alist ucs-struct))) (defun helm-ucs-init () "Initialize a Helm buffer with ucs symbols. Only math* symbols are collected." (unless helm-ucs--max-len (setq helm-ucs--max-len (helm-calculate-ucs-max-len))) (or helm-ucs--names (setq helm-ucs--names (helm-ucs-collect-symbols (ucs-names))))) ;; Actions (insertion) (defun helm-ucs-match (candidate n) "Return the N part of an ucs CANDIDATE. Where N=1 is the ucs code, N=2 the ucs char and N=3 the ucs name." (when (string-match "^(\\(#x[a-f0-9]+\\)): *\\(.\\) *\\([^:]+\\)+" candidate) (match-string n candidate))) (defun helm-ucs-save-recentest (candidate) (let ((lst (cons candidate (delete candidate helm-ucs-recent)))) (setq helm-ucs-recent (if (> (length lst) helm-ucs-recent-size) (nbutlast lst 1) lst)))) (defun helm-ucs-insert (candidate n) "Insert the N part of CANDIDATE." (with-helm-current-buffer (helm-ucs-save-recentest candidate) (insert (helm-ucs-match candidate n)))) (defun helm-ucs-insert-char (candidate) "Insert ucs char part of CANDIDATE at point." (helm-ucs-insert candidate 2)) (defun helm-ucs-insert-code (candidate) "Insert ucs code part of CANDIDATE at point." (helm-ucs-insert candidate 1)) (defun helm-ucs-insert-name (candidate) "Insert ucs name part of CANDIDATE at point." (helm-ucs-insert candidate 3)) ;; Kill actions (defun helm-ucs-kill-char (_candidate) "Action that concatenate ucs marked chars." (let ((marked (helm-marked-candidates))) (cl-loop for candidate in marked do (helm-ucs-save-recentest candidate)) (kill-new (mapconcat (lambda (x) (helm-ucs-match x 2)) marked "")))) (defun helm-ucs-kill-code (candidate) (helm-ucs-save-recentest candidate) (kill-new (helm-ucs-match candidate 1))) (defun helm-ucs-kill-name (candidate) (helm-ucs-save-recentest candidate) (kill-new (helm-ucs-match candidate 3))) ;; Describe char (defun helm-ucs-describe-char (candidate) "Describe char CANDIDATE." (with-temp-buffer (insert (helm-ucs-match candidate 2)) (describe-char (point-min)))) ;; Navigation in current-buffer (persistent) (defun helm-ucs-forward-char (_candidate) (with-helm-current-buffer (forward-char 1))) (defun helm-ucs-backward-char (_candidate) (with-helm-current-buffer (forward-char -1))) (defun helm-ucs-delete-backward (_candidate) (with-helm-current-buffer (delete-char -1))) (defun helm-ucs-insert-space (_candidate) (with-helm-current-buffer (insert " "))) (defun helm-ucs-persistent-forward () (interactive) (with-helm-alive-p (helm-set-attr 'action-forward 'helm-ucs-forward-char) (helm-execute-persistent-action 'action-forward))) (put 'helm-ucs-persistent-forward 'helm-only t) (defun helm-ucs-persistent-backward () (interactive) (with-helm-alive-p (helm-set-attr 'action-back 'helm-ucs-backward-char) (helm-execute-persistent-action 'action-back))) (put 'helm-ucs-persistent-backward 'helm-only t) (defun helm-ucs-persistent-delete () (interactive) (with-helm-alive-p (helm-set-attr 'action-delete 'helm-ucs-delete-backward) (helm-execute-persistent-action 'action-delete))) (put 'helm-ucs-persistent-delete 'helm-only t) (defun helm-ucs-persistent-insert-space () (interactive) (with-helm-alive-p (helm-set-attr 'action-insert-space 'helm-ucs-insert-space) (helm-execute-persistent-action 'action-insert-space))) (defvar helm-source-ucs-recent (helm-build-sync-source "Recent UCS" :action 'helm-ucs-actions :candidates (lambda () helm-ucs-recent) :help-message helm-ucs-help-message :keymap helm-ucs-map :volatile t)) (defvar helm-source-ucs (helm-build-in-buffer-source "UCS names" :data #'helm-ucs-init :get-line #'buffer-substring :help-message 'helm-ucs-help-message :filtered-candidate-transformer (lambda (candidates _source) (sort candidates #'helm-generic-sort-fn)) :action 'helm-ucs-actions :persistent-action (lambda (candidate) (helm-ucs-insert-char candidate) (helm-force-update)) :keymap helm-ucs-map) "Source for collecting `ucs-names' math symbols.") ;;;###autoload (defun helm-select-xfont () "Preconfigured `helm' to select Xfont." (interactive) (helm :sources 'helm-source-xfonts :buffer "*helm select xfont*")) ;;;###autoload (defun helm-ucs (arg) "Preconfigured `helm' for `ucs-names'. Called with a prefix arg force reloading cache." (interactive "P") (when arg (setq helm-ucs--names nil helm-ucs--max-len nil ucs-names nil)) (let ((char (helm-aif (char-after) (string it)))) (helm :sources (list helm-source-ucs-recent helm-source-ucs) :history 'helm-ucs-history :input (and char (multibyte-string-p char) char) :buffer "*helm ucs*"))) (provide 'helm-font) ;;; helm-font.el ends here helm-4.0.3/helm-for-files.el000066400000000000000000000364611501106761700156140ustar00rootroot00000000000000;;; helm-for-files.el --- helm-for-files and related. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm-files) (require 'helm-external) (require 'helm-bookmark) (defvar recentf-list) (defcustom helm-multi-files-toggle-locate-binding "C-c p" "Default binding to switch back and forth locate in `helm-multi-files'." :group 'helm-files :type 'string) (defcustom helm-for-files-preferred-list '(helm-source-buffers-list helm-source-recentf helm-source-bookmarks helm-source-file-cache helm-source-files-in-current-dir helm-source-locate) "Your preferred sources for `helm-for-files' and `helm-multi-files'. When adding a source here it is up to you to ensure the library of this source is accessible and properly loaded." :type '(repeat (choice symbol)) :group 'helm-files) (defcustom helm-for-files-tramp-not-fancy t "Colorize remote files when non nil. Be aware that a nil value will make tramp display very slow." :group 'helm-files :type 'boolean) ;;; File Cache ;; ;; (defvar file-cache-alist) (defclass helm-file-cache (helm-source-in-buffer helm-type-file) ((init :initform (lambda () (require 'filecache))))) (defun helm-file-cache-get-candidates () (cl-loop for item in file-cache-alist append (cl-destructuring-bind (base &rest dirs) item (cl-loop for dir in dirs collect (concat dir base))))) (defvar helm-source-file-cache nil) (defcustom helm-file-cache-fuzzy-match nil "Enable fuzzy matching in `helm-source-file-cache' when non--nil." :group 'helm-files :type 'boolean :set (lambda (var val) (set var val) (setq helm-source-file-cache (helm-make-source "File Cache" 'helm-file-cache :fuzzy-match helm-file-cache-fuzzy-match :data 'helm-file-cache-get-candidates)))) (cl-defun helm-file-cache-add-directory-recursively (dir &optional match (ignore-dirs t)) (require 'filecache) (cl-loop for f in (helm-walk-directory dir :path 'full :directories nil :match match :skip-subdirs ignore-dirs) do (file-cache-add-file f))) (defun helm-transform-file-cache (actions _candidate) (let ((source (helm-get-current-source))) (if (string= (assoc-default 'name source) "File Cache") (append actions '(("Remove marked files from file-cache" . helm-ff-file-cache-remove-file))) actions))) ;;; Recentf files ;; ;; (defvar helm-recentf--basename-flag nil) (defvar helm-recentf-cache nil) (defun helm-recentf-pattern-transformer (pattern) (let ((pattern-no-flag (replace-regexp-in-string " -b" "" pattern))) (cond ((and (string-match " " pattern-no-flag) (string-match " -b\\'" pattern)) (setq helm-recentf--basename-flag t) pattern-no-flag) ((string-match "\\([^ ]*\\) -b\\'" pattern) (prog1 (match-string 1 pattern) (setq helm-recentf--basename-flag t))) (t (setq helm-recentf--basename-flag nil) pattern)))) (defcustom helm-turn-on-recentf t "Automatically turn on `recentf-mode' when non-nil." :group 'helm-files :type 'boolean) (defclass helm-recentf-source (helm-source-sync helm-type-file) ((init :initform (lambda () (require 'recentf) (when helm-turn-on-recentf (recentf-mode 1)) ;; Use a copy of not recentf-list itself but the of its ;; elements to not corrupt them with text props. (setq helm-recentf-cache (helm-copy-sequence recentf-list)))) (candidates :initform 'helm-recentf-cache) (pattern-transformer :initform 'helm-recentf-pattern-transformer) (match-part :initform (lambda (candidate) (if (or helm-ff-transformer-show-only-basename helm-recentf--basename-flag) (helm-basename candidate) candidate))) ;; When real candidate is equal to display it gets corrupted with the text ;; properties added by the transformer, so ensure to strip out properties ;; before passing the candidate to action otherwise recentf will save the ;; candidate passed to find-file with the properties and corrupt ;; recentf-list. This happen when abbreviate-file-name is passed to a ;; candidate with no leading "~" e.g. "/foo" bug#2709. (coerce :initform 'substring-no-properties) (migemo :initform t) (persistent-action :initform 'helm-ff-kill-or-find-buffer-fname))) (cl-defmethod helm--setup-source :after ((source helm-recentf-source)) (setf (slot-value source 'action) (append (symbol-value (helm-actions-from-type-file)) '(("Delete file(s) from recentf" . (lambda (_candidate) (cl-loop for file in (helm-marked-candidates) do (setq recentf-list (delete file recentf-list))))))))) (defvar helm-source-recentf nil "See (info \"(emacs)File Conveniences\"). Set `recentf-max-saved-items' to a bigger value if default is too small.") (defcustom helm-recentf-fuzzy-match nil "Enable fuzzy matching in `helm-source-recentf' when non-nil." :group 'helm-files :type 'boolean :set (lambda (var val) (set var val) (let ((helm-fuzzy-sort-fn 'helm-fuzzy-matching-sort-fn-preserve-ties-order)) (setq helm-source-recentf (helm-make-source "Recentf" 'helm-recentf-source :fuzzy-match val))))) ;;; Transformer for helm-type-file ;; ;; (defvar helm-sources-for-files-no-basename '("Recentf" "File Cache")) ;; Function `helm-highlight-files' is used in type `helm-type-file'. Ensure that ;; the definition is available for clients, should they need it. ;; See https://github.com/bbatsov/helm-projectile/issues/184. ;;;###autoload (defun helm-highlight-files (files source) "A basic transformer for helm files sources. Colorize only symlinks, directories and files." (cl-loop with mp-fn = (or (assoc-default 'match-part (helm-get-current-source)) 'identity) with sname = (helm-get-attr 'name source) for i in files for disp = (if (or (and (not helm-ff-show-dot-file-path) (helm-ff-dot-file-p i)) (and helm-ff-transformer-show-only-basename (not (member sname helm-sources-for-files-no-basename)) (not (helm-ff-dot-file-p i)) (not (and helm--url-regexp (string-match helm--url-regexp i))) (not (string-match helm-ff-url-regexp i)))) (helm-basename i) (abbreviate-file-name i)) for isremote = (or (file-remote-p i) (helm-file-on-mounted-network-p i)) ;; file-attributes is too slow on remote files, ;; so call it only if: ;; - file is not remote ;; - helm-for-files--tramp-not-fancy is nil and file is remote AND ;; connected. (Bug#1679) for type = (and (or (null isremote) (and (null helm-for-files-tramp-not-fancy) (file-remote-p i nil t))) (car (file-attributes i))) collect (cond (;; No fancy display on remote files with basic predicates. (and (null type) isremote) (cons disp i)) (;; Symlinks (stringp type) (add-text-properties 0 (length disp) `(face helm-ff-symlink match-part ,(funcall mp-fn disp) help-echo ,(expand-file-name i)) disp) (cons (helm-ff-prefix-filename disp i) i)) (;; Dotted dirs (and (eq type t) (helm-ff-dot-file-p i)) (add-text-properties 0 (length disp) `(face helm-ff-dotted-directory match-part ,(funcall mp-fn disp) help-echo ,(expand-file-name i)) disp) (cons (helm-ff-prefix-filename disp i) i)) (;; Dirs (eq type t) (add-text-properties 0 (length disp) `(face helm-ff-directory match-part ,(funcall mp-fn disp) help-echo ,(expand-file-name i)) disp) (cons (helm-ff-prefix-filename disp i) i)) (t ;; Files. (add-text-properties 0 (length disp) `(face helm-ff-file match-part ,(funcall mp-fn disp) help-echo ,(expand-file-name i)) disp) (helm-aif (helm-file-name-extension disp) (when (condition-case _err (string-match (format "\\.\\(%s\\)\\'" it) disp) (invalid-regexp nil)) (add-face-text-property (match-beginning 1) (match-end 1) 'helm-ff-file-extension t disp))) (cons (helm-ff-prefix-filename disp i) i))))) ;;; Files in current dir ;; ;; (defclass helm-files-in-current-dir-source (helm-source-sync helm-type-file) ((candidates :initform (lambda () (with-helm-current-buffer (let ((dir (helm-current-directory))) (when (file-accessible-directory-p dir) (directory-files dir t)))))) (pattern-transformer :initform 'helm-recentf-pattern-transformer) (match-part :initform (lambda (candidate) (if (or helm-ff-transformer-show-only-basename helm-recentf--basename-flag) (helm-basename candidate) candidate))) (fuzzy-match :initform t) (popup-info :initform (lambda (candidate) (unless (helm-ff-dot-file-p candidate) (helm-file-attributes candidate :dired t :human-size t :octal nil)))) (migemo :initform t))) (cl-defmethod helm--setup-source :after ((source helm-files-in-current-dir-source)) (helm-aif (slot-value source 'filtered-candidate-transformer) (setf (slot-value source 'filtered-candidate-transformer) (append '(helm-ff-sort-candidates) (helm-mklist it))))) (defvar helm-source-files-in-current-dir (helm-make-source "Files from Current Directory" 'helm-files-in-current-dir-source :header-name (lambda (_name) (format "Files from `%s'" (abbreviate-file-name (helm-default-directory)))))) ;;;###autoload (defun helm-for-files () "Preconfigured `helm' for opening files. Run all sources defined in `helm-for-files-preferred-list'." (interactive) (require 'helm-x-files) (unless helm-source-buffers-list (setq helm-source-buffers-list (helm-make-source "Buffers" 'helm-source-buffers))) (helm :sources helm-for-files-preferred-list :ff-transformer-show-only-basename nil :buffer "*helm for files*" :truncate-lines helm-buffers-truncate-lines)) (defun helm-multi-files-toggle-to-locate () (interactive) (with-helm-alive-p (with-helm-buffer (if (setq helm-multi-files--toggle-locate (not helm-multi-files--toggle-locate)) (progn (helm-set-sources (unless (memq 'helm-source-locate helm-sources) (cons 'helm-source-locate helm-sources))) (helm-set-source-filter '(helm-source-locate))) (helm-kill-async-processes) (helm-set-sources (remove 'helm-source-locate helm-for-files-preferred-list)) (helm-set-source-filter nil))))) (put 'helm-multi-files-toggle-to-locate 'helm-only t) ;;;###autoload (defun helm-multi-files () "Preconfigured helm like `helm-for-files' but running locate only on demand. Allow toggling back and forth from locate to others sources with `helm-multi-files-toggle-locate-binding' key. This avoids launching locate needlessly when what you are searching for is already found." (interactive) (require 'helm-x-files) (unless helm-source-buffers-list (setq helm-source-buffers-list (helm-make-source "Buffers" 'helm-source-buffers))) (setq helm-multi-files--toggle-locate nil) (helm-locate-set-command) (helm-set-local-variable 'helm-async-outer-limit-hook (list (lambda () (when (and helm-locate-fuzzy-match (not (string-match-p "\\s-" helm-pattern))) (helm-redisplay-buffer)))) 'helm-ff-transformer-show-only-basename nil) (let ((sources (remove 'helm-source-locate helm-for-files-preferred-list)) (helm-locate-command (if helm-locate-fuzzy-match (unless (string-match-p "\\`locate -b" helm-locate-command) (replace-regexp-in-string "\\`locate" "locate -b" helm-locate-command)) helm-locate-command)) (old-key (lookup-key helm-map (read-kbd-macro helm-multi-files-toggle-locate-binding)))) (with-helm-temp-hook 'helm-after-initialize-hook (define-key helm-map (kbd helm-multi-files-toggle-locate-binding) 'helm-multi-files-toggle-to-locate)) (unwind-protect (helm :sources sources :buffer "*helm multi files*" :truncate-lines helm-buffers-truncate-lines) (define-key helm-map (kbd helm-multi-files-toggle-locate-binding) old-key)))) ;;;###autoload (defun helm-recentf () "Preconfigured `helm' for `recentf'." (interactive) (helm :sources 'helm-source-recentf :ff-transformer-show-only-basename nil :buffer "*helm recentf*")) (provide 'helm-for-files) ;;; helm-for-files.el ends here helm-4.0.3/helm-global-bindings.el000066400000000000000000000103541501106761700167520ustar00rootroot00000000000000;;; helm-global-bindings.el --- Bind global helm commands -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm-lib) ; For helm-aif (bug #2520). ;;; Command Keymap ;; ;; (defgroup helm-global-bindings nil "Global bindings for Helm." :group 'helm) (defcustom helm-command-prefix-key (helm-aif (car (where-is-internal 'Control-X-prefix (list global-map))) (concat it [?c])) "The prefix key used to call Helm commands from the `global-map'. Its default value is `C-x c'. This key is bound to the function `helm-command-prefix' in the global map. The definition of `helm-command-prefix' is the keymap `helm-command-map'. Using `setq' to modify this variable will have no effect." :type '(choice (string :tag "Key") (const :tag "no binding")) :set (lambda (var key) (helm-aif (and (boundp var) (symbol-value var)) (global-unset-key (read-kbd-macro it))) (when key (global-set-key (read-kbd-macro key) 'helm-command-prefix)) (set var key))) (defvar helm-command-map (let ((map (make-sparse-keymap))) (define-key map (kbd "a") 'helm-apropos) (define-key map (kbd "e") 'helm-etags-select) (define-key map (kbd "l") 'helm-locate) (define-key map (kbd "L") 'helm-locate-library) (define-key map (kbd "s") 'helm-surfraw) (define-key map (kbd "r") 'helm-regexp) (define-key map (kbd "m") 'helm-man-woman) (define-key map (kbd "t") 'helm-top) (define-key map (kbd "o") 'helm-outline) (define-key map (kbd "/") 'helm-find) (define-key map (kbd "i") 'helm-imenu) (define-key map (kbd "I") 'helm-imenu-in-all-buffers) (define-key map (kbd "") 'helm-lisp-completion-at-point) (define-key map (kbd "p") 'helm-list-emacs-process) (define-key map (kbd "C-x r b") 'helm-filtered-bookmarks) (define-key map (kbd "M-y") 'helm-show-kill-ring) (define-key map (kbd "C-c ") 'helm-all-mark-rings) (define-key map (kbd "C-x C-f") 'helm-find-files) (define-key map (kbd "f") 'helm-multi-files) (define-key map (kbd "C-:") 'helm-eval-expression-with-eldoc) (define-key map (kbd "C-,") 'helm-calcul-expression) (define-key map (kbd "M-x") 'helm-M-x) (define-key map (kbd "M-s o") 'helm-occur) (define-key map (kbd "M-g a") 'helm-do-grep-ag) (define-key map (kbd "c") 'helm-colors) (define-key map (kbd "F") 'helm-select-xfont) (define-key map (kbd "8") 'helm-ucs) (define-key map (kbd "C-c f") 'helm-recentf) (define-key map (kbd "C-c g") 'helm-google-suggest) (define-key map (kbd "h i") 'helm-info-at-point) (define-key map (kbd "h r") 'helm-info-emacs) (define-key map (kbd "h g") 'helm-info-gnus) (define-key map (kbd "h h") 'helm-documentation) (define-key map (kbd "C-x C-b") 'helm-buffers-list) (define-key map (kbd "C-x r i") 'helm-register) (define-key map (kbd "C-c C-x") 'helm-run-external-command) (define-key map (kbd "b") 'helm-resume) (define-key map (kbd "M-g i") 'helm-gid) (define-key map (kbd "@") 'helm-packages) (define-key map (kbd "h p") 'helm-finder) map) "Default keymap for \\[helm-command-prefix] commands. The normal global definition of the character \\[helm-command-prefix] indirects to this keymap.") (fset 'helm-command-prefix helm-command-map) ;;; Menu (require 'helm-easymenu) ;;; Provide (provide 'helm-global-bindings) ;;; helm-global-bindings.el ends here helm-4.0.3/helm-grep.el000066400000000000000000002364561501106761700146710ustar00rootroot00000000000000;;; helm-grep.el --- Helm Incremental Grep. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'ansi-color) (require 'cl-lib) (require 'format-spec) (require 'helm) (require 'helm-help) (require 'helm-regexp) ;;; load wgrep proxy if it's available (require 'wgrep-helm nil t) (declare-function helm-buffer-list "helm-buffers") (declare-function View-quit "view") (declare-function doc-view-goto-page "doc-view" (page)) (declare-function pdf-view-goto-page "pdf-view" (page &optional window)) (declare-function helm-mm-split-pattern "helm-multi-match") (declare-function helm-comp-read "helm-mode") (declare-function helm-occur "helm-occur") (defvar helm--ansi-color-regexp) (defvar helm-ff-default-directory) (defvar helm-tramp-verbose) (defvar helm-grep-ack-types-cache) (defvar helm-grep-git-grep-command) (defvar helm-source-grep-git) (defvar tramp-verbose) (defvar helm-current-error) ;;; Internals vars ;; ;; (defvar helm-rzgrep-cache (make-hash-table :test 'equal)) (defvar helm-grep-default-function 'helm-grep-init) (defvar helm-zgrep-recurse-flag nil) (defvar helm-grep-history nil) (defvar helm-grep-ag-history nil) (defvar helm-grep-last-targets nil) (defvar helm-grep-include-files nil) (defvar helm-grep-in-recurse nil) (defvar helm-grep-use-zgrep nil) (defvar helm-grep-default-directory-fn nil "A function that should return a directory to expand candidate to. It is intended to use as a let-bound variable, DON'T set this globaly.") (defvar helm-pdfgrep-targets nil) (defvar helm-grep-last-cmd-line nil) (defvar helm-grep-split-line-regexp "^\\([[:lower:][:upper:]]?:?.*?\\):\\([0-9]+\\):\\(.*\\)") ;;; Keymaps ;; ;; (defvar helm-grep-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "M-") 'helm-goto-next-file) (define-key map (kbd "M-") 'helm-goto-precedent-file) (define-key map (kbd "C-c o") 'helm-grep-run-other-window-action) (define-key map (kbd "C-c C-o") 'helm-grep-run-other-frame-action) (define-key map (kbd "C-x C-s") 'helm-grep-run-save-buffer) (define-key map (kbd "DEL") 'helm-delete-backward-no-update) map) "Keymap used in Grep sources.") (defvar helm-pdfgrep-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "M-") 'helm-goto-next-file) (define-key map (kbd "M-") 'helm-goto-precedent-file) (define-key map (kbd "DEL") 'helm-delete-backward-no-update) map) "Keymap used in pdfgrep.") (defvar helm-grep-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "RET") 'helm-grep-mode-jump) (define-key map (kbd "C-o") 'helm-grep-mode-jump-other-window) (define-key map (kbd "") 'helm-grep-mode-jump-other-window-forward) (define-key map (kbd "") 'helm-grep-mode-jump-other-window-backward) (define-key map (kbd "") 'helm-gm-next-file) (define-key map (kbd "") 'helm-gm-precedent-file) (define-key map (kbd "M-n") 'helm-grep-mode-jump-other-window-forward) (define-key map (kbd "M-p") 'helm-grep-mode-jump-other-window-backward) (define-key map (kbd "M-N") 'helm-gm-next-file) (define-key map (kbd "M-P") 'helm-gm-precedent-file) map)) (defgroup helm-grep nil "Grep related Applications and libraries for Helm." :group 'helm) (defcustom helm-grep-default-command "grep --color=always -a -d skip %e -n%cH -e %p %f" "Default grep format command for `helm-do-grep-1'. Where: '%e' format spec is for --exclude or --include grep options or ack-grep --type option. (Not mandatory) '%c' format spec is for case-fold-search, whether to use the -i option of grep. (Not mandatory) When you specify this spec, helm grep will use smartcase that is when a upcase character is found in pattern case will be respected and no \\='-i' option will be used, otherwise, when no upcase character is found in pattern always use \\='-i'. If you don't want this behavior, don't use this spec and specify or not the \\='-i' option. Note that with ack-grep this is not needed, just specify the \\='--smart-case' option. '%p' format spec is for pattern. (Mandatory) '%f' format spec is for filenames. (Mandatory) If your grep version doesn't support the --exclude/include args don't specify the \\='%e' format spec. Helm also support ack-grep and git-grep. The following is a default command example for ack-grep: \(setq helm-grep-default-command \"ack-grep -Hn --color --smart-case --no-group %e -- %p %f\" helm-grep-default-recurse-command \"ack-grep -H --color --smart-case --no-group %e -- %p %f\") You can ommit the %e spec if you don't want to be prompted for types. NOTE: Helm for ack-grep support ANSI sequences, so you can remove the \"--no-color\" option safely (recommended). However you should specify --color to enable multi matches highlighting because ack disable it when output is piped. Same for grep you can use safely the option \"--color=always\" (default). You can customize the color of matches using GREP_COLORS env var. e.g: (setenv \"GREP_COLORS\" \"ms=30;43:mc=30;43:sl=01;37:cx=:fn=35:ln=32:bn=32:se=36\") To enable ANSI color in git-grep just add \"--color=always\". To customize the ANSI color in git-grep, GREP_COLORS have no effect, you will have to setup this in your .gitconfig: [color \"grep\"] match = black yellow Where \"black\" is the foreground and \"yellow\" the background. See the git documentation for more infos. `helm-grep-default-command' and `helm-grep-default-recurse-command' are independent, so you can enable `helm-grep-default-command' with ack-grep and `helm-grep-default-recurse-command' with grep if you want to be faster on recursive grep. NOTE: Remote grepping is not available with ack-grep, and badly supported with grep because tramp handles badly repeated remote processes in a short delay (< to 5s)." :type 'string) (defcustom helm-grep-default-recurse-command "grep --color=always -a -d recurse %e -n%cH -e %p %f" "Default recursive grep format command for `helm-do-grep-1'. See `helm-grep-default-command' for format specs and infos about ack-grep." :type 'string) (defcustom helm-default-zgrep-command "zgrep --color=always -a -n%cH -e %p %f" "Default command for Zgrep. See `helm-grep-default-command' for infos on format specs. Option --color=always is supported and can be used safely to replace the Helm internal match highlighting. See `helm-grep-default-command' for more infos." :type 'string) (defcustom helm-pdfgrep-default-command "pdfgrep --color always -niH %s %s" "Default command for pdfgrep. Option \"--color always\" is supported starting Helm version 1.7.8. When used matches will be highlighted according to GREP_COLORS env var." :type 'string) (defcustom helm-pdfgrep-default-recurse-command "pdfgrep --color always -rniH %s %s" "Default recurse command for pdfgrep. Option \"--color always\" is supported starting Helm version 1.7.8. When used matches will be highlighted according to GREP_COLORS env var." :type 'string) (defcustom helm-pdfgrep-default-read-command nil "Default command to read pdf files from pdfgrep. Where \\='%f' format spec is filename and \\='%p' is page number. E.g. In Ubuntu you can set it to: \"evince --page-label=%p \\='%f'\" If set to nil either `doc-view-mode' or `pdf-view-mode' will be used instead of an external command." :type 'string) (defcustom helm-grep-max-length-history 100 "Max number of elements to save in `helm-grep-history'." :type 'integer) (defcustom helm-zgrep-file-extension-regexp ".*\\(\\.gz\\|\\.bz\\|\\.xz\\|\\.lzma\\)$" "Default file extensions zgrep will search in." :type 'string) (defcustom helm-grep-preferred-ext nil "This file extension will be preselected for grep." :type 'string) (defcustom helm-grep-save-buffer-name-no-confirm nil "When *hgrep* already exists, auto append suffix." :type 'boolean) (defcustom helm-grep-ignored-files (cons ".#*" (delq nil (mapcar (lambda (s) (unless (string-match-p "/\\'" s) (concat "*" s))) completion-ignored-extensions))) "List of file names which `helm-grep' shall exclude." :type '(repeat string)) (defcustom helm-grep-ignored-directories helm-walk-ignore-directories "List of names of sub-directories which `helm-grep' shall not recurse into." :type '(repeat string)) (defcustom helm-grep-truncate-lines t "When nil the grep line that appears will not be truncated." :type 'boolean) (defcustom helm-grep-file-path-style 'basename "File path display style when grep results are displayed. Possible value are: basename: displays only the filename, none of the directory path absolute: displays absolute path relative: displays relative path from root grep directory." :type '(choice (const :tag "Basename" basename) (const :tag "Absolute" absolute) (const :tag "Relative" relative))) (defcustom helm-grep-actions (helm-make-actions "Find File" 'helm-grep-action "Find file other frame" 'helm-grep-other-frame "Save results in grep buffer" 'helm-grep-save-results "Find file other window (C-u vertically)" 'helm-grep-other-window) "Actions for helm grep." :type '(alist :key-type string :value-type function)) (defcustom helm-grep-pipe-cmd-switches nil "A list of additional parameters to pass to grep pipe command. This will be used to pipe command for multiple pattern matching for grep, zgrep ack-grep and git-grep backends. If you add extra args for ack-grep, use ack-grep options, for others (grep, zgrep and git-grep) use grep options. Here are the commands where you may want to add switches: grep --color=always ack-grep --smart-case --color You probably don't need to use this unless you know what you are doing." :type '(repeat string)) (defcustom helm-grep-ag-pipe-cmd-switches nil "A list of additional parameters to pass to grep-ag pipe command. Use parameters compatibles with the backend you are using \(i.e. AG for AG, PT for PT or RG for RG) Here are the commands where you may want to add switches: ag -S --color rg -N -S --color=? For RG the value of --color= is computed according to the --color= value used in `helm-grep-ag-command'. Note also that by default the \"--\" option is always used, you don't need to add it here. You probably don't need to use this unless you know what you are doing." :type '(repeat string)) (defcustom helm-grep-input-idle-delay 0.1 "Idle time before updating, specified in seconds. A lower value (default) means Helm will display the results faster. Increasing it to a higher value (e.g. 0.6) prevents the buffer from flickering when updating." :type 'float) (defcustom helm-grep-use-ioccur-style-keys t "Use Arrow keys to jump to occurences. Note that if you define this variable with `setq' your change will have no effect, use customize instead." :type 'boolean :set (lambda (var val) (set var val) (if val (progn (define-key helm-grep-map (kbd "") 'helm-execute-persistent-action) (define-key helm-grep-map (kbd "") 'helm-grep-run-default-action)) (define-key helm-grep-map (kbd "") nil) (define-key helm-grep-map (kbd "") nil)))) (defcustom helm-grep-ag-command (cond ((executable-find "rg") "rg --color=always --smart-case --search-zip --no-heading --line-number %s -- %s %s") ((executable-find "ag") "ag --line-numbers -S --color --nogroup %s -- %s %s")) "The default command for RG or AG. Prefer RG by default if available. Update: PT is still mentionned in this documentation but it seems it doesn't exists anymore, or at least it is no more maintained. Takes three format specs, the first for type(s), the second for pattern and the third for directory. You can use safely \"--color\" (used by default) with AG RG and PT. NOTE: Usage of \"--color=never\" is discouraged as it uses Elisp to colorize matched items which is slower than using the native colorization of backend, however it is still supported. For ripgrep here is the command line to use: rg --color=always --smart-case --no-heading --line-number %s -- %s %s And to customize colors (always for ripgrep) use something like this: rg --color=always --colors \\='match:bg:yellow' --colors \\='match:fg:black' \--smart-case --no-heading --line-number %s -- %s %s This will change color for matched items from foreground red (the default) to a yellow background with a black foreground. Note that your color settings for RG will not work properly with multiples pattern if you have configured colors in rg config file instead of command line. For more enhanced settings of ansi colors see https://github.com/emacs-helm/helm/issues/2313 You must use an output format that fit with helm grep, that is: \"filename:line-number:string\" The option \"--nogroup\" allow this. The option \"--line-numbers\" is also mandatory except with PT (not supported). For RG the options \"--no-heading\" and \"--line-number\" are the ones to use. When modifying the default colors of matches with e.g. \"--color-match\" option of AG or \"--colors\" option of ripgrep you may want to modify as well `helm-grep-ag-pipe-cmd-switches' to have all matches colorized with the same color in multi match. Of course you can use several other options, see the man page of the backend you are using." :type 'string) (defcustom helm-grep-git-grep-command "git --no-pager grep -n%cH --color=always --full-name -e %p -- %f" "The git grep default command line. The option \"--color=always\" can be used safely. The color of matched items can be customized in your .gitconfig See `helm-grep-default-command' for more infos. The \"--exclude-standard\" and \"--no-index\" switches allow skipping unwanted files specified in ~/.gitignore_global and searching files not already staged (not enabled by default). You have also to enable this in global \".gitconfig\" with \"git config --global core.excludesfile ~/.gitignore_global\"." :type 'string) ;;; Faces ;; ;; (defgroup helm-grep-faces nil "Customize the appearance of helm-grep." :prefix "helm-" :group 'helm-grep :group 'helm-faces) (defface helm-grep-match `((((background light)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "#b00000") (((background dark)) ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "gold1")) "Face used to highlight grep matches. Have no effect when grep backend use \"--color=\"." :group 'helm-grep-faces) (defface helm-grep-file `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "BlueViolet" :underline t)) "Face used to highlight grep results filenames." :group 'helm-grep-faces) (defface helm-grep-lineno `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Darkorange1")) "Face used to highlight grep number lines." :group 'helm-grep-faces) (defface helm-grep-finish `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Green")) "Face used in mode line when grep is finish." :group 'helm-grep-faces) (defface helm-grep-cmd-line `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit font-lock-type-face)) "Face used to highlight grep command line when no results." :group 'helm-grep-faces) ;;; Init ;; ;; (defun helm-grep-prepare-candidates (candidates in-directory) "Prepare filenames and directories CANDIDATES for grep command line." ;; If one or more candidate is a directory, search in all files ;; of this candidate (e.g /home/user/directory/*). ;; If r option is enabled search also in subdidrectories. ;; We need here to expand wildcards to support crap windows filenames ;; as grep doesn't accept quoted wildcards (e.g "dir/*.el"). (if helm-zgrep-recurse-flag (mapconcat 'shell-quote-argument candidates " ") ;; When candidate is a directory, search in all its files. ;; NOTE that `file-expand-wildcards' will return also ;; directories, they will be ignored by grep but not ;; by ack-grep that will grep all files of this directory ;; without recursing in their subdirs though, see that as a one ;; level recursion with ack-grep. ;; So I leave it as it is, considering it is a feature. [1] (cl-loop for i in candidates append (cond ((string-match "^git" helm-grep-default-command) (list i)) ;; Candidate is a directory and we use recursion or ack. ((and (file-directory-p i) (or helm-grep-in-recurse ;; ack-grep accept directory [1]. (helm-grep-use-ack-p))) (list (expand-file-name i))) ;; Grep doesn't support directory only when not in recurse. ((file-directory-p i) (file-expand-wildcards (concat (file-name-as-directory (expand-file-name i)) "*") t)) ;; Candidate is a file or wildcard and we use recursion, use the ;; current directory instead of candidate. ((and (or (file-exists-p i) (string-match "[*]" i)) helm-grep-in-recurse) (list (expand-file-name (directory-file-name ; Needed for windoze. (file-name-directory (directory-file-name i)))))) ;; Else should be one or more file/directory ;; possibly marked. ;; When real is a normal filename without wildcard ;; file-expand-wildcards returns a list of one file. ;; wildcards should have been already handled by ;; helm-read-file-name or helm-find-files but do it from ;; here too in case we are called from elsewhere. (t (file-expand-wildcards i t))) into all-files ; [1] finally return (let ((files (if (file-remote-p in-directory) ;; Grep don't understand tramp filenames ;; use the local name. (mapcar (lambda (x) (file-remote-p x 'localname)) all-files) all-files))) ;; When user mark files and use recursion with grep ;; backend enabled, the loop collect on each marked ;; candidate its `file-name-directory' and we endup with ;; duplicates (Bug#1714). FIXME: For now as a quick fix ;; I just remove dups here but I should handle this inside ;; the cond above. (setq files (helm-fast-remove-dups files :test 'equal)) (if (string-match "^git" helm-grep-default-command) (mapconcat 'identity files " ") (mapconcat 'shell-quote-argument files " ")))))) (defun helm-grep-command (&optional recursive grep) (let* ((com (if recursive helm-grep-default-recurse-command helm-grep-default-command)) (exe (if grep (symbol-name grep) (and com (car (split-string com " ")))))) (if (and exe (string= exe "git")) "git-grep" exe))) (cl-defun helm-grep-use-ack-p (&key where) (let* ((rec-com (helm-grep-command t)) (norm-com (helm-grep-command)) (norm-com-ack-p (string-match "\\`ack" norm-com)) (rec-com-ack-p (and rec-com (string-match "\\`ack" rec-com)))) (cl-case where (default (and norm-com norm-com-ack-p)) (recursive (and rec-com rec-com-ack-p)) (strict (and norm-com rec-com rec-com-ack-p norm-com-ack-p)) (t (and (not (and norm-com (string= norm-com "git-grep"))) (or (and norm-com norm-com-ack-p) (and rec-com rec-com-ack-p))))))) (defun helm-grep--pipe-command-for-grep-command (smartcase pipe-switches &optional grep-cmd) (helm-acase (or grep-cmd (helm-grep-command)) ;; Use grep for GNU regexp based tools. (("grep" "zgrep" "git-grep") (format "grep --color=always%s %s" (if smartcase " -i" "") pipe-switches)) ;; Use ack-grep for PCRE based tools. ;; Sometimes ack-grep cmd is ack only so compare by matching ack. ((guard* (string-match-p "ack" it)) (format "%s --smart-case --color %s" it pipe-switches)))) (defun helm-grep--prepare-cmd-line (only-files &optional include zgrep) (let* ((default-directory (or helm-ff-default-directory (helm-default-directory) default-directory)) (fnargs (helm-grep-prepare-candidates only-files default-directory)) (ignored-files (unless (helm-grep-use-ack-p) (mapconcat (lambda (x) (concat "--exclude=" (shell-quote-argument x))) helm-grep-ignored-files " "))) (ignored-dirs (unless (helm-grep-use-ack-p) (mapconcat ;; Need grep version >=2.5.4 ;; of Gnuwin32 on windoze. (lambda (x) (concat "--exclude-dir=" (shell-quote-argument x))) helm-grep-ignored-directories " "))) (exclude (unless (helm-grep-use-ack-p) (let ((inc (and include (concat include " "))) (igfiles (and ignored-files (concat ignored-files " "))) (igdirs (and helm-grep-in-recurse ignored-dirs))) (concat inc igfiles igdirs)))) (types (and (helm-grep-use-ack-p) ;; When %e format spec is not specified ;; in `helm-grep-default-command' ;; we need to pass an empty string ;; to types to avoid error. (or include ""))) (smartcase (if (helm-grep-use-ack-p) "" (unless (let ((case-fold-search nil)) (string-match-p "[[:upper:]]" helm-pattern)) "i"))) (helm-grep-default-command (concat helm-grep-default-command " %m")) ; `%m' like multi. (patterns (helm-mm-split-pattern helm-pattern t)) (pipe-switches (mapconcat 'identity helm-grep-pipe-cmd-switches " ")) (pipes (helm-aif (cdr patterns) (cl-loop with pipcom = (helm-grep--pipe-command-for-grep-command smartcase pipe-switches) for p in it concat (format " | %s %s" pipcom (shell-quote-argument p))) ""))) (format-spec helm-grep-default-command (delq nil (list (unless zgrep (if types (cons ?e types) (cons ?e exclude))) (cons ?c (or smartcase "")) (cons ?p (shell-quote-argument (car patterns))) (cons ?f fnargs) (cons ?m pipes)))))) (defun helm-grep-init (cmd-line) "Start an asynchronous grep process with CMD-LINE using ZGREP if non-nil." (let* ((default-directory (or helm-ff-default-directory (helm-default-directory) default-directory)) (zgrep (string-match "\\`zgrep" cmd-line)) ;; Use pipe only with grep, zgrep or git-grep. (process-connection-type (and (not zgrep) (helm-grep-use-ack-p))) (tramp-verbose helm-tramp-verbose) (start-time (float-time)) (proc-name (if helm-grep-use-zgrep "Zgrep" (capitalize (if helm-grep-in-recurse (helm-grep-command t) (helm-grep-command))))) non-essential) ;; Start grep process. (helm-log "helm-grep-init" "Starting Grep process in directory `%s'" default-directory) (helm-log "helm-grep-init" "Command line used was:\n\n%s" (concat ">>> " (propertize cmd-line 'face 'helm-grep-cmd-line) "\n\n")) (prog1 ; This function should return the process first. (start-file-process-shell-command proc-name helm-buffer cmd-line) ;; Init sentinel. (set-process-sentinel (get-buffer-process helm-buffer) (lambda (process event) (let* ((err (process-exit-status process)) (noresult (= err 1))) (unless (and err (> err 0)) (helm-process-deferred-sentinel-hook process event (helm-default-directory))) (cond ((and noresult ;; This is a workaround for zgrep ;; that exit with code 1 ;; after a certain amount of results. (with-helm-buffer (helm-empty-buffer-p))) (with-helm-buffer (insert (concat "* Exit with code 1, no result found," " command line was:\n\n " (propertize helm-grep-last-cmd-line 'face 'helm-grep-cmd-line))) (setq mode-line-format `(" " mode-line-buffer-identification " " (:eval (format "L%s" (helm-candidate-number-at-point))) " " (:eval (propertize (format "[%s process finished - (no results)] " ,proc-name) 'face 'helm-grep-finish)))))) ((or (string= event "finished\n") (and noresult ;; This is a workaround for zgrep ;; that exit with code 1 ;; after a certain amount of results. (with-helm-buffer (not (helm-empty-buffer-p))))) (helm-log "helm-grep-init" "%s process finished with %s results in %fs" proc-name (helm-get-candidate-number) (- (float-time) start-time)) (helm-maybe-show-help-echo) (with-helm-window (setq mode-line-format `(" " mode-line-buffer-identification " " (:eval (format "L%s" (helm-candidate-number-at-point))) " " (:eval (propertize (format "[%s process finished in %.2fs - (%s results)] " ,proc-name ,(- (float-time) start-time) (helm-get-candidate-number)) 'face 'helm-grep-finish)))) (force-mode-line-update) (when (and helm-allow-mouse helm-selection-point) (helm--bind-mouse-for-selection helm-selection-point)))) ;; Catch error output in log. (t (helm-log "helm-grep-init" "Error: %s %s" proc-name (replace-regexp-in-string "\n" "" event)))))))))) (defun helm-grep-collect-candidates () (let ((cmd-line (helm-grep--prepare-cmd-line helm-grep-last-targets helm-grep-include-files helm-grep-use-zgrep))) (set (make-local-variable 'helm-grep-last-cmd-line) cmd-line) (funcall helm-grep-default-function cmd-line))) ;;; Actions ;; ;; (defun helm-grep-action (candidate &optional where) "Define a default action for `helm-do-grep-1' on CANDIDATE. WHERE can be `other-window' or `other-frame'." (let* ((split (helm-grep-split-line candidate)) (split-pat (helm-mm-split-pattern helm-input)) (lineno (string-to-number (nth 1 split))) (loc-fname (or (with-current-buffer (if (eq major-mode 'helm-grep-mode) (current-buffer) helm-buffer) (get-text-property (pos-bol) 'helm-grep-fname)) (car split))) (tramp-fname (file-remote-p (or helm-ff-default-directory default-directory))) (fname (if tramp-fname (concat tramp-fname loc-fname) loc-fname))) (helm-log "helm-grep-action" "helm-grep-action fname: %s" fname ) (cl-case where (other-window (helm-window-show-buffers (list (find-file-noselect fname)) t)) (other-frame (find-file-other-frame fname)) (grep (helm-grep-save-results-1)) (pdf (if helm-pdfgrep-default-read-command (helm-pdfgrep-action-1 split lineno (car split)) (find-file (car split)) (if (derived-mode-p 'pdf-view-mode) (pdf-view-goto-page lineno) (doc-view-goto-page lineno)))) (t (find-file fname))) (unless (or (eq where 'grep) (eq where 'pdf)) (helm-goto-line lineno) ;; Move point to the nearest matching regexp from bol. (cl-loop for reg in split-pat when (save-excursion (condition-case _err (if helm-migemo-mode (helm-mm-migemo-forward reg (pos-eol) t) (re-search-forward reg (pos-eol) t)) (invalid-regexp nil))) collect (match-beginning 0) into pos-ls finally (when pos-ls (goto-char (apply #'min pos-ls)))) ;; Save history (unless (or helm-in-persistent-action (eq major-mode 'helm-grep-mode) (string= helm-pattern "")) (setq helm-grep-history (cons helm-pattern (delete helm-pattern helm-grep-history))) (when (> (length helm-grep-history) helm-grep-max-length-history) (setq helm-grep-history (delete (car (last helm-grep-history)) helm-grep-history))))))) (defun helm-grep-persistent-action (candidate) "Persistent action for `helm-do-grep-1'. With a prefix arg record CANDIDATE in `mark-ring'." (helm-grep-action candidate) (helm-highlight-current-line)) (defun helm-grep-other-window (candidate) "Jump to result in other window from helm grep." (helm-grep-action candidate 'other-window)) (defun helm-grep-other-frame (candidate) "Jump to result in other frame from helm grep." (helm-grep-action candidate 'other-frame)) (defun helm-goto-next-or-prec-file (n) "Go to next or precedent candidate file in helm grep/etags buffers. If N is positive go forward otherwise go backward." (let* ((allow-mode (or (eq major-mode 'helm-grep-mode) (eq major-mode 'helm-moccur-mode) (eq major-mode 'helm-occur-mode))) (sel (if allow-mode (buffer-substring (pos-bol) (pos-eol)) (helm-get-selection nil t))) (current-line-list (helm-grep-split-line sel)) (current-fname (nth 0 current-line-list)) (bob-or-eof (if (eq n 1) 'eobp 'bobp)) (mark-maybe (lambda () (if allow-mode (ignore) (helm-mark-current-line))))) (catch 'break (while (not (funcall bob-or-eof)) (forward-line n) ; Go forward or backward depending of n value. ;; Exit when current-fname is not matched or in `helm-grep-mode' ;; the line is not a grep line i.e 'fname:num:tag'. (setq sel (buffer-substring (pos-bol) (pos-eol))) (when helm-allow-mouse (helm--mouse-reset-selection-help-echo)) (unless (or (string= current-fname (car (helm-grep-split-line sel))) (and (eq major-mode 'helm-grep-mode) (not (get-text-property (pos-bol) 'helm-grep-fname)))) (funcall mark-maybe) (throw 'break nil)))) (cond ((and (> n 0) (eobp)) (re-search-backward ".") (forward-line 0) (funcall mark-maybe)) ((and (< n 0) (bobp)) (helm-aif (next-single-property-change (pos-bol) 'helm-grep-fname) (goto-char it) (forward-line 1)) (funcall mark-maybe))) (unless allow-mode (helm-follow-execute-persistent-action-maybe) (helm-log-run-hook "helm-goto-next-or-prec-file" 'helm-move-selection-after-hook)))) ;;;###autoload (defun helm-goto-precedent-file () "Go to previous file in Helm grep/etags buffers." (interactive) (with-helm-alive-p (with-helm-window (helm-goto-next-or-prec-file -1)))) (put 'helm-goto-precedent-file 'helm-only t) ;;;###autoload (defun helm-goto-next-file () "Go to previous file in Helm grep/etags buffers." (interactive) (with-helm-window (helm-goto-next-or-prec-file 1))) (put 'helm-goto-next-file 'helm-only t) (helm-make-command-from-action helm-grep-run-default-action "Run grep default action from `helm-do-grep-1'." 'helm-grep-action) (helm-make-command-from-action helm-grep-run-other-window-action "Run grep goto other window action from `helm-do-grep-1'." 'helm-grep-other-window) (helm-make-command-from-action helm-grep-run-other-frame-action "Run grep goto other frame action from `helm-do-grep-1'." 'helm-grep-other-frame) (helm-make-command-from-action helm-grep-run-save-buffer "Run grep save results action from `helm-do-grep-1'." 'helm-grep-save-results) (defun helm-grep-quit-an-find-file-fn (source) (let* ((sel (helm-get-selection nil nil source)) (grep-line (and (stringp sel) (helm-grep-split-line sel)))) (if (and grep-line (file-exists-p (car grep-line))) (expand-file-name (car grep-line)) default-directory))) ;;; helm-grep-mode ;; ;; (defun helm-grep-save-results (candidate) (helm-grep-action candidate 'grep)) (defvar helm-grep-mode-use-pcre nil) (defun helm-grep-save-results-1 () "Save Helm grep result in a `helm-grep-mode' buffer." (let* ((buf "*hgrep*") new-buf (pattern (with-helm-buffer helm-input-local)) (src (helm-get-current-source)) (src-name (assoc-default 'name src))) (when (get-buffer buf) (if helm-grep-save-buffer-name-no-confirm (setq new-buf (format "*hgrep|%s|-%s" pattern (format-time-string "%H-%M-%S*"))) (setq new-buf (helm-read-string "GrepBufferName: " buf)) (cl-loop for b in (helm-buffer-list) when (and (string= new-buf b) (not (y-or-n-p (format "Buffer `%s' already exists overwrite? " new-buf)))) do (setq new-buf (helm-read-string "GrepBufferName: " "*hgrep ")))) (setq buf new-buf)) (with-current-buffer (get-buffer-create buf) (setq default-directory (or helm-ff-default-directory (helm-default-directory) default-directory)) (setq-local helm-grep-mode-use-pcre (helm-get-attr 'pcre src)) (setq buffer-read-only t) (let ((inhibit-read-only t) (map (make-sparse-keymap))) (erase-buffer) (insert "-*- mode: helm-grep -*-\n\n" (format "%s Results for `%s':\n\n" src-name pattern)) (save-excursion (insert (with-current-buffer helm-buffer (goto-char (point-min)) (forward-line 1) (buffer-substring (point) (point-max))))) (save-excursion (while (not (eobp)) (add-text-properties (pos-bol) (pos-eol) `(keymap ,map help-echo ,(concat (get-text-property (point) 'helm-grep-fname) "\nmouse-1: set point\nmouse-2: jump to selection") mouse-face highlight)) (define-key map [mouse-1] 'mouse-set-point) (define-key map [mouse-2] 'helm-grep-mode-mouse-jump) (define-key map [mouse-3] 'ignore) (forward-line 1)))) (helm-grep-mode)) (pop-to-buffer buf) (setq next-error-last-buffer (get-buffer buf)) (message "Helm %s Results saved in `%s' buffer" src-name buf))) (defun helm-grep-mode-mouse-jump (event) (interactive "e") (let* ((window (posn-window (event-end event))) (pos (posn-point (event-end event)))) (with-selected-window window (when (eq major-mode 'helm-grep-mode) (goto-char pos) (helm-grep-mode-jump))))) (put 'helm-grep-mode-mouse-jump 'helm-only t) (defun helm-grep-next-error (&optional argp reset) "Goto ARGP position from a `helm-grep-mode' buffer. RESET non-nil means rewind to the first match. This is the `next-error-function' for `helm-grep-mode'." (interactive "p") (goto-char (cond (reset (point-min)) ((and (< argp 0) helm-current-error) (line-beginning-position)) ((and (> argp 0) helm-current-error) (line-end-position)) ((point)))) (let ((fun (if (> argp 0) #'next-single-property-change #'previous-single-property-change))) (helm-aif (funcall fun (point) 'helm-grep-fname) (progn (goto-char it) ;; `helm-current-error' is set in ;; `helm-grep-mode-jump'. (helm-grep-mode-jump)) (user-error "No more matches")))) (put 'helm-grep-next-error 'helm-only t) ;;;###autoload (defun helm-revert-next-error-last-buffer () "Revert last `next-error' buffer from `current-buffer'. Accept to revert only `helm-grep-mode' or `helm-occur-mode' buffers. Use this when you want to revert the `next-error' buffer after modifications in `current-buffer'." (interactive) (let ((buffer (next-error-find-buffer)) (linum (line-number-at-pos)) (bufname (buffer-name))) (if buffer (with-current-buffer buffer (helm-aif (memq major-mode '(helm-grep-mode helm-occur-mode)) (progn (revert-buffer) ;; helm-occur-mode revert fn is synchronous so ;; reajust from here (it is done with ;; helm-grep-mode in its sentinel). (when (eq (car it) 'helm-occur-mode) (helm-grep-goto-closest-from-linum linum bufname))) (error "No suitable buffer to revert found"))) (error "No suitable buffer to revert found")))) (define-derived-mode helm-grep-mode special-mode "helm-grep" "Major mode to provide actions in helm grep saved buffer. Special commands: \\{helm-grep-mode-map}" (set (make-local-variable 'helm-grep-last-cmd-line) (with-helm-buffer helm-grep-last-cmd-line)) (set (make-local-variable 'revert-buffer-function) #'helm-grep-mode--revert-buffer-function) (set (make-local-variable 'next-error-function) #'helm-grep-next-error) (set (make-local-variable 'helm-current-error) nil)) (put 'helm-grep-mode 'helm-only t) (defun helm-grep-mode--revert-buffer-function (&optional _ignore-auto _noconfirm) (goto-char (point-min)) (when (re-search-forward helm-grep-split-line-regexp nil t) (forward-line 0)) (let ((inhibit-read-only t)) (delete-region (point) (point-max))) (message "Reverting buffer...") (let ((process-connection-type ;; Git needs a nil value otherwise it tries to use a pager. (null (string-match-p "\\`git" helm-grep-last-cmd-line)))) (set-process-sentinel (start-file-process-shell-command "hgrep" (generate-new-buffer "*hgrep revert*") helm-grep-last-cmd-line) 'helm-grep-mode--sentinel))) (defun helm-grep-mode--sentinel (process event) (when (string= event "finished\n") (with-current-buffer (if (eq major-mode 'helm-grep-mode) (current-buffer) (next-error-find-buffer)) (let ((inhibit-read-only t)) (save-excursion (cl-loop for l in (with-current-buffer (process-buffer process) (prog1 (split-string (buffer-string) "\n") (kill-buffer))) for line = (if (string-match-p helm--ansi-color-regexp l) (ansi-color-apply l) l) when (string-match helm-grep-split-line-regexp line) do (insert (propertize (car (helm-grep-filter-one-by-one line helm-grep-mode-use-pcre)) ;; needed for wgrep. 'helm-realvalue line) "\n")))) (when (fboundp 'wgrep-cleanup-overlays) (wgrep-cleanup-overlays (point-min) (point-max)))) (unless (eq major-mode 'helm-grep-mode) (let ((bufname (buffer-name)) (linum (line-number-at-pos))) (with-current-buffer (next-error-find-buffer) (helm-grep-goto-closest-from-linum linum bufname)))) (message "Reverting buffer done") (when executing-kbd-macro (sit-for 1)))) (defun helm-grep-goto-closest-from-linum (linum bufname) (goto-char (point-min)) (catch 'break (while (re-search-forward (format "^%s:\\([0-9]+\\):" (regexp-quote bufname)) nil t) (let ((numline (string-to-number (match-string 1)))) (when (< (- linum numline) 0) (forward-line -1) (throw 'break nil)))))) (defun helm-gm-next-file () (interactive) (helm-goto-next-or-prec-file 1)) (put 'helm-gm-next-file 'helm-only t) (defun helm-gm-precedent-file () (interactive) (helm-goto-next-or-prec-file -1)) (put 'helm-gm-precedent-file 'helm-only t) (defun helm-grep-mode-jump () (interactive) (setq next-error-last-buffer (current-buffer)) (setq-local helm-current-error (point-marker)) (helm-grep-action (buffer-substring (pos-bol) (pos-eol))) (helm-match-line-cleanup-pulse)) (defun helm-grep-mode-jump-other-window-1 (arg) (condition-case nil (progn (when (or (eq last-command 'helm-grep-mode-jump-other-window-forward) (eq last-command 'helm-grep-mode-jump-other-window-backward)) (forward-line arg)) (save-selected-window (helm-grep-action (buffer-substring (pos-bol) (pos-eol)) 'other-window) (helm-match-line-cleanup-pulse) (recenter))) (error nil))) (defun helm-grep-mode-jump-other-window-forward (arg) (interactive "p") (helm-grep-mode-jump-other-window-1 arg)) (defun helm-grep-mode-jump-other-window-backward (arg) (interactive "p") (helm-grep-mode-jump-other-window-1 (- arg))) (defun helm-grep-mode-jump-other-window () (interactive) (setq next-error-last-buffer (current-buffer)) (setq-local helm-current-error (point-marker)) (let ((candidate (buffer-substring (pos-bol) (pos-eol)))) (condition-case nil (progn (helm-grep-action candidate 'other-window) (helm-match-line-cleanup-pulse)) (error nil)))) ;;; ack-grep types ;; ;; (defun helm-grep-hack-types () "Return a list of known ack-grep types." (with-temp-buffer ;; "--help-types" works with both 1.96 and 2.1+, while ;; "--help types" works only with 1.96 Bug#422. ;; `helm-grep-command' should return the ack executable ;; when this function is used in the right context ;; i.e After checking is we are using ack-grep with ;; `helm-grep-use-ack-p'. (call-process (helm-grep-command t) nil t nil "--help-types") (goto-char (point-min)) (cl-loop while (re-search-forward "^ +\\([^. ]+\\) +\\(.*\\)" nil t) collect (cons (concat (match-string 1) " [" (match-string 2) "]") (match-string 1)) collect (cons (concat "no" (match-string 1) " [" (match-string 2) "]") (concat "no" (match-string 1)))))) (defun helm-grep-ack-types-transformer (candidates _source) (cl-loop for i in candidates if (stringp i) collect (rassoc i helm-grep-ack-types-cache) else collect i)) (defvar helm-grep-ack-types-cache nil) (defun helm-grep-read-ack-type () "Select types for the \\='--type' argument of ack-grep." (require 'helm-mode) (require 'helm-adaptive) (setq helm-grep-ack-types-cache (helm-grep-hack-types)) (let ((types (helm-comp-read "Types: " helm-grep-ack-types-cache :name "*Ack-grep types*" :marked-candidates t :must-match t :fc-transformer '(helm-adaptive-sort helm-grep-ack-types-transformer) :buffer "*helm ack-types*"))) (mapconcat (lambda (type) (concat "--type=" type)) types " "))) ;;; grep extensions ;; ;; (defun helm-grep-guess-extensions (files) "Try to guess file extensions in FILES list when using grep recurse. These extensions will be added to command line with --include arg of grep." (cl-loop with ext-list = (list helm-grep-preferred-ext "*") with lst = (if (file-directory-p (car files)) (directory-files (car files) nil directory-files-no-dot-files-regexp) files) for i in lst for ext = (file-name-extension i 'dot) for glob = (and ext (not (string= ext "")) (concat "*" ext)) unless (or (not glob) (and glob-list (member glob glob-list)) (and glob-list (member glob ext-list)) (and glob-list (member glob helm-grep-ignored-files))) collect glob into glob-list finally return (delq nil (append ext-list glob-list)))) (defun helm-grep-get-file-extensions (files) "Try to return a list of file extensions to pass to \\='--include' arg of grep." (require 'helm-adaptive) (let* ((all-exts (helm-grep-guess-extensions (mapcar 'expand-file-name files))) (extensions (helm-comp-read "Search Only in: " all-exts :marked-candidates t :fc-transformer 'helm-adaptive-sort :buffer "*helm grep exts*" :name "*helm grep extensions*"))) (when (listp extensions) ; Otherwise it is empty string returned by C-RET. ;; If extensions is a list of one string containing spaces, ;; assume user entered more than one glob separated by space(s) and ;; split this string to pass it later to mapconcat. ;; e.g '("*.el *.py") (cl-loop for i in extensions append (split-string-and-unquote i " "))))) ;;; Set up source ;; ;; (defvar helm-grep-before-init-hook nil "Hook that runs before initialization of the Helm buffer.") (defvar helm-grep-after-init-hook nil "Hook that runs after initialization of the Helm buffer.") (defclass helm-grep-class (helm-source-async) ((candidates-process :initform 'helm-grep-collect-candidates) (filtered-candidate-transformer :initform #'helm-grep-fc-transformer) (popup-info :initform #'helm-grep-popup-info-fn) (keymap :initform 'helm-grep-map) (pcre :initarg :pcre :initform nil :documentation " Backend is using pcre regexp engine when non-nil.") (nohighlight :initform t) (nomark :initform t) (backend :initarg :backend :initform nil :documentation " The grep backend that will be used. It is currently used only as an internal flag and doesn't set the backend by itself. You probably don't want to modify this.") (candidate-number-limit :initform 9999) (help-message :initform 'helm-grep-help-message) (history :initform 'helm-grep-history) (action :initform 'helm-grep-actions) (persistent-action :initform 'helm-grep-persistent-action) (persistent-help :initform "Jump to line (`C-u' Record in mark ring)") (requires-pattern :initform 2) (before-init-hook :initform 'helm-grep-before-init-hook) (after-init-hook :initform 'helm-grep-after-init-hook) (find-file-target :initform #'helm-grep-quit-an-find-file-fn) (group :initform 'helm-grep))) (defvar helm-source-grep nil) (cl-defmethod helm--setup-source ((source helm-grep-class)) (cl-call-next-method) (helm-aif (and helm-follow-mode-persistent (if (eq (slot-value source 'backend) 'git) helm-source-grep-git helm-source-grep)) (setf (slot-value source 'follow) (assoc-default 'follow it)))) (cl-defun helm-do-grep-1 (targets &optional recurse backend exts default-input input (source 'helm-source-grep)) "Launch helm using backend BACKEND on a list of TARGETS files. When RECURSE is given and BACKEND is \\='grep' use -r option of BACKEND and prompt user for EXTS to set the --include args of BACKEND. Interactively you can give more than one arg separated by space at prompt. E.g.: $Pattern: *.el *.py *.tex From Lisp use the EXTS argument as a list of extensions as above. If you are using ack-grep, you will be prompted for --type instead and EXTS will be ignored. If prompt is empty `helm-grep-ignored-files' are added to --exclude. Argument DEFAULT-INPUT is use as `default' arg of `helm' and INPUT is used as `input' arg of `helm'. See `helm' docstring. Arg BACKEND when non-nil specifies which backend to use. It is used currently to specify \\='zgrep' or \\='git'. When BACKEND \\='zgrep' is used don't prompt for a choice in recurse, and ignore EXTS, search being made recursively on files matching `helm-zgrep-file-extension-regexp' only." (let* (non-essential (ack-rec-p (helm-grep-use-ack-p :where 'recursive)) (exts (and recurse ;; [FIXME] I could handle this from helm-walk-directory. (not (eq backend 'zgrep)) ; zgrep doesn't handle -r opt. (not ack-rec-p) (or exts (helm-grep-get-file-extensions targets)))) (include-files (and exts (mapconcat (lambda (x) (concat "--include=" (shell-quote-argument x))) (if (> (length exts) 1) (remove "*" exts) exts) " "))) (types (and (not include-files) (not (eq backend 'zgrep)) recurse ack-rec-p ;; When %e format spec is not specified ;; ignore types and do not prompt for choice. (string-match "%e" helm-grep-default-command) (helm-grep-read-ack-type))) (src-name (capitalize (helm-grep-command recurse backend))) (com (cond ((eq backend 'zgrep) helm-default-zgrep-command) ((eq backend 'git) helm-grep-git-grep-command) (recurse helm-grep-default-recurse-command) ;; When resuming, the local value of ;; `helm-grep-default-command' is used, only git-grep ;; should need this. (t helm-grep-default-command)))) ;; When called as action from an other source e.g *-find-files ;; we have to kill action buffer. (when (get-buffer helm-action-buffer) (kill-buffer helm-action-buffer)) ;; If `helm-find-files' haven't already started, ;; give a default value to `helm-ff-default-directory' ;; and set locally `default-directory' to this value . See below [1]. (unless helm-ff-default-directory (setq helm-ff-default-directory default-directory)) ;; We need to store these vars locally ;; to pass infos later to `helm-resume'. (helm-set-local-variable 'helm-zgrep-recurse-flag (and recurse (eq backend 'zgrep)) 'helm-grep-last-targets targets 'helm-grep-include-files (or include-files types) 'helm-grep-in-recurse recurse 'helm-grep-use-zgrep (eq backend 'zgrep) 'helm-grep-default-command com 'helm-input-idle-delay helm-grep-input-idle-delay 'default-directory helm-ff-default-directory) ;; [1] ;; Setup the source. (set source (helm-make-source src-name 'helm-grep-class :backend backend :pcre (string-match-p "\\`ack" com))) (helm :sources source :buffer (format "*helm %s*" (helm-grep-command recurse backend)) :default default-input :input input :keymap helm-grep-map :history 'helm-grep-history :truncate-lines helm-grep-truncate-lines))) ;;; zgrep ;; ;; (defun helm-ff-zgrep-1 (flist recursive) (unwind-protect (let* ((def-dir (or helm-ff-default-directory default-directory)) (only (if recursive (or (gethash def-dir helm-rzgrep-cache) (puthash def-dir (helm-walk-directory def-dir :directories nil :path 'full :match helm-zgrep-file-extension-regexp) helm-rzgrep-cache)) flist))) (helm-do-grep-1 only recursive 'zgrep)) (setq helm-zgrep-recurse-flag nil))) ;;; transformers ;; ;; (defun helm-grep-split-line (line) "Split a grep output line." ;; The output of grep may send a truncated line in this chunk, ;; so don't split until grep line is valid, that is ;; once the second part of the line comes with next chunk ;; send by process. (when (string-match helm-grep-split-line-regexp line) ;; Don't use split-string because buffer/file name or string ;; may contain a ":". (cl-loop for n from 1 to 3 collect (match-string n line)))) (defun helm-grep--filter-candidate-1 (candidate &optional dir pcre) (let* ((root (or dir (and helm-grep-default-directory-fn (funcall helm-grep-default-directory-fn)))) (ansi-p (string-match-p helm--ansi-color-regexp candidate)) (line (if ansi-p (ansi-color-apply candidate) candidate)) (split (helm-grep-split-line line)) (fname (if (and root split) ;; Filename should always be provided as a local ;; path, if the root directory is remote, the ;; tramp prefix will be added before executing ;; action, see `helm-grep-action' and Bug#2032. (expand-file-name (car split) (or (file-remote-p root 'localname) root)) (car-safe split))) (lineno (nth 1 split)) (str (nth 2 split)) (display-fname (cl-ecase helm-grep-file-path-style (basename (and fname (file-name-nondirectory fname))) (absolute fname) (relative (and fname root (file-relative-name fname root)))))) (if (and display-fname lineno str) (cons (concat (propertize display-fname 'face 'helm-grep-file 'help-echo (abbreviate-file-name fname) 'helm-grep-fname fname) ":" (propertize lineno 'face 'helm-grep-lineno) ":" (if ansi-p str (helm-grep-highlight-match str pcre))) line) ""))) (defun helm-grep-filter-one-by-one (candidate &optional pcre) "`filter-one-by-one' transformer function for `helm-do-grep-1'." (let ((helm-grep-default-directory-fn (or helm-grep-default-directory-fn (lambda () (or helm-ff-default-directory (and helm-alive-p (helm-default-directory)) default-directory))))) (if (consp candidate) ;; Already computed do nothing (default as input). candidate (and (stringp candidate) (helm-grep--filter-candidate-1 candidate nil pcre))))) (defun helm-grep-popup-info-fn (_candidate) (helm-aif (get-text-property (pos-bol) 'helm-grep-fname) (abbreviate-file-name it))) (defun helm-grep-fc-transformer (candidates source) (let ((helm-grep-default-directory-fn (or helm-grep-default-directory-fn (lambda () (or helm-ff-default-directory (and (null (eq major-mode 'helm-grep-mode)) (helm-default-directory)) default-directory)))) (pcre (helm-get-attr 'pcre source))) (cl-loop for c in candidates collect (helm-grep--filter-candidate-1 c nil pcre)))) (defun helm-grep-highlight-match (str &optional pcre) "Highlight in string STR all occurences matching `helm-pattern'." (let (beg end) (condition-case-unless-debug nil (with-temp-buffer (insert (propertize str 'read-only nil)) ; Fix bug#1176 (goto-char (point-min)) (cl-loop for reg in (cl-loop for r in (helm-mm-split-pattern helm-input) unless (string-match "\\`!" r) collect (helm-aif (and helm-migemo-mode (assoc r helm-mm--previous-migemo-info)) (cdr it) r)) do (while (and (re-search-forward (if pcre (helm--translate-pcre-to-elisp reg) reg) nil t) (> (- (setq end (match-end 0)) (setq beg (match-beginning 0))) 0)) (helm-add-face-text-properties beg end 'helm-grep-match)) do (goto-char (point-min))) (buffer-string)) (error nil)))) ;;; Grep from buffer list ;; ;; (defun helm-grep-buffers-1 (candidate &optional zgrep) "Run grep on all file buffers or CANDIDATE if it is a file buffer. If one of selected buffers is not a file buffer, it is ignored and grep will run on all others file-buffers. If only one candidate is selected and it is not a file buffer, switch to this buffer and run `helm-occur'. If a prefix arg is given run grep on all buffers ignoring non-file buffers." (let* ((prefarg (or current-prefix-arg helm-current-prefix-arg)) (helm-ff-default-directory (if (and helm-ff-default-directory (file-remote-p helm-ff-default-directory)) default-directory helm-ff-default-directory)) (cands (if prefarg (buffer-list) (helm-marked-candidates))) (win-conf (current-window-configuration)) ;; Non--fname and remote buffers are ignored. (bufs (cl-loop for buf in cands for fname = (buffer-file-name (get-buffer buf)) when (and fname (not (file-remote-p fname))) collect (expand-file-name fname)))) (if bufs (if zgrep (helm-do-grep-1 bufs nil 'zgrep) (helm-do-grep-1 bufs)) ;; bufs is empty, thats mean we have only CANDIDATE ;; and it is not a buffer-filename, fallback to occur. (switch-to-buffer candidate) (when (get-buffer helm-action-buffer) (kill-buffer helm-action-buffer)) (helm-occur) (when (eq helm-exit-status 1) (set-window-configuration win-conf))))) (defun helm-grep-buffers (candidate) "Action to grep buffers." (helm-grep-buffers-1 candidate)) (defun helm-zgrep-buffers (candidate) "Action to zgrep buffers." (helm-grep-buffers-1 candidate 'zgrep)) ;;; Helm interface for pdfgrep ;; pdfgrep program ;; and a pdf-reader (e.g xpdf) are needed. ;; (defvar helm-pdfgrep-default-function 'helm-pdfgrep-init) (defun helm-pdfgrep-init (only-files &optional recurse) "Start an asynchronous pdfgrep process in ONLY-FILES list." (let* ((default-directory (or helm-ff-default-directory default-directory)) (fnargs (helm-grep-prepare-candidates (if (file-remote-p default-directory) (mapcar (lambda (x) (file-remote-p x 'localname)) only-files) only-files) default-directory)) (cmd-line (format (if recurse helm-pdfgrep-default-recurse-command helm-pdfgrep-default-command) helm-pattern fnargs)) process-connection-type) ;; Start pdf grep process. (helm-log "helm-pdfgrep-init" "Starting Pdf Grep process in directory `%s'" default-directory) (helm-log "helm-pdfgrep-init" "Command line used was:\n\n%s" (concat ">>> " (propertize cmd-line 'face 'helm-grep-cmd-line) "\n\n")) (prog1 (start-file-process-shell-command "pdfgrep" helm-buffer cmd-line) (message nil) (set-process-sentinel (get-buffer-process helm-buffer) (lambda (_process event) (if (string= event "finished\n") (with-helm-window (setq mode-line-format '(" " mode-line-buffer-identification " " (:eval (format "L%s" (helm-candidate-number-at-point))) " " (:eval (propertize (format "[Pdfgrep Process Finish - %s result(s)] " (max (1- (count-lines (point-min) (point-max))) 0)) 'face 'helm-grep-finish)))) (force-mode-line-update) (when helm-allow-mouse (helm--bind-mouse-for-selection helm-selection-point))) (helm-log "helm-pdfgrep-init" "Error: Pdf grep %s" (replace-regexp-in-string "\n" "" event)))))))) (defun helm-do-pdfgrep-1 (only &optional recurse) "Launch pdfgrep with a list of ONLY files." (unless (executable-find "pdfgrep") (error "Error: No such program `pdfgrep'.")) (let (helm-grep-in-recurse) ; recursion is implemented differently in *pdfgrep. ;; When called as action from an other source e.g *-find-files ;; we have to kill action buffer. (when (get-buffer helm-action-buffer) (kill-buffer helm-action-buffer)) (setq helm-pdfgrep-targets only) (helm :sources (helm-build-async-source "PdfGrep" :init (lambda () ;; If `helm-find-files' haven't already started, ;; give a default value to `helm-ff-default-directory'. (setq helm-ff-default-directory (or helm-ff-default-directory default-directory))) :candidates-process (lambda () (funcall helm-pdfgrep-default-function helm-pdfgrep-targets recurse)) :nohighlight t :nomark t :filter-one-by-one #'helm-grep-filter-one-by-one :candidate-number-limit 9999 :history 'helm-grep-history :keymap helm-pdfgrep-map :help-message 'helm-pdfgrep-help-message :action #'helm-pdfgrep-action :persistent-help "Jump to PDF Page" :requires-pattern 2) :buffer "*helm pdfgrep*" :history 'helm-grep-history))) (defun helm-pdfgrep-action (candidate) (helm-grep-action candidate 'pdf)) (defun helm-pdfgrep-action-1 (_split pageno fname) (save-selected-window (start-file-process-shell-command "pdf-reader" nil (format-spec helm-pdfgrep-default-read-command (list (cons ?f fname) (cons ?p pageno)))))) ;;; AG - PT - RG ;; ;; https://github.com/ggreer/the_silver_searcher ;; https://github.com/monochromegane/the_platinum_searcher ;; https://github.com/BurntSushi/ripgrep (defun helm-grep--ag-command () (and helm-grep-ag-command (car (helm-remove-if-match "\\`[A-Z]*=" (split-string helm-grep-ag-command))))) (defun helm-grep-ag-get-types () "Returns a list of AG types if available with AG version. See AG option \"--list-file-types\" Ripgrep (rg) types are also supported if this backend is used." (with-temp-buffer (let* ((com (helm-grep--ag-command)) (ripgrep (string= com "rg")) (regex (if ripgrep "^\\(.*\\):" "^ *\\(--[a-z]*\\)")) (prefix (if ripgrep "-t " ""))) (when (equal (call-process com nil t nil (if ripgrep "--type-list" "--list-file-types")) 0) (goto-char (point-min)) (cl-loop while (re-search-forward regex nil t) for type = (match-string 1) collect (cons type (concat prefix type))))))) (defun helm-grep-ag-prepare-cmd-line (pattern directory &optional type) "Prepare AG command line to search PATTERN in DIRECTORY. When TYPE is specified it is one of what `helm-grep-ag-get-types' returns if available with current AG version." (let* ((patterns (helm-mm-split-pattern pattern t)) (pipe-switches (mapconcat 'identity helm-grep-ag-pipe-cmd-switches " ")) (pipe-cmd (helm-acase (helm-grep--ag-command) (("ag" "pt") (format "%s -S --color%s" it (concat " " pipe-switches))) ("rg" (format "rg -N -S --color=%s%s" (when (string-match "--color=\\([a-z]+\\) " helm-grep-ag-command) (match-string 1 helm-grep-ag-command)) (concat " " pipe-switches))))) (cmd (format helm-grep-ag-command (mapconcat 'identity type " ") (shell-quote-argument (car patterns)) (shell-quote-argument directory)))) (helm-aif (cdr patterns) (concat cmd (cl-loop for p in it concat (format " | %s -- %s" pipe-cmd (shell-quote-argument p)))) cmd))) (defun helm-grep-ag-init (directory &optional type) "Start AG process in DIRECTORY maybe searching only files of type TYPE." (let ((default-directory (or helm-ff-default-directory (helm-default-directory) default-directory)) (cmd-line (helm-grep-ag-prepare-cmd-line ;; NOTE Encode directory name and pattern, ;; or it may not work with Chinese and maybe other non-utf8 ;; characters on MSWindows systems issue#2677 and issue#2678. (encode-coding-string helm-pattern locale-coding-system) (or (file-remote-p directory 'localname) (encode-coding-string directory locale-coding-system)) type)) (start-time (float-time)) (proc-name (helm-grep--ag-command))) (set (make-local-variable 'helm-grep-last-cmd-line) cmd-line) (helm-log "helm-grep-ag-init" "Starting %s process in directory `%s'" proc-name directory) (helm-log "helm-grep-ag-init" "Command line used was:\n\n%s" (concat ">>> " cmd-line "\n\n")) (prog1 (start-file-process-shell-command proc-name helm-buffer cmd-line) (set-process-sentinel (get-buffer-process helm-buffer) (lambda (process event) (let* ((err (process-exit-status process)) (noresult (= err 1))) (cond (noresult (with-helm-buffer (insert (concat "* Exit with code 1, no result found," " command line was:\n\n " (propertize helm-grep-last-cmd-line 'face 'helm-grep-cmd-line))) (setq mode-line-format `(" " mode-line-buffer-identification " " (:eval (format "L%s" (helm-candidate-number-at-point))) " " (:eval (propertize (format "[%s process finished - (no results)] " ,(upcase proc-name)) 'face 'helm-grep-finish)))))) ((string= event "finished\n") (helm-log "helm-grep-ag-init" "%s process finished with %s results in %fs" proc-name (helm-get-candidate-number) (- (float-time) start-time)) (helm-maybe-show-help-echo) (with-helm-window (setq mode-line-format `(" " mode-line-buffer-identification " " (:eval (format "L%s" (helm-candidate-number-at-point))) " " (:eval (propertize (format "[%s process finished in %.2fs - (%s results)] " ,(upcase proc-name) ,(- (float-time) start-time) (helm-get-candidate-number)) 'face 'helm-grep-finish)))) (force-mode-line-update) (when helm-allow-mouse (helm--bind-mouse-for-selection helm-selection-point)))) (t (helm-log "helm-grep-ag-init" "Error: %s %s" proc-name (replace-regexp-in-string "\n" "" event)))))))))) (defun helm-grep-ag-search-results (_candidate) "Launch a new helm session on the current results. Allow narrowing the grep ag results to a specific file or pattern without continuing calling grep ag, i.e. once called, what you type in minibuffer will be searched with helm match functions in all the candidates found previously by grep ag." (with-helm-buffer (let* ((src (helm-get-current-source)) (candidates (nthcdr 1 (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n"))) (transfo (helm-get-attr 'filtered-candidate-transformer src)) (actions (helm-get-attr 'action src)) (name (helm-get-attr 'name src)) (directory (helm-get-attr 'directory src))) (helm :sources (helm-build-sync-source (format "Search in %s" name) :candidates candidates :keymap 'helm-grep-map :candidate-transformer (lambda (candidates) (let ((helm-grep-default-directory-fn (lambda () directory))) (funcall transfo candidates nil))) :action actions) :buffer "*helm search ag*")))) (helm-make-command-from-action helm-grep-ag-run-search-results "Run `helm-grep-ag-search-results' action." 'helm-grep-ag-search-results) (defvar helm-grep-ag-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-grep-map) (define-key map (kbd "C-s") 'helm-grep-run-ag-grep-parent-directory) (define-key map (kbd "C-c s") 'helm-grep-ag-run-search-results) map)) (defclass helm-grep-ag-class (helm-source-async) ((nohighlight :initform t) (pcre :initarg :pcre :initform t :documentation " Backend is using pcre regexp engine when non--nil.") (keymap :initform 'helm-grep-ag-map) (history :initform 'helm-grep-ag-history) (help-message :initform 'helm-grep-help-message) (filtered-candidate-transformer :initform #'helm-grep-fc-transformer) (popup-info :initform #'helm-grep-popup-info-fn) (persistent-action :initform 'helm-grep-persistent-action) (persistent-help :initform "Jump to line (`C-u' Record in mark ring)") (candidate-number-limit :initform 99999) (directory :initarg :directory :initform nil :documentation " Directory currently searched.") (requires-pattern :initform 2) (nomark :initform t) (action :initform 'helm-grep-actions) (find-file-target :initform #'helm-grep-quit-an-find-file-fn) (group :initform 'helm-grep))) (defvar helm-source-grep-ag nil) (cl-defmethod helm--setup-source ((source helm-grep-ag-class)) (cl-call-next-method) (helm-aif (and helm-follow-mode-persistent helm-source-grep-ag (assoc-default 'follow helm-source-grep-ag)) (setf (slot-value source 'follow) it))) (defun helm-grep-ag-1 (directory &optional type input) "Start helm ag in DIRECTORY maybe searching in files of type TYPE. If INPUT is provided, use it as the search string." (setq helm-source-grep-ag (helm-make-source (upcase (helm-grep--ag-command)) 'helm-grep-ag-class :header-name (lambda (name) (format "%s [%s]" name (abbreviate-file-name directory))) :directory directory :action (append helm-grep-actions `((,(format "%s grep parent directory" (upcase (helm-grep--ag-command))) . helm-grep-ag-grep-parent-directory) (,(format "Search results in grep %s buffer" (upcase (helm-grep--ag-command))) . helm-grep-ag-search-results))) :candidates-process (lambda () (helm-grep-ag-init directory type)))) (helm-set-local-variable 'helm-input-idle-delay helm-grep-input-idle-delay) (helm :sources 'helm-source-grep-ag :history 'helm-grep-ag-history :input input :truncate-lines helm-grep-truncate-lines :buffer (format "*helm %s*" (helm-grep--ag-command)))) (defun helm-grep-ag-grep-parent-directory (_candidate) "Restart helm-grep-ag in the parent of the currently searched directory." (let* ((src (with-helm-buffer (car helm-sources))) (directory (helm-basedir (helm-get-attr 'directory src) t)) (input helm-pattern)) (helm-grep-ag-1 directory nil input))) (helm-make-command-from-action helm-grep-run-ag-grep-parent-directory "Ag grep parent directory." 'helm-grep-ag-grep-parent-directory) (defun helm-grep-ag (directory with-types) "Start grep AG in DIRECTORY. When WITH-TYPES is non-nil provide completion on AG types." (require 'helm-adaptive) (let ((com (capitalize (helm-grep--ag-command)))) (helm-grep-ag-1 directory (helm-aif (and with-types (helm-grep-ag-get-types)) (helm-comp-read (format "%s type: " com) it :must-match t :marked-candidates t :fc-transformer 'helm-adaptive-sort :buffer (format "*helm %s types*" com)))))) ;;; Git grep ;; ;; (defvar helm-source-grep-git nil) (defun helm-grep-git-1 (directory &optional all default input) "Run git-grep on DIRECTORY. If DIRECTORY is not inside or part of a git repo exit with error. If optional arg ALL is non-nil grep the whole repo otherwise start at DIRECTORY. Arg DEFAULT is what you will have with `next-history-element', arg INPUT is what you will have by default at prompt on startup." (require 'vc) (let* (helm-grep-default-recurse-command ;; Expand filename of each candidate with the git root dir. ;; The filename will be in the helm-grep-fname prop. (helm-grep-default-directory-fn (lambda () (vc-find-root directory ".git"))) (helm-ff-default-directory (funcall helm-grep-default-directory-fn))) (cl-assert helm-ff-default-directory nil "Not inside a Git repository") (helm-do-grep-1 (if all '("") `(,(expand-file-name directory))) nil 'git nil default input 'helm-source-grep-git))) ;;;###autoload (defun helm-do-grep-ag (arg) "Preconfigured `helm' for grepping with AG in `default-directory'. With prefix arg prompt for type if available with your AG version." (interactive "P") (require 'helm-files) (helm-grep-ag (expand-file-name default-directory) arg)) ;;;###autoload (defun helm-grep-do-git-grep (arg) "Preconfigured `helm' for git-grepping `default-directory'. With a prefix arg ARG git-grep the whole repository." (interactive "P") (require 'helm-files) (helm-grep-git-1 default-directory arg)) (provide 'helm-grep) ;;; helm-grep.el ends here helm-4.0.3/helm-help.el000066400000000000000000003077401501106761700146570ustar00rootroot00000000000000;;; helm-help.el --- Help messages for Helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm) (defgroup helm-help nil "Embedded help for `helm'." :group 'helm) (defface helm-helper `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit helm-header)) "Face for Helm help string in minibuffer." :group 'helm-help) (defvar helm-help--string-list '(helm-help-message helm-buffer-help-message helm-ff-help-message helm-read-file-name-help-message helm-generic-file-help-message helm-fd-help-message helm-grep-help-message helm-pdfgrep-help-message helm-etags-help-message helm-ucs-help-message helm-bookmark-help-message helm-esh-help-message helm-buffers-ido-virtual-help-message helm-moccur-help-message helm-top-help-message helm-M-x-help-message helm-imenu-help-message helm-colors-help-message helm-semantic-help-message helm-kmacro-help-message helm-kill-ring-help-message) "A list of help messages (strings) used by `helm-documentation'.") (defvar helm-documentation-buffer-name "*helm documentation*") ;;;###autoload (defun helm-documentation () "Preconfigured `helm' for Helm documentation. With a prefix arg refresh the documentation. Find here the documentation of all documented sources." (interactive) (let ((buf (get-buffer-create helm-documentation-buffer-name))) (switch-to-buffer buf) (set-buffer buf) (let ((inhibit-read-only t)) (erase-buffer) (save-excursion (cl-loop for elm in helm-help--string-list for str = (helm-interpret-value elm) do (insert (substitute-command-keys str) "\n\n"))) (org-mode)) (setq buffer-read-only t) (view-mode))) ;;; Local help messages. ;;; `helm-buffer-list' help ;; ;; (defvar helm-buffer-help-message "* Helm Buffer ** Tips *** Completion **** Major-mode You can enter a partial major-mode name (e.g. lisp, sh) to narrow down buffers. To specify the major-mode, prefix it with \"*\" e.g. \"*lisp\". If you want to match all buffers but the ones with a specific major-mode \(negation), prefix the major-mode with \"!\" e.g. \"*!lisp\". If you want to specify more than one major-mode, separate them with \",\", e.g. \"*!lisp,!sh,!fun\" lists all buffers but the ones in lisp-mode, sh-mode and fundamental-mode. Then enter a space followed by a pattern to narrow down to buffers matching this pattern. **** Search inside buffers If you enter a space and a pattern prefixed by \"@\", Helm searches for text matching this pattern *inside* the buffer (i.e. not in the name of the buffer). Negation are supported i.e. \"!\". When you specify more than one of such patterns, it will match buffers with contents matching each of these patterns i.e. AND, not OR. That means that if you specify \"@foo @bar\" the contents of buffer will have to be matched by foo AND bar. If you specify \"@foo @!bar\" it means the contents of the buffer have to be matched by foo but NOT bar. If you enter a pattern prefixed with an escaped \"@\", Helm searches for a buffer matching \"@pattern\" but does not search inside the buffer. **** Search by directory name If you prefix the pattern with \"/\", Helm matches over the directory names of the buffers. This feature can be used to narrow down the search to one directory while subsequent strings entered after a space match over the buffer name only. Note that negation is not supported for matching on buffer filename. Starting from Helm v1.6.8, you can specify more than one directory. **** Fuzzy matching `helm-buffers-fuzzy-matching' turns on fuzzy matching on buffer names, but not on directory names or major modes. A pattern starting with \"^\" disables fuzzy matching and matching is done litteraly IOW do not use regexps (\"^\" or whatever special regexp character) when you want to fuzzy match. **** Examples With the following pattern \"*lisp ^helm @moc\" Helm narrows down the list by selecting only the buffers that are in lisp mode, start with \"helm\" and which content matches \"moc\". Without the \"@\" \"*lisp ^helm moc\" Helm looks for lisp mode buffers starting with \"helm\" and containing \"moc\" in their name. With this other pattern \"*!lisp !helm\" Helm narrows down to buffers that are not in \"lisp\" mode and that do not match \"helm\". With this last pattern /helm/ w3 Helm narrows down to buffers that are in any \"helm\" subdirectory and matching \"w3\". *** Creating buffers When creating a new buffer, use `\\[universal-argument]' to choose a mode from a list. This list is customizable, see `helm-buffers-favorite-modes'. *** Killing buffers You can kill buffers either one by one or all the marked buffers at once. One kill-buffer command leaves Helm while the other is persistent. Run the persistent kill-buffer command either with the regular `helm-execute-persistent-action' called with a prefix argument (`\\[universal-argument] \\\\[helm-execute-persistent-action]') or with its specific command `helm-buffer-run-kill-persistent'. See the bindings below. *** Switching to buffers To switch to a buffer, press RET, to switch to a buffer in another window, select this buffer and press \\\\[helm-buffer-switch-other-window], when called with a prefix arg the buffer will be displayed vertically in other window. If you mark more than one buffer, the marked buffers will be displayed in different windows. *** Saving buffers If buffer is associated to a file and is modified, it is by default colorized in orange, see [[Meaning of colors and prefixes for buffers][Meaning of colors and prefixes for buffers]]. You can save these buffers with \\\\[helm-buffer-save-persistent]. If you want to save all these buffers, you can mark them with \\[helm-buffers-mark-similar-buffers] and save them with \\[helm-buffer-save-persistent]. You can also do this in one step with \\[helm-buffer-run-save-some-buffers]. Note that you will not be asked for confirmation. *** Meaning of colors and prefixes for buffers Remote buffers are prefixed with '@'. Red => Buffer's file was modified on disk by an external process. Indianred2 => Buffer exists but its file has been deleted. Orange => Buffer is modified and not saved to disk. Italic => A non-file buffer. Yellow => Tramp archive buffer. DimGray => Indirect buffer. ** Commands \\ |Keys|Description| |-------------+----------| |\\[helm-buffer-run-zgrep]|Grep Buffer(s) works as zgrep too (`\\[universal-argument]' to grep all buffers but non-file buffers). |\\[helm-buffers-run-occur]|Multi-Occur buffer or marked buffers (`\\[universal-argument]' to toggle force-searching current-buffer). |\\[helm-buffer-switch-other-window]|Switch to other window. |\\[helm-buffer-switch-other-frame]|Switch to other frame. |\\[helm-buffers-run-browse-project]|Browse project from buffer. |\\[helm-buffer-run-query-replace-regexp]|Query-replace-regexp in marked buffers. |\\[helm-buffer-run-query-replace]|Query-replace in marked buffers. |\\[helm-buffer-run-ediff]|Ediff current buffer with candidate. With two marked buffers, ediff those buffers. |\\[helm-buffer-run-ediff-merge]|Ediff-merge current buffer with candidate. With two marked buffers, ediff-merge those buffers. |\\[helm-buffer-diff-persistent]|Toggle Diff-buffer with saved file without leaving Helm. |\\[helm-buffer-revert-persistent]|Revert buffer without leaving Helm. |\\[helm-buffer-save-persistent]|Save buffer without leaving Helm. |\\[helm-buffer-run-save-some-buffers]|Save all unsaved buffers. |\\[helm-buffer-run-kill-buffers]|Delete marked buffers and leave Helm. |\\[helm-buffer-run-kill-persistent]|Delete buffer without leaving Helm. |\\[helm-buffer-run-rename-buffer]|Rename buffer. |\\[helm-toggle-all-marks]|Toggle all marks. |\\[helm-mark-all]|Mark all. |\\[helm-toggle-buffers-details]|Toggle details. |\\[helm-buffers-toggle-show-hidden-buffers]|Show hidden buffers. |\\[helm-buffers-mark-similar-buffers]|Mark all buffers of the same type (color) as current buffer.") ;;; Find files help (`helm-find-files') ;; ;; (defvar helm-ff-help-message "* Helm Find Files ** Tips *** Navigation summary For a better experience you can enable auto completion by setting `helm-ff-auto-update-initial-value' to non-nil in your init file. It is not enabled by default to not confuse new users. **** Navigate with arrow keys You can use and arrows to go down or up one level, to disable this customize `helm-ff-lynx-style-map'. Note that using `setq' will NOT work. **** Use `\\\\[helm-execute-persistent-action]' (persistent action) on a directory to go down one level On a symlinked directory a prefix argument expands to its true name. **** Use `\\\\[helm-find-files-up-one-level]' or `DEL' on a directory to go up one level ***** `DEL' behavior `DEL' by default deletes char backward. But when `helm-ff-DEL-up-one-level-maybe' is non nil `DEL' behaves differently depending on the contents of helm-pattern. It goes up one level if the pattern is a directory ending with \"/\" or disables HFF auto update and delete char backward if the pattern is a filename or refers to a non existing path. Going up one level can be disabled if necessary by deleting \"/\" at the end of the pattern using \\\\[backward-char] and \\[helm-delete-minibuffer-contents]. Note that when deleting char backward, Helm takes care of disabling update giving you the opportunity to edit your pattern for e.g. renaming a file or creating a new file or directory. When `helm-ff-auto-update-initial-value' is non nil you may want to disable it temporarily, see [[Toggle auto-completion][Toggle auto-completion]] for this. **** Use `\\\\[helm-find-files-down-last-level]' to walk back the resulting tree of all the `\\\\[helm-find-files-up-one-level]' or DEL you did The tree is reinitialized each time you browse a new tree with `\\\\[helm-execute-persistent-action]' or by entering some pattern in the prompt. **** `RET' behavior It behaves differently depending on `helm-selection' (current candidate in helm-buffer): - candidate basename is \".\" => Open it in dired. - candidate is a directory => Expand it. - candidate is a file => Open it. If you have marked candidates and you press RET on a directory, Helm will navigate to this directory. If you want to exit with RET with default action with these marked candidates, press RET a second time while you are on the root of this directory e.g. \"/home/you/dir/.\" or press RET on any file which is not a directory. You can also exit with default action at any moment with `f1'. Note that when copying, renaming, etc. from `helm-find-files' the destination file is selected with `helm-read-file-name'. **** `TAB' behavior Normally `TAB' is bound to `helm-select-action' in helm-map which display the action menu. You can change this behavior by setting in `helm-find-files-map' a new command for `TAB': (define-key helm-find-files-map (kbd \"C-i\") 'helm-ff-TAB) It will then behave slighly differently depending of `helm-selection': - candidate basename is \".\" => open the action menu. - candidate is a directory => expand it (behave as \\\\[helm-execute-persistent-action]). - candidate is a file => open action menu. Called with a prefix arg open menu unconditionally. *** Show information on files (permissions etc...) Turn on `helm-popup-tip-mode'. *** Filter out files or directories You can show files or directories only with respectively \\\\[helm-ff-toggle-dirs-only] and \\\\[helm-ff-toggle-files-only]. These are toggle commands i.e. filter/show_all. Changing directory disable filtering. *** Sort directory contents When listing a directory without narrowing its contents, i.e. when pattern ends with \"/\", you can sort alphabetically, by newest or by size by using respectively \\\\[helm-ff-sort-alpha], \\[helm-ff-sort-by-newest] or \\[helm-ff-sort-by-size]. NOTE: When starting back narrowing i.e. entering something in minibuffer after \"/\" sorting is done again with fuzzy sorting and no more with sorting methods previously selected. You can use these sort functions only on files or directory, see [[Filter out files or directories][Filter out files or directories]]. *** Find file at point Helm uses `ffap' partially or completely to find file at point depending on the value of `helm-ff-guess-ffap-filenames': if non-nil, support is complete \(annoying), if nil, support is partial. Note that when the variable `helm-ff-allow-non-existing-file-at-point' is non nil Helm will insert the filename at point even if file with this name doesn't exists. If non existing file at point ends with numbers prefixed with \":\" the \":\" and numbers are stripped. **** Find file at line number When text at point is in the form of ~/elisp/helm/helm.el:1234 Helm finds this file at the indicated line number, here 1234. **** Find URL at point When a URL is found at point, Helm expands to that URL only. Pressing `RET' opens that URL using `browse-url-browser-function'. **** Find e-mail address at point When an e-mail address is found at point, Helm expands to this e-mail address prefixed with \"mailto:\". Pressing `RET' opens a message buffer with that e-mail address. *** Quick pattern expansion **** Enter `~/' at end of pattern to quickly reach home directory **** Enter `/' at end of pattern to quickly reach the file system root **** Enter `./' at end of pattern to quickly reach `default-directory' \(As per its value at the beginning of the session.) If you already are in the `default-directory' this will move the cursor to the top. **** Enter `../' at end of pattern will reach upper directory, moving cursor to the top This is different from using `\\\\[helm-find-files-up-one-level]' in that it moves the cursor to the top instead of remaining on the previous subdir name. **** Enter `..name/' at end of pattern to start a recursive search It searches directories matching \"name\" under the current directory, see the [[Recursive completion on subdirectories][Recursive completion on subdirectories]] section below for more details. **** Any environment variable (e.g. `$HOME') at end of pattern gets expanded **** Any valid filename yanked after pattern gets expanded **** Special case: URL at point The quick expansions do not take effect after end a URL, you must kill the pattern first (`\\[helm-delete-minibuffer-contents]'). *** Helm-find-files supports fuzzy matching It starts from the third character of the pattern. For instance \"fob\" or \"fbr\" will complete \"foobar\" but \"fb\" needs a third character in order to complete it. *** Watch briefly files contents while navigating You can use `\\[helm-execute-persistent-action]' on a filename for this, then: - First hit expands to that filename in the Helm buffer. - Second hit displays the buffer filename. - Third hit kills the buffer filename. Note: `\\[universal-argument] \\[helm-execute-persistent-action]' displays the buffer directly. *** Browse images directories with `helm-follow-mode' and navigate up/down Before Emacs-27 Helm was using image-dired that works with external ImageMagick tools. From Emacs-27 Helm use native display of images with image-mode by default for Emacs-27 (see `helm-ff-display-image-native'), this allows automatic resize when changing window size, zooming with `\\[helm-ff-increase-image-size-persistent]' and `\\[helm-ff-decrease-image-size-persistent]' and rotate images as before. You can also use `helm-follow-action-forward' and `helm-follow-action-backward' with `\\[helm-follow-action-forward]' and `\\[helm-follow-action-backward]' respectively. Note that these commands have different behavior when `helm-follow-mode' is enabled (go to next/previous line only). Use `\\[universal-argument] \\[helm-execute-persistent-action]' to display an image or kill its buffer. TIP: Use `\\\\[helm-toggle-resplit-and-swap-windows]' and `\\[helm-enlarge-window]' to display Helm window vertically and to enlarge it while viewing images. Note this may not work with exotic Helm windows settings such as the ones in Spacemacs. **** Show thumbnails Helm use image-dired to show thumbnails on image files, you can toggle the thumbnail view with \\`\\[helm-ff-toggle-thumbnails]'. **** Launch a slideshow from marked files Helm provides an action from `helm-find-files' that allows running a slideshow on marked files. Just mark image files and launch slideshow from action menu, bindings are self documented in mode-line. NOTE: When hitting any other keys than the ones mentionned in mode-line, slideshow will come in pause, to restart it you will have to press twice SPACE. *** Open files externally - Open file with external program (`\\\\[helm-ff-run-open-file-externally]',`C-u' to choose). Helm is looking what is used by default to open file externally (mailcap files) but have its own variable `helm-external-programs-associations' to store external applications. If you call the action or its binding without prefix arg Helm will see if there is an application suitable in `helm-external-programs-associations', otherwise it will look in mailcap files. If you want to specify which external application to use (and its options) use a prefix arg. If you have to pass arguments after filename use `%s' in your command e.g. \"foo %s -a -b\" If you want to detach your program from Emacs, you can use e.g. \"(foo %s &)\" (only supported on Linux/Unix). When using `%s' do not quote it (i.e. \"%s\"), helm is already quoting filename argument. Note: What you configure for Helm in `helm-external-programs-associations' will take precedence on mailcap files. - Preview file with external program (`\\[helm-ff-run-preview-file-externally]'). Same as above but doesn't quit Helm session, it is apersistent action. - Open file externally with default tool (`\\[helm-ff-run-open-file-with-default-tool]'). Use `xdg-open' to open files. *** Toggle auto-completion Normally auto-completion in helm-find-files is disabled by default but you can toggle it with `\\[helm-ff-run-toggle-auto-update]'. To enable it on startup by default, customize `helm-ff-auto-update-initial-value'. It is useful when trying to create a new file or directory and you don't want Helm to complete what you are writing. Note: On a terminal, the default binding `C-' may not work. In this case use `C-c '. *** Show infos of files To have infos on files like size, permissions etc... hit `\\[helm-ff-properties-persistent]'. To have automatically brief infos on selected file, turn on `helm-popup-tip-mode'. *** You can create a new directory and a new file at the same time Simply write the path in the prompt and press `RET', e.g. \"~/new/newnew/newnewnew/my_newfile.txt\". *** To create a new directory, append a \"/\" to the new name and press `RET' *** To create a new file, enter a filename not ending with \"/\" Note that when you enter a new name, this one is prefixed with [+]. *** Recursive search from Helm-find-files **** You can use Helm-browse-project (see binding below) - With no prefix argument: If the current directory is under version control with either git or hg and helm-ls-git and/or helm-ls-hg are installed, it lists all the files under version control. Otherwise it falls back to Helm-find-files. See https://github.com/emacs-helm/helm-ls-git and https://github.com/emacs-helm/helm-ls-hg. - With one prefix argument: List all the files under this directory and other subdirectories \(recursion) and this list of files will be cached. - With two prefix arguments: Same but the cache is refreshed. **** You can start a recursive search with \"locate\", \"find\" or [[https://github.com/sharkdp/fd][Fd]] See \"NOTE\" in the [[Recursive completion on subdirectories][section on subdirectories]]. Using \"locate\", you can enable the local database with a prefix argument. If the local database doesn't already exists, you will be prompted for its creation. If it exists and you want to refresh it, give it two prefix args. When using locate the Helm buffer remains empty until you type something. Regardless Helm uses the basename of the pattern entered in the helm-find-files session by default. Hitting `\\[next-history-element]' should just kick in the locate search with this pattern. If you want Helm to automatically do this, add `helm-source-locate' to `helm-sources-using-default-as-input'. NOTE: On Windows use Everything with its command line ~es~ as a replacement of locate. See [[https://github.com/emacs-helm/helm/wiki/Locate#windows][Locate on Windows]] If your system use plocate as backend and you have no results when searching, see [[Recursive completion on subdirectories][section on subdirectories]]. **** Recursive completion on subdirectories Starting from the directory you are currently browsing, it is possible to have completion of all directories underneath. Say you are at \"/home/you/foo/\" and you want to go to \"/home/you/foo/bar/baz/somewhere/else\", simply type \"/home/you/foo/..else\" and enter the final \"/\". Helm will then list all possible directories under \"foo\" matching \"else\". You can use either find, locate or fdfind as backend, see the variable `helm-locate-recursive-dirs-command', the default is to use find as backend. NOTE: When using `locate' as backend which uses an index, the directory tree displayed may be out-of-date and not reflect the latest change until you update the index (using \"updatedb\" for \"locate\"). On recent systems plocate is used instead of mlocate and the corresponding updatedb command doesn't index anymore user directories, see the option PRUNE_BIND_MOUNTS in the updatedb man page. If a locale db file is found under current directory it will be used instead of the global updatedb index. To create a locale db file under current directory, use `C-u C-u C-x C-f' from helm-find-files. *** Insert filename at point or complete filename at point On insertion (i.e. there is nothing at point): - `\\[helm-ff-run-complete-fn-at-point]': insert absolute file name. - `\\[universal-argument] \\[helm-ff-run-complete-fn-at-point]': insert abbreviated file name. - `\\[universal-argument] \\[universal-argument] \\[helm-ff-run-complete-fn-at-point]': insert relative file name. - `\\[universal-argument] \\[universal-argument] \\[universal-argument] \\[helm-ff-run-complete-fn-at-point]': insert basename. On completion (\\[helm-ff-run-complete-fn-at-point]): - Target starts with \"~/\": insert abbreviate file name. - target starts with \"/\" or \"[a-z]:/\": insert full path. - Otherwise: insert relative file name. *** Use the wildcard to select multiple files Use of wildcard is supported to run an action over a set of files. Example: You can copy all the files with \".el\" extension by using \"​*.el\" and then run copy action. Similarly, \"​*​*.el\" (note the two stars) will recursively select all \".el\" files under the current directory. Note that when recursively copying files, you may have files with same name dispatched across different subdirectories, so when copying them in the same directory they will get overwritten. To avoid this Helm has a special action called \"backup files\" that has the same behavior as the command line \"cp -f --backup=numbered\": it allows you to copy many files with the same name from different subdirectories into one directory. Files with same name are renamed as follows: \"foo.txt.~1~\". Like with the --force option of cp, it is possible to backup files in current directory. This command is available only when `dired-async-mode' is active. When using an action that involves an external backend (e.g. grep), using \"**\" is not recommended (even though it works fine) because it will be slower to select all the files. You are better off leaving the backend to do it, it will be faster. However, if you know you have not many files it is reasonable to use this, also using not recursive wildcard (e.g. \"*.el\") is perfectly fine for this. The \"**\" feature is active by default in the option `helm-file-globstar'. It is different from the Bash \"shopt globstar\" feature in that to list files with a named extension recursively you would write \"​*​*.el\" whereas in Bash it would be \"​*​*/​*.el\". Directory selection with \"​*​*/\" like Bash \"shopt globstar\" option is not supported yet. Helm supports different styles of wildcards: - `sh' style, the ones supported by `file-expand-wildcards'. e.g. \"​*.el\", \"​*.[ch]\" which match respectively all \".el\" files or all \".c\" and \".h\" files. - `bash' style (partially) In addition to what allowed in `sh' style you can specify file extensions that have more than one character like this: \"*.{sh,py}\" which match \".sh\" and \".py\" files. Of course in both styles you can specify one or two \"*\". *** Query replace regexp on filenames Replace different parts of a file basename with something else. When calling this action you will be prompted twice as with `query-replace', first for the matching expression of the text to replace and second for the replacement text. Several facilities, however, are provided to make the two prompts more powerfull. **** Syntax of the first prompt In addition to simple regexps, these shortcuts are available: - Basename without extension => \"%.\" - Only extension => \".%\" - Substring => \"%::\" - Whole basename => \"%\" **** Syntax of the second prompt In addition to a simple string to use as replacement, here is what you can use: - A placeholder refering to what you have selected in the first prompt: \"\\@\". After this placeholder you can use a search-and-replace syntax à-la sed: \"\\@/// You can select a substring from the string represented by the placeholder: \"\\@::\" - A special character representing a number which is incremented: \"\\#\". - Shortcuts for `upcase', `downcase' and `capitalize' are available as`%u', `%d' and `%c' respectively. **** Examples ***** Recursively rename all files with \".JPG\" extension to \".jpg\" Use the `helm-file-globstar' feature described in [[Use the wildcard to select multiple files][recursive globbing]] by entering \"**.JPG\" at the end of the Helm-find-files pattern, then hit \\\\[helm-ff-run-query-replace-fnames-on-marked] and enter \"JPG\" on first prompt, then \"jpg\" on second prompt and hit `RET'. Alternatively you can enter \".%\" at the first prompt, then \"jpg\" and hit `RET'. Note that when using this instead of using \"JPG\" at the first prompt, all extensions will be renamed to \"jpg\" even if the extension of one of the files is, say, \"png\". If you want to keep the original extension you can use \"%d\" at the second prompt (downcase). ***** Batch-rename files from number 001 to 00x Use \"\\#\" inside the second prompt. Example 1: To rename the files foo.jpg bar.jpg baz.jpg to foo-001.jpg foo-002.jpg foo-003.jpg use \"%.\" as matching regexp and \"foo-\\#\" as replacement string. Example 2: To rename the files foo.jpg bar.jpg baz.jpg to foo-001.jpg bar-002.jpg baz-003.jpg use as matching regexp \"%.\" and as replacement string \"\\@-\\#\". ***** Replace a substring Use \"%::\". Example: To rename files foo.jpg bar.jpg baz.jpg to fOo.jpg bAr.jpg bAz.jpg use as matching regexp \"%:1:2\" and as replacement string \"%u\" (upcase). Note that you \*cannot* use \"%.\" and \".%\" along with substring replacement. ***** Modify the string from the placeholder (\\@) - By substring, i.e. only using the substring of the placeholder: \"\\@::\". The length of placeholder is used for when unspecified. Example 1: \"\\@:0:2\" replaces from the beginning to the second char of the placeholder. Example 2: \\@:2: replaces from the second char of the placeholder to the end. - By search-and-replace: \"\\@///\". Incremental replacement is also handled in . Example 3: \"\\@/foo/bar/\" replaces \"foo\" by \"bar\" in the placeholder. Example 4: \"\\@/foo/-\\#/\" replaces \"foo\" in the placeholder by 001, 002, etc. ***** Clash in replacements (avoid overwriting files) When performing any of these replacement operations you may end up with same names as replacement. In such cases Helm numbers the file that would otherwise overwritten. For instance, should you remove the \"-m\" part from the files \"emacs-m1.txt\", \"emacs-m2.txt\" and \"emacs-m3.txt\" you would end up with three files named \"emacs.txt\", the second renaming overwriting first file, and the third renaming overwriting second file and so on. Instead Helm will automatically rename the second and third files as \"emacs(1).txt\" and \"emacs(2).txt\" respectively. ***** Query-replace on filenames vs. serial-rename action Unlike the [[Serial renaming][serial rename]] actions, the files renamed with the query-replace action stay in their initial directory and are not moved to the current directory. As such, using \"\\#\" to serial-rename files only makes sense for files inside the same directory. It even keeps renaming files with an incremental number in the next directories. *** Serial renaming You can use the serial-rename actions to rename, copy or symlink marked files to a specific directory or in the current directory with all the files numbered incrementally. - Serial-rename by renaming: Rename all marked files with incremental numbering to a specific directory. - Serial-rename by copying: Copy all marked files with incremental numbering to a specific directory. - Serial-rename by symlinking: Symlink all marked files with incremental numbering to a specific directory. *** Edit marked files in a dired buffer You can open a dired buffer containing only marked files with `\\\\[helm-ff-run-marked-files-in-dired]'. With a prefix argument you can open this same dired buffer in wdired mode for editing. Note that wildcards are supported as well, so you can use e.g. \"*.txt\" to select all \".txt\" files in the current directory or \"**.txt\" to select all files recursively from the current directory. See [[Use the wildcard to select multiple files]] section above. *** Defining default target directory for copying, renaming, etc You can customize `helm-dwim-target' to behave differently depending on the windows open in the current frame. Default is to provide completion on all directories associated to each window. *** Copying/Renaming from or to remote directories Never use ssh tramp method to copy/rename large files, use instead its scp method if you want to avoid out of memory problems and crash Emacs or the whole system. Moreover when using scp method, you will hit a bug when copying more than 3 files at the time, see [[https://github.com/emacs-helm/helm/issues/1945][bug#1945]]. The best way currently is using Rsync to copy files from or to remote, see [[Use Rsync to copy files][Use Rsync to copy files]]. Also if you often work on remote you may consider using SSHFS instead of relying on tramp. *** Copying and renaming asynchronously If you have the async library installed (if you got Helm from MELPA you do), you can use it for copying/renaming files by enabling `dired-async-mode'. Note that even when async is enabled, running a copy/rename action with a prefix argument will execute action synchronously. Moreover it will follow the first file of the marked files in its destination directory. When `dired-async-mode' is enabled, an additional action named \"Backup files\" will be available. (Such command is not natively available in Emacs). See [[Use the wildcard to select multiple files]] for details. *** Multiple copies of a file The command \\\\[helm-ff-run-mcp] allows copying a single file to multiple directories. To use it, mark the file you want to copy first and then mark the directories where you want to copy file. For example if you run \\[helm-ff-run-mcp] on the marked candidates '(\"foo.txt\" \"bar/\" \"baz\"), \"foo.txt\" will be copied to directories \"bar/\" and \"baz/\". *** Use Rsync to copy files If Rsync is available, you can use it to copy/sync files or directories with some restrictions though: - Copying from/to tramp sudo method may not work (permissions). - Copying from remote to remote is not supported (rsync restriction) however you can mount a remote with sshfs and copy to it (best), otherwise you have to modify the command line with a prefix arg, see [[https://unix.stackexchange.com/questions/183504/how-to-rsync-files-between-two-remotes][how-to-rsync-files-between-two-remotes]] for the command line to use. This command is mostly useful when copying large files as it is fast, asynchronous and provide a progress bar in mode-line. Each rsync process have its own progress bar, so you can run several rsync jobs, they are independents. If rsync fails you can consult the \"*helm-rsync*\" buffer to see rsync errors. An help-echo (move mouse over progress bar) is provided to see which file is in transfer. Note that when copying directories, no trailing slashes are added to directory names, which mean that directory is created on destination if it doesn't already exists, see rsync documentation for more infos on rsync behavior. To synchronize a directory, mark all in the directory and rsync all marked to the destination directory or rsync the directory itself to its parent, e.g. remote:/home/you/music => /home/you. The options are configurable through `helm-rsync-switches', but you can modify them on the fly when needed by using a prefix arg, in this case you will be prompted for modifications. NOTE: When selecting a remote file, if you use the tramp syntax for specifying a port, i.e. host#2222, helm will add automatically \"-e 'ssh -p 2222'\" to the rsync command line unless you have specified yourself the \"-e\" option by editing rsync command line with a prefix arg (see above). *** Drag and drop files from Helm When mouse support is enabled in Helm (which is the default, see `helm-allow-mouse') you can drag and drop files to `default-directory' of the buffer you drag-and-drop in or in an external application. By default Helm copy files when using drag-and-drop, you can customize `helm-ff-drag-mouse-1-default-action' to modify this, however note that other actions than `copy' are not supported when dropping in dired buffers as of now, it works when dropping in external apps like Thunar though. Also drag-and-drop is not working when trying to drop in a window that is in another desktop, in this case use the following solution. To drag-and-drop to external applications you can also use the external application [[https://github.com/mwh/dragon][Dragon]] and use it as follow: #+begin_src elisp (defun helm-ff-dragon (files) \"Create a small window with FILES ready to drag and drop. Use this to drop files on externals applications or desktop. Dropping on emacs buffers with this is not supported. Needs `dragon' executable: https://github.com/mwh/dragon.\" (interactive (list (helm-marked-candidates))) (cl-assert (executable-find \"dragon\") nil \"Dragon executable not found\") (apply #'call-process \"dragon\" nil nil nil \"--all\" \"--and-exit\" files)) (define-key helm-find-files-map (kbd \"C-c m\") 'helm-ff-dragon) #+end_src Tip: From the Dragon window, you can move your mouse to an other desktop where the external application you want to drag-and-drop is. NOTE: Drag-and-dropping from elsewhere to Helm is forbidden. *** Access files on Android phones from Helm Since Android doesn't provide anymore mass storage for USB, it is not simple to access files on Android, the best way to do this currently seems to use Adb, here some hints to set this up, read in addition the Tramp documentation. 1) Install Adb, most distribution provide it. 2) Enable on your phone USB debug in System/dvlpmnt settings. 3) From helm-find-files use adb tramp method: /adb::/ From there you can navigate as usual, mark and copy files etc... *** Bookmark the `helm-find-files' session You can bookmark the `helm-find-files' session with `\\[helm-ff-bookmark-set]'. You can later retrieve these bookmarks by calling `helm-filtered-bookmarks' or, from the current `helm-find-files' session, by hitting `\\[helm-find-files-switch-to-bookmark]'. *** Grep files from `helm-find-files' You can grep individual files from `helm-find-files' by using \`\\\\[helm-ff-run-grep]'. This same command can also recursively grep files from the current directory when called with a prefix argument. In this case you will be prompted for the file extensions to use \(grep backend) or the types of files to use (ack-grep backend). See the `helm-grep-default-command' documentation to set this up. For compressed files or archives, use zgrep with \`\\\\[helm-ff-run-zgrep]'. Otherwise you can use recursive commands like \`\\\\[helm-ff-run-grep-ag]' or `\\\\[helm-ff-run-git-grep]' that are much faster than using `\\\\[helm-ff-run-grep]' with a prefix argument. See `helm-grep-ag-command' and `helm-grep-git-grep-command' to set this up. You can also use \"id-utils\"' GID with \`\\\\[helm-ff-run-gid]' by creating an ID index file with the \"mkid\" shell command. All those grep commands use the symbol at point as the default pattern. Note that default is different from input (nothing is added to the prompt until you hit `\\[next-history-element]'). **** Grepping on remote files On remote files grep is not well supported by TRAMP unless you suspend updates before entering the pattern and re-enable it once your pattern is ready. To toggle suspend-update, use `\\\\[helm-toggle-suspend-update]'. *** Compressing or uncompressing files from helm-find-files **** Compressing/uncompressing using Helm commands Helm provide commands like dired (reusing dired code) to (un)compress files from `helm-find-files', however these commands are asynchronous. You can use `\\\\[helm-ff-run-compress-marked-files]' to compress marked files. To compress file(s) to an archive use `\\\\[helm-ff-run-compress-to]'. To quickly compress/uncompress small files without quitting Helm use `\\\\[helm-ff-persistent-compress]'. NOTE: This persistent action is NOT asynchronous, IOW it will block Helm for a while until compression/uncompression finish. **** Compressing/uncompressing using external commands in Eshell You can use Eshell aliases to uncompress files, see [[Execute Eshell commands on files][Execute Eshell commands on files]] for more infos. Here some aliases using commands from the excellent =atools= package: alias pack2zip apack -e -F .zip $* & alias pack2gz apack -e -F .tar.gz $* & alias pack2bz apack -e -F .tar.bz $* & alias pack2xz apack -e -F .tar.xz $* & alias unpack aunpack $1 & Note the \"&\" at end of commands that make eshell aliases asynchronous. NOTE: Using the ampersand at end of command to make it asynchronous is broken in all emacs versions before emacs-28 (see emacs bug#50209). Of course you can use any other commands of your choice as aliases. *** Execute Eshell commands on files Setting up aliases in Eshell allows you to set up powerful customized commands. Your aliases for using eshell command on file should allow specifying one or more files, use e.g. \"alias foo $1\" or \"alias foo $*\", if you want your command to be asynchronous add at end \"&\", e.g. \"alias foo $* &\". Adding Eshell aliases to your `eshell-aliases-file' or using the `alias' command from Eshell allows you to create personalized commands not available in `helm-find-files' actions and use them from `\\\\[helm-ff-run-eshell-command-on-file]'. Example: You want a command to uncompress some \"*.tar.gz\" files from `helm-find-files': 1) Create an Eshell alias named, say, \"untargz\" with the command \"alias untargz tar zxvf $*\". 2) Now from `helm-find-files' select the \"*.tar.gz\" file (you can also mark files if needed) and hit `\\\\[helm-ff-run-eshell-command-on-file]'. Note: When using marked files with this, the meaning of the prefix argument is quite subtle. Say you have \"foo\", \"bar\" and \"baz\" marked; when you run the alias command `example' on these files with no prefix argument it will run `example' sequentially on each file: $ example foo $ example bar $ example baz With a prefix argument however it will apply `example' on all files at once: $ example foo bar baz Of course the alias command should support this. NOTE: Helm assume that any alias command ending with '$*' or '$*&' supports many files as arguments, so no need to give a prefix arg for such alias, however if your command is not an alias you have to specify a prefix arg if you want your command to apply all files at once. If you add %s to the command line %s will be replaced with the candidate, this mean you can add extra argument to your command e.g. command -extra-arg %s or command %s -extra-arg. If you want to pass many files inside %s, don't forget to use a prefix arg. You can also use special placeholders in extra-args, see the specific info page once you hit `\\\\[helm-ff-run-eshell-command-on-file]'. *** Using TRAMP with `helm-find-files' to read remote directories `helm-find-files' works fine with TRAMP despite some limitations. - Grepping files is not very well supported when used incrementally. See [[Grepping on remote files]]. - Locate does not work on remote directories. **** A TRAMP syntax crash course Please refer to TRAMP's documentation for more details. - Connect to host 192.168.0.4 as user \"foo\": /scp:192.168.0.4@foo: - Connect to host 192.168.0.4 as user \"foo\" on port 2222: /scp:192.168.0.4@foo#2222: - Connect to host 192.168.0.4 as root using multihops syntax: /ssh:192.168.0.4@foo|sudo:192.168.0.4: Note: You can also use `tramp-default-proxies-alist' when connecting often to the same hosts. As a rule of thumb, prefer the scp method unless using multihops (which only works with the ssh method), especially when copying large files. IMPORTANT: You need to hit `C-j' once on top of a directory on the first connection to complete the pattern in the minibuffer. **** Display color for directories, symlinks etc... with tramp Starting at helm version 2.9.7 it is somewhat possible to colorize fnames by listing files without loosing performances with external commands (ls and awk) if your system is compatible. For this you can use `helm-list-dir-external' as value for `helm-list-remote-directory-fn'. See `helm-list-remote-directory-fn' documentation for more infos. **** Completing host As soon as you enter the first \":\" after method e.g =/scp:= you will have some completion about previously used hosts or from your =~/.ssh/config= file, hitting `\\[helm-execute-persistent-action]' or `right' on a candidate will insert this host in minibuffer without addind the ending \":\", second hit insert the last \":\". As soon the last \":\" is entered TRAMP will kick in and you should see the list of candidates soon after. **** Completion on tramp methods If you enter \":\" directly after \"/\" or \"|\" you will have completion on tramp methods, hitting `\\[helm-execute-persistent-action]' or `right' on a method will insert it in minibuffer. When connection fails, be sure to delete your TRAMP connection with M-x `helm-delete-tramp-connection' before retrying. **** Editing local files as root Use the sudo method: \"/sudo:host:\" or simply \"/sudo::\". *** Attach files to a mail buffer (message-mode) If you are in a `message-mode' or `mail-mode' buffer, that action will appear in action menu, otherwise it is available at any time with \\\\[helm-ff-run-mail-attach-files]. It behaves as follows: - If you are in a (mail or message) buffer, files are attached there. - If you are not in a mail buffer but one or more mail buffers exist, you are prompted to attach files to one of these mail buffers. - If you are not in a mail buffer and no mail buffer exists, a new mail buffer is created with the attached files in it. *** Open files in separate windows When [[Marked candidates][marking]] multiple files or using [[Use the wildcard to select multiple files][wildcard]], helm allow opening all this files in separate windows using an horizontal layout or a vertical layout if you used a prefix arg, when no more windows can be displayed in frame, next files are opened in background without being displayed. When using \\\\[helm-ff-run-switch-other-window] the current buffer is kept and files are displayed next to it with same behavior as above. When using two prefix args, files are opened in background without beeing displayed. *** Expand archives as directories in a avfs directory If you have mounted your filesystem with 'mountavfs' command, you can expand archives in the \"~/.avfs\" directory with \\\\[helm-execute-persistent-action]. To umount Avfs, use ~fusermount -u ~/.avfs~ NOTE: You need the package 'avfs', on debian like distros use ~apt-get install avfs~. *** Tramp archive support (emacs-27+ only) As Tramp archive often crash Helm and Emacs, Helm does its best to disable it, however it is hard to do so as Tramp Archive is enabled inconditionally in Emacs. Here I build my Emacs without-dbus to ensure Tramp archive wont kickin unexpectedly. If you want to browse archives please use [[Expand archives as directories in a avfs directory][Avfs]] which is much better and stable. *** Touch files In the completion buffer, you can choose the default which is the current-time, it is the first candidate or the timestamp of one of the selected files. If you need to use something else, use \\\\[next-history-element] and edit the date in minibuffer. It is also a way to quickly create a new file without opening a buffer, saving it and killing it. To touch more than one new file, separate you filenames with a comma (\",\"). If one wants to create (touch) a new file with comma inside the name use a prefix arg, this will prevent splitting the name and create multiple files. *** Change mode on files (chmod) When running `\\\\[helm-ff-run-chmod]' on marked files, you can enter the new mode in prompt but you can also use the first marked file as model to use it as default. For example you can mark a file with mode 777 and mark other files with mode 664, press 'RET' and answer 'y', all marked files will be changed to 777. To see the default mode and eventually edit it use 'M-n'. You can use numeric mode or letters, see 'chmod' man page for more infos. NOTE: Another way to change modes on files in helm-find-files is running `\\\\[helm-ff-run-switch-to-shell]' and use 'chmod' directly. *** Delete files You can delete files without quitting helm with `\\\\[helm-ff-persistent-delete]' or delete files and quit helm with `\\[helm-ff-run-delete-file]'. In the second method you can choose to make this command asynchronous by customizing \`helm-ff-delete-files-function'. _WARNING_: When deleting files asynchronously you will NOT be WARNED if directories are not empty, that's mean non empty directories will be deleted recursively in background without asking. A good compromise is to trash your files when using asynchronous method (see [[Trashing files][Trashing files]]). When choosing synchronous delete, you can allow recursive deletion of directories with `helm-ff-allow-recursive-deletes'. Note that when trashing (synchronous) you are not asked for recursive deletion. Note that `helm-ff-allow-recursive-deletes' have no effect when deleting asynchronously. First method (persistent delete) is always synchronous. Note that when a prefix arg is given, trashing behavior is inversed. See [[Trashing files][Trashing files]]. **** Trashing files If you want to trash your files instead of deleting them you can set `delete-by-moving-to-trash' to non nil, like this your files will be moved to trash instead of beeing deleted. You can reverse at any time the behavior of `delete-by-moving-to-trash' by using a prefix arg with any of the delete files command. On GNULinux distributions, when navigating to a Trash directory you can restore any file in ..Trash/files directory with the 'Restore from trash' action you will find in action menu (needs the trash-cli package installed for remote files, see [[Trashing remote files with tramp][Here]]). You can as well delete files from Trash directories with the 'delete files from trash' action. If you want to know where a file will be restored, hit `M-i', you will find a trash info. Tip: Navigate to your Trash/files directories with `helm-find-files' and set a bookmark there with \\\\[helm-ff-bookmark-set] for fast access to Trash. NOTE: Restoring files from trash is working only on system using the [[http://freedesktop.org/wiki/Specifications/trash-spec][freedesktop trash specifications]]. ***** Trashing remote files with tramp Trashing remote files (or local files with sudo method) is disabled by default because tramp is requiring the 'trash' command to be installed, if you want to trash your remote files, customize `helm-trash-remote-files'. The package on most GNU/Linux based distributions is trash-cli, it is available [[https://github.com/andreafrancia/trash-cli][here]]. NOTE: When deleting your files with sudo method, your trashed files will not be listed with trash-list command line until you log in as root. *** Checksum file Checksum is calculated with the md5sum, sha1sum, sha224sum, sha256sum, sha384sum and sha512sum commands when available, otherwise the Emacs function `secure-hash' is used but it is slow and may crash Emacs and even the whole system as it eats all memory. So if your system doesn't have the md5sum and sha*sum command line tools be careful when checking sum of larges files e.g. isos. *** Ignored or boring files Helm-find-files can ignore files matching `helm-boring-file-regexp-list' or files that are git ignored, you can set this with `helm-ff-skip-boring-files' or `helm-ff-skip-git-ignored-files'. NOTE: This will slow down helm, be warned. *** Helm-find-files is using a cache Helm is caching each directory files list in a hash table for faster search, when a directory is modified it is removed from cache so that further visit in this directory refresh cache. You may have in some rare cases to refresh directory manually with `\\\\[helm-refresh]' for example when helm-find-files session is running and a file is modified/deleted in current visited directory by an external command from outside Emacs. NOTE: Helm is using file-notify to watch visited directories, nowaday most systems come with a notify package but if your system doesn't support this, you can turn off file notifications by customizing the variable `helm-ff-use-notify'. In this case you will have to refresh manually directories when needed with `\\\\[helm-refresh]'. *** Prefix file candidates with icons If `all-the-icons' package is installed, turning on `helm-ff-icon-mode' will show icons before files and directories. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-ff-run-locate]|Run `locate' (`\\[universal-argument]' to specify locate database, `M-n' to insert basename of candidate). |\\[helm-ff-run-browse-project]|Browse project (`\\[universal-argument]' to recurse, `\\[universal-argument] \\[universal-argument]' to recurse and refresh database). |\\[helm-ff-run-find-sh-command]|Run `find' shell command from this directory. |\\[helm-ff-run-grep]|Run Grep (`\\[universal-argument]' to recurse). |\\[helm-ff-run-pdfgrep]|Run Pdfgrep on marked files. |\\[helm-ff-run-zgrep]|Run zgrep (`\\[universal-argument]' to recurse). |\\[helm-ff-run-grep-ag]|Run AG grep on current directory. |\\[helm-ff-run-git-grep]|Run git-grep on current directory. |\\[helm-ff-run-gid]|Run gid (id-utils). |\\[helm-ff-run-etags]|Run Etags (`\\[universal-argument]' to use thing-at-point, `\\[universal-argument] \\[universal-argument]' to reload cache). |\\[helm-ff-run-rename-file]|Rename Files (`\\[universal-argument]' to follow). |\\[helm-ff-run-query-replace-fnames-on-marked]|Query replace on marked files. |\\[helm-ff-run-copy-file]|Copy Files (`\\[universal-argument]' to follow). |\\[helm-ff-run-mcp]|Copy car of marked to remaining directories. |\\[helm-ff-run-rsync-file]|Rsync Files (`\\[universal-argument]' to edit command). |\\[helm-ff-run-byte-compile-file]|Byte Compile Files (`\\[universal-argument]' to load). |\\[helm-ff-run-load-file]|Load Files. |\\[helm-ff-run-symlink-file]|Symlink Files. |\\[helm-ff-run-hardlink-file]|Hardlink files. |\\[helm-ff-run-relsymlink-file]|Relative symlink Files. |\\[helm-ff-run-chmod]|Change mode on Files. |\\[helm-ff-run-delete-file]|Delete Files. |\\[helm-ff-run-touch-files]|Touch files. |\\[helm-ff-run-kill-buffer-persistent]|Kill buffer candidate without leaving Helm. |\\[helm-ff-persistent-delete]|Delete file without leaving Helm. |\\[helm-ff-run-switch-to-shell]|Switch to prefered shell. |\\[helm-ff-run-eshell-command-on-file]|Eshell command on file (`\\[universal-argument]' to apply on marked files, otherwise treat them sequentially). |\\[helm-ff-run-ediff-file]|Ediff file. |\\[helm-ff-run-ediff-merge-file]|Ediff merge file. |\\[helm-ff-run-complete-fn-at-point]|Complete file name at point. |\\[helm-ff-run-switch-other-window]|Switch to other window. |\\[helm-ff-run-switch-other-frame]|Switch to other frame. |\\[helm-ff-run-open-file-externally]|Open file with external program (`\\[universal-argument]' to choose). |\\[helm-ff-run-preview-file-externally]|Preview file with external program. |\\[helm-ff-run-open-file-with-default-tool]|Open file externally with default tool. |\\[helm-ff-rotate-left-persistent]|Rotate image left. |\\[helm-ff-rotate-right-persistent]|Rotate image right. |\\[helm-ff-increase-image-size-persistent]|Zoom in image. |\\[helm-ff-decrease-image-size-persistent]|Zoom out image. |\\[helm-ff-toggle-thumbnails]|Show thumbnails on image files. |\\[helm-find-files-up-one-level]|Go to parent directory. |\\[helm-find-files-history]|Switch to the visited-directory history. |\\[helm-ff-file-name-history]|Switch to file name history. |\\[helm-ff-properties-persistent]|Show file properties in a tooltip. |\\[helm-mark-all]|Mark all visible candidates. |\\[helm-ff-run-toggle-auto-update]|Toggle auto-expansion of directories. |\\[helm-unmark-all]|Unmark all candidates, visible and invisible ones. |\\[helm-ff-run-mail-attach-files]|Attach files to message buffer. |\\[helm-ff-run-print-file]|Print file, (`\\[universal-argument]' to refresh printer list). |\\[helm-enlarge-window]|Enlarge Helm window. |\\[helm-narrow-window]|Narrow Helm window. |\\[helm-ff-run-toggle-basename]|Toggle basename/fullpath. |\\[helm-ff-run-find-file-as-root]|Find file as root. |\\[helm-ff-run-find-alternate-file]|Find alternate file. |\\[helm-ff-run-insert-org-link]|Insert org link. |\\[helm-ff-bookmark-set]|Set bookmark to current directory. |\\[helm-find-files-switch-to-bookmark]|Jump to bookmark list. |\\[helm-ff-sort-alpha]|Sort alphabetically. |\\[helm-ff-sort-by-newest]|Sort by newest. |\\[helm-ff-sort-by-size]|Sort by size. |\\[helm-ff-toggle-dirs-only]|Show only directories. |\\[helm-ff-toggle-files-only]|Show only files. |\\[helm-ff-sort-by-ext]|Sort by extensions. |\\[helm-ff-run-compress-to]|Compress file(s) to archive. |\\[helm-ff-run-compress-marked-files]|Compress file(s). |\\[helm-ff-persistent-compress]|Compress file(s) without quitting.") ;;; Help for file-name-history ;; ;; (defvar helm-file-name-history-help-message "* Helm file name history ** Tips You can open directly the selected file and exit helm or preselect the file in helm-find-files, see actions in action menu. You can toggle the view of deleted files, see commands below. ** Commands \\ \\[helm-file-name-history-show-or-hide-deleted]|Toggle deleted files view.") ;;; Help for `helm-read-file-name' ;; ;; (defun helm-read-file-name-help-message () (let ((name (if helm-alive-p (assoc-default 'name (helm-get-current-source)) "generic"))) (format "* Helm `%s' read file name completion This is `%s' read file name completion that have been \"helmized\" because you have enabled [[Helm mode][helm-mode]]. Don't confuse this with `helm-find-files' which is a native helm command, see [[Helm functions vs helmized Emacs functions]]. ** Tips *** Navigation **** Enter `~/' at end of pattern to quickly reach home directory **** Enter `/' at end of pattern to quickly reach the file system root **** Enter `./' at end of pattern to quickly reach `default-directory' \(As per its value at the beginning of the session.) If you already are in the `default-directory' this will move the cursor to the top. **** Enter `../' at end of pattern will reach upper directory, moving cursor on top This is different from using `\\[helm-find-files-up-one-level]' in that it moves the cursor to the top instead of remaining on the previous subdir name. **** You can complete with partial basename It starts from the third character of the pattern. For instance \"fob\" or \"fbr\" will complete \"foobar\" but \"fb\" needs a third character in order to complete it. *** Persistent actions By default `helm-read-file-name' uses the persistent actions of `helm-find-files'. **** Use `\\[universal-argument] \\\\[helm-execute-persistent-action]' to display an image **** `\\\\[helm-execute-persistent-action]' on a filename will expand to this filename in Helm-buffer Second hit displays the buffer filename. Third hit kills the buffer filename. Note: `\\[universal-argument] \\\\[helm-execute-persistent-action]' displays the buffer directly. **** Browse images directories with `helm-follow-mode' and navigate up/down *** Delete characters backward When you want to delete characters backward, e.g. to create a new file or directory, auto-update may come in the way when it keeps updating to an existent directory. In that case, type `C-' and then `'. This should not be needed when copying/renaming files because autoupdate is disabled by default in that case. Note: On a terminal, the default binding `C-' may not work. In this case use `C-c '. *** Create new directories and files **** You can create a new directory and a new file at the same time Simply write the path in prompt and press `RET', e.g. \"~/new/newnew/newnewnew/my_newfile.txt\". **** To create a new directory, append a \"/\" at to the new name and press `RET' **** To create a new file, enter a filename not ending with \"/\" File and directory creation works only with some commands (e.g. `find-file') and it will not work with others where it is not intended to return a file or a directory \(e.g `list-directory'). *** Exiting minibuffer with empty string You can exit minibuffer with empty string with \\\\[helm-cr-empty-string]. It is useful when some commands are prompting continuously until you enter an empty prompt. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-find-files-up-one-level]|Go to parent directory. |\\[helm-ff-run-toggle-auto-update]|Toggle auto-expansion of directories. |\\[helm-ff-run-toggle-basename]|Toggle basename. |\\[helm-ff-file-name-history]|File name history. |C/\\[helm-cr-empty-string]|Return empty string unless `must-match' is non-nil. |\\[helm-next-source]|Go to next source. |\\[helm-previous-source]|Go to previous source." name name))) ;;; FD help ;; ;; (defvar helm-fd-help-message "* Helm fd ** Tips \[[https://github.com/sharkdp/fd][The Fd command line tool]] is very fast to search files recursively. You may have to wait several seconds at first usage when your hard drive cache is \"cold\", then once the cache is initialized searchs are very fast. You can pass any [[https://github.com/sharkdp/fd#command-line-options][Fd options]] before pattern, e.g. \"-e py foo\". The [[https://github.com/sharkdp/fd][Fd]] command line can be customized with `helm-fd-switches' user variable. Always use =--color always= as option otherwise you will have no colors. To customize colors see [[https://github.com/sharkdp/fd#colorized-output][Fd colorized output]]. NOTE: Starting from fd version 8.2.1, you have to provide the env var LS_COLORS to Emacs to have a colorized output, the easiest way is to add to your =~/.profile= file =eval $(dircolors)=. Another way is using =setenv= in your init file. This is not needed when running Emacs from a terminal either with =emacs -nw= or =emacs= because emacs inherit the env vars of this terminal. See [[https://github.com/sharkdp/fd/issues/725][fd bugref#725]] Search is (pcre) regexp based (see [[https://docs.rs/regex/1.0.0/regex/#syntax][Regexp syntax]]), multi patterns are _not_ supported. ** Man page NAME fd - find entries in the filesystem SYNOPSIS fd [-HIEsiaLp0hV] [-d depth] [-t filetype] [-e ext] [-E exclude] [-c when] [-j num] [-x cmd] [pattern] [path...] DESCRIPTION fd is a simple, fast and user-friendly alternative to find(1). OPTIONS -H, --hidden Include hidden files and directories in the search results (default: hidden files and directories are skipped). -I, --no-ignore Show search results from files and directories that would other‐ wise be ignored by .gitignore, .ignore, .fdignore, or the global ignore file. -u, --unrestricted Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'. --no-ignore-vcs Show search results from files and directories that would other‐ wise be ignored by .gitignore files. -s, --case-sensitive Perform a case-sensitive search. By default, fd uses case-insen‐ sitive searches, unless the pattern contains an uppercase char‐ acter (smart case). -i, --ignore-case Perform a case-insensitive search. By default, fd uses case- insensitive searches, unless the pattern contains an uppercase character (smart case). -g, --glob Perform a glob-based search instead of a regular expression search. --regex Perform a regular-expression based seach (default). This can be used to override --glob. -F, --fixed-strings Treat the pattern as a literal string instead of a regular expression. -a, --absolute-path Shows the full path starting from the root as opposed to rela‐ tive paths. -l, --list-details Use a detailed listing format like 'ls -l'. This is basically an alias for '--exec-batch ls -l' with some additional 'ls' options. This can be used to see more metadata, to show symlink targets and to achieve a deterministic sort order. -L, --follow By default, fd does not descend into symlinked directories. Using this flag, symbolic links are also traversed. -p, --full-path By default, the search pattern is only matched against the file‐ name (or directory name). Using this flag, the pattern is matched against the full path. -0, --print0 Separate search results by the null character (instead of new‐ lines). Useful for piping results to xargs. --max-results count Limit the number of search results to 'count' and quit immedi‐ ately. -1 Limit the search to a single result and quit immediately. This is an alias for '--max-results=1'. --show-errors Enable the display of filesystem errors for situations such as insufficient permissions or dead symlinks. --one-file-system, --mount, --xdev By default, fd will traverse the file system tree as far as other options dictate. With this flag, fd ensures that it does not descend into a different file system than the one it started in. Comparable to the -mount or -xdev filters of find(1). -h, --help Print help information. -V, --version Print version information. -d, --max-depth d Limit directory traversal to at most d levels of depth. By default, there is no limit on the search depth. --min-depth d Only show search results starting at the given depth. See also: '--max-depth' and '--exact-depth'. --exact-depth d Only show search results at the exact given depth. This is an alias for '--min-depth --max-depth '. -t, --type filetype Filter search by type: f, file regular files d, directory directories l, symlink symbolic links x, executable executable (files) e, empty empty files or directories s, socket sockets p, pipe named pipes (FIFOs) This option can be used repeatedly to allow for multiple file types. -e, --extension ext Filter search results by file extension ext. This option can be used repeatedly to allow for multiple possible file extensions. -E, --exclude pattern Exclude files/directories that match the given glob pattern. This overrides any other ignore logic. Multiple exclude pat‐ terns can be specified. --ignore-file path Add a custom ignore-file in '.gitignore' format. These files have a low precedence. -c, --color when Declare when to colorize search results: auto Colorize output when standard output is connected to terminal (default). never Do not colorize output. always Always colorize output. -j, --threads num Set number of threads to use for searching & executing (default: number of available CPU cores). -S, --size size Limit results based on the size of files using the format <+-> '+' file size must be greater than or equal to this '-' file size must be less than or equal to this 'NUM' The numeric size (e.g. 500) 'UNIT' The units for NUM. They are not case-sensitive. Allowed unit values: 'b' bytes 'k' kilobytes (base ten, 10^3 = 1000 bytes) 'm' megabytes 'g' gigabytes 't' terabytes 'ki' kibibytes (base two, 2^10 = 1024 bytes) 'mi' mebibytes 'gi' gibibytes 'ti' tebibytes --changed-within date|duration Filter results based on the file modification time. The argument can be provided as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). --change-newer- than can be used as an alias. Examples: --changed-within 2weeks --change-newer-than \"2018-10-27 10:00:00\" --changed-before date|duration Filter results based on the file modification time. The argument can be provided as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). --change-older- than can be used as an alias. Examples: --changed-before \"2018-10-27 10:00:00\" --change-older-than 2weeks -o, --owner [user][:group] Filter files by their user and/or group. Format: [(user|uid)][:(group|gid)]. Either side is optional. Precede either side with a '!' to exclude files instead. Examples: --owner john --owner :students --owner \"!john:students\" -x, --exec command Execute command for each search result. The following placehold‐ ers are substituted by a path derived from the current search result: {} path {/} basename {//} parent directory {.} path without file extension {/.} basename without file extension -X, --exec-batch command Execute command with all search results at once. A single occurence of the following placeholders is authorized and sub stituted by the paths derived from the search results before the command is executed: {} path {/} basename {//} parent directory {.} path without file extension {/.} basename without file extension ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-ff-run-grep]|Run grep (`\\[universal-argument]' to recurse). |\\[helm-ff-run-zgrep]|Run zgrep. |\\[helm-ff-run-pdfgrep]|Run PDFgrep on marked files. |\\[helm-ff-run-copy-file]|Copy file(s) |\\[helm-ff-run-rename-file]|Rename file(s). |\\[helm-ff-run-symlink-file]|Symlink file(s). |\\[helm-ff-run-hardlink-file]|Hardlink file(s). |\\[helm-ff-run-delete-file]|Delete file(s). |\\[helm-ff-run-byte-compile-file]|Byte compile Elisp file(s) (`\\[universal-argument]' to load). |\\[helm-ff-run-load-file]|Load Elisp file(s). |\\[helm-ff-run-ediff-file]|Ediff file. |\\[helm-ff-run-ediff-merge-file]|Ediff-merge file. |\\[helm-ff-run-switch-other-window]|Switch to other window. |\\[helm-ff-properties-persistent]|Show file properties. |\\[helm-ff-run-open-file-externally]|Open file with external program (`\\[universal-argument]' to choose). |\\[helm-ff-run-open-file-with-default-tool]|Open file externally with default tool. |\\[helm-ff-run-insert-org-link]|Insert org link. |\\[helm-fd-previous-directory]|Move to previous directory. |\\[helm-fd-next-directory]|Move to next directory.") ;;; Generic file help - Used by locate. ;; ;; (defvar helm-generic-file-help-message "* Helm Generic files ** Tips *** Locate You can append to the search pattern any of the locate command line options, e.g. -b, -e, -n , etc. See the locate(1) man page for more details. Some other sources (at the moment \"recentf\" and \"file in current directory\") support the -b flag for compatibility with locate when they are used with it. When you enable fuzzy matching on locate with `helm-locate-fuzzy-match', the search will be performed on basename only for efficiency (so don't add \"-b\" at prompt). As soon as you separate the patterns with spaces, fuzzy matching will be disabled and search will be done on the full filename. Note that in multi-match, fuzzy is completely disabled, which means that each pattern is a match regexp (i.e. \"helm\" will match \"helm\" but \"hlm\" will *not* match \"helm\"). NOTE: On Windows use Everything with its command line ~es~ as a replacement of locate. See [[https://github.com/emacs-helm/helm/wiki/Locate#windows][Locate on Windows]] On recent systems, the updatedb command doesn't index anymore user directories, see the option PRUNE_BIND_MOUNTS in the updatedb man page. *** Browse project When the current directory is not under version control, don't forget to refresh the cache when files have been added/removed in the directory. *** Find command Recursively search files using the \"find\" shell command. Candidates are all filenames that match all given globbing patterns. This respects the options `helm-case-fold-search' and `helm-findutils-search-full-path'. You can pass arbitrary \"find\" options directly after a \"*\" separator. For example, this would find all files matching \"book\" that are larger than 1 megabyte: book * -size +1M ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-ff-run-toggle-basename]|Toggle basename. |\\[helm-ff-run-grep]|Run grep (`\\[universal-argument]' to recurse). |\\[helm-ff-run-zgrep]|Run zgrep. |\\[helm-ff-run-pdfgrep]|Run PDFgrep on marked files. |\\[helm-ff-run-copy-file]|Copy file(s) |\\[helm-ff-run-rename-file]|Rename file(s). |\\[helm-ff-run-symlink-file]|Symlink file(s). |\\[helm-ff-run-hardlink-file]|Hardlink file(s). |\\[helm-ff-run-delete-file]|Delete file(s). |\\[helm-ff-run-byte-compile-file]|Byte compile Elisp file(s) (`\\[universal-argument]' to load). |\\[helm-ff-run-load-file]|Load Elisp file(s). |\\[helm-ff-run-ediff-file]|Ediff file. |\\[helm-ff-run-ediff-merge-file]|Ediff-merge file. |\\[helm-ff-run-switch-other-window]|Switch to other window. |\\[helm-ff-properties-persistent]|Show file properties. |\\[helm-ff-run-open-file-externally]|Open file with external program (`\\[universal-argument]' to choose). |\\[helm-ff-run-open-file-with-default-tool]|Open file externally with default tool. |\\[helm-ff-run-insert-org-link]|Insert org link.") ;;; Grep help ;; ;; (defvar helm-grep-help-message "* Helm Grep ** Tips With Helm supporting Git-grep and AG/RG, you are better off using one of them for recursive searches, keeping grep or ack-grep to grep individual or marked files. See [[Helm AG][Helm AG]]. *** Meaning of the prefix argument **** With grep or ack-grep Grep recursively, in this case you are prompted for types (ack-grep) or for wild cards (grep). **** With AG or RG the prefix arg allows you to specify a type of file to search in. *** You can use wild cards when selecting files (e.g. \"*.el\") Note that a way to grep specific files recursively is to use e.g. \"**.el\" to select files, the variable `helm-file-globstar' controls this (it is non nil by default), however it is much slower than using grep recusively (see helm-find-files documentation about this feature). *** Grep hidden files You may want to customize your command line for grepping hidden files, for AG/RG use \"--hidden\", see man page of your backend for more infos. *** You can grep in different directories by marking files or using wild cards *** You can save the result in a `helm-grep-mode' buffer See [[Commands][commands]] below. Once in that buffer you can use [[https://github.com/mhayashi1120/Emacs-wgrep][emacs-wgrep]] (external package not bundled with Helm) to edit your changes, for Helm the package name is `wgrep-helm', it is hightly recommended. Type `g' to update (revert) the buffer (after saving your buffer's changes to file). NOTE: `next-error' is available from this `helm-grep-mode' buffer. When you are running `next-error' from elsewhere, you can update the buffer with `helm-revert-next-error-last-buffer' (up to you to bind it to a convenient key). *** Helm-grep supports multi-matching \(Starting from version 1.9.4.) Simply add a space between each pattern as for most Helm commands. NOTE: Depending the regexp you use it may match as well the filename, this because we pipe the first grep command which send the filename in output. *** See full path of selected candidate Add (helm-popup-tip-mode 1) in your init file or enable it interactively with M-x helm-popup-tip-mode, however it is generally enough to just put your mouse cursor over candidate. *** Stop grepping with AG or RG and search in the current results Once you have found your results with helm grep ag you can search in these results (helm search, no grep). This allows narrowing the results to a specific file for example or to a specific pattern without launching grep ag/rg. The command is \\\\[helm-grep-ag-run-search-results]. If you want to continue grepping, switch to the previous helm session which is helm-grep-ag session with \\\\[helm-resume-previous-session-after-quit]. _NOTE_: This is available only in AG/RG, not grep. *** Grep AG/RG on parent directory The command \\\\[helm-grep-run-ag-grep-parent-directory] allow to grep the parent directory of the currently searched directory. *** Open file in other window The command \\\\[helm-grep-run-other-window-action] allow you to open file in other window horizontally or vertically if a prefix arg is supplied. *** Performance over TRAMP Grepping works but it is badly supported as TRAMP doesn't support multiple processes running in a short delay (less than 5s) among other things. Helm uses a special hook to suspend the process automatically while you are typing. Even if Helm handles this automatically by delaying each process by 5s, you are adviced to this manually by hitting `\\\\[helm-toggle-suspend-update]' (suspend process) before typing, and hit again `\\\\[helm-toggle-suspend-update]' when the regexp is ready to send to the remote process. For simple regexps, there should be no need for this. Another solution is to not use TRAMP at all and mount your remote file system via SSHFS. * Helm GID Still supported, but mostly deprecated, using AG/RG or Git-grep is much more efficient, also `id-utils' seems no more maintained. ** Tips Helm-GID reads the database created with the `mkid' command from id-utils. The name of the database file can be customized with `helm-gid-db-file-name', it is usually \"ID\". Helm-GID use the symbol at point as default-input. This command is also accessible from `helm-find-files' which allow you to navigate to another directory to consult its database. Note: Helm-GID supports multi-matches but only the last pattern entered will be highlighted since there is no ~--color~-like option in GID itself. * Helm AG ** Tips Helm-AG is different from grep or ack-grep in that it works on a directory recursively and not on a list of files. It is called helm-AG but it support several backend, namely AG, RG and PT. Nowaday the best backend is Ripgrep aka RG, it is the fastest and is actively maintained, see `helm-grep-ag-command' and `helm-grep-ag-pipe-cmd-switches' to configure it. You can ignore files and directories with a \".agignore\" or \".rgignore\" file, local to a directory or global when placed in the home directory. (See the AG/RG man pages for more details.) Note that `helm-grep-ignored-files'and `helm-grep-ignored-directories' have no effect in helm-AG/RG. As always you can access Helm AG from `helm-find-files'. Starting with version 0.30, AG accepts one or more TYPE arguments on its command line. Helm provides completion on these TYPE arguments when available with your AG version. Use a prefix argument when starting a Helm-AG session to enable this completion. See RG and AG man pages on how to add new types. Note: You can mark several types to match in the AG query. The first AG versions providing this feature allowed only one type, so in this case only the last mark will be used. * Helm git-grep Helm-git-grep searches the current directory, i.e. the default directory or the directory in Helm-find-files. If this current directory is a subdirectory of a project and you want to also match parent directories (i.e the whole project), use a prefix argument. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-goto-next-file]|Next File. |\\[helm-goto-precedent-file]|Previous File. |\\[helm-yank-text-at-point]|Yank text at point in minibuffer. |\\[helm-grep-run-other-window-action]|Jump to other window. |\\[helm-grep-run-other-frame-action]|Jump to other frame. |\\[helm-grep-run-default-action]|Run default action (same as `RET'). |\\[helm-grep-run-save-buffer]|Save to a `helm-grep-mode' enabled buffer.") ;;; PDF grep help ;; ;; (defvar helm-pdfgrep-help-message "* Helm PDFgrep Map ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-goto-next-file]|Next file. |\\[helm-goto-precedent-file]|Previous file. |\\[helm-yank-text-at-point]|Yank text at point in minibuffer.") ;;; Etags help ;; ;; (defvar helm-etags-help-message "* Helm Etags Map ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-goto-next-file]|Next file. |\\[helm-goto-precedent-file]|Previous file. |\\[helm-yank-text-at-point]|Yank text at point in minibuffer.") ;;; UCS help ;; ;; (defvar helm-ucs-help-message "* Helm UCS ** Tips Use commands below to insert unicode characters in current buffer without leaving Helm. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-ucs-persistent-insert]|Insert character. |\\[helm-ucs-persistent-forward]|Forward character. |\\[helm-ucs-persistent-backward]|Backward character. |\\[helm-ucs-persistent-delete]|Delete character backward. |\\[helm-ucs-persistent-insert-space]|Insert space.") ;;; Bookmark help ;; ;; (defvar helm-bookmark-help-message "* Helm bookmark name ** Tips *** Display bookmarks with Icons When `helm-bookmark-use-icon' is non nil and you have a `helm-x-icons-provider' either `all-the-icons' or `nerd-icons' package is installed icons before candidates will be displayed. *** Reset bookmark position When using `helm-filtered-bookmarks' you can modify your bookmark position by resetting it, to do this, jump to your bookmark, move point to the new location, then call again `helm-filtered-bookmarks' and find your current bookmark, yank its name in minibuffer with `\\\\[helm-yank-selection]' switch to the \"Set bookmark\" source and hit RET. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-bookmark-run-jump-other-window]|Jump other window. |\\[helm-bookmark-run-delete]|Delete bookmark. |\\[helm-bookmark-run-edit]|Edit bookmark. |\\[helm-bookmark-toggle-filename]|Toggle bookmark location visibility.") ;;; Eshell command on file help ;; ;; (defvar helm-esh-help-message "* Helm Eshell on file ** Tips *** Pass extra arguments after filename Normally the command or alias will be called with file as argument. For instance candidate_file But you can also pass an argument or more after \"candidate_file\" like this: %s [extra_args] \"candidate_file\" will be added at \"%s\" and the command will look at this: candidate_file [extra_args] **** Use placeholders in extra arguments placeholder for file without extension: \\@ placeholder for incremental number: \\# \"candidate_file\" will be added at \"%s\" and \\@ but without extension. \"candidate_file\" will be added at \"%s\" and \\# will be replaced by an incremental number. %s \\# Here examples: Say you want to use the =convert= command to convert all your .png files in a directory to .jpg. This will convert all your files to jpg keeping the same basename. convert %s \\@.jpg This will convert all your files to foo-001.jpg, foo-002.jpg etc... convert %s foo-\\#.jpg You can of course combine both placeholders if needed. convert %s \\@-\\#.jpg *** Specify marked files as arguments When you have marked files and your command support only one file as arg, helm will execute command sequencially on each file like this: Example: file1 file2 ...etc When you have marked files and your command accept many files at once helm will run your command with all files at once like this: Example: file1 file2 etc... The two use case above are applied automatically by Helm depending if your command is an eshell alias which value ends by '$1' or '$*'. If your command is not an alias, i.e. you entered an arbitrary command on prompt with '%s' to specify filenames, you will have to pass one prefix argument from the command selection buffer. Note: This does not work on remote files. With two prefix-args the output is printed to the `current-buffer', the command being executed in the same conditions as described above. NOTE: If your command is not an alias, you can't pass all files at once and print in current buffer at the same time. Also note that running multiple files at once is not supported with remote files. *** Run eshell commands asynchronously You can run your commands asynchronously by adding \"&\" at end of any commands, e.g. \"foo %s &\". You can also directly setup your alias in the eshell alias file with e.g. \"alias foo $1 &\". ** Commands \\") ;;; Ido virtual buffer help ;; ;; (defvar helm-buffers-ido-virtual-help-message "* Helm Ido virtual buffers ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-ff-run-switch-other-window]|Switch to other window. |\\[helm-ff-run-switch-other-frame]|Switch to other frame. |\\[helm-ff-run-grep]|Grep file. |\\[helm-ff-run-zgrep]|Zgrep file. |\\[helm-ff-run-delete-file]|Delete file. |\\[helm-ff-run-open-file-externally]|Open file externally.") ;;; helm-occur help ;; ;; (defvar helm-moccur-help-message "* Helm Moccur ** Tips *** Searching in many buffers Start from `helm-buffers-list' or `helm-mini', mark some buffers and hit \\\\[helm-execute-persistent-action]' (persistent-action), to do it repeatedly you can use `\\\\[helm-follow-action-forward]' and `\\\\[helm-follow-action-backward]' or enable `helm-follow-mode' with `\\\\[helm-follow-mode]'. Follow mode is enabled by default in helm-occur. *** Switch to buffer in other window The command \\\\[helm-moccur-run-goto-line-ow] allow you to switch to buffer in other window horizontally or vertically if a prefix arg is supplied. *** Save the results Similarly to Helm-grep, you can save the results with `\\\\[helm-occur-run-save-buffer]'. Once in the saved buffer, you can edit it, see [[Edit a saved buffer][below]]. Of course if you don't save the results, you can resume the Helm session with `helm-resume'. *** Refresh the resumed session When the buffer(s) where you ran helm-(m)occur get(s) modified, the Helm buffer will flash red as a warning. You can refresh the buffer by running `\\\\[helm-refresh]'. This can be done automatically by customizing `helm-moccur-auto-update-on-resume'. *** Refresh a saved buffer Type `g' to update (revert) the buffer. When you are running `next-error' from elsewhere, you can update the buffer with `helm-revert-next-error-last-buffer' (up to you to bind it to a convenient key). *** Edit a saved buffer First, install wgrep (https://github.com/mhayashi1120/Emacs-wgrep) and then: 1) `C-c C-p' (`wgrep-change-to-wgrep-mode') to edit the buffer(s). 2) `C-x C-s' to save your changes. Tip: Use the excellent iedit (https://github.com/victorhge/iedit) to modify all occurences at once in the buffer. NOTE: `next-error' is available from this `helm-occur-mode' buffer. *** Search in region When searching in current-buffer with `helm-occur', if a region is found helm will search in this region only. If you marked this region with `mark-defun' the symbol that was at point before marking defun will be used when `helm-source-occur' is member of `helm-sources-using-default-as-input'. *** Switch to next or previous source See [[Moving in `helm-buffer'][Moving in `helm-buffer']]. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-occur-run-goto-line-ow]|Go to line in other window. |\\[helm-occur-run-goto-line-of]|Go to line in new frame. |\\[helm-occur-run-save-buffer]|Save results in new buffer.") ;;; Helm Top ;; ;; (defvar helm-top-help-message "* Helm Top ** Tips *** Auto update You can enable auto updating in `helm-top' by turning on `helm-top-poll-mode' either interactively or in your init file with (helm-top-poll-mode 1). Calling `helm-top' with a prefix arg also toggle auto updating. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-top-run-sort-by-com]|Sort by commands. |\\[helm-top-run-sort-by-cpu]|Sort by CPU usage. |\\[helm-top-run-sort-by-user]|Sort alphabetically by user. |\\[helm-top-run-sort-by-mem]|Sort by memory.") ;;; Helm M-x ;; ;; (defvar helm-M-x-help-message "* Helm M-x ** Tips *** Display docstring without quitting session (persistent action) You can get help on any command with persistent action (\\\\[helm-execute-persistent-action]) *** Display short docstring in helm buffer You can toggle short docstring description with \\\\[helm-M-x-toggle-short-doc]. if you want this at startup you can configure `helm-M-x-show-short-doc'. NOTE: helm-M-x will be slower with this enabled. *** History source Helm-M-x is displaying two sources, one for the commands themselves and one for the command history, more exactly `extended-command-history', by default the history source is displayed in first position, however you can put it in second position if you don't like that by customizing `helm-M-x-reverse-history'. **** Duplicate entries in helm-M-x history helm-M-x history obey to history variables, if you have duplicates in your helm-M-x history set `history-delete-duplicates' to non nil. **** Number of entries in history The number of entries saved is controlled by `history-length' global value, however if you want a different value for `extended-command-history' e.g. 50 you can add to your config: (put 'extended-command-history 'history-length 50) *** Enabled modes are highlighted in helm-M-x *** Prefix arguments You can pass prefix arguments *after* starting `helm-M-x'. A mode-line counter will display the number of given prefix arguments. If you pass prefix arguments before running `helm-M-x', it will be displayed in the prompt. The first `\\\\[universal-argument]' after `helm-M-x' clears those prefix arguments. NOTE: When you specify prefix arguments once `helm-M-x' is started, the prefix argument apply on the next command, so if you hit RET, it will apply on the selected command, but if you type a new character at prompt to narrow down further candidates, the prefix arg will apply to `self-insert-command' (e.g. if you type `C-u e' \"eeee\" will be inserted in prompt) so select the command you want to execute before specifying prefix arg. ** Commands \\ |Keys|Description |-----------+------------| |\\[helm-M-x-universal-argument]|Universal argument for selected command |\\[helm-M-x-toggle-short-doc]|Toggle details on commands") ;;; Helm imenu ;; ;; (defvar helm-imenu-help-message "* Helm Imenu ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-imenu-next-section]|Go to next section. |\\[helm-imenu-previous-section]|Go to previous section.") ;;; Helm colors ;; ;; (defvar helm-colors-help-message "* Helm colors ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-color-run-insert-name]|Insert the entry name. |\\[helm-color-run-kill-name]|Kill the entry name. |\\[helm-color-run-insert-rgb]|Insert entry in RGB format. |\\[helm-color-run-kill-rgb]|Kill entry in RGB format.") ;;; Helm Semantic ;; ;; (defvar helm-semantic-help-message "* Helm Semantic ** Commands \\") ;;; Helm kmacro ;; ;; (defvar helm-kmacro-help-message "* Helm kmacro ** Tips - Start recording a kmacro with `f3'. - End the kmacro recording with `f4'. - Run `helm-execute-kmacro' to list all your kmacros. When you press RET, your macro goes on top of ring and become the current macro, hit `f4' for further executions. Use `helm-execute-kmacro' again to change eventually your macro to execute. Note: You can't record keys running Helm commands except `helm-M-x', under the condition that you don't choose a command using Helm completion. See [[info:emacs#Keyboard Macros][Keyboard Macros]] for further infos on macros. ** Commands \\") ;;; Kill ring ;; ;; (defvar helm-kill-ring-help-message "* Helm kill ring ** Tips Every Helm session lets you save a candidate to the kill-ring / clipboard / primary-selection with `\\\\[helm-kill-selection-and-quit]'. To save space, Helm-kill-ring truncates the candidates longer than `helm-kill-ring-max-offset'. `\\\\[helm-kill-ring-kill-selection]' then saves the whole text and not the truncated value. The view of truncated candidates can be toggled; see the command list below. As opposed to `yank', numeric prefix arguments are ignored with `helm-show-kill-ring': there is no need for them since selection happens within Helm. Moreover Helm has [[Shortcuts for executing the default action on the n-th candidate][Shortcuts for executing the default action on the n-th candidate]]. It is recommended to globally bind `M-y' to `helm-show-kill-ring'. Once in the Helm-kill-ring session you can navigate to next/previous line with `M-y' and `M-u' for convenience. Of course `\\[helm-next-line]' and `\\[helm-previous-line]' are still available. It is possible to delete candidates from the kill ring with `\\\\[helm-kill-ring-delete]' but also persistently with `\\\\[helm-kill-ring-run-persistent-delete]'. You can concatenate marked candidates and yank them in the current buffer, thus creating a new entry in the kill ring. Candidates are concatenated with `helm-kill-ring-separator' as default but you can change interactively the separator while yanking by using two prefix args. When you have something else than \"\\n\" as default value for `helm-kill-ring-separator' and you want to use \"\\n\" from prompt, use `C-q C-j' to enter a newline in prompt. To not push a new entry in the kill ring, use `\\\\[helm-copy-to-buffer]' instead of RET \(note that you can't change separator with this). When inserting candidates with the default action (`RET'), `point' is placed at the end of the candidate and `mark' at the beginning. You can revert this behavior by using a prefix argument, i.e. `C-u RET', like the regular `yank' command does. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-next-line]|Next line. |\\[helm-previous-line]|Previous line. |\\[helm-kill-ring-delete]|Delete entry. |\\[helm-kill-ring-toggle-truncated]|Toggle truncated view of candidate. |\\[helm-kill-ring-kill-selection]|Kill non-truncated of selection.") ;;; Completing-read ;; (defun helm-comp-read-help-message () (let ((com (assoc-default 'name (helm-get-current-source)))) (format "* Helm completing-read completion for `%s' Command `%s' is using a `completing-read' for completion on your input, this completion have been \"helmized\" because you have enabled [[Helm mode][helm-mode]]'. ** Tips *** Disabling or use something else than helm for completion of some commands You can disable helm completion or use something else for specific commands of your choice, for this customize variable `helm-completing-read-handlers-alist'. *** Exiting minibuffer with empty string You can exit minibuffer with empty string with \\\\[helm-cr-empty-string]. It is useful when some commands are prompting continuously until you enter an empty prompt. ** Commands \\ |Keys|Description |-----------+----------| |\\[helm-cr-empty-string]|Exit minibuffer with empty string." com com))) ;;; Mode line strings ;; ;; ;;;###autoload (defvar helm-comp-read-mode-line "\ \\\ C/\\[helm-cr-empty-string]:Empty \ \\\ \\[helm-help]:Help \ \\[helm-select-action]:Act \ \\[helm-maybe-exit-minibuffer]/\ f1/f2/f-n:NthAct \ \\[helm-toggle-suspend-update]:Tog.suspend \ \\[helm-customize-group]:Conf") ;;;###autoload (defvar helm-read-file-name-mode-line-string "\ \\\ \\[helm-help]:Help \ C/\\[helm-cr-empty-string]:Empty \ \\\ \\[helm-select-action]:Act \ \\[helm-maybe-exit-minibuffer]/\ f1/f2/f-n:NthAct \ \\[helm-toggle-suspend-update]:Tog.suspend \ \\[helm-customize-group]:Conf" "String displayed in mode-line in `helm-source-find-files'.") ;;;###autoload (defvar helm-top-mode-line "\ \\\ \\[helm-help]:Help \ \\\ \\[helm-select-action]:Act \ \\[helm-maybe-exit-minibuffer]/\ f1/f2/f-n:NthAct \ \\[helm-toggle-suspend-update]:Tog.suspend \ \\[helm-customize-group]:Conf") (provide 'helm-help) ;;; helm-help.el ends here helm-4.0.3/helm-id-utils.el000066400000000000000000000122211501106761700154440ustar00rootroot00000000000000;;; helm-id-utils.el --- Helm interface for id-utils. -*- lexical-binding: t -*- ;; Copyright (C) 2015 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm-grep) (require 'helm-help) (defgroup helm-id-utils nil "ID-Utils related Applications and libraries for Helm." :group 'helm) (defcustom helm-gid-program "gid" "Name of gid command (usually `gid'). For Mac OS X users, if you install GNU coreutils, the name `gid' might be occupied by `id' from GNU coreutils, and you should set it to correct name (or absolute path). For example, if using MacPorts to install id-utils, it should be `gid32'." :group 'helm-id-utils :type 'file) (defcustom helm-gid-db-file-name "ID" "Name of a database file created by `mkid' command from `ID-utils'." :group 'helm-id-utils :type 'string) (defun helm-gid-candidates-process () (let* ((patterns (helm-mm-split-pattern helm-pattern)) (default-com (format "%s -r %s" helm-gid-program (shell-quote-argument (car patterns)))) (cmd (helm-aif (cdr patterns) (concat default-com (cl-loop for p in it concat (format " | grep --color=always %s" (shell-quote-argument p)))) default-com)) (proc (start-process-shell-command "gid" helm-buffer cmd))) (set (make-local-variable 'helm-grep-last-cmd-line) cmd) (prog1 proc (set-process-sentinel proc (lambda (_process event) (when (string= event "finished\n") (helm-maybe-show-help-echo) (with-helm-window (setq mode-line-format '(" " mode-line-buffer-identification " " (:eval (format "L%s" (helm-candidate-number-at-point))) " " (:eval (propertize (format "[Helm Gid process finished - (%s results)]" (max (1- (count-lines (point-min) (point-max))) 0)) 'face 'helm-locate-finish)))) (force-mode-line-update)) (helm-log "helm-gid-candidates-process" "Error: Gid %s" (replace-regexp-in-string "\n" "" event)))))))) (defun helm-gid-filtered-candidate-transformer (candidates _source) ;; "gid -r" may add dups in some rare cases. (cl-loop for c in (helm-fast-remove-dups candidates :test 'equal) collect (helm-grep--filter-candidate-1 c))) (defclass helm-gid-source (helm-source-async) ((header-name :initform (lambda (name) (concat name " [" (helm-get-attr 'db-dir) "]"))) (db-dir :initarg :db-dir :initform nil :custom string :documentation " Location of ID file.") (candidates-process :initform #'helm-gid-candidates-process) (filtered-candidate-transformer :initform #'helm-gid-filtered-candidate-transformer) (popup-info :initform #'helm-grep-popup-info-fn) (candidate-number-limit :initform 99999) (action :initform (helm-make-actions "Find File" 'helm-grep-action "Find file other frame" 'helm-grep-other-frame "Save results in grep buffer" 'helm-grep-save-results "Find file other window" 'helm-grep-other-window)) (persistent-action :initform 'helm-grep-persistent-action) (history :initform 'helm-grep-history) (nohighlight :initform t) (help-message :initform 'helm-grep-help-message) (requires-pattern :initform 2))) ;;;###autoload (defun helm-gid () "Preconfigured `helm' for `gid' command line of `ID-Utils'. Need A database created with the command `mkid' above `default-directory'. Need id-utils as dependency which provide `mkid', `gid' etc.. See ." (interactive) (let* ((db (locate-dominating-file default-directory helm-gid-db-file-name)) (helm-grep-default-directory-fn (lambda () default-directory)) (helm-maybe-use-default-as-input t)) (cl-assert db nil "No DataBase found, create one with `mkid'") (helm :sources (helm-make-source "Gid" 'helm-gid-source :db-dir db) :buffer "*helm gid*" :keymap helm-grep-map :truncate-lines helm-grep-truncate-lines))) (provide 'helm-id-utils) ;;; helm-id-utils ends here helm-4.0.3/helm-imenu.el000066400000000000000000000617221501106761700150410ustar00rootroot00000000000000;;; helm-imenu.el --- Helm interface for Imenu -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-lib) (require 'imenu) (require 'helm-utils) (require 'helm-help) (require 'helm-x-icons) (defvar all-the-icons-default-adjust) (defvar all-the-icons-scale-factor) (defvar nerd-icons-default-adjust) (defvar nerd-icons-scale-factor) (declare-function which-function "which-func") (defgroup helm-imenu nil "Imenu related libraries and applications for Helm." :group 'helm) (defcustom helm-imenu-delimiter " / " "Delimit types of candidates and their value in `helm-buffer'." :group 'helm-imenu :type 'string) (defcustom helm-imenu-execute-action-at-once-if-one #'helm-imenu--execute-action-at-once-p "Goto the candidate when only one is remaining." :group 'helm-imenu :type 'function) (defcustom helm-imenu-all-buffer-assoc nil "Major mode association alist for `helm-imenu-in-all-buffers'. Allow `helm-imenu-in-all-buffers' searching in these associated buffers even if they are not derived from each other. The alist is bidirectional, i.e. no need to add \\='((foo . bar) (bar . foo)), only \\='((foo . bar)) is needed." :type '(alist :key-type symbol :value-type symbol) :group 'helm-imenu) (defcustom helm-imenu-in-all-buffers-separate-sources t "Display imenu index of each buffer in its own source when non-nil. When nil all candidates are displayed in a single source. NOTE: Each source will have as name \"Imenu \". `helm-source-imenu-all' will not be set, however it will continue to be used as a flag for using default as input. If you do not want this behavior, remove it from `helm-sources-using-default-as-input' even if not using a single source to display imenu in all buffers." :type 'boolean :group 'helm-imenu) (defcustom helm-imenu-type-faces '(("^Variables$" . font-lock-variable-name-face) ("^\\(Function\\|Functions\\|Defuns\\)$" . font-lock-function-name-face) ("^\\(Types\\|Provides\\|Requires\\|Classes\\|Class\\|Includes\\|Imports\\|Misc\\|Code\\)$" . font-lock-type-face)) "Faces for showing type in helm-imenu. This is a list of cons cells. The cdr of each cell is a face to be used, and it can also just be like \\='(:foreground \"yellow\"). Each car is a regexp match pattern of the imenu type string." :group 'helm-faces :type '(repeat (cons (regexp :tag "Imenu type regexp pattern") (sexp :tag "Face")))) (defcustom helm-imenu-extra-modes nil "Extra modes where `helm-imenu-in-all-buffers' should look into." :group 'helm-imenu :type '(repeat symbol)) (defcustom helm-imenu-hide-item-type-name nil "Hide display name of imenu item type along with the icon when non nil. This value can be toggled with \\\\[helm-imenu-toggle-type-view]. Don't use `setq' to set this." :group 'helm-imenu :type 'boolean :set (lambda (var val) (if (require helm-x-icons-provider nil t) (set var val) (set var nil)))) (defcustom helm-imenu-use-icon nil "Display an icon from `helm-x-icons-provider' package when non nil. Don't use `setq' to set this." :group 'helm-imenu :type 'boolean :set (lambda (var val) (if (require helm-x-icons-provider nil t) (set var val) (set var nil)))) (defcustom helm-imenu-icon-type-alist '(("Array" . (helm-x-icons-generic "crop" :face font-lock-builtin-face)) ("Arrays" . (helm-x-icons-generic "crop" :face font-lock-builtin-face)) ("Boolean" . (helm-x-icons-generic "crop" :face font-lock-builtin-face)) ("Booleans" . (helm-x-icons-generic "crop" :face font-lock-builtin-face)) ("Class" . (helm-x-icons-generic "package" :face font-lock-type-face)) ("Classes" . (helm-x-icons-generic "package" :face font-lock-type-face)) ("Color" . (helm-x-icons-generic "color_lens" :face font-lock-builtin-face)) ("Colors" . (helm-x-icons-generic "color_lens" :face font-lock-builtin-face)) ("Constant" . (helm-x-icons-generic "crop" :face font-lock-builtin-face)) ("Constants" . (helm-x-icons-generic "crop" :face font-lock-builtin-face)) ("Constructor" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Constructors" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Enum Member" . (helm-x-icons-generic "three-bars" :face font-lock-type-face)) ("Enum Members" . (helm-x-icons-generic "three-bars" :face font-lock-type-face)) ("Enum" . (helm-x-icons-generic "cog" :face font-lock-type-face)) ("Enums" . (helm-x-icons-generic "cog" :face font-lock-type-face)) ("Event" . (helm-x-icons-generic "lightning" :face font-lock-builtin-face)) ("Events" . (helm-x-icons-generic "lightning" :face font-lock-builtin-face)) ("Field" . (helm-x-icons-generic "three-bars" :face font-lock-type-face)) ("Fields" . (helm-x-icons-generic "three-bars" :face font-lock-type-face)) ("File" . (helm-x-icons-generic "file" :face font-lock-variable-name-face)) ("Files" . (helm-x-icons-generic "file" :face font-lock-variable-name-face)) ("Folder" . (helm-x-icons-generic "folder" :face font-lock-variable-name-face)) ("Folders" . (helm-x-icons-generic "folder" :face font-lock-variable-name-face)) ("Interface" . (helm-x-icons-generic "package" :face font-lock-builtin-face)) ("Interfaces" . (helm-x-icons-generic "package" :face font-lock-builtin-face)) ("Keyword" . (helm-x-icons-generic "key" :face font-lock-builtin-face)) ("Keywords" . (helm-x-icons-generic "key" :face font-lock-builtin-face)) ("Method" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Methods" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Defun" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Defuns" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Fn" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Fns" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Function" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Functions" . (helm-x-icons-generic "cube" :face font-lock-function-name-face)) ("Misc" . (helm-x-icons-generic "globe" :face font-lock-function-name-face)) ("Miscs" . (helm-x-icons-generic "globe" :face font-lock-function-name-face)) ("Module" . (helm-x-icons-generic "angle-double-right" :face font-lock-builtin-face)) ("Modules" . (helm-x-icons-generic "angle-double-right" :face font-lock-builtin-face)) ("Numeric" . (helm-x-icons-generic "crop" :face font-lock-builtin-face)) ("Numerics" . (helm-x-icons-generic "crop" :face font-lock-builtin-face)) ("Object" . (helm-x-icons-generic "angle-double-right" :face font-lock-builtin-face)) ("Objects" . (helm-x-icons-generic "angle-double-right" :face font-lock-builtin-face)) ("Operator" . (helm-x-icons-generic "calculator" :face font-lock-builtin-face)) ("Operators" . (helm-x-icons-generic "calculator" :face font-lock-builtin-face)) ("Property" . (helm-x-icons-generic "book" :face font-lock-variable-name-face)) ("Properties" . (helm-x-icons-generic "book" :face font-lock-variable-name-face)) ("Reference" . (helm-x-icons-generic "book" :face font-lock-variable-name-face)) ("References" . (helm-x-icons-generic "book" :face font-lock-variable-name-face)) ("Snippet" . (helm-x-icons-generic "border_style" :face font-lock-variable-name-face)) ("Snippets" . (helm-x-icons-generic "border_style" :face font-lock-variable-name-face)) ("String" . (helm-x-icons-generic "text_fields" :face font-lock-variable-name-face)) ("Strings" . (helm-x-icons-generic "text_fields" :face font-lock-variable-name-face)) ("Struct" . (helm-x-icons-generic "cog" :face font-lock-type-face)) ("Structs" . (helm-x-icons-generic "cog" :face font-lock-type-face)) ("Text" . (helm-x-icons-generic "text_fields" :face font-lock-variable-name-face)) ("Texts" . (helm-x-icons-generic "text_fields" :face font-lock-variable-name-face)) ("Top level" . (helm-x-icons-generic "globe" :face font-lock-function-name-face)) ("Trait" . (helm-x-icons-generic "package" :face font-lock-builtin-face)) ("Traits" . (helm-x-icons-generic "package" :face font-lock-builtin-face)) ("Type" . (helm-x-icons-generic "cog" :face font-lock-type-face)) ("Types" . (helm-x-icons-generic "cog" :face font-lock-type-face)) ("Type Parameter" . (helm-x-icons-generic "code" :face font-lock-type-face)) ("Type Parameters" . (helm-x-icons-generic "code" :face font-lock-type-face)) ("Unit" . (helm-x-icons-generic "bar-chart" :face font-lock-builtin-face)) ("Units" . (helm-x-icons-generic "bar-chart" :face font-lock-builtin-face)) ("Value" . (helm-x-icons-generic "cog" :face font-lock-type-face)) ("Values" . (helm-x-icons-generic "cog" :face font-lock-type-face)) ("Variable" . (helm-x-icons-generic "book" :face font-lock-variable-name-face)) ("Variables" . (helm-x-icons-generic "book":face font-lock-variable-name-face))) "An alist of types associated with a sexp returning an icon. The sexp should be an `all-the-icons' function with its args." :type '(alist :key-type string :value-type sexp) :group 'helm-imenu) (defcustom helm-imenu-default-type-sexp '(helm-x-icons-generic "globe" :face font-lock-function-name-face) "Default sexp to use when no type for an object is found." :type 'sexp :group 'helm-imenu) ;;; keymap (defvar helm-imenu-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "M-") 'helm-imenu-next-section) (define-key map (kbd "M-") 'helm-imenu-previous-section) (define-key map (kbd "C-]") 'helm-imenu-toggle-type-view) map)) (defun helm-imenu-toggle-type-view () "Toggle candidate type view." (interactive) (with-helm-window (setq helm-imenu-hide-item-type-name (not helm-imenu-hide-item-type-name)) (let* ((sel (substring (helm-get-selection nil 'withprop) (if helm-imenu-use-icon 2 0))) (type (get-text-property 1 'type-name sel))) (setq sel (substring-no-properties sel)) (helm-force-update (if helm-imenu-hide-item-type-name (format "^[ ]*%s$" (car (last (split-string sel helm-imenu-delimiter t)))) (format "^[ ]*%s / %s$" type sel)))))) (put 'helm-imenu-toggle-type-view 'no-helm-mx t) (defcustom helm-imenu-lynx-style-map nil "Use Arrow keys to jump to occurences." :group 'helm-imenu :type 'boolean :set (lambda (var val) (set var val) (if val (progn (define-key helm-imenu-map (kbd "") 'helm-execute-persistent-action) (define-key helm-imenu-map (kbd "") 'helm-maybe-exit-minibuffer)) (define-key helm-imenu-map (kbd "") nil) (define-key helm-imenu-map (kbd "") nil)))) (defun helm-imenu-next-or-previous-section (n) (with-helm-window (let* ((fn (lambda () (let ((str (buffer-substring (pos-bol) (pos-eol)))) (if helm-imenu-hide-item-type-name (get-text-property 1 'type-name str) (car (split-string str helm-imenu-delimiter)))))) (curtype (funcall fn)) (stop-fn (if (> n 0) #'helm-end-of-source-p #'helm-beginning-of-source-p))) (while (and (not (funcall stop-fn)) (string= curtype (funcall fn))) (forward-line n)) (helm-mark-current-line) (helm-follow-execute-persistent-action-maybe)))) (defun helm-imenu-next-section () (interactive) (helm-imenu-next-or-previous-section 1)) (defun helm-imenu-previous-section () (interactive) (helm-imenu-next-or-previous-section -1)) ;;; Internals (defvar helm-cached-imenu-alist nil) (make-variable-buffer-local 'helm-cached-imenu-alist) (defvar helm-cached-imenu-candidates nil) (make-variable-buffer-local 'helm-cached-imenu-candidates) (defvar helm-cached-imenu-tick nil) (make-variable-buffer-local 'helm-cached-imenu-tick) (defvar helm-imenu--in-all-buffers-cache nil) (defvar helm-source-imenu nil "See (info \"(emacs)Imenu\")") (defvar helm-source-imenu-all nil) (defclass helm-imenu-source (helm-source-sync) ((candidates :initform 'helm-imenu-candidates) (candidate-transformer :initform 'helm-imenu-transformer) (persistent-action :initform 'helm-imenu-persistent-action) (persistent-help :initform "Show this entry") (nomark :initform t) (keymap :initform 'helm-imenu-map) (help-message :initform 'helm-imenu-help-message) (action :initform 'helm-imenu-action) (find-file-target :initform #'helm-imenu-quit-and-find-file-fn) (group :initform 'helm-imenu))) (defcustom helm-imenu-fuzzy-match nil "Enable fuzzy matching in `helm-source-imenu'." :group 'helm-imenu :type 'boolean :set (lambda (var val) (set var val) (setq helm-source-imenu (helm-make-source "Imenu" 'helm-imenu-source :fuzzy-match helm-imenu-fuzzy-match)))) (defun helm-imenu--maybe-switch-to-buffer (candidate) (let ((cand (cdr candidate))) (helm-aif (and (markerp cand) (marker-buffer cand)) (helm-buffers-switch-to-buffer-or-tab it)))) (defun helm-imenu--execute-action-at-once-p () (let ((cur (helm-get-selection)) (mb (with-helm-current-buffer (save-excursion (goto-char (pos-bol)) (point-marker))))) ;; Happen when cursor is on the line where a definition is. This ;; prevent jumping to the definition where we are already, instead ;; display helm with all definitions and preselection to the place ;; we already are. (if (equal (cdr cur) mb) (prog1 nil (helm-set-pattern "") (helm-force-update (concat "\\_<" (regexp-quote (car cur)) "\\_>"))) t))) (defun helm-imenu-quit-and-find-file-fn (source) (let ((sel (helm-get-selection nil nil source))) (when (and (consp sel) (markerp (cdr sel))) (buffer-file-name (marker-buffer (cdr sel)))))) (defun helm-imenu-action (candidate) "Default action for `helm-source-imenu'." (helm-log-run-hook "helm-imenu-action" 'helm-goto-line-before-hook) (helm-imenu--maybe-switch-to-buffer candidate) (imenu candidate) ;; If semantic is supported in this buffer ;; imenu used `semantic-imenu-goto-function' ;; and position have been highlighted, ;; no need to highlight again. (unless (eq imenu-default-goto-function 'semantic-imenu-goto-function) (helm-highlight-current-line))) (defun helm-imenu-persistent-action (candidate) "Default persistent action for `helm-source-imenu'." (helm-imenu--maybe-switch-to-buffer candidate) (imenu candidate) (helm-highlight-current-line)) (defun helm-imenu-candidates (&optional buffer) (with-current-buffer (or buffer helm-current-buffer) (let ((tick (buffer-modified-tick))) (if (eq helm-cached-imenu-tick tick) helm-cached-imenu-candidates (setq imenu--index-alist nil) (prog1 (setq helm-cached-imenu-candidates (let ((index (imenu--make-index-alist t))) (helm-imenu--candidates-1 (delete (assoc "*Rescan*" index) index)))) (setq helm-cached-imenu-tick tick)))))) (defun helm-imenu-candidates-in-all-buffers (&optional build-sources) (let* ((lst (buffer-list)) (progress-reporter (make-progress-reporter "Imenu indexing buffers..." 1 (length lst)))) (prog1 (cl-loop with cur-buf = (if build-sources (current-buffer) helm-current-buffer) for b in lst for count from 1 when (with-current-buffer b (and (or (member major-mode helm-imenu-extra-modes) (derived-mode-p 'prog-mode)) (helm-same-major-mode-p cur-buf helm-imenu-all-buffer-assoc))) if build-sources collect (helm-make-source (format "Imenu in %s" (buffer-name b)) 'helm-imenu-source :candidates (with-current-buffer b (helm-imenu-candidates b)) :fuzzy-match helm-imenu-fuzzy-match) else append (with-current-buffer b (helm-imenu-candidates b)) do (progress-reporter-update progress-reporter count)) (progress-reporter-done progress-reporter)))) (defun helm-imenu--candidates-1 (alist) (cl-loop for elm in alist nconc (cond ((imenu--subalist-p elm) (helm-imenu--candidates-1 (cl-loop for (e . v) in (cdr elm) collect (cons (propertize e 'helm-imenu-type (car elm)) ;; If value is an integer, convert it ;; to a marker, otherwise it is a cons cell ;; and it will be converted on next recursions. ;; (Bug#1060) [1]. (if (integerp v) (copy-marker v) v))))) ((listp (cdr elm)) (and elm (list elm))) (t ;; bug in imenu, should not be needed. (and (cdr elm) ;; Semantic uses overlays whereas imenu uses ;; markers (Bug#1706). (setcdr elm (helm-acase (cdr elm) ; Same as [1]. ((guard* (overlayp it)) (copy-overlay it)) ((guard* (or (markerp it) (integerp it))) (copy-marker it)))) (list elm)))))) (defun helm-imenu--get-prop (item) ;; property value of ITEM can have itself ;; a property value which have itself a property value ;; ...and so on; Return a list of all these ;; properties values starting at ITEM. (let* ((prop (get-text-property 0 'helm-imenu-type item)) (lst (list prop item))) (when prop (while prop (setq prop (get-text-property 0 'helm-imenu-type prop)) (and prop (push prop lst))) lst))) (defun helm-imenu-icon-for-type (type) "Return an icon for type TYPE. The icon is found in `helm-imenu-icon-type-alist', if not `helm-imenu-default-type-sexp' is evaled to provide a default icon." (let ((all-the-icons-scale-factor 1.0) (all-the-icons-default-adjust 0.0) (nerd-icons-scale-factor 1.0) (nerd-icons-default-adjust 0.0)) (or (helm-aand (assoc-default type helm-imenu-icon-type-alist) (apply (car it) (cdr it))) (apply (car helm-imenu-default-type-sexp) (cdr helm-imenu-default-type-sexp))))) (defun helm-imenu-transformer (candidates) (cl-loop for (k . v) in candidates ;; (k . v) == (symbol-name . marker) for bufname = (buffer-name (helm-acase v ((guard* (overlayp it)) (overlay-buffer it)) ((guard* (markerp it)) (marker-buffer it)))) for types = (or (helm-imenu--get-prop k) (list (if (with-current-buffer bufname (derived-mode-p 'prog-mode)) "Function" "Top level") k)) for type-icon = (and helm-imenu-use-icon (helm-imenu-icon-for-type (car types))) for type-name = (propertize (car types) 'face (cl-loop for (p . f) in helm-imenu-type-faces when (string-match p (car types)) return f finally return 'default)) for disp1 = (mapconcat 'identity (cdr types) (propertize helm-imenu-delimiter 'face 'shadow)) for disp = (concat (if helm-imenu-use-icon (concat (propertize " " 'display type-icon) " ") "") (if helm-imenu-hide-item-type-name "" (concat type-name (propertize helm-imenu-delimiter 'face 'shadow))) (propertize disp1 'help-echo bufname 'types types)) collect (cons (propertize disp 'type-name type-name) (cons k v)))) ;;;###autoload (defun helm-imenu () "Preconfigured `helm' for `imenu'." (interactive) (require 'which-func) (unless helm-source-imenu (setq helm-source-imenu (helm-make-source "Imenu" 'helm-imenu-source :fuzzy-match helm-imenu-fuzzy-match))) (let* ((imenu-auto-rescan t) (helm-highlight-matches-around-point-max-lines 'never) (str (thing-at-point 'symbol)) (init-reg (and str (concat "\\_<" (regexp-quote str) "\\_>"))) (helm-execute-action-at-once-if-one helm-imenu-execute-action-at-once-if-one)) (helm :sources 'helm-source-imenu :default (and str (list init-reg str)) :preselect (helm-aif (which-function) (concat "\\_<" (regexp-quote it) "\\_>") init-reg) :buffer "*helm imenu*"))) ;;;###autoload (defun helm-imenu-in-all-buffers () "Fetch Imenu entries in all buffers with similar mode as current. A mode is similar as current if it is the same, it is derived i.e. `derived-mode-p' or it have an association in `helm-imenu-all-buffer-assoc'." (interactive) (require 'which-func) (unless helm-imenu-in-all-buffers-separate-sources (unless helm-source-imenu-all (setq helm-source-imenu-all (helm-make-source "Imenu in all buffers" 'helm-imenu-source :init (lambda () ;; Use a cache to avoid repeatedly sending ;; progress-reporter message when updating ;; (Bug#1704). (setq helm-imenu--in-all-buffers-cache (helm-imenu-candidates-in-all-buffers))) :candidates 'helm-imenu--in-all-buffers-cache :fuzzy-match helm-imenu-fuzzy-match)))) (let* ((imenu-auto-rescan t) (helm-highlight-matches-around-point-max-lines 'never) (str (thing-at-point 'symbol)) (init-reg (and str (concat "\\_<" (regexp-quote str) "\\_>"))) (helm-execute-action-at-once-if-one helm-imenu-execute-action-at-once-if-one) (helm-maybe-use-default-as-input (not (null (memq 'helm-source-imenu-all helm-sources-using-default-as-input)))) (sources (if helm-imenu-in-all-buffers-separate-sources (helm-imenu-candidates-in-all-buffers 'build-sources) '(helm-source-imenu-all)))) (helm :sources sources :default (and str (list init-reg str)) :preselect (helm-aif (which-function) (concat "\\_<" (regexp-quote it) "\\_>") init-reg) :buffer "*helm imenu all*"))) (provide 'helm-imenu) ;;; helm-imenu.el ends here helm-4.0.3/helm-info.el000066400000000000000000000376571501106761700146710ustar00rootroot00000000000000;;; helm-info.el --- Browse info index with helm -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'info) ;; helm-utils is requiring helm which is requiring helm-lib, but let's require ;; them explicitely anyway to make it clear what we need. helm-core is needed to ;; build all the helm-info-* commands and sources. (require 'helm) (require 'helm-lib) (require 'helm-utils) ; for `helm-goto-line'. (declare-function Info-index-nodes "info" (&optional file)) (declare-function Info-goto-node "info" (&optional fork)) (declare-function Info-find-node "info" (filename nodename &optional no-going-back)) (declare-function ring-insert "ring") (declare-function ring-empty-p "ring") (declare-function ring-ref "ring") (defvar Info-history) (defvar Info-directory-list) (defvar helm-completions-detailed) ;; `Info-minibuf-history' is not declared in Emacs, see emacs bug/58786. (when (and (> emacs-major-version 28) (not (boundp 'Info-minibuf-history))) (defvar Info-minibuf-history nil)) ;;; Customize (defgroup helm-info nil "Info-related applications and libraries for Helm." :group 'helm) (defcustom helm-info-default-sources '(helm-source-info-elisp helm-source-info-cl helm-source-info-eieio helm-source-info-pages) "Default sources to use for looking up symbols at point in Info files with `helm-info-at-point'." :group 'helm-info :type '(repeat (choice symbol))) ;;; Build info-index sources with `helm-info-source' class. (cl-defun helm-info-init (&optional (file (helm-get-attr 'info-file))) "Initialize candidates for info FILE. If FILE have nodes, loop through all nodes and accumulate candidates found in each node, otherwise scan only the current info buffer." ;; Allow reinit candidate buffer when using edebug. (helm-aif (and debug-on-error (helm-candidate-buffer)) (kill-buffer it)) (unless (helm-candidate-buffer) (save-window-excursion (info file " *helm info temp buffer*") (let ((tobuf (helm-candidate-buffer 'global)) Info-history) (helm-aif (Info-index-nodes) (dolist (node it) (Info-goto-node node) (helm-info-scan-current-buffer tobuf)) (helm-info-scan-current-buffer tobuf)) (bury-buffer))))) (defun helm-info-scan-current-buffer (tobuf) "Scan current info buffer and print lines to TOBUF. Argument TOBUF is the `helm-candidate-buffer'." (let (start end line) (goto-char (point-min)) (while (search-forward "\n* " nil t) (unless (search-forward "Menu:\n" (1+ (pos-eol)) t) (setq start (pos-bol) ;; Fix Bug#1503 by getting the invisible ;; info displayed on next line in long strings. ;; e.g "* Foo.\n (line 12)" instead of ;; "* Foo.(line 12)" end (or (save-excursion (goto-char (pos-bol)) (re-search-forward "(line +[0-9]+)" nil t)) (pos-eol)) ;; Long string have a new line inserted before the ;; invisible spec, remove it. line (replace-regexp-in-string "\n" "" (buffer-substring start end))) (with-current-buffer tobuf (insert line) (insert "\n")))))) (defun helm-info-goto (node-line) "The helm-info action to jump to NODE-LINE." (require 'helm-utils) (let ((alive (buffer-live-p (get-buffer "*info*")))) (Info-goto-node (car node-line)) (when alive (revert-buffer nil t)) (helm-goto-line (cdr node-line)))) (defvar helm-info--node-regexp "^\\* +\\(.+\\):[[:space:]]+\\(.*\\)\\(?:[[:space:]]*\\)(line +\\([0-9]+\\))" "A regexp that should match file name, node name and line number in a line like this: \* bind: Bash Builtins. (line 21).") (defun helm-info-display-to-real (line) "Transform LINE to an acceptable argument for `info'. If line have a node use the node, otherwise use directly first name found." (let ((info-file (helm-get-attr 'info-file)) nodename linum) (when (string-match helm-info--node-regexp line) (setq nodename (match-string 2 line) linum (match-string 3 line))) (if nodename (cons (format "(%s)%s" info-file (replace-regexp-in-string ":\\'" "" nodename)) (string-to-number (or linum "1"))) (cons (format "(%s)%s" info-file (helm-aand (replace-regexp-in-string "^* " "" line) (replace-regexp-in-string "::?.*\\'" "" it))) 1)))) (defclass helm-info-source (helm-source-in-buffer) ((info-file :initarg :info-file :initform nil :custom 'string) (init :initform #'helm-info-init) (filtered-candidate-transformer :initform (lambda (candidates _source) (require 'helm-mode) (cl-loop for line in candidates when (string-match helm-info--node-regexp line) do (progn (helm-add-face-text-properties (match-beginning 1) (match-end 1) 'font-lock-keyword-face nil line) (helm-add-face-text-properties (match-beginning 2) (match-end 2) 'helm-completions-detailed nil line)) collect line))) (display-to-real :initform #'helm-info-display-to-real) (get-line :initform #'buffer-substring) (action :initform '(("Goto node" . helm-info-goto))))) (defmacro helm-build-info-source (fname &rest args) `(helm-make-source (concat "Info Index: " ,fname) 'helm-info-source :info-file ,fname ,@args)) (defun helm-build-info-index-command (name doc source buffer) "Define a Helm command NAME with documentation DOC. Arg SOURCE will be an existing helm source named `helm-source-info-' and BUFFER a string buffer name." (defalias (intern (concat "helm-info-" name)) (lambda () (interactive) (helm :sources source :buffer buffer :candidate-number-limit 1000)) doc)) (defun helm-define-info-index-sources (info-list &optional commands) "Define Helm info sources for all entries in INFO-LIST. Sources will be named named helm-source-info- where NAME is an element of INFO-LIST. Sources are generated for all entries of `helm-default-info-index-list' which is generated by `helm-get-info-files'. If COMMANDS arg is non-nil, also build commands named `helm-info-'." (cl-loop for str in info-list for sym = (intern (concat "helm-source-info-" str)) do (set sym (helm-build-info-source str)) when commands do (helm-build-info-index-command str (format "Predefined helm for %s info." str) sym (format "*helm info %s*" str)))) (defun helm-info-index-set (var value) (set var value) (helm-define-info-index-sources value t)) ;;; Search Info files ;; `helm-info' is the main entry point here. It prompts the user for an Info ;; file, then a term in the file's index to jump to. (defvar helm-info-searched (make-ring 32) "Ring of previously searched Info files.") (defun helm-get-info-files () "Return list of Info files to use for `helm-info'. Elements of the list are strings of Info file names without extensions (e.g., \"emacs\" for file \"emacs.info.gz\"). Info files are found by searching directories in `Info-directory-list'." (info-initialize) ; Build Info-directory-list from INFOPATH (Bug#2118) (let ((files (cl-loop for d in (or Info-directory-list Info-default-directory-list) when (file-directory-p d) append (directory-files d nil "\\.info")))) (helm-fast-remove-dups (cl-loop for f in files collect (helm-file-name-sans-extension f)) :test 'equal))) (defcustom helm-default-info-index-list (helm-get-info-files) "Info files to search in with `helm-info'." :group 'helm-info :type '(repeat (choice string)) :set 'helm-info-index-set) (defun helm-info-search-index (candidate) "Search the index of CANDIDATE's Info file using the function helm-info-." (let ((helm-info-function (intern-soft (concat "helm-info-" candidate)))) (when (fboundp helm-info-function) (funcall helm-info-function) (ring-insert helm-info-searched candidate)))) (defvar helm-info--files-cache nil) (defvar helm-info--files-doc-cache (make-hash-table :test 'equal)) (defun helm-info-file-doc (file) "Return dir entry from the Info FILE." (or (with-temp-buffer (ignore-errors (info-insert-file-contents file)) (goto-char (point-min)) (when (re-search-forward "^START-INFO-DIR-ENTRY$" nil t) (forward-line 1) (when (re-search-forward "^\\*[[:space:]]+\\([^:]+\\):[[:space:]]+([^)]+)\\.[[:space:]]+\\(.+\\)" (pos-eol) t) (format-message "%s: %s" (match-string 1) (match-string 2))))) "No summary")) (defun helm-def-source--info-files () "Return a Helm source for Info files." (require 'helm-mode) (helm-build-sync-source "Helm Info" :candidates (lambda () (copy-sequence helm-default-info-index-list)) :candidate-number-limit 999 :candidate-transformer (lambda (candidates) (let ((candidates (sort candidates #'string-lessp)) (longest (apply #'max (mapcar #'length candidates))) done) (cl-loop with reporter = (unless done (make-progress-reporter "Scanning libraries..." 0 (length candidates))) for c in candidates for count from 0 for sep = (helm-make-separator c longest) for file = (or (assoc-default c helm-info--files-cache) (helm-aif (Info-find-file c t) (prog1 it (push (cons c it) helm-info--files-cache)))) for doc = (and file (or completions-detailed helm-completions-detailed) (or (gethash file helm-info--files-doc-cache) (puthash file (helm-info-file-doc file) helm-info--files-doc-cache))) for disp = (and file (if (and doc (or completions-detailed helm-completions-detailed)) (helm-aand (propertize doc 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it)) (concat c it)) c)) when disp collect (cons disp c) when reporter do (progress-reporter-update reporter count) finally do (setq done t)))) :nomark t :action '(("Search index" . helm-info-search-index)))) ;;;###autoload (defun helm-info (&optional refresh) "Preconfigured `helm' for searching Info files' indices. With a prefix argument \\[universal-argument], set REFRESH to non-nil. Optional parameter REFRESH, when non-nil, re-evaluates `helm-default-info-index-list' and clears caches (see below). If the variable has been customized, set it to its saved value. If not, set it to its standard value. See `custom-reevaluate-setting' for more. REFRESH is useful when new Info files are installed. If `helm-default-info-index-list' has not been customized, the new Info files are made available. When `completions-detailed' or `helm-completions-detailed' is non nil, a description of Info files is provided. The Info files are partially cached in the variables `helm-info--files-cache' and `helm-info--files-docs-cache'. TIP: You can make these vars persistent for faster start with the psession package, using \\[psession-make-persistent-variable]. NOTE: The caches affect as well `info-dislpay-manual' when `helm-mode' is enabled and `completions-detailed' is non nil. When new Info files are installed, for example a library update changed Info dir node entry, you can reset the caches with a prefix arg." (interactive "P") (let ((default (unless (ring-empty-p helm-info-searched) (ring-ref helm-info-searched 0)))) (when refresh (custom-reevaluate-setting 'helm-default-info-index-list) (setq helm-info--files-cache nil) (clrhash helm-info--files-doc-cache)) (helm :sources (helm-def-source--info-files) :buffer "*helm Info*" :preselect (and default (concat "\\_<" (regexp-quote default) "\\_>"))))) ;;;; Info at point ;; `helm-info-at-point' is the main entry point here. It searches for the ;; symbol at point through the Info sources defined in ;; `helm-info-default-sources' and jumps to it. (defvar helm-info--pages-cache nil "Cache for all Info pages on the system.") (defvar helm-source-info-pages (helm-build-sync-source "Info Pages" :init #'helm-info-pages-init :candidates (lambda () helm-info--pages-cache) :action `(("Show with Info" . ,(lambda (node-str) (info (replace-regexp-in-string "^[^:]+: " "" node-str))))) :requires-pattern 2) "Helm source for Info pages.") (defun helm-info-pages-init () "Collect candidates for initial Info node Top." (or helm-info--pages-cache (let ((info-topic-regexp "\\* +\\([^:]+: ([^)]+)[^.]*\\)\\.")) (save-selected-window (info "dir" " *helm info temp buffer*") (Info-find-node "dir" "top") (goto-char (point-min)) (while (re-search-forward info-topic-regexp nil t) (push (match-string-no-properties 1) helm-info--pages-cache)) (kill-buffer))))) ;;;###autoload (defun helm-info-at-point () "Preconfigured `helm' for searching info at point." (interactive) ;; Symbol at point is used as default as long as one of the sources ;; in `helm-info-default-sources' is member of ;; `helm-sources-using-default-as-input'. (let* ((current (and Info-current-file (intern-soft (concat "helm-source-info-" (helm-basename Info-current-file))))) (helm-info-default-sources (if (and current (not (memq current helm-info-default-sources))) (cons current helm-info-default-sources) helm-info-default-sources))) (cl-loop for src in helm-info-default-sources for name = (if (symbolp src) (assoc 'name (symbol-value src)) (assoc 'name src)) unless name do (warn "Couldn't build source `%S' without its info file" src)) (helm :sources helm-info-default-sources :buffer "*helm info*"))) (provide 'helm-info) ;;; helm-info.el ends here helm-4.0.3/helm-lib.el000066400000000000000000002700431501106761700144700ustar00rootroot00000000000000;;; helm-lib.el --- Helm routines. -*- lexical-binding: t -*- ;; Copyright (C) 2015 ~ 2025 Thierry Volpiatto ;; Author: Thierry Volpiatto ;; URL: http://github.com/emacs-helm/helm ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; All helm functions that don't require specific helm code should go here. ;;; Code: (require 'cl-lib) (declare-function ansi-color--find-face "ansi-color.el") (declare-function ansi-color-apply-sequence "ansi-color.el") (declare-function dired-current-directory "dired.el") (declare-function ffap-file-remote-p "ffap.el") (declare-function ffap-url-p "ffap.el") (declare-function helm-get-attr "helm-core.el") (declare-function helm-set-attr "helm-core.el") (declare-function helm-follow-mode-p "helm-core.el") (declare-function helm-get-current-source "helm-core.el") (declare-function helm-get-selection "helm-core.el") (declare-function helm-get-sources "helm-core.el") (declare-function helm-interpret-value "helm-core.el") (declare-function helm-log-run-hook "helm-core.el") (declare-function helm-next-line "helm-core.el") (declare-function helm-get-next-header-pos "helm-core.el") (declare-function helm-mark-current-line "helm-core.el") (declare-function helm-marked-candidates "helm-core.el") (declare-function helm-set-case-fold-search "helm-core.el") (declare-function helm-get-previous-header-pos "helm-core.el") (declare-function helm-source--cl--print-table "helm-source.el") (declare-function helm-update "helm-core.el") (declare-function org-content "org.el") (declare-function org-mark-ring-goto "org.el") (declare-function org-mark-ring-push "org.el") (declare-function org-table-p "org-compat.el") (declare-function org-table-align "org-table.el") (declare-function org-table-end "org-table.el") (declare-function org-open-at-point "org.el") (declare-function helm-read-file-name "helm-mode.el") (declare-function find-function-library "find-func.el") (declare-function find-library-name "find-func.el") (defvar helm-sources) (defvar helm-initial-frame) (defvar helm-current-position) (defvar helm-persistent-action-display-window) (defvar helm--buffer-in-new-frame-p) (defvar helm-completion-style) (defvar helm-completion-styles-alist) (defvar helm-persistent-action-window-buffer) (defvar helm-help-buffer-name) (defvar completion-flex-nospace) (defvar find-function-source-path) (defvar ffap-machine-p-unknown) (defvar ffap-machine-p-local) (defvar ffap-machine-p-known) (defvar helm-debug-output-buffer) ;;; User vars. ;; (defcustom helm-file-globstar t "Same as globstar bash shopt option. When non-nil a pattern beginning with two stars will expand recursively. Directories expansion is not supported yet." :group 'helm :type 'boolean) (defcustom helm-yank-text-at-point-function nil "The function used to forward point with `helm-yank-text-at-point'. With a nil value, fallback to default `forward-word'. The function should take one arg, an integer like `forward-word'. NOTE: Using `forward-symbol' here is not very useful as it is already provided by \\\\[next-history-element]." :type 'function :group 'helm) (defcustom helm-scroll-amount nil "Scroll amount when scrolling helm window or other window in a helm session. It is used by `helm-scroll-other-window', `helm-scroll-up', `helm-scroll-down' and `helm-scroll-other-window-down'. If you prefer scrolling line by line, set this value to 1." :group 'helm :type 'integer) (defcustom helm-help-full-frame t "Display help window in full frame when non nil. Even when nil probably the same result (full frame) can be reached by tweaking `display-buffer-alist', but it is much more convenient to use a simple boolean value here." :type 'boolean :group 'helm-help) (defvar helm-ff--boring-regexp nil) (defun helm-ff--setup-boring-regex (var val) (set var val) (setq helm-ff--boring-regexp (cl-loop for r on val if (cdr r) concat (concat (car r) "\\|") else concat (car r)))) (defcustom helm-boring-file-regexp-list (mapcar (lambda (f) (let ((rgx (regexp-quote f))) (if (string-match-p "[^/]$" f) ;; files: e.g .o => \\.o$ (concat rgx "$") ;; To not ignore files with same prefix as directory names ;; (bug#2009) use e.g. .git/ => \.git\?$. ;; See also PR in bug#2012. (concat rgx "?$")))) completion-ignored-extensions) "A list of regexps matching boring files. This list is build by default on `completion-ignored-extensions'. The directory names should end with \"/?$\" e.g. \"\\.git/?$\" and the file names should end with \"$\" e.g. \"\\.o$\". These regexps may be used to match the entire path, not just the file name, so for example to ignore files with a prefix \".bak.\", use \"\\.bak\\..*$\" as the regexp. NOTE: When modifying this, be sure to use customize interface or the customize functions e.g. `customize-set-variable' and NOT `setq'." :group 'helm-files :type '(repeat (choice regexp)) :set 'helm-ff--setup-boring-regex) (defcustom helm-describe-function-function 'describe-function "Function used to describe functions in Helm." :group 'helm-elisp :type 'function) (defcustom helm-describe-variable-function 'describe-variable "Function used to describe variables in Helm." :group 'helm-elisp :type 'function) (defcustom helm-current-directory-alist '((dired-mode . dired-current-directory) (mu4e-main-mode . mu4e-root-maildir) (gnus-group-mode . gnus-directory) (gnus-summary-mode . gnus-directory) (gnus-article-mode . gnus-directory)) "Tell `helm-current-directory' what to use according to `major-mode'. Each element of alist is (MAJOR-MODE . SYMBOL) where SYMBOL is either a variable or a function." :type '(alist :key-type symbol :value-type sexp) :group 'helm-files) ;;; Internal vars ;; (defvar helm-yank-point nil) (defvar helm-pattern "" "The input pattern used to update the helm buffer.") (defvar helm-buffer "*helm*" "Buffer showing completions.") (defvar helm-current-buffer nil "Current buffer when `helm' is invoked.") (defvar helm-suspend-update-flag nil) (defvar helm-action-buffer "*helm action*" "Buffer showing actions.") (defvar helm-current-prefix-arg nil "Record `current-prefix-arg' when exiting minibuffer.") (defvar helm-current-error nil "Same as `compilation-current-error' but for helm-occur and helm-grep.") ;;; Compatibility ;; (defun helm-add-face-text-properties (beg end face &optional append object) "Add the face property to the text from START to END. It is a compatibility function which behaves exactly like `add-face-text-property' if available, otherwise like `add-text-properties'. When only `add-text-properties' is available APPEND is ignored." (if (fboundp 'add-face-text-property) (add-face-text-property beg end face append object) (add-text-properties beg end `(face ,face) object))) ;;; Override `push-mark' ;; ;; Fix duplicates in `mark-ring' and `global-mark-ring' and update ;; buffers in `global-mark-ring' to recentest mark. (defun helm--advice-push-mark (&optional location nomsg activate) (unless (null (mark t)) (let ((marker (copy-marker (mark-marker)))) (setq mark-ring (cons marker (delete marker mark-ring)))) (when (> (length mark-ring) mark-ring-max) ;; Move marker to nowhere. (set-marker (car (nthcdr mark-ring-max mark-ring)) nil) (setcdr (nthcdr (1- mark-ring-max) mark-ring) nil))) (set-marker (mark-marker) (or location (point)) (current-buffer)) ;; Now push the mark on the global mark ring. (setq global-mark-ring (cons (copy-marker (mark-marker)) ;; Avoid having multiple entries ;; for same buffer in `global-mark-ring'. (cl-loop with mb = (current-buffer) for m in global-mark-ring for nmb = (marker-buffer m) unless (eq mb nmb) collect m))) (when (> (length global-mark-ring) global-mark-ring-max) (set-marker (car (nthcdr global-mark-ring-max global-mark-ring)) nil) (setcdr (nthcdr (1- global-mark-ring-max) global-mark-ring) nil)) (or nomsg executing-kbd-macro (> (minibuffer-depth) 0) (message "Mark set")) (when (or activate (not transient-mark-mode)) (set-mark (mark t))) nil) (defcustom helm-advice-push-mark t "Override `push-mark' with a version avoiding duplicates when non-nil." :group 'helm :type 'boolean :set (lambda (var val) (set var val) (if val (advice-add 'push-mark :override #'helm--advice-push-mark '((depth . 100))) (advice-remove 'push-mark #'helm--advice-push-mark)))) ;; This the version of Emacs-27 written by Stefan (defun helm-advice--ffap-read-file-or-url (prompt guess) (or guess (setq guess default-directory)) (if (ffap-url-p guess) (read-string prompt guess nil nil t) (unless (ffap-file-remote-p guess) (setq guess (abbreviate-file-name (expand-file-name guess)))) (read-file-name prompt (file-name-directory guess) nil nil (file-name-nondirectory guess)))) ;; The native-comp branch of emacs "is a modified Emacs capable of compiling ;; and running Emacs Lisp as native code in form of re-loadable elf files." ;; (https://akrl.sdf.org/gccemacs.html). The function subr-native-elisp-p is a ;; native function available only in this branch and evaluates to true if the ;; argument supplied is a natively compiled lisp function. Use this function ;; if it's available, otherwise return nil. Helm needs to distinguish compiled ;; functions from other symbols in a various places. (defun helm-subr-native-elisp-p (object) (when (fboundp 'subr-native-elisp-p) (subr-native-elisp-p object))) ;; Available only in Emacs-28+ (unless (fboundp 'file-modes-number-to-symbolic) (defun file-modes-number-to-symbolic (mode &optional filetype) "Return a string describing a file's MODE. For instance, if MODE is #o700, then it produces `-rwx------'. FILETYPE if provided should be a character denoting the type of file, such as `?d' for a directory, or `?l' for a symbolic link and will override the leading `-' char." (string (or filetype (pcase (ash mode -12) ;; POSIX specifies that the file type is included in st_mode ;; and provides names for the file types but values only for ;; the permissions (e.g., S_IWOTH=2). ;; (#o017 ??) ;; #define S_IFMT 00170000 (#o014 ?s) ;; #define S_IFSOCK 0140000 (#o012 ?l) ;; #define S_IFLNK 0120000 ;; (8 ??) ;; #define S_IFREG 0100000 (#o006 ?b) ;; #define S_IFBLK 0060000 (#o004 ?d) ;; #define S_IFDIR 0040000 (#o002 ?c) ;; #define S_IFCHR 0020000 (#o001 ?p) ;; #define S_IFIFO 0010000 (_ ?-))) (if (zerop (logand 256 mode)) ?- ?r) (if (zerop (logand 128 mode)) ?- ?w) (if (zerop (logand 64 mode)) (if (zerop (logand 2048 mode)) ?- ?S) (if (zerop (logand 2048 mode)) ?x ?s)) (if (zerop (logand 32 mode)) ?- ?r) (if (zerop (logand 16 mode)) ?- ?w) (if (zerop (logand 8 mode)) (if (zerop (logand 1024 mode)) ?- ?S) (if (zerop (logand 1024 mode)) ?x ?s)) (if (zerop (logand 4 mode)) ?- ?r) (if (zerop (logand 2 mode)) ?- ?w) (if (zerop (logand 512 mode)) (if (zerop (logand 1 mode)) ?- ?x) (if (zerop (logand 1 mode)) ?T ?t))))) (unless (and (fboundp 'pos-bol) (fboundp 'pos-eol)) (defalias 'pos-bol 'line-beginning-position) (defalias 'pos-eol 'line-end-position)) ;;; Compatibility with < Emacs-29 ;; Needed by helm-packages.el and affixations functions for helm-mode (27) ;; waiting package.el moves on Elpa. Slightly modified to fit with ;; Emacs-27/28. (when (eval-when-compile (< emacs-major-version 29)) ; Avoid warnings. (progn (require 'package) (eval-and-compile (defun package--archives-initialize () "Make sure the list of installed and remote packages are initialized." (unless package--initialized (package-initialize t)) (unless package-archive-contents (package-refresh-contents))) (defun package-get-descriptor (pkg-name) "Return the `package-desc' of PKG-NAME." (unless package--initialized (package-initialize 'no-activate)) (or (cadr (assq pkg-name package-alist)) (cadr (assq pkg-name package-archive-contents)))) (defun package-upgrade (name) "Upgrade package NAME if a newer version exists." (let* ((package (if (symbolp name) name (intern name))) (pkg-desc (cadr (assq package package-alist)))) ;; `pkg-desc' will be nil when the package is an "active built-in". (when pkg-desc (package-delete pkg-desc 'force 'dont-unselect)) (package-install package ;; An active built-in has never been "selected" ;; before. Mark it as installed explicitly. (and pkg-desc 'dont-select)))) (defun package-recompile (pkg) "Byte-compile package PKG again. PKG should be either a symbol, the package name, or a `package-desc' object." (let ((pkg-desc (if (package-desc-p pkg) pkg (cadr (assq pkg package-alist))))) ;; Delete the old .elc files to ensure that we don't inadvertently ;; load them (in case they contain byte code/macros that are now ;; invalid). (dolist (elc (directory-files-recursively (package-desc-dir pkg-desc) "\\.elc\\'")) (delete-file elc)) (package--compile pkg-desc)))))) ;;; Provide `help--symbol-class' not available in emacs-27 ;; (unless (fboundp 'help--symbol-class) (defun help--symbol-class (s) "Return symbol class characters for symbol S." (when (stringp s) (setq s (intern-soft s))) (concat (when (fboundp s) (concat (cond ((commandp s) "c") ((eq (car-safe (symbol-function s)) 'macro) "m") (t "f")) (and (let ((flist (indirect-function s))) (advice--p (if (eq 'macro (car-safe flist)) (cdr flist) flist))) "!") (and (get s 'byte-obsolete-info) "-"))) (when (boundp s) (concat (if (custom-variable-p s) "u" "v") (and (local-variable-if-set-p s) "'") (and (ignore-errors (not (equal (symbol-value s) (default-value s)))) "*") (and (get s 'byte-obsolete-variable) "-"))) (and (facep s) "a") (and (fboundp 'cl-find-class) (cl-find-class s) "t")))) ;; Inline `kmacro--to-vector' from E29 to fix compatibility of ;; `helm-kbd-macro-concat-macros' with E29 and E28. (unless (fboundp 'kmacro--to-vector) (defun kmacro--to-vector (object) "Normalize an old-style key sequence to the vector form." (if (not (stringp object)) object (let ((vec (string-to-vector object))) (unless (multibyte-string-p object) (dotimes (i (length vec)) (let ((k (aref vec i))) (when (> k 127) (setf (aref vec i) (+ k ?\M-\C-@ -128)))))) vec)))) (defun helm-proper-list-p (obj) "Compatibility function for `proper-list-p'." (if (fboundp 'proper-list-p) (proper-list-p obj) ; 27+ (and (listp obj) (not (cdr (last obj)))))) ;;; Macros helper. ;; (defmacro helm-with-gensyms (symbols &rest body) "Bind the SYMBOLS to fresh uninterned symbols and eval BODY." (declare (indent 1)) `(let ,(mapcar (lambda (s) ;; Use cl-gensym here instead of make-symbol ;; to ensure a symbol that have a live that go ;; beyond the live of its macro have different name. ;; i.e symbols created with `with-helm-temp-hook' ;; should have random names. `(,s (cl-gensym (symbol-name ',s)))) symbols) ,@body)) ;;; Command loop helper ;; (defconst helm-this-command-black-list '(helm-maybe-exit-minibuffer helm-confirm-and-exit-minibuffer helm-exit-minibuffer exit-minibuffer helm-M-x)) (defconst helm-this-command-functions '(read-multiple-choice--long-answers) "The functions that should be returned by `helm-this-command' when found.") (defun helm-this-command () "Return the actual command in action. Like `this-command' but return the real command, and not `exit-minibuffer' or other unwanted functions." (cl-loop for count from 1 to 50 for btf = (backtrace-frame count) for fn = (cl-second btf) ;; Some commands like `kill-buffer' may call another function ;; involving a completing-read, in this case we want to stop at this ;; function and not go up to the initial interactive call (in this ;; case kill-buffer) See Issue#2634. if (or (memq fn helm-this-command-functions) (and ;; In some cases we may have in the way an ;; advice compiled resulting in byte-code, ;; ignore it (Bug#691). (symbolp fn) (commandp fn) (not (memq fn helm-this-command-black-list)))) return fn else if (and (eq fn 'call-interactively) (> (length btf) 2)) return (cadr (cdr btf)))) ;;; Iterators ;; (defun helm-iter-list (seq &optional cycle) "Return an iterator object from SEQ. The iterator die and return nil when it reach end of SEQ. When CYCLE is specified the iterator never ends." (let ((lis seq)) (lambda () (let ((elm (car lis))) (setq lis (if cycle (or (cdr lis) seq) (cdr lis))) elm)))) (defun helm-iter-circular (seq) "Infinite iteration on SEQ." (helm-iter-list seq 'cycle)) (cl-defun helm-iter-sub-next-circular (seq elm &key (test 'eq)) "Infinite iteration of SEQ starting at ELM." (let* ((pos (1+ (helm-position elm seq :test test))) (sub (append (nthcdr pos seq) (helm-take seq pos))) (iterator (helm-iter-circular sub))) (lambda () (helm-iter-next iterator)))) (defun helm-iter-next (iterator) "Return next elm of ITERATOR." (and iterator (funcall iterator))) ;;; Anaphoric macros. ;; (defmacro helm-aif (test-form then-form &rest else-forms) "Anaphoric version of `if'. Like `if' but set the result of TEST-FORM in a temporary variable called `it'. THEN-FORM and ELSE-FORMS are then executed just like in `if'." (declare (indent 2) (debug t)) `(let ((it ,test-form)) (if it ,then-form ,@else-forms))) (defmacro helm-awhile (sexp &rest body) "Anaphoric version of `while'. Same usage as `while' except that SEXP is bound to a temporary variable called `it' at each turn. An implicit nil block is bound to the loop so usage of `cl-return' is possible to exit the loop." (declare (indent 1) (debug t)) (helm-with-gensyms (flag) `(let ((,flag t)) (cl-block nil (while ,flag (helm-aif ,sexp (progn ,@body) (setq ,flag nil))))))) (defmacro helm-acond (&rest clauses) "Anaphoric version of `cond'. In each clause of CLAUSES, the result of the car of clause is stored in a temporary variable called `it' and usable in the cdr of this same clause. Each `it' variable is independent of its clause. The usage is the same as `cond'." (declare (debug cond)) (unless (null clauses) (helm-with-gensyms (sym) (let ((clause1 (car clauses))) `(let ((,sym ,(car clause1))) (helm-aif ,sym (if (cdr ',clause1) (progn ,@(cdr clause1)) it) (helm-acond ,@(cdr clauses)))))))) (defmacro helm-aand (&rest conditions) "Anaphoric version of `and'. Each condition is bound to a temporary variable called `it' which is usable in next condition." (declare (debug (&rest form))) (cond ((null conditions) t) ((null (cdr conditions)) (car conditions)) (t `(helm-aif ,(car conditions) (helm-aand ,@(cdr conditions)))))) (defmacro helm-acase (expr &rest clauses) "Check if EXPR match KEYLIST and then execute BODY. `helm-acase' is a small macro mixing the features of `cl-case' and `cond'. KEYLIST can be any object that will be compared with `equal' or an expression starting with special symbol `guard*' which is then evaluated. Once evaluated the symbol `guard' is bound to the returned value that can be used in the cdr of clause. When KEYLIST match EXPR or `guard*' sexp evaluation is non-nil, BODY is executed and `helm-acase' exits with its value. Note: `guard*' sexp should not be a single symbol to eval, e.g. (guard* foo) in such cases you should use (guard* (progn foo)). KEYLIST can also be a list starting with special symbol `dst*' followed by an expression suitable for `cl-destructuring-bind'. In this case all elements of `it' are bound to the elements of this expression e.g. (helm-acase \\='(1 2 3 4 5) ((dst* (l &rest args)) args)) => (2 3 4 5) If KEYLIST is a list, it is compared with EXPR, also each elements of the list are checked with `member' to see if one matches EXPR, ensure to not use special symbols `guard*' and `dst*' at start of such KEYLIST to avoid confusing helm-acase even if this is partially supported. The last clause can use `t' or \\='otherwise as KEYLIST to specify a fallback clause when previous clauses didn't match, if such a clause starting with `t' or \\='otherwise is specified before last clause it will override all next clauses, if you want to match an EXPR value equal to `t' in any clauses quote it, i.e. `'t' or use an explicit \(guard* (eq it t)). EXPR is bound to a temporary variable called `it' which is usable in all clauses to refer to EXPR. \(fn EXPR (KEYLIST BODY...)...)" (declare (indent 1) (debug (form &rest ([&or (symbolp form) sexp] body)))) (unless (null clauses) (let* ((clause1 (car clauses)) (key (car clause1)) (sexp (car-safe (cdr-safe key))) (sp-sym (car-safe key)) ;; Ensure dst* and guard* are not treated as special symbols when ;; they are not followed by one sexp and nothing else, however if the ;; following sexp is not meant to be evaluated but just compared we ;; fail miserably, is it worth fixing it? (issexp (and (consp sexp) (= (length key) 2))) (isguard (and (eq 'guard* sp-sym) issexp)) (isdst (and (eq 'dst* sp-sym) issexp)) (special (or isguard isdst)) (guard-sexp (and isguard sexp)) (dst-sexp (and isdst sexp))) `(let* ((it ,expr) (guard ,guard-sexp) (dst (and (consp it) ',dst-sexp))) (cond ((or guard (and ,(not special) (or (equal it ',key) (and (listp ',key) (member it ',key)))) (and (symbolp ',key) (or (eq ',key t) (eq ',key 'otherwise)))) ;; When this branch is expanded the compiler complains about not ;; referenced variables (the ones in `dst' that will be used in ;; next branch) so prevent warnings instead of using a progn ;; Merci Stefan! (with-no-warnings ,@(cdr clause1))) ((and dst (condition-case nil (cl-destructuring-bind ,dst-sexp it ,@(cdr clause1)) (wrong-number-of-arguments nil)))) (t (helm-acase it ,@(cdr clauses)))))))) ;;; Fuzzy matching routines ;; (defsubst helm--mapconcat-pattern (pattern) "Transform string PATTERN in regexp for further fuzzy matching. E.g.: helm.el$ => \"[^h]*h[^e]*e[^l]*l[^m]*m[^.]*\\\\.[^e]*e[^l]*l$\" ^helm.el$ => \"helm\\\\.el$\"." (let ((ls (split-string-and-unquote pattern ""))) (if (string= "^" (car ls)) ;; Exact match. (mapconcat (lambda (c) (if (and (string= c "$") (string-match "$\\'" pattern)) c (regexp-quote c))) (cdr ls) "") ;; Fuzzy match. (mapconcat (lambda (c) (if (and (string= c "$") (string-match "$\\'" pattern)) c (format "[^%s]*%s" c (regexp-quote c)))) ls "")))) (defsubst helm--collect-pairs-in-string (string) ;; We want to collect e.g. ;; in "abcd" -> (("a" "b") ("b" "c") ("c" "d")) ;; and not (("a" "b") ("c" "d")) so we use by #'cdr which is the default. ;; If the last pair have no cdr i.e. (s1 nil) ignore it. (cl-loop for (s1 s2) on (split-string string "" t) when s2 collect (list s1 s2))) ;;; Help routines. ;; (defvar helm-help--iter-org-state nil) (defvar helm-help-mode-before-hook nil "A hook that runs before helm-help starts.") (defvar helm-help-mode-after-hook nil "A hook that runs when helm-help exits.") (defcustom helm-help-default-prompt "[SPC,C-v,next:ScrollUp b,M-v,prior:ScrollDown TAB:Cycle M-TAB:All C-s/r:Isearch q:Quit]" "The prompt used in `helm-help'." :type 'string :group 'helm) (defcustom helm-help-hkmap '(("C-v" . helm-help-scroll-up) ("SPC" . helm-help-scroll-up) ("" . helm-help-scroll-up) ("M-v" . helm-help-scroll-down) ("b" . helm-help-scroll-down) ("" . helm-help-scroll-down) ("C-s" . isearch-forward) ("C-r" . isearch-backward) ("C-a" . move-beginning-of-line) ("C-e" . move-end-of-line) ("C-f" . forward-char) ("" . forward-char) ("C-b" . backward-char) ("" . backward-char) ("C-n" . helm-help-next-line) ("C-p" . helm-help-previous-line) ("" . helm-help-next-line) ("" . helm-help-previous-line) ("M-a" . backward-sentence) ("M-e" . forward-sentence) ("M-f" . forward-word) ("M-b" . backward-word) ("M->" . end-of-buffer) ("C-M-f" . forward-sexp) ("C-M-b" . backward-sexp) ("M-<" . beginning-of-buffer) ("C-SPC" . helm-help-toggle-mark) ("C-M-SPC" . mark-sexp) ("TAB" . org-cycle) ("C-m" . helm-help-org-open-at-point) ("C-&" . helm-help-org-mark-ring-goto) ("C-%" . org-mark-ring-push) ("M-TAB" . helm-help-org-cycle) ("M-w" . helm-help-copy-region-as-kill) ("q" . helm-help-quit)) "Alist of (KEY . FUNCTION) for `helm-help'. This is not a standard keymap, just an alist where it is possible to define a simple KEY (a string with no spaces) associated with a FUNCTION. More complex key like \"C-x C-x\" are not supported. Interactive functions will be called interactively whereas other functions will be called with funcall except commands that are in `helm-help-not-interactive-command'. For convenience you can add bindings here with `helm-help-define-key'." :type '(alist :key-type string :key-value symbol) :group 'helm) (defvar helm-help-not-interactive-command '(isearch-forward isearch-backward) "Commands that we don't want to call interactively in `helm-help'.") (defun helm-help-internal (bufname insert-content-fn) "Show long message during Helm session in BUFNAME. INSERT-CONTENT-FN is the function that inserts text to be displayed in BUFNAME." (let ((winconf (current-frame-configuration)) (hframe (selected-frame))) (helm-log-run-hook "helm-help-internal" 'helm-help-mode-before-hook) (with-selected-frame helm-initial-frame (select-frame-set-input-focus helm-initial-frame) (unwind-protect (progn (setq helm-suspend-update-flag t) (set-buffer (get-buffer-create bufname)) (switch-to-buffer bufname) (when helm-help-full-frame (delete-other-windows)) (delete-region (point-min) (point-max)) (org-mode) (save-excursion (funcall insert-content-fn) (goto-char (point-min)) (while (re-search-forward "^[|]" nil t) (when (org-table-p t) (org-table-align) (goto-char (org-table-end))))) (org-mark-ring-push) ; Put mark at bob (buffer-disable-undo) (helm-help-event-loop)) (raise-frame hframe) (helm-log-run-hook "helm-help-internal" 'helm-help-mode-after-hook) (setq helm-suspend-update-flag nil) (set-frame-configuration winconf))))) (cl-defun helm-help-scroll-up (&optional (amount helm-scroll-amount)) "Scroll up in `helm-help'." (condition-case _err (scroll-up-command amount) (beginning-of-buffer nil) (end-of-buffer nil))) (cl-defun helm-help-scroll-down (&optional (amount helm-scroll-amount)) "Scroll down in `helm-help'." (condition-case _err (scroll-down-command amount) (beginning-of-buffer nil) (end-of-buffer nil))) (defun helm-help-next-line () "Next line function for `helm-help'." (condition-case _err (call-interactively #'next-line) (beginning-of-buffer nil) (end-of-buffer nil))) (defun helm-help-previous-line () "Previous line function for `helm-help'." (condition-case _err (call-interactively #'previous-line) (beginning-of-buffer nil) (end-of-buffer nil))) (defun helm-help-toggle-mark () "Toggle mark in `helm-help'." (if (region-active-p) (deactivate-mark) (push-mark nil nil t))) (defun helm-help-org-cycle () "Runs `org-cycle' in `helm-help'." (helm-acase (helm-iter-next helm-help--iter-org-state) ((guard* (numberp it)) (org-content)) ;; See `helm--help-org-prefargs' about `org-cycle' ARG. (t (org-cycle it)))) (defun helm-help-copy-region-as-kill () "Copy region function for `helm-help'" (copy-region-as-kill (region-beginning) (region-end)) (deactivate-mark)) (defun helm-help-quit () "Quit `helm-help'." (if (or (get-buffer-window helm-help-buffer-name 'visible) (get-buffer-window helm-debug-output-buffer 'visible)) (throw 'helm-help-quit nil) (quit-window))) (defun helm-help-org-open-at-point () "Calls `org-open-at-point' ignoring errors." (ignore-errors (org-open-at-point))) (defun helm-help-org-mark-ring-goto () "Calls `org-mark-ring-goto' ignoring errors." (ignore-errors (org-mark-ring-goto))) (defvar helm--help-org-prefargs (if (> emacs-major-version 28) '(1 (4) (16)) '(1 (16) (64))) "`org-cycle' ARG have not the same meaning across Emacs versions.") (defun helm-help-event-loop () "The loop in charge of scanning keybindings in `helm-help'." (let ((prompt (propertize helm-help-default-prompt 'face 'helm-helper)) scroll-error-top-bottom (helm-help--iter-org-state (helm-iter-circular helm--help-org-prefargs))) (catch 'helm-help-quit (helm-awhile (read-key prompt) (let ((fun (cl-loop for (k . v) in helm-help-hkmap when (eql (aref (kbd k) 0) it) return v))) (when fun (if (and (commandp fun) (not (memq fun helm-help-not-interactive-command))) ;; For movement of cursor in help buffer we need to ;; call interactively commands for impaired people ;; using a synthetizer (Bug#1347). (call-interactively fun) (funcall fun)))))))) (defun helm-help-define-key (key function &optional override) "Add KEY bound to fUNCTION in `helm-help-hkmap'. If OVERRIDE is non nil, all bindings associated with FUNCTION are removed and only (KEY . FUNCTION) is kept. If FUNCTION is nil (KEY . FUNCTION) is not added and removed from alist if already present. See `helm-help-hkmap' for supported keys and functions." (cl-assert (not (cdr (split-string key))) nil (format "Error: Unsuported key `%s'" key)) (when override (helm-awhile (rassoc function helm-help-hkmap) (setq helm-help-hkmap (delete it helm-help-hkmap)))) (helm-aif (and (null function) (assoc key helm-help-hkmap)) (setq helm-help-hkmap (delete it helm-help-hkmap)) (and function (add-to-list 'helm-help-hkmap `(,key . ,function))))) ;;; Multiline transformer ;; (defun helm-multiline-transformer (candidates _source) (cl-loop with offset = (helm-interpret-value (assoc-default 'multiline (helm-get-current-source))) for cand in candidates for disp = (or (car-safe cand) cand) for real = (or (cdr-safe cand) cand) if (numberp offset) collect (cons (helm--multiline-get-truncated-candidate disp offset) real) else collect (cons disp real))) (defun helm--multiline-get-truncated-candidate (candidate offset) "Truncate CANDIDATE when its length is > than OFFSET." (with-temp-buffer (insert candidate) (goto-char (point-min)) (if (and offset (> (buffer-size) offset)) (let ((end-str "[...]")) (concat (buffer-substring (point) (save-excursion (forward-char offset) (setq end-str (if (looking-at "\n") end-str (concat "\n" end-str))) (point))) end-str)) (buffer-string)))) ;;; List processing ;; (defun helm-flatten-list (seq) "Return a list of all single elements of sublists in SEQ. Example: (helm-flatten-list \\='(1 (2 . 3) nil (4 5 (6) 7) 8 (9 . 10))) => (1 2 3 4 5 6 7 8 9 10)" (let (result) (cl-labels ((flatten (seq) (cl-loop for elm in seq if (consp elm) do (flatten (if (atom (cdr elm)) (list (car elm) (cdr elm)) elm)) else do (and elm (push elm result))))) (flatten seq)) (nreverse result))) (defun helm-mklist (obj) "Return OBJ as a proper list. Otherwise make a proper list with one element OBJ. Anonymous functions (lambdas) are treated as single elements." (if (and (helm-proper-list-p obj) (not (functionp obj))) obj (list obj))) (cl-defun helm-fast-remove-dups (seq &key (test 'eq)) "Remove duplicates elements in list SEQ. This is same as `remove-duplicates' but with memoisation. It is much faster, especially in large lists. A test function can be provided with TEST argument key. Default is `eq'. NOTE: Comparison of special Elisp objects (e.g., markers etc.) fails because their printed representations which are stored in hash-table can't be compared with with the real object in SEQ. This is a bug in `puthash' which store the printable representation of object instead of storing the object itself, this to provide at the end a printable representation of hashtable itself." (let ((table (make-hash-table :test test))) (mapcan (lambda (x) (unless (gethash x table) (list (puthash x x table)))) seq))) (defsubst helm--string-join (strings &optional separator) "Join all STRINGS using SEPARATOR." (mapconcat 'identity strings separator)) (defun helm--concat-regexps (regexp-list) "Return a regexp which matches any of the regexps in REGEXP-LIST." (if regexp-list (concat "\\(?:" (helm--string-join regexp-list "\\)\\|\\(?:") "\\)") "\\`\\'")) ; Match nothing (defun helm-skip-entries (seq black-regexp-list &optional white-regexp-list) "Remove entries which match one of REGEXP-LIST from SEQ." (let ((black-regexp (helm--concat-regexps black-regexp-list)) (white-regexp (helm--concat-regexps white-regexp-list))) (cl-loop for i in seq unless (and (stringp i) (string-match-p black-regexp i) (null (string-match-p white-regexp i))) collect i))) (defun helm-boring-directory-p (directory black-list) "Check if one regexp in BLACK-LIST matches DIRECTORY." (helm-awhile (helm-basedir (directory-file-name (expand-file-name directory))) ;; Break at root to avoid infloop, root is / or on Windows ;; C:/ i.e. :/ (Bug#2308). (when (string-match-p "\\`[A-Za-z]?:?/\\'" it) (cl-return nil)) (when (cl-loop for r in black-list thereis (string-match-p r (directory-file-name directory))) (cl-return t)) (setq directory it))) (defun helm-shadow-entries (seq regexp-list) "Put shadow property on entries in SEQ matching a regexp in REGEXP-LIST." (let ((face 'italic)) (cl-loop for i in seq if (cl-loop for regexp in regexp-list thereis (and (stringp i) (string-match regexp i))) collect (propertize i 'face face) else collect i))) (defun helm-remove-if-not-match (regexp seq) "Remove all elements of SEQ that don't match REGEXP." (cl-loop for s in seq for str = (cond ((symbolp s) (symbol-name s)) ((consp s) (car s)) (t s)) when (string-match-p regexp str) collect s)) (defun helm-remove-if-match (regexp seq) "Remove all elements of SEQ that match REGEXP." (cl-loop for s in seq for str = (cond ((symbolp s) (symbol-name s)) ((consp s) (car s)) (t s)) unless (string-match-p regexp str) collect s)) (defun helm-transform-mapcar (fn seq) "Apply function FN on all elements of list SEQ. When SEQ is a list of cons cells apply FN on the cdr of each element, keeping their car unmodified. Examples: (helm-transform-mapcar \\='upcase \\='(\"foo\" \"bar\")) => (\"FOO\" \"BAR\") (helm-transform-mapcar \\='upcase \\='((\"1st\" . \"foo\") (\"2nd\" . \"bar\"))) => ((\"1st\" . \"FOO\") (\"2nd\" . \"BAR\")) " (cl-loop for elm in seq if (consp elm) collect (cons (car elm) (funcall fn (cdr elm))) else collect (funcall fn elm))) (defun helm-append-at-nth (seq elm index) "Append ELM at INDEX in SEQ. When INDEX is > to the SEQ length ELM is added at end of SEQ. When INDEX is 0 or negative, ELM is added at beginning of SEQ. Examples: (helm-append-at-nth \\='(a b c d) \\='(z) 2) =>(a b z c d) (helm-append-at-nth \\='(a b c d) \\='((x . 1) (y . 2)) 2) =>(a b (x . 1) (y . 2) c d) (helm-append-at-nth \\='((a . 1) (b . 2) (c . 3)) \\='(x . 1) 1) =>((a . 1) (x . 1) (b . 2) (c . 3)) " (setq index (min (max index 0) (length seq)) elm (helm-mklist elm)) (if (zerop index) (append elm seq) (let* ((end-part (nthcdr index seq)) (len (length end-part)) (beg-part (butlast seq len))) (append beg-part elm end-part)))) ;;;###autoload (defun helm-add-to-list (var elm index &optional replace) "Add or move ELM to the value of VAR at INDEX unless already here. If ELM is member of var value and at index INDEX, return var value unchanged, if INDEX value is different move ELM at this `nth' INDEX value. If ELM is not present in list add it at `nth' INDEX. If REPLACE is non nil replace element at INDEX by ELM. Do not use this function in helm code, use `helm-append-at-nth' instead. It is meant to be used in config files only." (cl-assert (boundp var) nil "Unbound variable `%s'" var) (let ((val (symbol-value var)) flag) (cond ((and (member elm val) (equal elm (nth index val)))) ((member elm val) (setq val (delete elm val) flag t)) ((and replace (not (< index 0)) (not (> index (length val)))) (setq val (delete (nth index val) val) flag t)) (t (setq flag t))) (if flag (set var (helm-append-at-nth val elm index)) val))) (cl-defgeneric helm-take (seq n) "Return the first N elements of SEQ if SEQ is longer than N. It is used for narrowing list of candidates to the `helm-candidate-number-limit'." (if (> (length seq) n) (cl-subseq seq 0 n) seq)) (cl-defmethod helm-take ((seq list) n) "`helm-take' optimized for lists." (let ((store '())) (if (> n (length seq)) seq (while (> (1+ (cl-decf n)) 0) (push (pop seq) store)) (nreverse store)))) (defalias 'helm-take-first-elements 'helm-take) (make-obsolete 'helm-take-first-elements 'helm-take "3.9.1") (defun helm-source-by-name (name &optional sources) "Get a Helm source in SOURCES by NAME. Optional argument SOURCES is a list of Helm sources which default to `helm-sources'." (cl-loop with src-list = (if sources (cl-loop for src in sources collect (if (listp src) src (symbol-value src))) helm-sources) for source in src-list thereis (and (string= name (assoc-default 'name source)) source))) (defun helm-make-actions (&rest args) "Build an alist with (NAME . ACTION) elements with each pairs in ARGS. Where NAME is a string or a function returning a string or nil and ACTION a function. If NAME returns nil the pair is skipped. \(fn NAME ACTION ...)" (cl-loop for (name fn) on args by #'cddr when (functionp name) do (setq name (funcall name)) when name collect (cons name fn))) (defun helm-closest-number-in-list (num list) "Return closest number to NUM found in LIST. LIST is a list of numbers and NUM a number." (cl-loop for i in list for diff = (if (> num i) (- num i) (- i num)) collect (cons diff i) into res minimize diff into min finally return (cdr (assq min res)))) (defun helm-group-candidates-by (candidates function &optional selection separate) "Group similar items in CANDIDATES according to FUNCTION. Items not matching FUNCTION are grouped as well in a separate group. Example: (setq B \\='(1 2 3 4 5 6 7 8 9)) (helm-group-candidates-by B #\\='cl-oddp 2 \\='separate) => ((2 4 6 8) (1 3 5 7 9)) SELECTION specify where to start in CANDIDATES. Similar candidates to SELECTION will be listed on top. If SEPARATE is non-nil returns a list of groups i.e. a list of lists, otherwise a plain list is returned." (cl-loop with sel = (or selection (helm-get-selection) "") with lst = (copy-sequence candidates) while lst for group = (cl-loop for c in lst when (equal (funcall function c) (funcall function sel)) collect c into grp and do (setq lst (delete c lst)) finally return (prog1 grp (setq sel (car lst)))) if separate collect group else append group)) (defun helm-reorganize-sequence-from-elm (sequence elm &optional reverse) "Reorganize SEQUENCE from ELM. Examples: (helm-reorganize-sequence-from-elm \\='(a b c d e f g h i j k l) \\='e) => (f g h i j k l a b c d e) (helm-reorganize-sequence-from-elm \\='(a b c d e f g h i j k l) \\='e t) => (d c b a l k j i h g f e) " (let* ((new-seq (if reverse (reverse sequence) sequence)) (pos (1+ (helm-position elm new-seq :test 'equal)))) (append (nthcdr pos new-seq) (helm-take new-seq pos)))) (cl-defun helm-position (elm seq &key (test 'eq) from-end) "Return position of ELM in SEQ. Comparison is tested with keyword TEST which default to `eq'. If keyword FROM-END is non nil search from end." (let ((count (if from-end (1- (length seq)) 0))) (while (if from-end (not (zerop count)) (<= count (1- (length seq)))) (when (funcall test (if (listp seq) (car (nthcdr count seq)) (aref seq count)) elm) (cl-return-from helm-position count)) (setq count (funcall (if from-end #'1- #'1+) count))))) (defsubst helm-copy-sequence (seq) "Return a copy of SEQ. When the elements of SEQ are strings, they are copied; they are not shared with the original. Otherwise returns SEQ unchanged, the elements are not copied." (cl-loop for elm in seq collect (if (stringp elm) (copy-sequence elm) elm))) ;;; Strings processing. ;; (defun helm-stringify (elm) "Return the representation of ELM as a string. ELM can be a string, a number or a symbol." (helm-acase elm ((guard* (stringp it)) it) ((guard* (numberp it)) (number-to-string it)) ((guard* (symbolp it)) (symbol-name it)))) (defun helm-substring (str width) "Return the substring of string STR from 0 to WIDTH. Handle multibyte characters by moving by columns." (with-temp-buffer (save-excursion (insert str)) (move-to-column width) (buffer-substring (pos-bol) (point)))) (defun helm-substring-by-width (str width &optional endstr) "Truncate string STR to end at column WIDTH. Similar to `truncate-string-to-width'. Add ENDSTR at end of truncated STR. Add spaces at end if needed to reach WIDTH when STR is shorter than WIDTH." (cl-loop for ini-str = str then (substring ini-str 0 (1- (length ini-str))) for sw = (string-width ini-str) when (<= sw width) return (concat ini-str endstr (make-string (- width sw) ? )))) (defun helm-string-multibyte-p (str) "Check if string STR contains multibyte characters." (cl-loop for c across str thereis (> (char-width c) 1))) (defun helm-get-pid-from-process-name (process-name) "Get pid from running process PROCESS-NAME." ;; Protect system processes calls (Issue #2497) ;; Ensure `list-system-processes' and `process-attributes' don't run ;; on remote (only Emacs-28/29+). (cl-loop with default-directory = temporary-file-directory with process-list = (list-system-processes) for pid in process-list for process = (assoc-default 'comm (process-attributes pid)) when (and process (string-match process-name process)) return pid)) (defun helm-ff-find-printers () "Return a list of available printers on Unix systems." (when (executable-find "lpstat") (let ((printer-list (with-temp-buffer (call-process "lpstat" nil t nil "-a") (split-string (buffer-string) "\n")))) (cl-loop for p in printer-list for printer = (car (split-string p)) when printer collect printer)))) (defun helm-region-active-p () (and transient-mark-mode mark-active (/= (mark) (point)))) (defun helm-quote-whitespace (candidate) "Quote whitespace, if some, in string CANDIDATE." (replace-regexp-in-string " " "\\\\ " candidate)) (defun helm-current-line-contents () "Current line string without properties." (buffer-substring-no-properties (pos-bol) (pos-eol))) (defun helm--replace-regexp-in-buffer-string (regexp rep str &optional fixedcase literal subexp start) "Replace REGEXP by REP in string STR. Same as `replace-regexp-in-string' but handle properly REP as function with SUBEXP specified. E.g.: (helm--replace-regexp-in-buffer-string \"e\\\\(m\\\\)acs\" \\='upcase \"emacs\" t nil 1) => \"eMacs\" (replace-regexp-in-string \"e\\\\(m\\\\)acs\" \\='upcase \"emacs\" t nil 1) => \"eEMACSacs\" Also START argument behaves as expected unlike `replace-regexp-in-string'. E.g.: (helm--replace-regexp-in-buffer-string \"f\" \"r\" \"foofoo\" t nil nil 3) => \"fooroo\" (replace-regexp-in-string \"f\" \"r\" \"foofoo\" t nil nil 3) => \"roo\" Unlike `replace-regexp-in-string' this function is buffer-based implemented i.e. replacement is computed inside a temp buffer, so REGEXP should be used differently than with `replace-regexp-in-string'. NOTE: This function is used internally for `helm-ff-query-replace-on-filenames' and builded for this. You should use `replace-regexp-in-string' instead unless the behaviour of this function is really needed." (with-temp-buffer (insert str) (goto-char (or start (point-min))) (while (re-search-forward regexp nil t) (replace-match (cond ((and (functionp rep) subexp) (funcall rep (match-string subexp))) ((functionp rep) (funcall rep str)) (t rep)) fixedcase literal nil subexp)) (buffer-string))) (defun helm-url-unhex-string (str) "Same as `url-unhex-string' but ensure STR is completely decoded." (setq str (or str "")) (with-temp-buffer (save-excursion (insert str)) (while (re-search-forward "%[A-Za-z0-9]\\{2\\}" nil t) (replace-match (byte-to-string (string-to-number (substring (match-string 0) 1) 16)) t t) ;; Restart from beginning until string is completely decoded. (goto-char (point-min))) (decode-coding-string (buffer-string) 'utf-8))) (cl-defun helm-read-answer (prompt answer-list &optional (help 'helm-read-answer-default-help-fn)) "Prompt user for an answer. Arg PROMPT is the prompt to present user the different possible answers, ANSWER-LIST is a list of strings. If user enters an answer which is one of ANSWER-LIST return this answer, otherwise keep prompting for a valid answer. Note that answer should be a single char, only short answer are accepted. When HELP is provided, it is a string or a function that returns a string which will be displayed in a buffer when \"h\" is pressed (don't forget to add \"h\" in prompt). Example: (helm-acase (helm-read-answer \"answer [y,n,!,q,h]: \" \\='(\"y\" \"n\" \"!\" \"q\")) (\"y\" \"yes\") (\"n\" \"no\") (\"!\" \"all\") (\"q\" \"quit\")) " (unwind-protect (helm-awhile (read-key (propertize prompt 'face 'minibuffer-prompt)) (let ((str (and (characterp it) (string it))) (choices (remove "h" answer-list))) (cond ((and str (member str choices)) (cl-return str)) ((and help (string= str "h")) (helm-aif (get-buffer-window "*choices help*" 'visible) (quit-window t it) (with-current-buffer-window "*choices help*" '(display-buffer-at-bottom (window-height . fit-window-to-buffer) (preserve-size . (nil . t))) nil (progn (insert (if (functionp help) (funcall help) help)) (setq-local cursor-type nil))))) (t (message "Please answer by %s" (mapconcat 'identity choices ", ")) (sit-for 1))))) (helm-aand help (get-buffer-window "*choices help*") (quit-window t it)))) (defun helm-read-answer-default-help-fn () "Return a string suitable for `helm-read-answer' help." (with-temp-buffer (save-excursion (insert "y: yes\n" "n: no\n" "!: yes for all\n" "q: quit\n" "h: toggle this help")) (while (re-search-forward "^\\(.\\):" nil t) (helm-add-face-text-properties (match-beginning 1) (match-end 1) 'font-lock-variable-name-face)) (buffer-string))) (defun helm-read-answer-dolist-with-action (prompt list action &optional prompt-formater help-function) "Read answer with PROMPT and execute ACTION on each element of LIST. Argument PROMPT is a format spec string e.g. \"Do this on %s?\" which take each elements of LIST as argument, no need to provide the help part i.e. [y,n,!,q] it will be already added. While looping through LIST, ACTION is executed on each elements differently depending of answer: - y Execute ACTION on element. - n Skip element. - ! Don't ask anymore and execute ACTION on remaining elements. - q Skip all remaining elements. PROMPT-FORMATER may be a function or a list containing strings and functions. Functions either in list or alone are called on each element in LIST to be displayed in PROMPT." (let (dont-ask) (catch 'break (dolist (elm list) (if dont-ask (funcall action elm) (helm-acase (helm-read-answer (apply #'format (concat prompt "[y,n,!,q,h]") (helm-acase prompt-formater ((guard* (consp it)) (mapcar (lambda (x) (if (functionp x) (funcall x elm) x)) it)) ((guard* (functionp it)) (list (funcall it elm))) (t (list elm)))) '("y" "n" "!" "q") (or help-function #'helm-read-answer-default-help-fn)) ("y" (funcall action elm)) ("n" (ignore)) ("!" (prog1 (funcall action elm) (setq dont-ask t))) ("q" (throw 'break nil)))))))) (defsubst helm-string-numberp (str) "Return non nil if string STR represent a number." (cl-assert (stringp str) t) (cl-loop with chars = '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9) for c across str always (memql c chars))) (defsubst helm-re-search-forward (regexp &optional bound noerror count) "Same as `re-search-forward' but return nil when point doesn't move. This avoid possible infloop when a wrong regexp is entered in minibuffer." ;; See Issue#2652 and Issue#2653. (let ((pos (point))) (helm-acase (re-search-forward regexp bound noerror count) ((guard* (eql it pos)) nil) (t it)))) ;;; Symbols routines ;; (defun helm-symbolify (str-or-sym) "Get symbol of STR-OR-SYM." (helm-acase str-or-sym ((guard* (symbolp it)) it) ("" nil) (t (intern it)))) (defun helm-symbol-name (obj) "Return name of OBJ. If object is a lambda, return \"Anonymous\"." ;; lambdas are no more represented as list in ;; Emacs-29+ Bug#2666. (if (or (and (not (symbolp obj)) (functionp obj)) (byte-code-function-p obj) (helm-subr-native-elisp-p obj)) "Anonymous" (symbol-name obj))) (defun helm-describe-class (class) "Display documentation of Eieio CLASS, a symbol or a string." (let ((advicep (advice-member-p #'helm-source--cl--print-table 'cl--print-table))) (unless advicep (advice-add 'cl--print-table :override #'helm-source--cl--print-table '((depth . 100)))) (unwind-protect (if (fboundp 'cl-describe-type) (cl-describe-type (helm-symbolify class)) (let ((helm-describe-function-function 'describe-function)) (helm-describe-function (helm-symbolify class)))) (unless advicep (advice-remove 'cl--print-table #'helm-source--cl--print-table))))) (defun helm-describe-function (func) "Display documentation of FUNC, a symbol or string." (cl-letf (((symbol-function 'message) #'ignore)) (funcall helm-describe-function-function (helm-symbolify func)))) (defun helm-describe-variable (var) "Display documentation of VAR, a symbol or a string." (cl-letf (((symbol-function 'message) #'ignore)) (funcall helm-describe-variable-function (helm-symbolify var)))) (defun helm-describe-face (face) "Display documentation of FACE, a symbol or a string." (let ((faces (helm-marked-candidates))) (cl-letf (((symbol-function 'message) #'ignore)) (describe-face (if (cdr faces) (mapcar 'helm-symbolify faces) (helm-symbolify face)))))) (defun helm-describe-re-char-classes-1 (exp) (helm-acase exp (":xdigit:" (format "%s This matches the hexadecimal digits. ‘0’ through ‘9’, ‘a’ through ‘f’ and ‘A’ through ‘F’." it)) (":word:" (format "%s This matches any character that has word syntax. Note that the syntax of a character, and thus which characters are considered “word-constituent”, depends on the major mode." it)) (":upper:" (format "%s This matches any upper-case letter. It is determined by the current case table. If ‘case-fold-search’ is non-‘nil’, this also matches any lower-case letter. Note that a buffer can have its own local case table different from the default one." it)) (":unibyte:" (format "%s This matches any unibyte character." it)) (":space:" (format "%s This matches any character that has whitespace syntax. Note that the syntax of a character, and thus which characters are considered “whitespace”, depends on the major mode." it)) (":punct:" (format "%s This matches any punctuation character. At present, for multibyte characters, it matches anything that has non-word syntax, and thus its exact definition can vary from one major mode to another, since the syntax of a character depends on the major mode.)" it)) (":print:" (format "%s This matches any printing character. Either spaces or graphic characters matched by ‘[:graph:]’." it)) (":nonascii:" (format "%s This matches any non-ASCII character." it)) (":multibyte:" (format "%s This matches any multibyte character." it)) (":lower:" (format "%s This matches any lower-case letter. It is determined by the current case table. If ‘case-fold-search’ is non-‘nil’, this also matches any upper-case letter. Note that a buffer can have its own local case table different from the default one." it)) (":graph:" (format "%s This matches graphic characters. Everything except spaces, ASCII and non-ASCII control characters, surrogates, and codepoints unassigned by Unicode, as indicated by the Unicode ‘general-category’ property." it)) (":digit:" (format "%s This matches ‘0’ through ‘9’. Thus, ‘[-+[:digit:]]’ matches any digit,as well as ‘+’ and ‘-’." it)) (":cntrl:" (format "%s This matches any cntrl character. That is any character whose code is in the range 0–31." it)) (":blank:" (format "%s This matches horizontal whitespace. It is defined by Annex C of the Unicode Technical Standard #18. In particular, it matches spaces,tabs, and other characters whose Unicode ‘general-category’ property indicates they are spacing separators." it)) (":alpha:" (format "%s This matches any letter. For multibyte characters, it matches characters whose Unicode ‘general-category’ property indicates they are alphabetic characters." it)) (":alnum:" (format "%s This matches any letter or digit. For multibyte characters, it matches characters whose Unicode ‘general-category’ property indicates they are alphabetic or decimal number characters." it)) (":ascii:" (format "%s This matches any ASCII character (codes 0–127)." it)))) (defun helm-describe-re-char-classes (exp) "Describe Char Classes for regexps." (with-output-to-temp-buffer "*help*" (princ (helm-describe-re-char-classes-1 exp)))) (defun helm-elisp--persistent-help (candidate fun &optional name) "Used to build persistent actions describing CANDIDATE with FUN. Argument NAME is used internally to know which command to use when symbol CANDIDATE refers at the same time to a variable and a function. See `helm-elisp-show-help'." (let ((hbuf (get-buffer (help-buffer)))) (cond ((helm-follow-mode-p) (if name (funcall fun candidate name) (funcall fun candidate))) ((or (and (helm-get-attr 'help-running-p) (string= candidate (helm-get-attr 'help-current-symbol)))) (progn ;; When started from a help buffer, ;; Don't kill this buffer as it is helm-current-buffer. (unless (equal hbuf helm-current-buffer) (kill-buffer hbuf) (set-window-buffer (get-buffer-window hbuf) ;; It is generally ;; helm-current-buffer but it may ;; be another buffer when helm have ;; been started from a dedicated window. (if helm--buffer-in-new-frame-p helm-current-buffer helm-persistent-action-window-buffer))) (helm-set-attr 'help-running-p nil)) ;; Force running update hook to may be delete ;; helm-persistent-action-display-window, this is done in ;; helm-persistent-action-display-window (the function). (unless helm--buffer-in-new-frame-p (helm-update (regexp-quote (helm-get-selection))))) (t (if name (funcall fun candidate name) (funcall fun candidate)) (helm-set-attr 'help-running-p t))) (helm-set-attr 'help-current-symbol candidate))) (defcustom helm-find-function-default-project nil "Default directories to search symbols definitions from `helm-apropos'. A list of directories or a single directory name. Helm will allow you selecting one of those directories with `M-n' when using a prefix arg with the `find-function' action in `helm-apropos'. This is a good idea to add the directory names of the projects you are working on to quickly jump to the definitions in the project source files instead of jumping to the loaded files located in `load-path'." :type '(choice (repeat string) string) :group 'helm-elisp) (defun helm-find-function-noselect (func &optional root-dir type) "Find FUNC definition without selecting buffer. FUNC can be a symbol or a string. Instead of looking in LOAD-PATH to find library, this function search in all subdirs of ROOT-DIR, if ROOT-DIR is unspecified ask for it with completion. TYPE when nil specify function, for other values see `find-function-regexp-alist'." (require 'find-func) (let* ((sym (helm-symbolify func)) (dir (or root-dir (helm-read-file-name "Project directory: " :test 'file-directory-p :default (helm-mklist helm-find-function-default-project) :must-match t))) (find-function-source-path (cons dir (helm-walk-directory dir :directories 'only :path 'full))) (symbol-lib (helm-acase type ((defvar defface) (or (symbol-file sym it) (help-C-file-name sym 'var))) ;; Sometimes e.g. with prefix key symbols ;; `find-function-library' returns a list of only one ;; element, the symbol itself i.e. no library. (t (cdr (find-function-library sym))))) (library (and symbol-lib (find-library-name (helm-basename symbol-lib t))))) (if library (find-function-search-for-symbol sym type library) (error "Don't know where `%s' is defined" sym)))) (defun helm-find-function (func) "Try to jump to FUNC definition. With a prefix arg ask for the project directory to search in instead of using LOAD-PATH." (if (not helm-current-prefix-arg) (find-function (helm-symbolify func)) (let ((place (helm-find-function-noselect func))) (if (cdr place) (progn (switch-to-buffer (car place)) (goto-char (cdr place))) (helm-aif (car place) (message "Couldn't find Function `%s' in `%s'" func (buffer-name it)) (message "Couldn't find Function `%s'" func)))))) (defun helm-find-variable (var) "Try to jump to VAR definition. With a prefix arg ask for the project directory to search in instead of using LOAD-PATH." (if (not helm-current-prefix-arg) (find-variable (helm-symbolify var)) (let ((place (helm-find-function-noselect var nil 'defvar))) (when place (switch-to-buffer (car place)) (goto-char (cdr place)))))) (defun helm-find-face-definition (face) "Try to jump to FACE definition. With a prefix arg ask for the project directory to search in instead of using LOAD-PATH." (if (not helm-current-prefix-arg) (find-face-definition (helm-symbolify face)) (let ((place (helm-find-function-noselect face nil 'defface))) (when place (switch-to-buffer (car place)) (goto-char (cdr place)))))) (defun helm-kill-new (candidate &optional replace) "CANDIDATE is symbol or string. See `kill-new' for argument REPLACE." (kill-new (helm-stringify candidate) replace)) (defun helm-group-p (symbol) "Return non nil when SYMBOL is a group." (or (and (get symbol 'custom-loads) (not (get symbol 'custom-autoload))) (get symbol 'custom-group))) ;;; Modes ;; (defun helm-same-major-mode-p (start-buffer alist) "Decide if current-buffer is related to START-BUFFER. Argument ALIST is an alist of associated major modes." ;; START-BUFFER is the current-buffer where we start searching. ;; Determine the major-mode of START-BUFFER as `cur-maj-mode'. ;; Each time the loop go in another buffer we try from this buffer ;; to determine if its `major-mode' is: ;; - same as the `cur-maj-mode' ;; - derived from `cur-maj-mode' and from ;; START-BUFFER if its mode is derived from the one in START-BUFFER. ;; - have an assoc entry (major-mode . cur-maj-mode) ;; - have an rassoc entry (cur-maj-mode . major-mode) ;; - check if one of these entries inherit from another one in ;; `alist'. (let* ((cur-maj-mode (with-current-buffer start-buffer major-mode)) (maj-mode major-mode) (c-assoc-mode (assq cur-maj-mode alist)) (c-rassoc-mode (rassq cur-maj-mode alist)) (o-assoc-mode (assq major-mode alist)) (o-rassoc-mode (rassq major-mode alist)) (cdr-c-assoc-mode (cdr c-assoc-mode)) (cdr-o-assoc-mode (cdr o-assoc-mode))) (or (eq major-mode cur-maj-mode) (derived-mode-p cur-maj-mode) (with-current-buffer start-buffer (derived-mode-p maj-mode)) (or (eq cdr-c-assoc-mode major-mode) (eq (car c-rassoc-mode) major-mode) (eq (cdr (assq cdr-c-assoc-mode alist)) major-mode) (eq (car (rassq cdr-c-assoc-mode alist)) major-mode)) (or (eq cdr-o-assoc-mode cur-maj-mode) (eq (car o-rassoc-mode) cur-maj-mode) (eq (cdr (assq cdr-o-assoc-mode alist)) cur-maj-mode) (eq (car (rassq cdr-o-assoc-mode alist)) cur-maj-mode))))) ;;; Source processing ;; (defun helm-map-candidates-in-source (src fn pred) "Map over all candidates in SRC and execute FN if PRED returns non nil. Arg FN and PRED are functions called with current display part of candidate as arg." (declare (indent 1)) (save-excursion (goto-char (helm-get-previous-header-pos)) (helm-next-line) (let* ((next-head (helm-get-next-header-pos)) (end (and next-head (save-excursion (goto-char next-head) (forward-line -1) (point)))) (maxpoint (or end (point-max)))) (while (< (point) maxpoint) (helm-mark-current-line) (let ((cand (helm-get-selection nil 'withprop src))) (when (funcall pred cand) (funcall fn cand))) (forward-line 1) (end-of-line))))) ;;; Files routines ;; (defun helm-file-name-sans-extension (filename) "Same as `file-name-sans-extension' but remove all extensions." (helm-aif (file-name-sans-extension filename) ;; Start searching at index 1 for files beginning with a dot ;; (bug#1335). (if (string-match "\\." (helm-basename it) 1) (helm-file-name-sans-extension it) it))) (defsubst helm-file-name-extension (file) "Returns FILE extension if it is not a number." (helm-aif (file-name-extension file) (and (not (helm-string-numberp it)) it))) (defun helm-basename (fname &optional ext) "Print FNAME with any leading directory components removed. If specified, also remove filename extension EXT. If FNAME is a directory EXT arg is ignored. Arg EXT can be specified as a string, a number or `t' . When specified as a string, this string is stripped from end of FNAME. e.g. (helm-basename \"tutorial.el.gz\" \".el.gz\") => tutorial. When `t' no checking of `file-name-extension' is done and the first extension is removed unconditionally with `file-name-sans-extension'. e.g. (helm-basename \"tutorial.el.gz\" t) => tutorial.el. When a number, remove that many times extensions from FNAME until FNAME ends with its real extension which is by default \".el\". e.g. (helm-basename \"tutorial.el.gz\" 2) => tutorial To specify the extension where to stop use a cons cell where the cdr is a regexp matching extension e.g. (2 . \\\\.py$). e.g. (helm-basename \"~/ucs-utils-6.0-delta.py.gz\" \\='(2 . \"\\\\.py\\\\\\='\")) =>ucs-utils-6.0-delta." (let ((non-essential t) (ext-regexp (cond ((consp ext) (cdr ext)) ((numberp ext) "\\.el\\'") (t ext))) result) (cond ((or (null ext) (file-directory-p fname)) (file-name-nondirectory (directory-file-name fname))) ((or (numberp ext) (consp ext)) (cl-dotimes (_ (if (consp ext) (car ext) ext)) (let ((bn (file-name-nondirectory (or result fname)))) (helm-aif (file-name-sans-extension bn) (if (string-match-p ext-regexp bn) (cl-return (setq result (file-name-sans-extension bn))) (setq result (file-name-sans-extension bn)))))) result) ((eq t ext) (file-name-sans-extension (file-name-nondirectory fname))) ((stringp ext) (replace-regexp-in-string (concat (regexp-quote ext) "\\'") "" (file-name-nondirectory fname)))))) (defun helm-basedir (fname &optional parent) "Return the base directory of FNAME ending by a slash. If PARENT is non nil and FNAME is a directory return the parent directory of FNAME, if PARENT is a number return the parent directory up to PARENT level. If PARENT is not specified but FNAME doesn't end by a slash, the returned value is same as with PARENT." (helm-aif (and fname (or (and (string= fname "~") "~") (file-name-directory (helm-acase parent ((guard* (numberp it)) (cl-loop repeat it for bd = (helm-basedir (or bd fname) t) finally return bd)) ((guard* (eq it t)) (directory-file-name fname)) (t fname))))) (file-name-as-directory it))) (defun helm-current-directory () "Return current-directory name at point. It is done according to `helm-current-directory-alist'." (expand-file-name (helm-acase major-mode ((guard* (assoc-default it helm-current-directory-alist)) (helm-interpret-value guard)) (t default-directory)))) (defun helm-shadow-boring-files (files) "Files matching `helm-boring-file-regexp' will be displayed with the `file-name-shadow' face if available." (helm-shadow-entries files helm-boring-file-regexp-list)) (defun helm-skip-boring-files (files) "Files matching `helm-boring-file-regexp' will be skipped." (helm-skip-entries files helm-boring-file-regexp-list)) (defun helm-skip-current-file (files) "Current file will be skipped." (remove (buffer-file-name helm-current-buffer) files)) (defun helm-w32-pathname-transformer (args) "Change undesirable features of windows pathnames to ones more acceptable to other candidate transformers." (if (eq system-type 'windows-nt) (helm-transform-mapcar (lambda (x) (replace-regexp-in-string "/cygdrive/\\(.\\)" "\\1:" (replace-regexp-in-string "\\\\" "/" x))) args) args)) (defun helm-w32-prepare-filename (file) "Convert filename FILE to something usable by external w32 executables." (replace-regexp-in-string ; For UNC paths "/" "\\" (replace-regexp-in-string ; Strip cygdrive paths "/cygdrive/\\(.\\)" "\\1:" file nil nil) nil t)) (defun helm-w32-shell-execute-open-file (file) (with-no-warnings (w32-shell-execute "open" (helm-w32-prepare-filename file)))) ;; Same as `vc-directory-exclusion-list'. (defvar helm-walk-ignore-directories '("SCCS/" "RCS/" "CVS/" "MCVS/" ".svn/" ".git/" ".hg/" ".bzr/" "_MTN/" "_darcs/" "{arch}/" ".gvfs/")) (defsubst helm--dir-file-name (file dir) (expand-file-name (substring file 0 (1- (length file))) dir)) (defsubst helm--dir-name-p (str) (char-equal (aref str (1- (length str))) ?/)) (cl-defun helm-walk-directory (directory &key (path 'basename) directories match skip-subdirs noerror) "Walk through DIRECTORY tree. Argument PATH can be one of basename, relative, full, or a function called on file name, default to basename. Argument DIRECTORIES when t return also directories names, otherwise skip directories names, with a value of `only' returns only subdirectories, i.e. files are skipped. Argument MATCH is a regexp matching files or directories. Argument SKIP-SUBDIRS when t will skip `helm-walk-ignore-directories', otherwise if it is given as a list of directories, this list will be used instead of `helm-walk-ignore-directories'. Argument NOERROR when t will skip directories which are not accessible." (let ((fn (cl-case path (basename 'file-name-nondirectory) (relative 'file-relative-name) (full 'identity) (t path)))) ; A function. (setq skip-subdirs (if (listp skip-subdirs) skip-subdirs helm-walk-ignore-directories)) (cl-labels ((ls-rec (dir) (unless (file-symlink-p dir) (cl-loop for f in (sort (file-name-all-completions "" dir) 'string-lessp) unless (member f '("./" "../")) ;; A directory. ;; Use `helm--dir-file-name' to remove the final slash. ;; Needed to avoid infloop on directory symlinks. if (and (helm--dir-name-p f) (helm--dir-file-name f dir)) nconc (unless (or (member f skip-subdirs) (and noerror (not (file-accessible-directory-p it)))) (if (and directories (or (null match) (string-match match f))) (nconc (list (concat (funcall fn it) "/")) (ls-rec it)) (ls-rec it))) ;; A regular file. else nconc (when (and (null (eq directories 'only)) (or (null match) (string-match match f))) (list (funcall fn (expand-file-name f dir)))))))) (ls-rec directory)))) (defun helm-file-expand-wildcards (pattern &optional full) "Same as `file-expand-wildcards' but allow recursion. Recursion happens when PATTERN starts with two stars. Directories expansion is not supported." (let ((bn (helm-basename pattern)) (case-fold-search nil)) (if (and helm-file-globstar (string-match "\\`\\*\\{2\\}\\(.*\\)" bn)) (helm-walk-directory (helm-basedir pattern) :path (cl-case full (full 'full) (relative 'relative) ((basename nil) 'basename) (t 'full)) :directories nil :match (or (helm-wildcard-to-regexp bn) (wildcard-to-regexp bn)) :skip-subdirs t) (helm-aif (helm-wildcard-to-regexp bn) (directory-files (helm-basedir pattern) full it) ;; `file-expand-wildcards' fails to expand weird directories ;; like "[ foo.zz ] bar.*.avi", fallback to `directory-files' ;; in such cases. (or (file-expand-wildcards pattern full) (directory-files (helm-basedir pattern) full (wildcard-to-regexp bn))))))) (defun helm-wildcard-to-regexp (wc) "Transform wilcard WC like \"**.{jpg,jpeg}\" in REGEXP." (when (string-match ".*\\(\\*\\{1,2\\}\\)\\.[{]\\(.*\\)[}]\\'" wc) (format ".*\\.\\(%s\\)$" (replace-regexp-in-string "," "\\\\|" (match-string 2 wc))))) (defun helm-locate-lib-get-summary (file) "Extract library description from FILE." (with-temp-buffer (let (desc) (cl-letf (((symbol-function 'message) #'ignore)) (insert-file-contents file nil 0 128)) (goto-char (point-min)) (when (re-search-forward "^;;;?\\(.*\\) ---? \\(.*\\)" (pos-eol) t) (setq desc (match-string-no-properties 2))) (if (or (null desc) (string= "" desc) (string-match "\\`-\\*-" desc)) "Not documented" (car (split-string desc "-\\*-" nil "[ \t\n\r-]+")))))) (defun helm-local-directory-files (directory &rest args) "Run `directory-files' without tramp file name handlers. Take same args as `directory-files'." (require 'tramp) (let ((file-name-handler-alist (cl-loop for (re . sym) in file-name-handler-alist unless (and (symbolp sym) (string-prefix-p "tramp-" (symbol-name sym))) collect `(,re . ,sym))) tramp-mode) ;; Avoid error with 5nth arg COUNT which is not available in previous Emacs, ;; at least 27.1, see bug#2662. (apply #'directory-files directory args))) (defun helm-common-dir-1 (files) "Find the common directories of FILES." (if (cdr files) (cl-loop with base = (car files) with others = nil for file in files for cpart = (fill-common-string-prefix base file) if cpart do (setq base cpart) else do (push file others) finally return (if (and others base) (nconc (list (directory-file-name base)) (helm-common-dir-1 others)) (list (and base (directory-file-name base))))) (and files (list (directory-file-name (file-name-directory (car files))))))) (defun helm-common-dir (files) "Return the longest common directory path of FILES list. If FILES are not all common to the same drive (Windows) a list of common directory is returned." (let ((result (helm-common-dir-1 files))) (if (cdr result) result (car result)))) ;; Tests: ;; (helm-common-dir '("c:/foo" "c:/foo/bar/baz" ;; "f:/foo" "e:/foo" "f:/foo/bar" ;; "d:/foo" "d:/foo/bar/baz" ;; "e:/foo/bar/baz")) ;; ("c:/foo" "e:/foo" "f:/foo" "d:/foo") ;; (helm-common-dir '("/home/you/download/foo/" ;; "/home/you/download/foo/bar/baz" ;; "/home/you/tmp/foo")) ;; "/home/you" ;;; helm internals ;; (defun helm-set-pattern (pattern &optional noupdate) "Set minibuffer contents to PATTERN. If optional NOUPDATE is non-nil, the Helm buffer is not changed." (with-selected-window (or (active-minibuffer-window) (minibuffer-window)) (delete-minibuffer-contents) (insert pattern)) (when noupdate (setq helm-pattern pattern))) (defun helm-minibuffer-completion-contents () "Return the user input in a minibuffer before point as a string. That is what completion commands operate on." (buffer-substring (field-beginning) (point))) (defmacro with-helm-buffer (&rest body) "Eval BODY inside `helm-buffer'." (declare (indent 0) (debug t)) `(with-current-buffer (helm-buffer-get) ,@body)) (defmacro with-helm-current-buffer (&rest body) "Eval BODY inside `helm-current-buffer'." (declare (indent 0) (debug t)) `(with-current-buffer (or (and (buffer-live-p helm-current-buffer) helm-current-buffer) (setq helm-current-buffer (current-buffer))) ,@body)) (defun helm-buffer-get () "Return `helm-action-buffer' if shown otherwise `helm-buffer'." (if (helm-action-window) helm-action-buffer helm-buffer)) (defun helm-window () "Window of `helm-buffer'." (get-buffer-window (helm-buffer-get) 0)) (defun helm-action-window () "Window of `helm-action-buffer'." (get-buffer-window helm-action-buffer 'visible)) (defmacro with-helm-window (&rest body) "Be sure BODY is excuted in the helm window." (declare (indent 0) (debug t)) `(with-selected-window (helm-window) ,@body)) (defmacro helm-without-follow (&rest body) "Ensure BODY runs without following. I.e. when using `helm-next-line' and friends in BODY." (declare (indent 0) (debug t)) `(cl-letf (((symbol-function 'helm-follow-mode-p) (lambda (&optional _) nil))) (let (helm-follow-mode-persistent) (progn ,@body)))) (defun helm-candidate-prefixed-p (candidate) "Return non nil when CANDIDATE is prefixed. Candidates files are prefixed with [+] or a specific icon when candidate is a non existing file, in other places candidates may be prefixed with an unknown symbol [?], these candidate have the text property or property." (or (get-text-property 0 'helm-new-file candidate) (get-text-property 0 'unknown candidate))) ;; Completion styles related functions ;; (defun helm--setup-completion-styles-alist () (cl-pushnew '(helm helm-completion-try-completion helm-completion-all-completions "helm multi completion style.") completion-styles-alist :test 'equal) (unless (assq 'flex completion-styles-alist) ;; Add helm-flex style only if flex is not available. (cl-pushnew '(helm-flex helm-flex-completion-try-completion helm-flex-completion-all-completions "helm flex completion style.\nProvide flex matching for emacs-26.") completion-styles-alist :test 'equal))) (defvar helm-blacklist-completion-styles '(emacs21 emacs22)) (defun helm--prepare-completion-styles (&optional com-or-mode styles) "Return a suitable list of styles for `completion-styles'. When `helm-completion-style' is not `emacs' the Emacs vanilla default `completion-styles' is used except for `helm-dynamic-completion' which uses inconditionally `emacs' as value for `helm-completion-style'. If styles are specified in `helm-completion-styles-alist' for a particular mode, use these styles for the corresponding mode. If COM-OR-MODE (a mode or a command) is specified it is used to find the corresponding styles in `helm-completion-styles-alist'. If STYLES is specified as a list of styles suitable for `completion-styles' these styles are used in the given order. Otherwise helm style is added to `completion-styles' always after flex or helm-flex completion style if present." ;; For `helm-completion-style' and `helm-completion-styles-alist'. (require 'helm-mode) (let ((from (if com-or-mode com-or-mode major-mode))) (if (memq helm-completion-style '(helm helm-fuzzy)) ;; Keep default settings, but probably nil is fine as well. '(basic partial-completion emacs22) (or styles (helm-acase (cdr (assq from helm-completion-styles-alist)) ((dst* (_l &rest args)) args)) ;; We need to have flex always behind helm, otherwise ;; when matching against e.g. '(foo foobar foao frogo bar ;; baz) with pattern "foo" helm style if before flex will ;; return foo and foobar only defeating flex that would ;; return foo foobar foao and frogo. (let* ((wflex (car (or (assq 'flex completion-styles-alist) (assq 'helm-flex completion-styles-alist)))) (styles (append (and (memq wflex completion-styles) (list wflex)) (cl-loop for s in completion-styles unless (or (memq s helm-blacklist-completion-styles) (memq wflex completion-styles)) collect s)))) (helm-append-at-nth styles '(helm) (if (memq wflex completion-styles) 1 0))))))) (defun helm-guess-filename-at-point () (with-helm-current-buffer ;; Ensure to disable the evil `ffap-machine-at-point' which may run here as ;; `file-name-at-point-functions' contains by default ;; `ffap-guess-file-name-at-point' See bug#2574. ;; Use same value as in Emacs-29 for next 3 vars to ensure `ffap-machine-p' ;; never ping. (let ((ffap-machine-p-known 'accept) (ffap-machine-p-local 'reject) (ffap-machine-p-unknown 'reject)) (run-hook-with-args-until-success 'file-name-at-point-functions)))) ;; Yank text at point. ;; ;; (defun helm-yank-text-at-point (arg) "Yank text at point in `helm-current-buffer' into minibuffer." (interactive "p") (with-helm-current-buffer (let ((fwd-fn (or helm-yank-text-at-point-function #'forward-word)) diff) ;; Start to initial point if C-w have never been hit. (unless helm-yank-point (setq helm-yank-point (car helm-current-position))) (save-excursion (goto-char helm-yank-point) (helm-set-pattern (if (< arg 0) (with-temp-buffer (insert helm-pattern) (let ((end (point-max))) (goto-char end) (funcall fwd-fn -1) (setq diff (- end (point))) (delete-region (point) end) (buffer-string))) (funcall fwd-fn arg) (concat ;; Allow yankink beyond eol allow inserting e.g long ;; urls in mail buffers. helm-pattern (replace-regexp-in-string "\\`\n" "" (buffer-substring-no-properties helm-yank-point (point)))))) (setq helm-yank-point (if diff (- (point) diff) (point))))))) (put 'helm-yank-text-at-point 'helm-only t) (defun helm-undo-yank-text-at-point () "Undo last entry added by `helm-yank-text-at-point'." (interactive) (helm-yank-text-at-point -1)) (put 'helm-undo-yank-text-at-point 'helm-only t) (defun helm-reset-yank-point () (setq helm-yank-point nil)) (add-hook 'helm-cleanup-hook 'helm-reset-yank-point) (add-hook 'helm-after-initialize-hook 'helm-reset-yank-point) ;;; Ansi ;; ;; (defvar helm--ansi-color-regexp "\033\\[\\(K\\|[0-9;]*m\\)") (defvar helm--ansi-color-drop-regexp "\033\\[\\([ABCDsuK]\\|[12][JK]\\|=[0-9]+[hI]\\|[0-9;]*[Hf]\\)") (with-suppressed-warnings ((obsolete ansi-color-apply-sequence)) (defun helm--ansi-color-apply (string) "A version of `ansi-color-apply' immune to upstream changes. Similar to the emacs-24.5 version without support to `ansi-color-context' which is buggy in Emacs. Modify also `ansi-color-regexp' by using own variable `helm--ansi-color-regexp' that matches whole STRING. This is needed to provide compatibility for both emacs-25 and emacs-24.5 as emacs-25 version of `ansi-color-apply' is partially broken." (require 'ansi-color) (let ((start 0) codes end escape-sequence result colorized-substring) ;; Find the next escape sequence. (while (setq end (string-match helm--ansi-color-regexp string start)) (setq escape-sequence (match-string 1 string)) ;; Colorize the old block from start to end using old face. (when codes (put-text-property start end 'font-lock-face (ansi-color--find-face codes) string)) (setq colorized-substring (substring string start end) start (match-end 0)) ;; Eliminate unrecognized ANSI sequences. (while (string-match helm--ansi-color-drop-regexp colorized-substring) (setq colorized-substring (replace-match "" nil nil colorized-substring))) (push colorized-substring result) ;; Create new face, by applying escape sequence parameters. (setq codes (ansi-color-apply-sequence escape-sequence codes))) ;; If the rest of the string should have a face, put it there. (when codes (put-text-property start (length string) 'font-lock-face (ansi-color--find-face codes) string)) ;; Save the remainder of the string to the result. (if (string-match "\033" string start) (push (substring string start (match-beginning 0)) result) (push (substring string start) result)) (apply 'concat (nreverse result))))) (when (< emacs-major-version 26) (advice-add 'ansi-color-apply :override #'helm--ansi-color-apply)) ;;; Fontlock (dolist (mode '(emacs-lisp-mode lisp-interaction-mode)) (font-lock-add-keywords mode '(("(\\<\\(with-helm-after-update-hook\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(with-helm-temp-hook\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(with-helm-window\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(with-helm-current-buffer\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(with-helm-buffer\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(with-helm-show-completion\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(with-helm-default-directory\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(with-helm-restore-variables\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-multi-key-defun\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-while-no-input\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-aif\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-awhile\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-acond\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-aand\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-with-gensyms\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-read-answer-dolist-with-action\\)\\>" 1 font-lock-keyword-face) ("(\\<\\(helm-read-answer\\)\\>" 1 font-lock-keyword-face)))) (provide 'helm-lib) ;;; helm-lib.el ends here helm-4.0.3/helm-locate.el000066400000000000000000000533311501106761700151700ustar00rootroot00000000000000;;; helm-locate.el --- helm interface for locate. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;; NOTE for WINDOZE users: ;; You have to install Everything with his command line interface here: ;; http://www.voidtools.com/download.php ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-types) (require 'helm-help) (defvar helm-ff-default-directory) (declare-function helm-read-file-name "helm-mode") (defgroup helm-locate nil "Locate related Applications and libraries for Helm." :group 'helm) (defcustom helm-locate-db-file-regexp "m?locate\\.db$" "Default regexp to match locate database. If nil Search in all files." :type 'string) (defcustom helm-ff-locate-db-filename "locate.db" "The basename of the locatedb file you use locally in your directories. When this is set and Helm finds such a file in the directory from where you launch locate, it will use this file and will not prompt you for a db file. Note that this happen only when locate is launched with a prefix arg." :type 'string) (defcustom helm-locate-command nil "A list of arguments for locate program. Helm will calculate a default value for your system on startup unless `helm-locate-command' is non-nil. Here are the default values it will use according to your system: Gnu/linux: \"locate %s -e -A -N --regex %s\" berkeley-unix: \"locate %s %s\" windows-nt: \"es %s %s\" Others: \"locate %s %s\" This string will be passed to format so it should end with `%s'. The first format spec is used for the \"-i\" value of locate/es, so don't set it directly but use `helm-locate-case-fold-search' for this. The last option must be the one preceding pattern i.e \"-r\" or \"--regex\". The option \"-N\" may not be available on old locate versions, it is needed on latest systems as locate send quoted filenames, it is BTW enabled by default, if this option is not recognized on your system, remove it. You will be able to pass other options such as \"-b\" or \"l\" during Helm invocation after entering pattern only when multi matching, not when fuzzy matching. Note that the \"-b\" option is added automatically by Helm when var `helm-locate-fuzzy-match' is non-nil and switching back from multimatch to fuzzy matching (this is done automatically when a space is detected in pattern)." :type 'string) (defcustom helm-locate-create-db-command "updatedb -l 0 -o '%s' -U '%s'" "Command used to create a locale locate db file." :type 'string) (defcustom helm-locate-case-fold-search helm-case-fold-search "It have the same meaning as `helm-case-fold-search'. The -i option of locate will be used depending of value of `helm-pattern' when this is set to \\='smart. When nil \"-i\" will not be used at all and when non-nil it will always be used. NOTE: the -i option of the \"es\" command used on windows does the opposite of \"locate\" command." :type 'symbol) (defcustom helm-locate-fuzzy-match nil "Enable fuzzy matching in `helm-locate'. Note that when this is enabled searching is done on basename." :type 'boolean) (defcustom helm-locate-fuzzy-sort-fn #'helm-locate-default-fuzzy-sort-fn "Default fuzzy matching sort function for locate." :type 'boolean) (defcustom helm-locate-project-list nil "A list of directories, your projects. When set, allow browsing recursively files in all directories of this list with `helm-projects-find-files'." :type '(repeat string)) (defcustom helm-locate-recursive-dirs-command "find %s -type d -regex .*%s.*$" "Command used for recursive directories completion in `helm-find-files'. For Windows and `es' use something like \"es -r ^%s.*%s.*$\" The two format specs are mandatory. We were using locate command as default in the past like this: \"locate -i -e -A --regex '^%s' '%s.*$'\" But in most distributions updatedb is not indexing user dirs among others (see PRUNE_BIND_MOUNTS in updatedb.conf man page). However if you use a local db file, it will be used instead of the global updatedb cache and will be very fast. So we use now the find shell command by default which is available on most distributions and doesn't suffer of these indexing problems. It is however slower than locate. Here the possible values you can use: \"find %s -type d -regex .*%s.*$\" \"find %s -type d -name '*%s*'\" You can use also the \"fdfind\" command which may be slow at first call because it creates an index, but is then very fast on subsequent calls, here is the command you can use: \"fdfind --hidden --type d --glob '*%s*' %s\" NOTE: The \"fdfind\" executable name may change on some systems, it can be \"fd\" or whatever. See `helm-find-files' embedded help for more infos." :type 'string :group 'helm-files) (defvar helm-locate-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-generic-files-map) (define-key map (kbd "DEL") 'helm-delete-backward-no-update) map)) (defface helm-locate-finish `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Green")) "Face used in mode line when locate process is finish." :group 'helm-locate) (defun helm-ff-find-locatedb (&optional from-ff) "Try to find if a local locatedb file is available. The search is done in `helm-ff-default-directory' or falls back to `default-directory' if FROM-FF is nil." (helm-aif (and helm-ff-locate-db-filename (locate-dominating-file (or (and from-ff helm-ff-default-directory) default-directory) helm-ff-locate-db-filename)) (expand-file-name helm-ff-locate-db-filename it))) (defun helm-locate-create-db-default-function (db-name directory) "Default function used to create a locale locate db file. Argument DB-NAME name of the db file. Argument DIRECTORY root of file system subtree to scan." (format helm-locate-create-db-command db-name (expand-file-name directory))) (defvar helm-locate-create-db-function #'helm-locate-create-db-default-function "Function used to create a locale locate db file. It should receive the same arguments as `helm-locate-create-db-default-function'.") (defun helm-locate-1 (&optional localdb init from-ff default) "Generic function to run Locate. Prefix arg LOCALDB when (4) search and use a local locate db file when it exists or create it, when (16) force update of existing db file even if exists. It has no effect when locate command is \\='es'. INIT is a string to use as initial input in prompt. See `helm-locate-with-db' and `helm-locate'." (require 'helm-mode) (helm-locate-set-command) (let ((pfn (lambda (candidate) (if (file-directory-p candidate) (message "Error: The locate Db should be a file") (if (= (shell-command (funcall helm-locate-create-db-function candidate helm-ff-default-directory)) 0) (message "New locatedb file `%s' created" candidate) (error "Failed to create locatedb file `%s'" candidate))))) (locdb (and localdb (not (string-match "^es" helm-locate-command)) (or (and (equal '(4) localdb) (helm-ff-find-locatedb from-ff)) (helm-read-file-name "Create Locate Db file: " :initial-input (expand-file-name "locate.db" (or helm-ff-default-directory default-directory)) :preselect helm-locate-db-file-regexp :test (lambda (x) (if helm-locate-db-file-regexp ;; Select only locate db files and directories ;; to allow navigation. (or (string-match helm-locate-db-file-regexp x) (file-directory-p x)) x))))))) (when (and locdb (or (equal localdb '(16)) (not (file-exists-p locdb)))) (funcall pfn locdb)) (helm-locate-with-db (and localdb locdb) init default))) (defun helm-locate-set-command () "Setup `helm-locate-command' if not already defined." (unless helm-locate-command (setq helm-locate-command (cl-case system-type ;; Use -N option by default (bug#2625) (gnu/linux "locate %s -e -A -N --regex %s") (berkeley-unix "locate %s %s") (windows-nt "es %s %s") (t "locate %s %s"))))) (defun helm-locate-initial-setup () (require 'helm-for-files) (helm-locate-set-command)) (defvar helm-file-name-history nil) (defun helm-locate-with-db (&optional db initial-input default) "Run locate -d DB. If DB is not given or nil use locate without -d option. Argument DB can be given as a string or list of db files. Argument INITIAL-INPUT is a string to use as initial-input. See also `helm-locate'." (require 'helm-files) (when (and db (stringp db)) (setq db (list db))) (helm-locate-set-command) (let ((helm-locate-command (if db (replace-regexp-in-string "locate" (format (if helm-locate-fuzzy-match "locate -b -d '%s'" "locate -d '%s'") (mapconcat 'identity ;; Remove eventually ;; marked directories by error. (cl-loop for i in db unless (file-directory-p i) ;; expand-file-name to resolve ;; abbreviated fnames not ;; expanding inside single ;; quotes i.e. '%s'. collect (expand-file-name i)) ":")) helm-locate-command) (if (and helm-locate-fuzzy-match (not (string-match-p "\\`locate -b" helm-locate-command))) (replace-regexp-in-string "\\`locate" "locate -b" helm-locate-command) helm-locate-command)))) (setq helm-file-name-history (mapcar 'helm-basename file-name-history)) (helm :sources 'helm-source-locate :buffer "*helm locate*" :ff-transformer-show-only-basename nil :input initial-input :default default :history 'helm-file-name-history))) (defun helm-locate-update-mode-line (process-name) "Update mode-line with PROCESS-NAME status information." (with-helm-window (setq mode-line-format `(" " mode-line-buffer-identification " " (:eval (format "L%s" (helm-candidate-number-at-point))) " " (:eval (propertize (format "[%s process finished - (%s results)]" (max (1- (count-lines (point-min) (point-max))) 0) ,process-name) 'face 'helm-locate-finish)))) (force-mode-line-update))) (defun helm-locate--default-process-coding-system () "Fix `default-process-coding-system' in locate for Windows systems." ;; This is an attempt to fix issue #1322. (if (and (eq system-type 'windows-nt) (boundp 'w32-ansi-code-page)) (let ((code-page-eol (intern (format "cp%s-%s" w32-ansi-code-page "dos")))) (if (ignore-errors (check-coding-system code-page-eol)) (cons code-page-eol code-page-eol) default-process-coding-system)) default-process-coding-system)) (defun helm-locate-init () "Initialize async locate process for `helm-source-locate'." (let* ((default-process-coding-system (helm-locate--default-process-coding-system)) (locate-is-es (string-match "\\`es" helm-locate-command)) (real-locate (string-match "\\`locate" helm-locate-command)) (case-sensitive-flag (if locate-is-es "-i" "")) (ignore-case-flag (if (or locate-is-es (not real-locate)) "" "-i")) (args (helm-mm-split-pattern helm-pattern)) (cmd (format helm-locate-command (cl-case helm-locate-case-fold-search (smart (let ((case-fold-search nil)) (if (string-match "[[:upper:]]" helm-pattern) case-sensitive-flag ignore-case-flag))) (t (if helm-locate-case-fold-search ignore-case-flag case-sensitive-flag))) (helm-aif (cdr args) (concat ;; The pattern itself. (shell-quote-argument (car args)) " " ;; Possible locate args added ;; after pattern, don't quote them. (mapconcat 'identity it " ")) (shell-quote-argument (car args))))) (default-directory (if (file-directory-p default-directory) default-directory "/"))) (helm-log "helm-locat-init" "Starting helm-locate process") (helm-log "helm-locat-init" "Command line used was:\n\n%s" (concat ">>> " (propertize cmd 'face 'font-lock-comment-face) "\n\n")) (prog1 (start-process-shell-command "locate-process" helm-buffer cmd) (set-process-sentinel (get-buffer-process helm-buffer) (lambda (process event) (let* ((err (process-exit-status process)) (noresult (= err 1))) (cond (noresult (with-helm-buffer (unless (cdr helm-sources) (insert (concat "* Exit with code 1, no result found," " command line was:\n\n " cmd))))) ((string= event "finished\n") (when (and helm-locate-fuzzy-match (not (string-match-p "\\s-" helm-pattern))) (helm-redisplay-buffer)) (helm-locate-update-mode-line "Locate")) (t (helm-log "helm-locat-init" "Error: Locate %s" (replace-regexp-in-string "\n" "" event)))))))))) (defun helm-locate-default-fuzzy-sort-fn (candidates) "Default sort function for files in fuzzy matching. Sort is done on basename of CANDIDATES." (helm-fuzzy-matching-default-sort-fn-1 candidates nil t)) (defclass helm-locate-source (helm-source-async helm-type-file) ((init :initform 'helm-locate-initial-setup) (candidates-process :initform 'helm-locate-init) (requires-pattern :initform 3) (history :initform 'helm-file-name-history) (persistent-action :initform 'helm-ff-kill-or-find-buffer-fname) (candidate-number-limit :initform 9999) (redisplay :initform (progn helm-locate-fuzzy-sort-fn)))) ;; Override helm-type-file class keymap. (cl-defmethod helm--setup-source :after ((source helm-locate-source)) (setf (slot-value source 'keymap) helm-locate-map) (setf (slot-value source 'group) 'helm-locate)) (defvar helm-source-locate (helm-make-source "Locate" 'helm-locate-source :pattern-transformer 'helm-locate-pattern-transformer ;; :match-part is only used here to tell helm which part ;; of candidate to highlight. :match-part (lambda (candidate) (if (or (string-match-p " -b\\'" helm-pattern) (and helm-locate-fuzzy-match (not (string-match "\\s-" helm-pattern)))) (helm-basename candidate) candidate)))) (defun helm-locate-pattern-transformer (pattern) (if helm-locate-fuzzy-match ;; When fuzzy is enabled helm add "-b" option on startup. (cond ((string-match-p " " pattern) (when (string-match "\\`locate -b" helm-locate-command) (setq helm-locate-command (replace-match "locate" t t helm-locate-command))) pattern) (t (unless (string-match-p "\\`locate -b" helm-locate-command) (setq helm-locate-command (replace-regexp-in-string "\\`locate" "locate -b" helm-locate-command))) (helm--mapconcat-pattern pattern))) pattern)) (defun helm-locate-find-dbs-in-projects (&optional update) (let* ((pfn (lambda (candidate directory) (unless (= (shell-command (funcall helm-locate-create-db-function candidate directory)) 0) (error "Failed to create locatedb file `%s'" candidate)))) (projects (cl-loop for p in helm-locate-project-list when (file-directory-p p) collect p))) (cl-loop for p in projects for db = (expand-file-name helm-ff-locate-db-filename (file-name-as-directory p)) if (and (null update) (file-exists-p db)) collect db else do (funcall pfn db p) and collect db))) ;;; Directory completion for hff. ;; (defclass helm-locate-subdirs-source (helm-source-in-buffer) ((basedir :initarg :basedir :initform nil :custom string) (subdir :initarg :subdir :initform nil :custom 'string) (data :initform #'helm-locate-init-subdirs) (group :initform 'helm-locate))) (defun helm-locate-init-subdirs () (let ((cmd (helm-acase helm-locate-recursive-dirs-command (;; Fd (guard* (string-match-p "\\`fd" it)) ;; fd pass path at end. (format it (helm-get-attr 'subdir) (helm-get-attr 'basedir))) (;; Es (guard* (string-match-p "\\`es" it)) (format it (replace-regexp-in-string "/" "\\\\\\\\" (helm-get-attr 'basedir)) (helm-get-attr 'subdir))) (;; Locate (guard* (string-match-p "\\`locate" it)) ;; Try to use a locale DB if some. (let* ((db (locate-dominating-file (helm-get-attr 'basedir) helm-ff-locate-db-filename)) (lcmd (if (and db (not (string-match-p "-d" it))) (mapconcat #'identity (helm-append-at-nth (split-string it) (format "-d %s" (expand-file-name helm-ff-locate-db-filename db)) 1) " ") it))) (format lcmd (helm-get-attr 'basedir) (helm-get-attr 'subdir)))) ;; Find (t (format it (helm-get-attr 'basedir) (helm-get-attr 'subdir)))))) (with-temp-buffer (call-process-shell-command cmd nil t nil) (buffer-string)))) ;;;###autoload (defun helm-projects-find-files (update) "Find files with locate in `helm-locate-project-list'. With a prefix arg refresh the database in each project." (interactive "P") (helm-locate-set-command) (cl-assert (and (string-match-p "\\`locate" helm-locate-command) (executable-find "updatedb")) nil "Unsupported locate program") (let ((dbs (helm-locate-find-dbs-in-projects update))) (if dbs (helm-locate-with-db dbs) (user-error "No projects found, please setup `helm-locate-project-list'")))) ;;;###autoload (defun helm-locate (arg) "Preconfigured `helm' for Locate. Note: you can add locate options after entering pattern. See \\='man locate' for valid options and also `helm-locate-command'. You can specify a local database with prefix argument ARG. With two prefix arg, refresh the current local db or create it if it doesn't exists. To create a user specific db, use \"updatedb -l 0 -o db_path -U directory\". Where db_path is a filename matched by `helm-locate-db-file-regexp'." (interactive "P") (helm-set-local-variable 'helm-async-outer-limit-hook (list (lambda () (when (and helm-locate-fuzzy-match (not (string-match-p "\\s-" helm-pattern))) (helm-redisplay-buffer))))) (setq helm-ff-default-directory default-directory) (helm-locate-1 arg nil nil (thing-at-point 'filename))) (provide 'helm-locate) ;;; helm-locate.el ends here helm-4.0.3/helm-man.el000066400000000000000000000115211501106761700144670ustar00rootroot00000000000000;;; helm-man.el --- Man and woman UI -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (defvar woman-topic-all-completions) (defvar woman-manpath) (defvar woman-path) (defvar woman-expanded-directory-path) (declare-function woman-file-name "woman.el" (topic &optional re-cache)) (declare-function woman-file-name-all-completions "woman.el" (topic)) (declare-function Man-getpage-in-background "man.el" (topic)) (declare-function woman-expand-directory-path "woman.el" (path-dirs path-regexps)) (declare-function woman-topic-all-completions "woman.el" (path)) (declare-function helm-generic-sort-fn "helm-utils.el" (S1 S2)) (declare-function helm-comp-read "helm-mode") (defgroup helm-man nil "Man and Woman applications for Helm." :group 'helm) (defcustom helm-man-or-woman-function 'Man-getpage-in-background "Default command to display a man page." :group 'helm-man :type '(radio :tag "Preferred command to display a man page" (const :tag "Man" Man-getpage-in-background) (const :tag "Woman" woman))) (defcustom helm-man-format-switches (cl-case system-type ((darwin macos) "%s") (t "-l %s")) "Arguments to pass to the `manual-entry' function. Arguments are passed to `manual-entry' with `format.'" :group 'helm-man :type 'string) ;; Internal (defvar helm-man--pages nil "All man pages on system. Will be calculated the first time you invoke Helm with this source.") (defvar helm-source-man-pages nil) (defun helm-man-default-action (candidate) "Default action for jumping to a woman or man page from Helm." (let ((wfiles (mapcar #'car (woman-file-name-all-completions candidate)))) (condition-case nil (let ((file (if (cdr wfiles) (helm-comp-read "ManFile: " wfiles :must-match t) (car wfiles)))) (if (eq helm-man-or-woman-function 'Man-getpage-in-background) (manual-entry (format helm-man-format-switches file)) (condition-case nil (woman-find-file file) ;; If woman is unable to format correctly ;; try Man instead. (error (kill-buffer) (manual-entry (format helm-man-format-switches file)))))) ;; If even Man failed with file as argument, try again with Man ;; but using Topic candidate instead of the file calculated by ;; woman. (error (kill-buffer) (Man-getpage-in-background candidate))))) (defun helm-man--init () (require 'woman) (require 'helm-utils) (unless helm-man--pages (setq woman-expanded-directory-path (woman-expand-directory-path woman-manpath woman-path)) (setq woman-topic-all-completions (woman-topic-all-completions woman-expanded-directory-path)) (setq helm-man--pages (mapcar 'car woman-topic-all-completions))) (helm-init-candidates-in-buffer 'global helm-man--pages)) (defun helm-man-popup-info (candidate) (let ((output (shell-command-to-string (format "man -f '%s'" candidate)))) (when (string-match (format "\\(%s ?([^(]+)\\) *- ?\\(.*\\)\n" candidate) output) (match-string 2 output)))) (defclass helm-man-pages-class (helm-source-in-buffer) ((popup-info :initform #'helm-man-popup-info))) ;;;###autoload (defun helm-man-woman (arg) "Preconfigured `helm' for Man and Woman pages. With a prefix ARG reinitialize the cache. To have a popup showing a basic description of selected candidate, turn on `helm-popup-tip-mode'." (interactive "P") (when arg (setq helm-man--pages nil)) (unless helm-source-man-pages (setq helm-source-man-pages (helm-make-source "Manual Pages" 'helm-man-pages-class :init #'helm-man--init :persistent-action #'ignore :filtered-candidate-transformer (lambda (candidates _source) (sort candidates #'helm-generic-sort-fn)) :action '(("Display Man page" . helm-man-default-action)) :group 'helm-man))) (helm :sources 'helm-source-man-pages :buffer "*helm man woman*")) (provide 'helm-man) ;;; helm-man.el ends here helm-4.0.3/helm-misc.el000066400000000000000000000365101501106761700146540ustar00rootroot00000000000000;;; helm-misc.el --- Various functions for helm -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'helm-types) (declare-function display-time-world-display "time.el") (defvar display-time-world-list) (declare-function LaTeX-math-mode "ext:latex.el") (declare-function jabber-chat-with "ext:jabber.el") (declare-function jabber-read-account "ext:jabber.el") (declare-function helm-comp-read "helm-mode") (declare-function outline-back-to-heading "outline.el") (declare-function outline-end-of-heading "outline.el") (declare-function helm-goto-char "helm-utils") (declare-function helm-highlight-current-line "helm-utils") (defgroup helm-misc nil "Various Applications and libraries for Helm." :group 'helm) (defcustom helm-time-zone-home-location "Paris" "The time zone of your home." :group 'helm-misc :type 'string) (defcustom helm-timezone-actions `(("Set timezone env (TZ)" . ,(lambda (candidate) (setenv "TZ" candidate)))) "Actions for helm-timezone." :group 'helm-misc :type '(alist :key-type string :value-type function)) (defface helm-time-zone-current `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "green")) "Face used to colorize current time in `helm-world-time'." :group 'helm-misc) (defface helm-time-zone-home `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "red")) "Face used to colorize home time in `helm-world-time'." :group 'helm-misc) ;;; Latex completion ;; ;; Test ;; (setq LaTeX-math-menu '("Math" ;; ["foo" val0 t] ;; ("bar" ;; ["baz" val1 t]) ;; ("aze" ;; ["zer" val2 t]) ;; ("AMS" ;; ("rec" ;; ["fer" val3 t]) ;; ("rty" ;; ["der" val4 t])) ;; ("ABC" ;; ("xcv" ;; ["sdf" val5 t]) ;; ("dfg" ;; ["fgh" val6 t])))) ;; (helm-latex-math-candidates) ;; => ;; (("foo" . val0) ;; ("baz" . val1) ;; ("zer" . val2) ;; ("fer" . val3) ;; ("der" . val4) ;; ("sdf" . val5) ;; ("fgh" . val6)) (defvar LaTeX-math-menu) (defun helm-latex-math-candidates () (cl-labels ((helm-latex--math-collect (L) (cond ((vectorp L) (list (cons (aref L 0) (aref L 1)))) ((listp L) (cl-loop for a in L nconc (helm-latex--math-collect a)))))) (helm-latex--math-collect LaTeX-math-menu))) (defvar helm-source-latex-math (helm-build-sync-source "Latex Math Menu" :init (lambda () (with-helm-current-buffer (LaTeX-math-mode 1))) :candidate-number-limit 9999 :candidates 'helm-latex-math-candidates :action (lambda (candidate) (call-interactively candidate)))) ;;; Jabber Contacts (jabber.el) (defun helm-jabber-online-contacts () "List online Jabber contacts." (with-no-warnings (cl-loop for item in (jabber-concat-rosters) when (get item 'connected) collect (if (get item 'name) (cons (get item 'name) item) (cons (symbol-name item) item))))) (defvar helm-source-jabber-contacts (helm-build-sync-source "Jabber Contacts" :init (lambda () (require 'jabber)) :candidates (lambda () (mapcar 'car (helm-jabber-online-contacts))) :action (lambda (x) (jabber-chat-with (jabber-read-account) (symbol-name (cdr (assoc x (helm-jabber-online-contacts)))))))) ;;; World time ;; (defvar zoneinfo-style-world-list) (defvar legacy-style-world-list) (defun helm-time-zone-transformer (candidates _source) (cl-loop for i in candidates for (z . p) in display-time-world-list collect (cons (cond ((string-match (format-time-string "%H:%M" (current-time)) i) (propertize i 'face 'helm-time-zone-current)) ((string-match helm-time-zone-home-location i) (propertize i 'face 'helm-time-zone-home)) (t i)) z))) (defvar helm-source-time-world (helm-build-in-buffer-source "Time World List" :init (lambda () (require 'time) (unless (and display-time-world-list (listp display-time-world-list)) ;; adapted from `time--display-world-list' from ;; emacs-27 for compatibility as ;; `display-time-world-list' is set by default to t. (setq display-time-world-list ;; Determine if zoneinfo style timezones are ;; supported by testing that America/New York and ;; Europe/London return different timezones. (let ((nyt (format-time-string "%z" nil "America/New_York")) (gmt (format-time-string "%z" nil "Europe/London"))) (if (string-equal nyt gmt) legacy-style-world-list zoneinfo-style-world-list))))) :data (lambda () (with-temp-buffer (display-time-world-display display-time-world-list) (buffer-string))) :action 'helm-timezone-actions :filtered-candidate-transformer 'helm-time-zone-transformer)) ;;; Minibuffer History ;; ;; (defvar helm-minibuffer-history-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map [remap helm-minibuffer-history] 'undefined) map)) (defcustom helm-minibuffer-history-must-match t "Allow inserting non matching elements when nil or \\='confirm." :group 'helm-misc :type '(choice (const :tag "Must match" t) (const :tag "Confirm" confirm) (const :tag "Always allow" nil))) (defcustom helm-minibuffer-history-key "C-r" "The key `helm-minibuffer-history' is bound to in minibuffer local maps." :type '(choice (string :tag "Key") (const :tag "no binding")) :group 'helm-mode) (defconst helm-minibuffer-history-old-key (cl-loop for map in '(minibuffer-local-completion-map minibuffer-local-filename-completion-map minibuffer-local-filename-must-match-map ; Emacs 23.1.+ minibuffer-local-isearch-map minibuffer-local-map minibuffer-local-must-match-filename-map ; Older Emacsen minibuffer-local-must-match-map minibuffer-local-ns-map) when (and (boundp map) (symbol-value map)) collect (cons map (lookup-key (symbol-value map) "\C-r")))) ;;;###autoload (define-minor-mode helm-minibuffer-history-mode "Bind `helm-minibuffer-history-key' in al minibuffer maps. This mode is enabled by `helm-mode', so there is no need to enable it directly." :group 'helm-misc :global t (if helm-minibuffer-history-mode (let ((key helm-minibuffer-history-key)) (dolist (map '(minibuffer-local-completion-map minibuffer-local-filename-completion-map minibuffer-local-filename-must-match-map ; Emacs 23.1.+ minibuffer-local-isearch-map minibuffer-local-map minibuffer-local-must-match-filename-map ; Older Emacsen minibuffer-local-must-match-map minibuffer-local-ns-map)) (let ((vmap (and (boundp map) (symbol-value map)))) (when (keymapp vmap) (let ((val (and (boundp 'helm-minibuffer-history-key) (symbol-value 'helm-minibuffer-history-key)))) (when val (define-key vmap (if (stringp val) (read-kbd-macro val) val) nil))) (when key (define-key (symbol-value map) (if (stringp key) (read-kbd-macro key) key) 'helm-minibuffer-history)))))) (dolist (map '(minibuffer-local-completion-map minibuffer-local-filename-completion-map minibuffer-local-filename-must-match-map minibuffer-local-isearch-map minibuffer-local-map minibuffer-local-must-match-filename-map minibuffer-local-must-match-map minibuffer-local-ns-map)) (let ((vmap (and (boundp map) (symbol-value map)))) (when (keymapp vmap) (let ((val (and (boundp 'helm-minibuffer-history-key) (symbol-value 'helm-minibuffer-history-key)))) (when val (define-key vmap (if (stringp val) (read-kbd-macro val) val) (assoc-default map helm-minibuffer-history-old-key))))))))) ;;; Helm ratpoison UI ;; ;; (defvar helm-source-ratpoison-commands (helm-build-in-buffer-source "Ratpoison Commands" :init 'helm-ratpoison-commands-init :action (helm-make-actions "Execute the command" 'helm-ratpoison-commands-execute) :display-to-real 'helm-ratpoison-commands-display-to-real :candidate-number-limit 999999)) (defun helm-ratpoison-commands-init () (unless (helm-candidate-buffer) (with-current-buffer (helm-candidate-buffer 'global) ;; with ratpoison prefix key (save-excursion (call-process "ratpoison" nil (current-buffer) nil "-c" "help")) (while (re-search-forward "^\\([^ ]+\\) \\(.+\\)$" nil t) (replace-match " \\1: \\2")) (goto-char (point-max)) ;; direct binding (save-excursion (call-process "ratpoison" nil (current-buffer) nil "-c" "help top")) (while (re-search-forward "^\\([^ ]+\\) \\(.+\\)$" nil t) (replace-match "\\1: \\2"))))) (defun helm-ratpoison-commands-display-to-real (display) (and (string-match ": " display) (substring display (match-end 0)))) (defun helm-ratpoison-commands-execute (candidate) (call-process "ratpoison" nil nil nil "-ic" candidate)) ;;; Helm stumpwm UI ;; ;; (defvar helm-source-stumpwm-commands (helm-build-in-buffer-source "Stumpwm Commands" :init 'helm-stumpwm-commands-init :action (helm-make-actions "Execute the command" 'helm-stumpwm-commands-execute) :candidate-number-limit 999999)) (defun helm-stumpwm-commands-init () (with-current-buffer (helm-candidate-buffer 'global) (save-excursion (call-process "stumpish" nil (current-buffer) nil "commands")) (while (re-search-forward "[ ]*\\([^ ]+\\)[ ]*\n?" nil t) (replace-match "\n\\1\n")) (delete-blank-lines) (sort-lines nil (point-min) (point-max)) (goto-char (point-max)))) (defun helm-stumpwm-commands-execute (candidate) (call-process "stumpish" nil nil nil candidate)) ;;;###autoload (defun helm-world-time () "Preconfigured `helm' to show world time. Default action change TZ environment variable locally to emacs." (interactive) (helm :sources 'helm-source-time-world :buffer "*helm world time*")) ;;;###autoload (defun helm-insert-latex-math () "Preconfigured helm for latex math symbols completion." (interactive) (helm :sources 'helm-source-latex-math :buffer "*helm latex*")) ;;;###autoload (defun helm-ratpoison-commands () "Preconfigured `helm' to execute ratpoison commands." (interactive) (helm :sources 'helm-source-ratpoison-commands :buffer "*helm ratpoison commands*")) ;;;###autoload (defun helm-stumpwm-commands() "Preconfigured helm for stumpwm commands." (interactive) (helm :sources 'helm-source-stumpwm-commands :buffer "*helm stumpwm commands*")) ;;;###autoload (defun helm-minibuffer-history () "Preconfigured `helm' for `minibuffer-history'." (interactive) (cl-assert (minibuffer-window-active-p (selected-window)) nil "Error: Attempt to use minibuffer history outside a minibuffer") (let* ((enable-recursive-minibuffers t) (query-replace-p (or (eq last-command 'query-replace) (eq last-command 'query-replace-regexp))) (elm (helm-comp-read "Next element matching (regexp): " (cl-loop for i in (symbol-value minibuffer-history-variable) unless (equal "" i) collect i into history finally return (if (consp (car history)) (mapcar 'prin1-to-string history) history)) :header-name (lambda (name) (format "%s (%s)" name minibuffer-history-variable)) :buffer "*helm minibuffer-history*" :must-match helm-minibuffer-history-must-match :multiline t :keymap helm-minibuffer-history-map :allow-nest t))) ;; Fix Bug#1667 with emacs-25+ `query-replace-from-to-separator'. (when (and (boundp 'query-replace-from-to-separator) query-replace-p) (let ((pos (string-match "\0" elm))) (and pos (add-text-properties pos (1+ pos) `(display ,query-replace-from-to-separator separator t) elm)))) (delete-minibuffer-contents) (insert elm))) ;;;###autoload (defun helm-outline (&optional arg) "Basic helm navigation tool for outline buffers." (interactive "P") (let ((outline-regexp (if arg (read-regexp "Outline regexp") outline-regexp))) (helm :sources (helm-build-sync-source "helm outline" :candidates (lambda () (with-helm-current-buffer (save-excursion (goto-char (point-min)) (cl-loop while (re-search-forward outline-regexp nil t) for beg = (match-beginning 0) for end = (progn (outline-end-of-heading) (point)) collect (cons (buffer-substring beg end) beg))))) :action (lambda (pos) (helm-goto-char pos) (helm-highlight-current-line))) :preselect (save-excursion (when (condition-case _err (outline-back-to-heading) (error nil)) (regexp-quote (buffer-substring (point) (progn (outline-end-of-heading) (point)))))) :buffer "*helm outline*"))) (provide 'helm-misc) ;;; helm-misc.el ends here helm-4.0.3/helm-mode.el000066400000000000000000004446341501106761700146570ustar00rootroot00000000000000;;; helm-mode.el --- Enable helm completion everywhere. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-lib) (require 'helm-files) (require 'helm-misc) (defvar crm-separator) (defvar ido-everywhere) (defvar completion-flex-nospace) (defvar helm-completion--sorting-done) (defvar helm-mode) (defvar password-cache) (defvar package--builtins) (defvar helm--locate-library-doc-cache) (defvar helm--locate-library-cache) (defvar completion-lazy-hilit) ; Emacs-30 only. (defvar eww-bookmarks) (defvar helm-info--files-cache) (defvar helm-info--files-doc-cache) (defvar Info-current-file) (defvar helm-M-x-prefix-argument) (defvar helm-M-x--timer) ;; No warnings in Emacs built --without-x (declare-function x-file-dialog "xfns.c") (declare-function ido-mode "ido.el") (declare-function helm-apropos-init "helm-elisp") (declare-function helm-lisp-completion-persistent-action "helm-elisp") (declare-function helm-lisp-completion-persistent-help "helm-elisp") (declare-function help--symbol-class "help-fns.el") (declare-function helm-get-first-line-documentation "helm-elisp") (declare-function package-desc-summary "package") (declare-function package-built-in-p "package") (declare-function package-desc-status "package") (declare-function package-get-descriptor "package") (declare-function print-coding-system-briefly "mul-diag.el") (declare-function color-rgb-to-hex "color.el") (declare-function find-library-name "find-func.el") (declare-function helm-info-file-doc "helm-info") (declare-function Info-find-file "info") (declare-function helm-M-x--notify-prefix-arg "helm-command") (declare-function helm-M-x--unwind-forms "helm-command") (declare-function helm-M-x--move-selection-after-hook "helm-command") (declare-function helm-M-x--before-action-hook "helm-command") (declare-function helm-M-x-persistent-action "helm-command") (defgroup helm-mode nil "Enable helm completion." :group 'helm) (defcustom helm-completing-read-handlers-alist '((find-tag . helm-completing-read-default-find-tag) (ggtags-find-tag-dwim . helm-completing-read-default-find-tag) (tmm-menubar . nil) (find-file . nil) (execute-extended-command . nil) (dired-do-rename . helm-read-file-name-handler-1) (dired-do-copy . helm-read-file-name-handler-1) (dired-do-symlink . helm-read-file-name-handler-1) (dired-do-relsymlink . helm-read-file-name-handler-1) (dired-do-hardlink . helm-read-file-name-handler-1) ;; Next two are using completing-read where not needed. (read-multiple-choice--long-answers . nil) (dired-do-touch . nil) (basic-save-buffer . helm-read-file-name-handler-1) (write-file . (default helm-read-file-name-handler-1)) (write-region . (default helm-read-file-name-handler-1)) (all-the-icons-insert . helm-mode-all-the-icons-handler)) "Completing read functions for specific Emacs commands. By default `helm-mode' use `helm-completing-read-default-handler' to provide helm completion in each `completing-read' or `read-file-name' found, but other functions can be specified here for specific commands. This also allows disabling helm completion for some commands when needed. Each entry is a cons cell like (EMACS_COMMAND . COMPLETING-READ_HANDLER) where key and value are symbols. However if a command is using in its definition both a `completing-read' AND a `read-file-name' we may want to specify a handler for both of them, this can be done by specifying value as a list of two symbols instead of a single symbol where the 1st element of the list specify the handler for the `completing-read' and the second the handler for the `read-file-name'. Special symbol \\='default' means use the default helm handler for either `completing-read' or `read-file-name'. e.g. (write-region . (default helm-read-file-name-handler-1)) means helm will use `helm-completing-read-default-handler' when `write-region' calls `completing-read' and `helm-read-file-name-handler-1' when it calls `read-file-name'. Each key is an Emacs command that use originaly `completing-read' or/and `read-file-name'. Each value maybe a helm function that takes same arguments as `completing-read' plus NAME and BUFFER, where NAME is the name of the new helm source and BUFFER the name of the buffer we will use, but it can be also a function not using helm, in this case the function should take the same args as `completing-read' and not be prefixed by \"helm-\". `helm' will use the name of the command calling `completing-read' as NAME and BUFFER will be computed as well with NAME but prefixed with \"*helm-mode-\". This function prefix name must start by \"helm-\" when it uses helm, otherwise `helm' assumes the function is not a helm function and expects the same args as `completing-read', this allows you to define a handler not using helm completion. Example: (defun foo/test () (interactive) (message \"%S\" (completing-read \"test: \" \\='(a b c d e)))) (defun helm-foo/test-completing-read-handler (prompt collection predicate require-match initial-input hist def inherit-input-method name buffer) (helm-comp-read prompt collection :marked-candidates t :name name :buffer buffer)) (add-to-list \\='helm-completing-read-handlers-alist \\='(foo/test . helm-foo/test-completing-read-handler)) We want here to make the regular `completing-read' in `foo/test' return a list of candidate(s) instead of a single candidate. Note that this function will be reused for ALL the `completing-read' of this command, so it should handle all cases. E.g., if first `completing-read' completes against symbols and second `completing-read' should handle only buffer, your specialized function should handle both. If the value of an entry is nil completion will fall back to Emacs vanilla behaviour. Example: If you want to disable helm completion for `describe-function', use: (describe-function . nil) Ido is also supported, you can use `ido-completing-read' and `ido-read-file-name' as value of an entry or just \\='ido. Example: Enable ido completion for `find-file': (find-file . ido) same as (find-file . ido-read-file-name) Note that you don't need to enable `ido-mode' for this to work, see `helm-mode' documentation." :group 'helm-mode :type '(alist :key-type symbol :value-type (choice function (list :tag "Specify the completing-read and read-file-name handlers" (choice (const :tag "Use default helm completing-read handler" default) (function :tag "Use this helm completing-read function")) (function :tag "Use this helm read file name function")) (other :tag "Disabled" nil)))) (defcustom helm-comp-read-case-fold-search helm-case-fold-search "Default Local setting of `helm-case-fold-search' for `helm-comp-read'. See `helm-case-fold-search' for more info." :group 'helm-mode :type 'symbol) (defcustom helm-mode-handle-completion-in-region t "Whether to replace or not `completion-in-region-function'. This enables support for `completing-read-multiple' and `completion-at-point' when non--nil." :group 'helm-mode :type 'boolean) (defcustom helm-mode-no-completion-in-region-in-modes nil "A list of modes that do not want helm for `completion-in-region'." :group 'helm-mode :type 'boolean) (defcustom helm-mode-reverse-history t "Display history source after current source when non nil. Apply only in `helm-mode' handled commands." :group 'helm-mode :type 'boolean) (defcustom helm-completion-in-region-default-sort-fn 'helm-completion-in-region-sort-fn "The default sort function to sort candidates in completion-in-region. When nil no sorting is done. The function is a `filtered-candidate-transformer' function which takes two args CANDIDATES and SOURCE. The function must use the flag `helm-completion--sorting-done' and return CANDIDATES unchanged when the flag is nil. See default function `helm-completion-in-region-sort-fn' as example. It will be used only when `helm-completion-style' is either Emacs or helm, otherwise when helm-fuzzy style is used, the fuzzy sort function will be used." :group 'helm-mode :type 'function) (defcustom helm-mode-ignore-diacritics nil "Ignore diacritics in completing-read." :group 'helm-mode :type 'boolean) (defcustom helm-completion-mark-suffix t "Push mark at end of suffix when non nil." :group 'helm-mode :type 'boolean) (defcustom helm-read-file-name-use-default-arg-behavior nil "Use emacs vanilla `read-file-name' behavior for default arg. The behavior of default arg in `read-file-name' and friends is using the default arg as default value when initial input is not modified, even if this initial input is a valid value i.e. an existing file. We expect generally a default arg to be used if nothing is specified in the prompt or if what is specified is invalid, but the emacs behavior here is really weird, so we use this variable to disable this behavior, letting user specify default if needed with `M-n'. However we keep the emacs default for `read-file-name' and derived fns, this variable affecting only `helm-read-file-name'." :type 'boolean :group 'helm-mode) (defvar helm-mode-minibuffer-setup-hook-black-list '(minibuffer-completion-help) "Incompatible `minibuffer-setup-hook' functions go here. A list of symbols. `helm-mode' is rejecting all lambda's, byte-code fns and all functions belonging in this list from `minibuffer-setup-hook'. This is mainly needed to prevent \"*Completions*\" buffers to popup.") (defvar helm-comp-read-require-match-overrides '((describe-function . t) (describe-command . t) (describe-minor-mode . t) (load-theme . t) (describe-theme . t)) "Allow overriding REQUIRE-MATCH completing-read arg for a specific function.") (defcustom helm-completions-detailed (and (boundp 'completions-detailed) completions-detailed) "Allow providing `completions-detailed' for Emacs < 28. Not guaranteed to work with Emacs < 27." :type 'boolean :group 'helm-mode) (defvar helm-mode-find-file-target-alist '(("switch-to-buffer" . helm-buffers-quit-and-find-file-fn)) "An alist composed of (SOURCE_NAME . FUNCTION) elements. Where FUNCTION is a function suitable for `helm-quit-and-find-file'.") (defface helm-mode-prefix `((t ,@(and (>= emacs-major-version 27) '(:extend t)) (:background "red" :foreground "black"))) "Face used for prefix completion." :group 'helm-mode) (defface helm-completion-invalid '((t :inherit font-lock-property-name-face)) "Face used to highlight invalid functions." :group 'helm-mode) (defface helm-completions-detailed '((t :inherit font-lock-warning-face)) "Face used to highlight completion-detailed informations." :group 'helm-mode) (defface helm-completions-annotations '((t :inherit font-lock-property-name-face)) "Face used to highlight annotations in completion." :group 'helm-mode) (defvar helm-comp-read-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "") 'helm-cr-empty-string) (define-key map (kbd "M-RET") 'helm-cr-empty-string) map) "Keymap for `helm-comp-read'.") (defvar helm-comp-in-region-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-comp-read-map) map) "Keymap for completion-at-point and friends.") (defun helm-mode-delete-char-backward-1 () (interactive) (condition-case err (call-interactively 'delete-backward-char) (text-read-only (if (with-selected-window (minibuffer-window) (not (string= (minibuffer-contents) ""))) (message "Trying to delete prefix completion, next hit will quit") (user-error "%s" (car err)))))) (put 'helm-mode-delete-char-backward-1 'helm-only t) (defun helm-mode-delete-char-backward-2 () (interactive) (condition-case _err (call-interactively 'delete-backward-char) (text-read-only (unless (with-selected-window (minibuffer-window) (string= (minibuffer-contents) "")) (with-helm-current-buffer (run-with-timer 0.1 nil (lambda () (call-interactively 'delete-backward-char)))) (helm-keyboard-quit))))) (put 'helm-mode-delete-char-backward-2 'helm-only t) (helm-multi-key-defun helm-mode-delete-char-backward-maybe "Delete char backward when text is not the prefix helm is completing against. First call warns user about deleting prefix completion. Second call deletes backward char in current-buffer and quits helm completion, letting the user start a new completion with a new prefix." '(helm-mode-delete-char-backward-1 helm-mode-delete-char-backward-2) 1) (defcustom helm-completion-style 'helm "Style of completion to use in `completion-in-region'. This affects only `completion-at-point' and friends, and the `completing-read' using the default handler i.e. `helm-completing-read-default-handler'. NB: This has nothing to do with `completion-styles', it is independent from helm, but when using \\='emacs as helm-completion-style helm will use the `completion-styles' for its completions. Up to the user to configure `completion-styles'. There are three possible values to use: - helm, use multi match regular helm completion. - helm-fuzzy, use fuzzy matching. Note that as usual when entering a space helm switches to multi matching mode. - emacs, use regular Emacs completion according to `completion-styles'. Note that even in this style, helm allows using multi match. Emacs-27 provides a style called `flex' that can be used aside `helm' style (see `completion-styles-alist'). When `flex' style is not available (Emacs<27) helm provides `helm-flex' style which is similar to `flex' and helm fuzzy matching. For a better experience with \\='emacs style, if you don't know what to use, set `completion-styles' to \\='(flex) if you are using emacs-27 or to \\='(helm-flex) if you are using emacs-26 or you want to force using helm-flex instead of flex (see note above about flex). See also `helm-completion-styles-alist' to override `helm-completion-style' for specific modes and commands. Of course when using `helm' or `helm-fuzzy' as `helm-completion-style' emacs `completion-styles' have no effect. Please use custom interface or `customize-set-variable' to set this, NOT `setq'." :group 'helm-mode :type '(choice (const :tag "Emacs" emacs) (const :tag "Helm" helm) (const :tag "Helm-fuzzy" helm-fuzzy)) :set (lambda (var val) (set var val) (if (memq val '(helm helm-fuzzy)) (define-key helm-comp-in-region-map (kbd "DEL") 'helm-mode-delete-char-backward-maybe) (define-key helm-comp-in-region-map (kbd "DEL") 'delete-backward-char)))) (defconst helm-completion--all-styles (let ((flex (if (assq 'flex completion-styles-alist) 'flex 'helm-flex))) (helm-fast-remove-dups (append (list 'helm flex) (mapcar 'car completion-styles-alist))))) (defconst helm-completion--styles-type `(repeat :tag "with other completion styles" (choice ,@(mapcar (lambda (x) (list 'const x)) helm-completion--all-styles)))) (defcustom helm-completion-styles-alist `((gud-mode . helm) ;; See https://github.com/djcb/mu/issues/2181. (mu4e-compose-mode . emacs) (wfnames-mode . (emacs helm ,(if (assq 'flex completion-styles-alist) 'flex 'helm-flex)))) "Allow configuring `helm-completion-style' per mode or command. NOTE: Commands involving `completing-read' specified in `helm-completing-read-handlers-alist' take precedence on commands you put here. Specifying a mode instead of a command affect only completion-in-region and not the completing-read's called in this mode, use `helm-completing-read-handlers-alist' for this. Each entry is a cons cell like (mode_or_command . style) where style must be a suitable value for `helm-completion-style'. When specifying emacs as style for a mode or a command, `completion-styles' can be specified by using a cons cell specifying completion-styles to use with helm emacs style, e.g. (foo-mode . (emacs helm flex)) will set `completion-styles' to \\='(helm flex) for foo-mode." :group 'helm-mode :type `(alist :key-type (symbol :tag "Major Mode") :value-type (choice :tag "Use helm style or completion styles" (radio :tag "Helm Style" (const helm) (const helm-fuzzy) (const emacs)) (cons :tag "Completion Styles" (const :tag "Using Helm `emacs' style" emacs) ,helm-completion--styles-type)))) ;;; helm-comp-read ;; ;; (defvar helm-comp-read-use-marked nil "[INTERNAL] When non nil `helm-comp-read' will return marked candidates. Use this ONLY in `let', NOT globally, this allows third party packages to use a list as return value when `helm-mode' is enabled, e.g. (let ((helm-comp-read-use-marked t)) (completing-read \"test: \" \\='(a b c d e f g))) ") (defun helm-cr-empty-string () "Return empty string." (interactive) (with-helm-alive-p (helm-exit-and-execute-action (lambda (_candidate) (identity ""))))) (put 'helm-cr-empty-string 'helm-only t) (defun helm-mode--keyboard-quit () ;; Use this instead of `keyboard-quit' ;; to avoid deactivating mark in current-buffer. (let ((debug-on-quit nil)) (signal 'quit nil))) (cl-defun helm-comp-read-get-candidates (collection &optional test sort-fn alistp (input helm-pattern)) "Convert COLLECTION to list removing elements that don't match TEST. See `helm-comp-read' about supported COLLECTION arguments. SORT-FN is a predicate to sort COLLECTION. ALISTP when non--nil will not use `all-completions' to collect candidates because it doesn't handle alists correctly for helm. i.e In `all-completions' the car of each pair is used as value. In helm we want to use the cdr instead like (display . real), so we return the alist as it is with no transformation by `all-completions'. e.g \(setq A \\='((a . 1) (b . 2) (c . 3))) ==>((a . 1) (b . 2) (c . 3)) \(helm-comp-read \"test: \" A :alistp nil :exec-when-only-one t :initial-input \"a\") ==>\"a\" Which is not what we expect. \(helm-comp-read \"test: \" A :alistp t :exec-when-only-one t :initial-input \"1\") ==>\"1\" See docstring of `all-completions' for more info. INPUT is the string you want to complete against, defaulting to `helm-pattern' which is the value of what you enter in minibuffer. Note that when using a function as COLLECTION this value will be available with the input argument of the function only when using a sync source from `helm-comp-read', i.e. not using `:candidates-in-buffer', otherwise the function is called only once with an empty string as value for `helm-pattern' because `helm-pattern' is not yet computed, which is what we want otherwise data would not be fully collected at init time. If COLLECTION is an `obarray', a TEST should be needed. See `obarray'." ;; Ensure COLLECTION is computed from `helm-current-buffer' ;; because some functions used as COLLECTION work ;; only in the context of current-buffer (Bug#1030) . (with-helm-current-buffer (let ((cands (cond ((and alistp (hash-table-p collection)) (cl-loop for k being the hash-keys of collection using (hash-values v) collect (cons k v))) ((vectorp collection) (all-completions input collection test)) ((and (symbolp collection) (boundp collection) ;; Bug#324 history is let-bounded and given ;; quoted as hist argument of completing-read. ;; See example in `rcirc-browse-url'. (symbolp (symbol-value collection))) nil) ;; When collection is a symbol, most of the time ;; it should be a symbol used as a minibuffer-history. ;; The value of this symbol in this case return a list ;; of string which maybe are converted later as symbol ;; in special cases. ;; we treat here commandp as a special case as it return t ;; also with a string unless its last arg is provided. ;; Also, the history collections generally collect their ;; elements as string, so intern them to call predicate. ((and (symbolp collection) (boundp collection) test) (let ((predicate (lambda (elm) (condition-case _err (if (eq test 'commandp) (funcall test (intern elm)) (funcall test elm)) (wrong-type-argument (funcall test (intern elm))))))) (all-completions input (symbol-value collection) predicate))) ((and (symbolp collection) (boundp collection)) (all-completions input (symbol-value collection))) ;; Normally file completion should not be handled here, ;; but special cases like `find-file-at-point' do it. ;; Handle here specially such cases. ((and (functionp collection) (not (string= input "")) (or minibuffer-completing-file-name (eq (completion-metadata-get (completion-metadata input collection test) 'category) 'file))) (cl-loop for f in (funcall collection input test t) unless (member f '("./" "../")) if (string-match helm--url-regexp input) collect f else collect (concat (file-name-as-directory (helm-basedir input)) f))) ((functionp collection) (funcall collection input test t)) ((and alistp (null test)) collection) ;; Next test ensure circular objects are removed ;; with `all-completions' (Bug#1530). (t (all-completions input collection test))))) (if sort-fn (sort cands sort-fn) cands)))) (cl-defun helm-cr--pattern-in-candidates-p (candidates &optional (pattern helm-pattern)) (or (assoc pattern candidates) (assoc (concat " " pattern) candidates) (assq (intern pattern) candidates) (member pattern candidates) (member (downcase pattern) candidates) (member (upcase pattern) candidates))) (defun helm-cr-default-transformer (candidates _source) "Default filter candidate function for `helm-comp-read'." ;; Annotation and affixation are already handled in completion-in-region and ;; in helm-completing-read-default-2 when emacs style is in use. ;; For helm-completing-read-default-1 we handle them in an extra FCT; This ;; allows extracting annotation and affixation from metadata which is not ;; accessible from here. (cl-loop for c in candidates for cand = (let ((elm (if (stringp c) (replace-regexp-in-string "\\s\\" "" c) c))) (cond ((and (stringp elm) (string-match "\n" elm)) (cons (replace-regexp-in-string "\n" "->" elm) c)) (t c))) collect cand)) (defun helm-cr-default (default cands) (delq nil (cond ((and (consp default) (string= helm-pattern "")) (append (cl-loop for d in default ;; Don't convert ;; nil to "nil" (i.e the string) ;; it will be delq'ed on top. for str = (if (null d) d (helm-stringify d)) when (member str cands) do (setq cands (delete d cands)) when str collect str) cands)) ;; Some functions like debug-on-entry use (symbol-name sym) ;; without checking if sym is non nil, so the return value become ;; "nil". ((and (not (member default '("" "nil"))) (string= helm-pattern "")) (cons default (delete (helm-stringify default) cands))) (t cands)))) ;;;###autoload (cl-defun helm-comp-read (prompt collection &key test initial-input default preselect (buffer "*Helm Completions*") must-match fuzzy reverse-history (requires-pattern 0) (history nil shistory) raw-history input-history (case-fold helm-comp-read-case-fold-search) (persistent-action nil) (persistent-help "DoNothing") (mode-line helm-comp-read-mode-line) help-message (keymap helm-comp-read-map) (name "Helm Completions") header-name candidates-in-buffer (get-line #'buffer-substring) diacritics match-part match-dynamic exec-when-only-one quit-when-no-cand (volatile t) sort fc-transformer hist-fc-transformer (marked-candidates helm-comp-read-use-marked) nomark (alistp t) (candidate-number-limit helm-candidate-number-limit) multiline allow-nest coerce raw-candidate (group 'helm) popup-info) "Read a string in the minibuffer, with helm completion. It is helm `completing-read' equivalent. - PROMPT is the prompt name to use. - COLLECTION can be a list, alist, vector, obarray or hash-table. For alists and hash-tables their car are use as real value of candidate unless ALISTP is non-nil. It can be also a function that receives three arguments: the values string, predicate and t. See `all-completions' for more details. Keys description: - TEST: A predicate called with one arg i.e candidate. - INITIAL-INPUT: Same as input arg in `helm'. - PRESELECT: See preselect arg of `helm'. - DEFAULT: This option is used only for compatibility with regular Emacs `completing-read' (Same as DEFAULT arg of `completing-read'). - BUFFER: Name of helm-buffer. - MUST-MATCH: Candidate selected must be one of COLLECTION. - FUZZY: Enable fuzzy matching. - REVERSE-HISTORY: When non--nil display history source after current source completion. - REQUIRES-PATTERN: Same as helm attribute, default is 0. - HISTORY: A symbol where each result will be saved. If not specified as a symbol an error will popup. When specified, all elements of HISTORY are displayed in a special source before or after COLLECTION according to REVERSE-HISTORY. The main difference with INPUT-HISTORY is that the result of the completion is saved whereas in INPUT-HISTORY it is the minibuffer contents which is saved when you exit. Don't use the same symbol for INPUT-HISTORY and HISTORY. NOTE: As mentionned above this has nothing to do with `minibuffer-history-variable', therefore if you want to save this history persistently, you will have to add this variable to the relevant variable of your favorite tool for persistent emacs session i.e. psession, desktop etc... - RAW-HISTORY: When non-nil do not remove backslashs if some in HISTORY candidates. - INPUT-HISTORY: A symbol. The minibuffer input history will be stored there, if nil or not provided, `minibuffer-history' will be used instead. You can navigate in this history with `M-p' and `M-n'. Don't use the same symbol for INPUT-HISTORY and HISTORY. - CASE-FOLD: Same as `helm-case-fold-search'. - PERSISTENT-ACTION: A function called with one arg i.e candidate. - PERSISTENT-HELP: A string to document PERSISTENT-ACTION. - MODE-LINE: A string or list to display in mode line. Default is `helm-comp-read-mode-line'. - KEYMAP: A keymap to use in this `helm-comp-read'. (the keymap will be shared with history source) - NAME: The name related to this local source. - HEADER-NAME: A function to alter NAME, see `helm'. - EXEC-WHEN-ONLY-ONE: Bound `helm-execute-action-at-once-if-one' to non--nil. (possibles values are t or nil). - VOLATILE: Use volatile attribute. - SORT: A predicate to give to `sort' e.g `string-lessp' Use this only on small data as it is inefficient. If you want to sort faster add a sort function to FC-TRANSFORMER. Note that FUZZY when enabled is already providing a sort function. - FC-TRANSFORMER: A `filtered-candidate-transformer' function or a list of functions. - HIST-FC-TRANSFORMER: A `filtered-candidate-transformer' function for the history source. - MARKED-CANDIDATES: If non-nil return candidate or marked candidates as a list. - NOMARK: When non--nil don't allow marking candidates. - ALISTP: When non-nil (default) pass the value of (DISPLAY . REAL) candidate in COLLECTION to action when COLLECTION is an alist or a hash-table, otherwise DISPLAY is always returned as result on exit, which is the default when using `completing-read'. See `helm-comp-read-get-candidates'. - CANDIDATES-IN-BUFFER: when non--nil use a source build with `helm-source-in-buffer' which is much faster. Argument VOLATILE have no effect when CANDIDATES-IN-BUFFER is non--nil. - GET-LINE: Specify the :get-line slot of `helm-source-in-buffer', has no effect when CANDIDATES-IN-BUFFER is nil. - MATCH-PART: Allow matching only one part of candidate. See match-part documentation in `helm-source'. - MATCH-DYNAMIC: See match-dynamic in `helm-source-sync' It has no effect when used with CANDIDATES-IN-BUFFER. - ALLOW-NEST: Allow nesting this `helm-comp-read' in a helm session. See `helm'. - MULTILINE: See multiline in `helm-source'. - COERCE: See coerce in `helm-source'. - RAW-CANDIDATE: Do not unquote the unknown candidate coming from helm-pattern when non nil. - GROUP: See group in `helm-source'. Any prefix args passed during `helm-comp-read' invocation will be recorded in `helm-current-prefix-arg', otherwise if prefix args were given before `helm-comp-read' invocation, the value of `current-prefix-arg' will be used. That means you can pass prefix args before or after calling a command that use `helm-comp-read'. See `helm-M-x' for example." ;; Handle error with HISTORY: ;; ;; Should show helm with one source at first run and save result on ;; exit, should show the history source along candidates source on ;; next run as soon as `test-hist' value is feeded. ;; (setq test-hist nil) ;; (helm-comp-read "test: " '(a b c d e) ;; :history 'test-hist) ;; ;; Should run normally as long as `test-hist' is bound and nil. As ;; soon `test-hist' becomes non-nil throw an error. ;; (helm-comp-read "test: " '(a b c d e) ;; :history test-hist) ;; ;; Should run normally. ;; (completing-read "test: " '(a b c d e)) (cl-assert (if shistory (or (null history) (and history (symbolp history))) t) nil "Error: History should be specified as a symbol") (when (get-buffer helm-action-buffer) (kill-buffer helm-action-buffer)) ;; The value of MUST-MATCH is given to ;; `helm--set-minibuffer-completion-confirm' which compute it and propagate it ;; to `minibuffer-completion-confirm' which is then used by ;; `helm-confirm-and-exit-minibuffer'. (unless (or (memq must-match '(confirm confirm-after-completion t nil)) (functionp must-match)) ;; Fix completing-read's using something else than (confirm ;; confirm-after-completion t nil) or a function e.g. 1 or ;; whatever (bug #2527). (setq must-match t)) (let ((action-fn `(("Sole action (Identity)" . ,(lambda (candidate) (if marked-candidates (helm-marked-candidates) (identity candidate))))))) (let* ((minibuffer-completion-predicate test) (minibuffer-completion-table (or minibuffer-completion-table collection)) (helm-read-file-name-mode-line-string (replace-regexp-in-string "helm-maybe-exit-minibuffer" "helm-confirm-and-exit-minibuffer" helm-read-file-name-mode-line-string)) (get-candidates (lambda () (let ((cands (helm-comp-read-get-candidates ;; If `helm-pattern' is passed as INPUT ;; and :alistp is nil INPUT is passed to ;; `all-completions' which defeat helm ;; matching functions (multi match, fuzzy ;; etc...) Bug#2134. collection test sort alistp (if (and match-dynamic (null candidates-in-buffer)) helm-pattern "")))) (helm-cr-default default cands)))) (history-get-candidates (lambda () (let ((cands (helm-comp-read-get-candidates history test nil alistp))) (when cands (delete "" (helm-cr-default default cands)))))) (src-hist (helm-build-sync-source (format "%s History" name) :candidates history-get-candidates :fuzzy-match fuzzy :multiline multiline :match-part match-part :filtered-candidate-transformer (append `((lambda (candidates _source) (if ,raw-history candidates (cl-loop for i in candidates ;; Input is added to history in completing-read's ;; and may be regexp-quoted, so unquote it ;; but check if cand is a string (it may be at this stage ;; a symbol or nil) Bug#1553. when (stringp i) collect (replace-regexp-in-string "\\s\\" "" i))))) (and hist-fc-transformer (helm-mklist hist-fc-transformer))) :persistent-action persistent-action :persistent-help persistent-help :keymap keymap :must-match must-match :group group :popup-info popup-info :coerce coerce :mode-line mode-line :help-message help-message :action action-fn)) (dummy-src (helm-build-dummy-source "Unknown candidate" :must-match must-match :keymap keymap :filtered-candidate-transformer (lambda (_candidates _source) (let ((pat (if raw-candidate helm-pattern (replace-regexp-in-string "\\s\\" "" helm-pattern)))) (unless (string= pat "") (list (cons (helm-aand (propertize "[?]" 'face 'helm-ff-prefix) (propertize " " 'display it 'unknown t) (concat it pat)) pat))))) :action action-fn)) (src (helm-build-sync-source name :candidates get-candidates :popup-info popup-info :match-part match-part :multiline multiline :header-name header-name :filtered-candidate-transformer (let ((transformers (helm-mklist fc-transformer))) (append transformers (unless (member 'helm-cr-default-transformer transformers) '(helm-cr-default-transformer)))) :requires-pattern requires-pattern :persistent-action persistent-action :persistent-help persistent-help :fuzzy-match fuzzy :diacritics diacritics :keymap keymap :must-match must-match :group group :coerce coerce :mode-line mode-line :match-dynamic match-dynamic :help-message help-message :action action-fn :volatile volatile)) (src-1 (helm-build-in-buffer-source name :data get-candidates :match-part match-part :get-line get-line :multiline multiline :header-name header-name :filtered-candidate-transformer (let ((transformers (helm-mklist fc-transformer))) (append transformers (unless (member 'helm-cr-default-transformer transformers) '(helm-cr-default-transformer)))) :popup-info popup-info :requires-pattern requires-pattern :persistent-action persistent-action :fuzzy-match fuzzy :diacritics diacritics :keymap keymap :must-match must-match :group group :coerce coerce :persistent-help persistent-help :mode-line mode-line :help-message help-message :action action-fn)) (src-list (list src-hist (if candidates-in-buffer src-1 src))) (helm-execute-action-at-once-if-one exec-when-only-one) (helm-quit-if-no-candidate quit-when-no-cand) result) (when nomark (setq src-list (cl-loop for src in src-list collect (cons '(nomark) src)))) (when reverse-history (setq src-list (nreverse src-list))) (unless (eq must-match t) (setq src-list (append src-list (list dummy-src)))) (when raw-candidate (cl-loop for src in src-list do (helm-set-attr 'raw-candidate t src))) (setq result (helm :sources src-list :input initial-input :default default :preselect preselect :prompt prompt :resume 'noresume :keymap keymap ;; Needed with empty collection. :allow-nest allow-nest :candidate-number-limit candidate-number-limit :case-fold-search case-fold :history (and (symbolp input-history) input-history) :buffer buffer)) ;; If `history' is a symbol save it, except when it is t. (when (and result history (symbolp history) (not (eq history t))) (set history ;; RESULT may be a a string or a list of strings bug #2461. (delete-dups (append (mapcar #'substring-no-properties (helm-mklist result)) (symbol-value history))))) (or result (helm-mode--keyboard-quit))))) ;; Generic completing-read ;; ;; Support also function as collection. ;; e.g M-x man is supported. ;; Support hash-table and vectors as collection. ;; NOTE: ;; Some crap emacs functions may not be supported ;; like ffap-alternate-file (bad use of completing-read) ;; and maybe others. ;; Provide a mode `helm-mode' which turn on ;; helm in all `completing-read' and `read-file-name' in Emacs. ;; (defvar helm-completion-mode-string " Helm") (defvar helm-completion-mode-quit-message "Helm completion disabled") (defvar helm-completion-mode-start-message "Helm completion enabled") ;;; Specialized handlers ;; ;; (defun helm-completing-read-symbols (prompt _collection test _require-match init hist default _inherit-input-method name buffer) "Specialized function for fast symbols completion in `helm-mode'." (require 'helm-elisp) (or (helm :sources (helm-build-in-buffer-source name :init (lambda () (helm-apropos-init (lambda (x) (and (funcall test x) (not (keywordp x)))) (or (car-safe default) default))) :filtered-candidate-transformer 'helm-apropos-default-sort-fn :help-message #'helm-comp-read-help-message :fuzzy-match (eq helm-completion-style 'helm-fuzzy) :persistent-action (lambda (candidate) (helm-lisp-completion-persistent-action candidate name)) :persistent-help (helm-lisp-completion-persistent-help)) :prompt prompt :buffer buffer :input init :history hist :resume 'noresume :default (or default "")) (helm-mode--keyboard-quit))) ;;; Extra metadata for completions-detailed ;; ;; (defvar helm-completing-read-extra-metadata '((buffer . (metadata (affixation-function . helm-completing-read-buffer-affixation) (category . buffer) (flags . (helm-completing-read--buffer-lgst-mode)))) (symbol-help . (metadata (affixation-function . helm-symbol-completion-table-affixation) (category . symbol-help))) (command-help . (metadata (prefix-arg . t) (affixation-function . helm-symbol-completion-table-affixation) (category . symbol-help))) (eww-help . (metadata ;; Emacs-30 only (affixation-function . helm-completion-eww-affixation) (category . eww-help))) (package . (metadata (affixation-function . helm-completion-package-affixation) (category . package))) (theme . (metadata (affixation-function . helm-completion-theme-affixation) (category . theme))) (coding-system . (metadata (affixation-function . helm-completion-coding-system-affixation) (category . coding-system))) (color . (metadata (affixation-function . helm-completion-color-affixation) (category . color))) (library . (metadata (affixation-function . helm-completion-library-affixation) (category . library))) (charset . (metadata (affixation-function . helm-completion-charset-affixation) (category . charset))) (man . (metadata (popup-info-function . helm-completion-man-popup-info) (category . man))) (info . (metadata (affixation-function . helm-completion-info-file-affixation) (category . info)))) "Extra metadatas for completing-read. It is used to add `affixation-function' or `annotation-function' if original metadata doesn't have some or to override existing metadata according to the category extracted from current metadata. It is an alist composed of (CATEGORY . METADATA) elements. CATEGORY is a symbol specifying the category according to the command in use and METADATA is a list composed like this: (metadata (affixation-function . fun) (annotation-function . fun) (category . category) (flags . flags)) Categories for a specific commands are stored in `helm-completing-read-command-categories'. FLAGS is a list of variables to renitialize to nil when exiting or quitting.") (defvar helm-completing-read-command-categories '(("man" . man) ("customize-variable" . symbol-help) ("customize-set-variable" . symbol-help) ("customize-set-value" . symbol-help) ("customize-save-variable" . symbol-help) ("describe-function" . symbol-help); For Emacs-27. ("describe-variable" . symbol-help); For Emacs-27. ("describe-symbol" . symbol-help) ; For Emacs-27. ("describe-command" . symbol-help) ; For Emacs-27. ("set-variable" . symbol-help) ("customize-group" . symbol-help) ("find-function" . symbol-help) ("find-variable" . symbol-help) ("trace-function" . symbol-help) ("trace-function-foreground" . symbol-help) ("trace-function-background" . symbol-help) ("describe-minor-mode" . symbol-help) ("where-is" . symbol-help) ("execute-extended-command" . symbol-help) ("execute-extended-command-for-buffer" . command-help) ("info-lookup-symbol" . symbol-help) ("Info-goto-emacs-command-node" . symbol-help) ("find-library" . library) ("locate-library" . library) ("kill-buffer" . buffer) ("package-install" . package) ("package-vc-install" . package) ("package-vc-checkout" . package) ("package-vc-log-incoming" . package) ("package-vc-prepare-patch" . package) ("package-vc-rebuild" . package) ("package-vc-upgrade" . package) ("describe-package" . package) ("load-theme" . theme) ("describe-theme" . theme) ("describe-coding-system" . coding-system) ("set-file-name-coding-system" . coding-system) ("set-keyboard-coding-system" . coding-system) ("set-terminal-coding-system" . coding-system) ("set-process-coding-system" . coding-system) ("set-buffer-process-coding-system" . coding-system) ("set-buffer-file-coding-system" . coding-system) ("set-selection-coding-system" . coding-system) ("set-next-selection-coding-system" . coding-system) ("set-clipboard-coding-system" . coding-system) ("universal-coding-system-argument" . coding-system) ("read-color" . color) ("list-charset-chars" . charset) ("info-display-manual" . info) ;; Emacs-30 only ("eww" . eww-help)) "An alist to specify metadata category by command. Some commands provide a completion-table with no category specified in metadata, we allow here specifying the category of the completion provided by a specific command. The command should be specified as a string and the category as a symbol.") ;; We can't reuse the function from helm-man.el because the candidates are here ;; differents e.g. apt vs apt(8). (defun helm-completion-man-popup-info (candidate) ;; Man has the stupid habit to use symbol at point as default, try to not ;; match it. (let* ((com (if (string-match "\\(.*\\) ?([^()]+)" candidate) (match-string 1 candidate) candidate)) (output (shell-command-to-string (format "man -f '%s'" com)))) (when (string-match (format "\\(%s ?([^(]+)\\) *- ?\\(.*\\)\n" com) output) (match-string 2 output)))) (defvar helm-completing-read--buffer-lgst-mode nil) (defun helm-completing-read-buffer-affixation (completions) (let ((len-mode (or helm-completing-read--buffer-lgst-mode (setq helm-completing-read--buffer-lgst-mode (cl-loop for bn in completions maximize (with-current-buffer bn (length (symbol-name major-mode)))))))) (lambda (comp) (let* ((buf (get-buffer comp)) (fname (buffer-file-name buf)) (modified (and fname (buffer-modified-p buf))) (prefix (cond (modified (propertize "fm " 'face 'font-lock-comment-face)) (fname (propertize " f " 'face 'helm-completions-annotations)) (t (propertize "nf " 'face 'font-lock-doc-face)))) (mode (with-current-buffer comp (propertize (symbol-name major-mode) 'face 'helm-completions-detailed))) (size (helm-buffer-size buf)) (max-len (helm-acase helm-buffer-max-length ((guard* (numberp it)) it) (t 20))) (bname (truncate-string-to-width comp max-len nil nil helm-buffers-end-truncated-string)) (suffix (format "%s%s%s%s%s `%s'" (make-string (1+ (- max-len (length bname))) ? ) (propertize size 'face 'helm-buffer-size) (make-string (- 7 (length size)) ? ) mode (make-string (1+ (- len-mode (length mode))) ? ) (helm-aif fname (propertize (abbreviate-file-name (file-name-directory it)) 'face 'font-lock-type-face) (propertize (with-current-buffer comp (abbreviate-file-name default-directory)) 'face 'font-lock-doc-face))))) (list (propertize bname 'face (if fname 'font-lock-builtin-face 'font-lock-doc-face)) (propertize " " 'display prefix) (propertize " " 'display suffix)))))) (defun helm-symbol-completion-table-affixation (_completions) "Override `help--symbol-completion-table-affixation'. Normally affixation functions use COMPLETIONS as arg, and return a list of modified COMPLETIONS. Now we allow affixations functions to return a function instead, just like annotation functions. The function should return a list of three elements like (comp prefix suffix). This increase significantly the speed avoiding one useless loop on complete list of candidates. Returns a function and not a list of completions. It affects currently describe-variable/function/command/symbol functions. It uses `helm-get-first-line-documentation' which allow providing documentation for `describe-variable' symbols and align properly documentation when helm style is used." (lambda (comp) (require 'help-fns) (let* ((sym (intern comp)) ;; When using in-buffer implementation we should have the ;; longest len to align documentation for free. ;; Check for style as well in case user switches to emacs ;; style and a candidate buffer remains (with its local vars ;; still available). (max-len (and (memq helm-completion-style '(helm helm-fuzzy)) (helm-in-buffer-get-longest-candidate))) (sep (if (or (null max-len) (zerop max-len)) " -- " ; Default separator. (helm-make-separator comp max-len))) (doc (ignore-errors (helm-get-first-line-documentation sym))) (symbol-class (with-helm-current-buffer (help--symbol-class sym))) (group (helm-group-p sym)) (key (helm-completion-get-key sym))) (list ;; Symbol (comp). (if (or (symbol-function sym) (boundp sym) (facep sym) group) comp ;; Not already defined function. To test add an advice on a non ;; existing function. (propertize comp 'face 'helm-completion-invalid)) ;; Prefixes. ;; " c " command ;; " - " obsolete, 'byte-obsolete-info ;; " v " var, not a defcustom ;; " ' " local-variable-if-set-p ;; " * " not default value if buffer local ;; " - " 'byte-obsolete-variable (helm-aand (cond ((and symbol-class group) (concat "g" symbol-class)) ((and (not (string= symbol-class "")) symbol-class)) (group "g") (t "i")) ; Not already defined function. (propertize it 'face 'helm-completions-detailed) ;; help--symbol-class currently can return at most 8 ;; characters long symbol class but it is very rare, it is ;; generally max 4 (bug#2656). (propertize ;; (format "%-4s" it) may make spaces inheriting text props ;; with emacs -nw in emacs<29. " " 'display (format "%-4s" it))) ;; Suffix. (if doc (helm-aand (propertize doc 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it key))) ""))))) (defun helm-completion-get-key (sym) "Return key description on symbol SYM." (with-helm-current-buffer (let* ((key (and (commandp sym) (where-is-internal sym nil 'first-only))) (binding (and key (key-description key)))) (when binding (propertize (format " (%s)" binding) 'face 'shadow))))) (defun helm-completion-package-affixation (_completions) (lambda (comp) (let* ((sym (intern-soft comp)) (id (package-get-descriptor sym)) (built-in (package-built-in-p sym)) (status (and id (package-desc-status id))) (desc (if built-in (aref (assoc-default sym package--builtins) 2) (and id (package-desc-summary id)))) (sep (helm-make-separator comp))) (list comp (propertize (if status (format "%s " (substring status 0 1)) "b ") 'face 'helm-completions-annotations) (or (helm-aand desc (propertize it 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it))) ""))))) (defun helm-completion-theme-affixation (_completions) (lambda (comp) (let* ((sym (intern-soft comp)) (sep (helm-make-separator comp)) (doc (if (custom-theme-p sym) (helm-get-first-line-documentation sym) (helm--get-theme-doc-1 sym)))) (list comp "" (helm-aand (propertize doc 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it))))))) (defun helm--get-theme-doc-1 (sym) (let ((fn (locate-file (concat (symbol-name sym) "-theme.el") (custom-theme--load-path) '("" "c"))) doc) ;; Avoid loading theme as much as possible. (when fn (with-temp-buffer (insert-file-contents fn) (helm-awhile (let ((read-circle nil)) (condition-case nil (read (current-buffer)) (end-of-file nil))) (let ((docstring (nth 2 it))) (when (and (eq (car-safe it) 'deftheme) docstring) (cl-return (setq doc (car (split-string docstring "\n"))))))) (unless doc (setq doc (helm--get-theme-doc-from-header))))) doc)) (defun helm--get-theme-doc-from-header () "Extract doc in first line of theme file." (goto-char (point-min)) (let (beg end) (when (re-search-forward "--- " (pos-eol) t) (setq beg (point))) (if (re-search-forward " -\\*-" (pos-eol) t) (setq end (match-beginning 0)) (setq end (pos-eol))) (when (and beg end) (buffer-substring beg end)))) (defun helm-completion-coding-system-affixation (_comps) (require 'mule-diag) (lambda (comp) (let ((doc (with-output-to-string (with-current-buffer standard-output (print-coding-system-briefly (intern comp) 'tightly)))) (sep (helm-make-separator comp))) (list comp "" (helm-aand (replace-regexp-in-string "^ *" "" doc) (replace-regexp-in-string "[\n]" "" it) (propertize it 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it))))))) (defun helm-completion-charset-affixation (_comps) (lambda (comp) (let ((doc (charset-description (intern comp))) (sep (helm-make-separator comp))) (list comp "" (helm-aand (propertize doc 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it))))))) (defun helm-completion-color-affixation (_comps) (lambda (comp) (let ((sep (helm-make-separator comp)) (rgb (condition-case nil (helm-acase comp ("foreground at point" (with-helm-current-buffer (foreground-color-at-point))) ("background at point" (with-helm-current-buffer (background-color-at-point))) (t (apply #'color-rgb-to-hex (color-name-to-rgb comp)))) (error "SAMPLE")))) (list comp "" (helm-aand (propertize rgb 'face `(:background ,rgb :distant-foreground "black")) (propertize " " 'display (concat sep it))))))) (defun helm-completion-library-affixation (_comps) ;; We share here the same cache as `helm-locate-library'. (require 'helm-elisp) (lambda (comp) ;; Because find-library-include-other-files default to t, we have all the ;; unrelated files and directories coming in ... Even if this modify the ;; behavior of find-library-include-other-files remove them for the benefit ;; of everybody. (unless (or (string-match "\\(\\.elc\\|/\\)\\'" comp) (string-match "\\`\\.#" comp)) ; (bug#2526) (let* ((sep (helm-make-separator comp)) (path (or (assoc-default comp helm--locate-library-cache) (let ((p (find-library-name comp))) (push (cons comp p) helm--locate-library-cache) p))) (doc (or (gethash comp helm--locate-library-doc-cache) (puthash comp (helm-locate-lib-get-summary path) helm--locate-library-doc-cache)))) (list comp "" (helm-aand (propertize doc 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it)))))))) (defun helm-completion-eww-affixation (_completions) (lambda (comp) (let* ((title (or (cl-loop for bmk in eww-bookmarks for title = (plist-get bmk :title) for url = (plist-get bmk :url) thereis (and (string= comp url) title)) "Unknown title")) (sep (helm-make-separator title 72))) (list (propertize comp 'display (truncate-string-to-width comp 72 nil nil t)) (helm-aand (propertize (truncate-string-to-width title 72) 'face 'helm-completions-detailed) (propertize " " 'display (concat it sep))) "")))) (defun helm-completion-info-file-affixation (_completions) ;; We share here the same cache as `helm-info'. (require 'info) (require 'helm-info) (lambda (comp) (let* ((sep (helm-make-separator comp)) (prefix "") (file (or ;; The `info-display-manual' favours `Info-mode' ;; buffers. However, each buffer like that may be opened from ;; a file that has not been installed and is not visible to a ;; `Info-find-file'. Retrieve the such a file name from the ;; buffer, and use it to scan for affixation, however don't ;; cache the result in `helm-info--files-cache' to avoid using ;; wrong file. For example, the latter can happen after a ;; development version of info file has been opened. (let ((manual-re (concat "\\(?:/\\|\\`\\)" comp "\\(?:\\.\\|\\'\\)")) (case-fold-search t)) (cl-loop for buffer in (buffer-list) thereis (with-current-buffer buffer (when (and (derived-mode-p 'Info-mode) (stringp Info-current-file) (string-match manual-re Info-current-file)) (setq prefix "*") Info-current-file)))) (assoc-default comp helm-info--files-cache) (helm-aif (Info-find-file comp t) (prog1 it (push (cons comp it) helm-info--files-cache))))) (summary (or (gethash file helm-info--files-doc-cache) (puthash file (helm-info-file-doc file) helm-info--files-doc-cache)))) (list comp (helm-aand (propertize prefix 'face 'helm-completions-detailed) (propertize " " 'display (format "%-2s" it))) (helm-aand (propertize summary 'face 'helm-completions-detailed) (propertize " " 'display (concat sep it))))))) ;;; Completing read handlers ;; ;; (defun helm-completing-read-default-1 (prompt collection test require-match init hist default _inherit-input-method name buffer &optional cands-in-buffer exec-when-only-one alistp get-line) "Helm `completing-read' handler not rebuilding its candidates dynamically. It is used usually with helm or helm-fuzzy `helm-completion-style'. Call `helm-comp-read' with same args as `completing-read'. Extra optional arg CANDS-IN-BUFFER means use `candidates-in-buffer' method which is faster. EXEC-WHEN-ONLY-ONE allow exiting when COLLECTION contains only one candidate. ALISTP is same as `helm-comp-read' :alistp slot. When using CANDS-IN-BUFFER, GET-LINE can be specified to exit with candidate handling properties, see `helm-comp-read'. This handler should be used when candidate list doesn't need to be rebuilt dynamically otherwise use `helm-completing-read-default-2'." (require 'helm-command) (let* ((history (or (car-safe hist) hist)) (initial-input (helm-acase init ((guard* (stringp it)) it) ((guard* (consp it)) (car it)))) (minibuffer-completion-table collection) (metadata (or (completion-metadata (or initial-input "") collection test) '(metadata))) (afun (or (plist-get completion-extra-properties :annotation-function) (completion-metadata-get metadata 'annotation-function))) (afix (or (plist-get completion-extra-properties :affixation-function) (completion-metadata-get metadata 'affixation-function))) (category (completion-metadata-get metadata 'category)) (sort-fn (unless (eq helm-completion-style 'helm-fuzzy) (or (completion-metadata-get metadata 'display-sort-function) (lambda (candidates) (sort candidates #'helm-generic-sort-fn))))) popup-info flags pref-arg keymap) (helm-aif (and (null category) (assoc-default name helm-completing-read-command-categories)) (setq metadata `(metadata (category . ,it)) category it)) (helm-aif (and (or (and (boundp 'completions-detailed) completions-detailed) helm-completions-detailed) (assoc-default category helm-completing-read-extra-metadata)) (progn (setq metadata it) (setq afun (completion-metadata-get metadata 'annotation-function) afix (completion-metadata-get metadata 'affixation-function) ;; prefix-arg metadata is only in command-help category. pref-arg (completion-metadata-get metadata 'prefix-arg) popup-info (completion-metadata-get metadata 'popup-info-function) flags (completion-metadata-get metadata 'flags)))) (setq keymap (if pref-arg (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-comp-read-map) (define-key map (kbd "C-u") 'helm-M-x-universal-argument) map) helm-comp-read-map) prompt (if pref-arg (concat (helm-acase helm-M-x-prefix-argument (- "-") ((dst* l &rest args) (if (eq l 4) "C-u " (format "%d " l))) ((guard* (integerp it)) (format "%d " it))) prompt) prompt) helm--mode-line-display-prefarg pref-arg) (when pref-arg (setq helm-M-x--timer (run-at-time 1 0.1 #'helm-M-x--notify-prefix-arg)) (add-hook 'helm-move-selection-after-hook #'helm-M-x--move-selection-after-hook) (add-hook 'helm-before-action-hook #'helm-M-x--before-action-hook) ;; Notify C-u entered before Hitting M-[xX]. (setq helm-M-x-prefix-argument current-prefix-arg) (setq current-prefix-arg nil) ;; Remove command-execute advice when execute-extended-command exit. (advice-add 'execute-extended-command :around #'helm--advice-execute-extended-command)) (unwind-protect (prog1 (helm-comp-read prompt collection :test test :keymap keymap :history history :reverse-history helm-mode-reverse-history :input-history history :must-match require-match :alistp alistp :diacritics helm-mode-ignore-diacritics :help-message #'helm-comp-read-help-message :persistent-action (and pref-arg #'helm-M-x-persistent-action) :persistent-help (if pref-arg "Toggle Describe command" "DoNothing") :name name :requires-pattern (if (and (stringp default) (string= default "") (memq require-match '(confirm confirm-after-completion))) 1 0) :fc-transformer ;; When afun afix and category are nil ;; helm-completion--decorate returns ;; candidates (COMPS) unmodified. (append (list (lambda (candidates _source) (helm-completion--decorate (if (and sort-fn (> (length helm-pattern) 0)) (funcall sort-fn candidates) candidates) afun afix category))) '(helm-cr-default-transformer)) :popup-info popup-info :quit-when-no-cand (eq require-match t) :nomark (null helm-comp-read-use-marked) :candidates-in-buffer cands-in-buffer :get-line (or get-line #'buffer-substring) :exec-when-only-one exec-when-only-one :fuzzy (eq helm-completion-style 'helm-fuzzy) :buffer buffer ;; If DEF is not provided, fallback to empty string ;; to avoid `thing-at-point' to be appended on top of list :default (or default "") ;; Fail with special characters (e.g in gnus "nnimap+gmail:") ;; if regexp-quote is not used. ;; when init is added to history, it will be unquoted by ;; helm-comp-read. :initial-input initial-input) (when pref-arg (advice-add 'command-execute :around #'helm--advice-command-execute))) (when pref-arg (helm-M-x--unwind-forms)) (dolist (f flags) (set f nil))))) (defun helm--advice-command-execute (old--fn &rest args) (helm-M-x--unwind-forms 'done) ;; `command-execute' is wrapped in a let with `prefix-arg' bound to the argument ;; PREFIXARG of `execute-extended-command' so set this let-bounded `prefix-arg' ;; to the value defined during helm completion. We use ;; helm-M-x-prefix-argument instead of initial PREFIXARG to allow changing the ;; initial prefix arg during helm completion e.g. C-u M-X foo C-u 2 foo, in ;; this case the initial C-u is replaced by C-u 2. (setq prefix-arg (or helm-current-prefix-arg helm-M-x-prefix-argument)) (apply old--fn args)) (defun helm--advice-execute-extended-command (old--fn &rest args) (prog1 (apply old--fn args) (advice-remove 'command-execute 'helm--advice-command-execute))) (defun helm-completing-read-default-2 (prompt collection predicate require-match init hist default _inherit-input-method name buffer &optional _cands-in-buffer exec-when-only-one) "Helm `completing-read' handler with dynamic matching. Call `helm-comp-read' with same args as `completing-read'. For the meaning of optional args see `helm-completing-read-default-1'. This handler uses dynamic matching which allows honouring `completion-styles'." (let* ((completion-lazy-hilit t) (history (or (car-safe hist) hist)) (input (helm-acase init ((guard* (stringp it)) it) ((guard* (consp it)) (car it)))) (completion-flex-nospace t) (minibuffer-completion-table collection) ;; (completion-styles ;; (helm--prepare-completion-styles 'nomode)) (metadata (or (completion-metadata (or input "") collection predicate) '(metadata))) (afun (or (plist-get completion-extra-properties :annotation-function) (completion-metadata-get metadata 'annotation-function))) (afix (or (plist-get completion-extra-properties :affixation-function) (completion-metadata-get metadata 'affixation-function))) (category (completion-metadata-get metadata 'category)) (compfn (lambda (str _predicate _action) (let* ((completion-ignore-case (helm-set-case-fold-search)) ;; Use a copy of metadata to avoid accumulation of ;; adjustment in metadata (This is not needed in ;; emacs-31+, it has been fixed in emacsbug ;; #74718). This also avoid the flex adjustment fn ;; reusing the previous sort fn. (md (copy-sequence metadata)) (comps (completion-all-completions str ; This is helm-pattern collection predicate (length str) md)) (last-data (last comps)) ;; Helm style sort fn is added to metadata only in ;; emacs-27 by adjustment , so in emacs-26 use ;; helm-generic-sort-fn which handle both helm and ;; helm-flex styles. When helm-completion-style is ;; helm or helm-fuzzy, sorting will be done later in ;; FCT. (sort-fn (and (eq helm-completion-style 'emacs) (or ;; Emacs-27+ (completion-metadata-get md 'display-sort-function) ;; Emacs-26 (lambda (candidates) (sort candidates #'helm-generic-sort-fn))))) all) (when (cdr last-data) ;; Remove the last element of ;; comps by side-effect. (setcdr last-data nil)) (setq helm-completion--sorting-done (and sort-fn t)) (setq all (copy-sequence comps)) ;; Default is passed here only with helm ;; h-c-styles, otherwise with emacs style it is ;; passed with the :default arg of helm-comp-read ;; and computed in its get-candidates function. (append (and default (memq helm-completion-style '(helm helm-fuzzy)) (list default)) (if (and sort-fn (> (length str) 0)) (funcall sort-fn all) all))))) (data (if (memq helm-completion-style '(helm helm-fuzzy)) (funcall compfn (or input "") nil nil) compfn)) (helm-completion-in-region-default-sort-fn (lambda (candidates _source) (if (or helm-completion--sorting-done (string= helm-pattern "")) candidates (sort candidates 'helm-generic-sort-fn)))) popup-info flags) (helm-aif (and (null category) (assoc-default name helm-completing-read-command-categories)) (setq metadata `(metadata (category . ,it)) category it)) (helm-aif (and (or (and (boundp 'completions-detailed) completions-detailed) helm-completions-detailed) (assoc-default category helm-completing-read-extra-metadata)) (progn (setq metadata it) (setq afun (completion-metadata-get metadata 'annotation-function) afix (completion-metadata-get metadata 'affixation-function) popup-info (completion-metadata-get metadata 'popup-info-function) flags (completion-metadata-get metadata 'flags)))) (unwind-protect (helm-comp-read ;; Completion-at-point and friends have no prompt. prompt data :name name :initial-input input :buffer buffer :history history :nomark (null helm-comp-read-use-marked) :reverse-history helm-mode-reverse-history ;; If DEF is not provided, fallback to empty string ;; to avoid `thing-at-point' to be appended on top of list. ;; FIXME: default is added first in the collection fn, and then it is ;; added here and appended to candidates with the get candidates fn of ;; helm-comp-read, later when sorting default may move somewhere ;; whereas it has to stay on top. :default (or default "") :fc-transformer ;; When afun afix and category are nil ;; helm-completion--decorate returns ;; candidates (COMPS) unmodified and ;; helm-completion-in-region-default-sort-fn returns ;; its candidates unmodified when sorting is already done. (append (list (lambda (candidates source) (helm-completion--decorate ;; When `helm-completion-in-region-default-sort-fn' is ;; nil use CANDIDATES unmodified (bug #2689). (if helm-completion-in-region-default-sort-fn (funcall helm-completion-in-region-default-sort-fn candidates source) candidates) afun afix category))) '(helm-cr-default-transformer)) :popup-info popup-info :match-dynamic (eq helm-completion-style 'emacs) :diacritics helm-mode-ignore-diacritics :fuzzy (eq helm-completion-style 'helm-fuzzy) :exec-when-only-one exec-when-only-one :quit-when-no-cand (eq require-match t) :must-match require-match) (setq helm-completion--sorting-done nil) (dolist (f flags) (set f nil))))) (defun helm-mode-all-the-icons-handler (prompt collection test require-match init hist default inherit-input-method name buffer) "Helm `completing-read' handler for `all-the-icons-insert'." (let* ((max-len 0) sname (cands (cl-loop for (desc . str) in collection ;; When the FAMILY argument is passed to ;; `all-the-icons-insert' DESC is the name of icon only ;; otherwise it is "name [family]" with unpredictable ;; spaces or tab numbers between name and [family]. for descnp = (substring-no-properties desc) for sdesc = (if (string-match "\\(.*\\)[[:blank:]]+\\(\\[.*\\]\\)" descnp) ;; This is all-the-icons-insert function. (match-string 1 descnp) ;; This is one of ;; all-the-icons-insert- ;; functions, extract the family name. (prog1 descnp (unless sname (setq sname (plist-get (get-text-property 0 'font-lock-face (get-text-property 0 'display desc)) :family))))) for sdesc2 = (match-string 2 descnp) do (setq max-len (max max-len (string-width sdesc))) collect (cons (concat sdesc " " str " " sdesc2) desc))) (fn (lambda () (with-helm-buffer (save-excursion (goto-char (point-min)) (helm-skip-header-and-separator-line 'next) (while (re-search-forward "^[[:alnum:]_-]+" nil t) (insert (make-string (- max-len (current-column)) ? ))))))) (helm-after-update-hook (append helm-after-update-hook `(,fn)))) (helm-completing-read-default-1 prompt cands test require-match init hist default inherit-input-method (or sname name) buffer t nil t 'buffer-substring))) (defun helm-completing-read-default-find-tag (prompt collection test require-match init hist default inherit-input-method name buffer) "Helm `completing-read' handler for `find-tag'." ;; Some commands like find-tag may use `read-file-name' from inside ;; the calculation of collection. in this case it clash with ;; candidates-in-buffer that reuse precedent data (files) which is wrong. ;; So (re)calculate collection outside of main helm-session. (let* ((cands (helm-comp-read-get-candidates collection test nil nil))) (helm-completing-read-default-1 prompt cands test require-match init hist default inherit-input-method name buffer t))) (defun helm-completing-read-sync-default-handler (prompt collection test require-match init hist default inherit-input-method name buffer) "Helm `completing-read' handler using sync source as backend." (helm-completing-read-default-1 prompt collection test require-match init hist default inherit-input-method name buffer)) (defun helm-completing-read-inbuffer-default-handler (prompt collection test require-match init hist default inherit-input-method name buffer) "Helm `completing-read' handler using inbuffer source as backend." (helm-completing-read-default-1 prompt collection test require-match init hist default inherit-input-method name buffer t)) (defun helm-completing-read-default-handler (prompt collection test require-match init hist default inherit-input-method name buffer) "Default Helm `completing-read' handler. Use either `helm-completing-read-default-1' or `helm-completing-read-default-2' according to `helm-completion-style'." (let* (;; Standard will be used as CANDS-IN-BUFFER arg. (standard (and (memq helm-completion-style '(helm helm-fuzzy)) t)) (fn (if standard #'helm-completing-read-default-1 #'helm-completing-read-default-2))) (funcall fn prompt collection test require-match init hist default inherit-input-method name buffer ;; CANDS-IN-BUFFER standard))) (defun helm-mode--read-buffer-to-switch (prompt) "[INTERNAL] This is used to advice `read-buffer-to-switch'. Don't use it directly." ;; `read-buffer-to-switch' is passing `minibuffer-completion-table' ;; to `read-buffer' through `minibuffer-setup-hook' which is too ;; late to be known by `read-buffer-function', in our case ;; `helm--generic-read-buffer'. It should let bind it to allow us ;; using it. (let ((minibuffer-completion-table (internal-complete-buffer-except))) (read-buffer prompt (other-buffer (current-buffer)) (confirm-nonexistent-file-or-buffer)))) (defun helm--generic-read-buffer (prompt &optional default require-match predicate) "The `read-buffer-function' for `helm-mode'. Affects `switch-to-buffer' `kill-buffer' and related." ;; `read-buffer' is using internally `Vbuffer_alist' which is an ;; alist with elements like (BUF-NAME . BUF-OBJ), therefore some ;; predicates in Emacs are working only on such cons cells. ;; However, helm is transforming COLLECTION in a list of strings and ;; such predicates are failing because they expect cons cells (see ;; bug#2506 with `project-switch-to-buffer'), even if they should ;; handle strings as well according to `read-buffer' ;; documentation. (let ((pred (when predicate (lambda (buffer) (let ((buf (cons buffer (get-buffer buffer)))) (condition-case _err (funcall predicate buffer) (wrong-type-argument (funcall predicate buf)))))))) (helm--completing-read-default prompt (or minibuffer-completion-table (internal-complete-buffer "" nil t)) pred require-match nil nil default))) (defun helm-mode--get-default-handler-for (comp-or-file entry) ;; Use 'comp for completing-read and 'file for 'read-file-name as ;; COMP-OR-FILE value. (let ((val (cdr-safe entry)) (reading-file (eq comp-or-file 'file))) (if (consp val) (helm-acase (if reading-file (cadr val) (car val)) (default (if reading-file #'helm-read-file-name #'helm-completing-read-default-handler)) (t it)) val))) (defun helm-mode--apply-helm-handler (handler arg-list) "Ensure `minibuffer-complete' is disabled when running HANDLER. ARG-LIST is a list of arguments to pass to HANDLER." ;; Some functions are calling `minibuffer-complete' ;; within `minibuffer-setup-hook' when calling their ;; `completing-read', like `woman-file-name' (bug #2527). ;; This defeat Helm which is already ;; completing minibuffer, so deactivate ;; minibuffer-complete one time for all [1]. (cl-letf (((symbol-function 'minibuffer-complete) #'ignore)) (apply handler arg-list))) (cl-defun helm--completing-read-default (prompt collection &optional predicate require-match initial-input hist def inherit-input-method) "An helm replacement of `completing-read'. This function should be used only as a `completing-read-function'. Don't use it directly, use instead `helm-comp-read' in your programs. See documentation of `completing-read' and `all-completions' for details." (let* ((current-command (or (helm-this-command) this-command)) (str-command (if current-command (helm-symbol-name current-command) "completing-read")) (buf-name (format "*helm-mode-%s*" str-command)) (entry (assq current-command helm-completing-read-handlers-alist)) (def-com (helm-mode--get-default-handler-for 'comp entry)) (str-defcom (and def-com (helm-symbol-name def-com))) (def-args (list prompt collection predicate (helm-aif (assq current-command helm-comp-read-require-match-overrides) (cdr it) require-match) initial-input hist def inherit-input-method)) ;; Append the two extra args needed to set the buffer and source name ;; in helm specialized functions. (others-args (append def-args (list str-command buf-name))) helm-completion-mode-start-message ; Be quiet helm-completion-mode-quit-message ;; Be sure this pesty *completion* buffer doesn't popup. ;; Note: `minibuffer-with-setup-hook' may setup a lambda ;; calling `minibuffer-completion-help' or other minibuffer ;; functions we DONT WANT here, in these cases removing the hook ;; (a symbol) have no effect. Bug#448. ;; Because `minibuffer-completion-table' and ;; `minibuffer-completion-predicate' are not bound ;; anymore here, these functions should have no effect now, ;; except in some rare cases like in `woman-file-name', ;; so remove all incompatible functions ;; from `minibuffer-setup-hook' (Bug#1205, Bug#1240). ;; otherwise helm have not the time to close its initial session. (minibuffer-setup-hook (cl-loop for h in minibuffer-setup-hook ;; lambdas are no more represented as list in ;; Emacs-29+ Bug#2666. unless (or (and (not (symbolp h)) (functionp h)) ; a lambda. (byte-code-function-p h) (helm-subr-native-elisp-p h) (memq h helm-mode-minibuffer-setup-hook-black-list)) collect h)) ;; Disable hack that could be used before `completing-read'. ;; i.e (push ?\t unread-command-events). unread-command-events ;; Let-bounding here helm-completion-style according to ;; helm-completion-styles-alist allow using helm style per commands. (helm-completion-style (helm-aif (cdr (assq current-command helm-completion-styles-alist)) (if (cdr-safe it) (car it) it) (default-value 'helm-completion-style))) (completion-styles (helm--prepare-completion-styles current-command)) (default-handler ;; If nothing is found in ;; helm-completing-read-handlers-alist use default ;; handler which will itself use `helm-completion-style'. #'helm-completing-read-default-handler)) (when (eq def-com 'ido) (setq def-com 'ido-completing-read)) (unless (or (not entry) def-com) ;; An entry in *read-handlers-alist exists but have ;; a nil value, so we exit from here, disable `helm-mode' ;; and run the command again with it original behavior. ;; `helm-mode' will be restored on exit. (cl-return-from helm--completing-read-default (unwind-protect (progn (helm-mode -1) (apply completing-read-function def-args)) (helm-mode 1)))) ;; If we use now `completing-read' we MUST turn off `helm-mode' ;; to avoid infinite recursion and CRASH. It will be reenabled on exit. (when (or (eq def-com 'completing-read) ;; All specialized functions are prefixed by "helm" (and (stringp str-defcom) (not (string-match "^helm" str-defcom)))) (helm-mode -1)) (unwind-protect (cond (;; An helm specialized function exists, run it. (and def-com helm-mode) ;; Disable `minibuffer-complete' for handlers using ;; helm (bug #2533). (helm-mode--apply-helm-handler def-com others-args)) (;; Try to handle `ido-completing-read' everywhere. (and def-com (eq def-com 'ido-completing-read)) (setcar (memq collection def-args) (all-completions "" collection predicate)) (apply def-com def-args)) (;; A non helm function specified in ;; `helm-completing-read-handlers-alist' use it with ;; exactly the same args as in `completing-read'. If ;; we are here `helm-mode' is now disabled. def-com (apply def-com def-args)) (;; Use by default a in-buffer handler unless ;; COLLECTION is a function. t ;; Disable `minibuffer-complete' for handlers using ;; helm (bug #2533). (helm-mode--apply-helm-handler default-handler others-args))) (helm-mode 1) ;; When exiting minibuffer, `this-command' is set to ;; `helm-exit-minibuffer', which is unwanted when starting ;; on another `completing-read', so restore `this-command' to ;; initial value when exiting. (setq this-command current-command)))) ;;; Generic read-file-name ;; ;; ;;;###autoload (cl-defun helm-read-file-name (prompt &key (name "Read File Name") initial-input (buffer "*Helm file completions*") test noret (case-fold helm-file-name-case-fold-search) preselect history must-match (fuzzy t) default marked-candidates all-marked (candidate-number-limit helm-ff-candidate-number-limit) nomark (alistp t) (persistent-action-if 'helm-find-files-persistent-action-if) (persistent-help "Hit1 Expand Candidate, Hit2 or (C-u) Find file") (mode-line helm-read-file-name-mode-line-string)) "Read a file name with helm completion. It is helm `read-file-name' emulation. Argument PROMPT is the default prompt to use. Keys description: - NAME: Source name, default to \"Read File Name\". - INITIAL-INPUT: Where to start reading file name, default to `default-directory' or $HOME. - BUFFER: `helm-buffer' name, defaults to \"*Helm Completions*\". - TEST: A predicate called with one arg \\='candidate'. - NORET: Allow disabling helm-ff-RET (have no effect if helm-ff-RET isn't bound to RET). - CASE-FOLD: Same as `helm-case-fold-search'. - PRESELECT: helm preselection. - HISTORY: Display HISTORY in a special source. - MUST-MATCH: Can be \\='confirm, nil, or t. - FUZZY: Enable fuzzy matching when non-nil (Enabled by default). - MARKED-CANDIDATES: When non--nil return a list of marked candidates otherwise a single filename is returned. - ALL-MARKED: Allow marking several dummy candidates. - NOMARK: When non--nil don't allow marking candidates. - ALISTP: Don't use `all-completions' in history (take effect only on history). - PERSISTENT-ACTION-IF: a persistent if action function. - PERSISTENT-HELP: persistent help message. - MODE-LINE: A mode line message, default is `helm-read-file-name-mode-line-string'." (require 'tramp) (unless initial-input (setq initial-input (or default-directory (getenv "HOME")))) (when (get-buffer helm-action-buffer) (kill-buffer helm-action-buffer)) (mapc (lambda (hook) (add-hook 'helm-after-update-hook hook)) '(helm-ff-update-when-only-one-matched helm-ff-auto-expand-to-home-or-root)) (let* ((action-fn `(("Sole action (Identity)" . ,(lambda (candidate) (if marked-candidates (helm-marked-candidates :with-wildcard t :all-sources all-marked) (identity candidate)))))) ;; Be sure we don't erase the underlying minibuffer if some. (helm-ff-auto-update-initial-value (and helm-ff-auto-update-initial-value (not (minibuffer-window-active-p (minibuffer-window))))) helm-follow-mode-persistent (helm-ff-fuzzy-matching (and fuzzy (not (memq helm-mm-matching-method '(multi1 multi3p))))) (hist (and history (helm-comp-read-get-candidates history nil nil alistp))) (helm-ff--RET-disabled noret) (minibuffer-completion-predicate test) ;; Since Emacs-30+ `minibuffer-completing-file-name' affect ;; `file-directory-p' in that it returns `t' when testing e.g. "/ssh" ;; which is not a directory, as a result we end up with error in ;; `helm-find-files-get-candidates' when minibuffer contains only ;; "/ssh" so we use now our own variable ;; `helm--minibuffer-completing-file-name'. See emacsbug 74191. (helm--minibuffer-completing-file-name t) ;; Ensure not being prompted for password each time we ;; navigate to a directory. (password-cache t) (helm--completing-file-name t) (helm-read-file-name-mode-line-string (replace-regexp-in-string "helm-maybe-exit-minibuffer" "helm-confirm-and-exit-minibuffer" helm-read-file-name-mode-line-string)) (dummy-src (unless (eq must-match t) ;; Non existing file or dir source. (helm-build-dummy-source "New file or directory" :keymap 'helm-read-file-map :must-match must-match :all-marked all-marked :nomark nomark :filtered-candidate-transformer (lambda (_candidates _source) (unless (file-exists-p helm-pattern) (list (helm-ff-filter-candidate-one-by-one helm-pattern nil t)))) :action action-fn))) (src-list (list ;; History source. (helm-build-sync-source (format "%s History" name) :header-name (lambda (name) (concat name (substitute-command-keys helm-find-files-doc-header))) :mode-line mode-line :candidates hist :nohighlight t :fuzzy-match fuzzy :persistent-action-if persistent-action-if :persistent-help persistent-help :keymap helm-read-file-map :must-match must-match :nomark nomark :action action-fn) ;; List files source. (helm-build-sync-source name :header-name (lambda (name) (concat name (substitute-command-keys helm-find-files-doc-header))) :init (lambda () (setq helm-ff-auto-update-flag helm-ff-auto-update-initial-value) (setq helm-ff--auto-update-state helm-ff-auto-update-flag)) :mode-line mode-line :help-message 'helm-read-file-name-help-message :nohighlight helm-ff-nohighlight-matches :candidate-number-limit 'helm-ff-candidate-number-limit :candidates (lambda () (if test (cl-loop with hn = (helm-ff--tramp-hostnames) ;; helm-find-files-get-candidates is ;; returning a list of cons cells. for (d . r) in (helm-find-files-get-candidates) when (or (member r hn) ; A tramp host (funcall test r)) ; Test ok collect (cons d r)) (helm-find-files-get-candidates))) :update (lambda () (remhash helm-ff-default-directory helm-ff--list-directory-cache)) :match-on-real t :filtered-candidate-transformer '(helm-ff-fct helm-ff-maybe-show-thumbnails helm-ff-sort-candidates) :all-marked all-marked :persistent-action-if persistent-action-if :persistent-help persistent-help :volatile t :keymap helm-read-file-map :must-match must-match :cleanup 'helm-find-files-cleanup :nomark nomark :action action-fn))) ;; Helm result. (result (helm :sources (append (if helm-mode-reverse-history (reverse src-list) src-list) (list dummy-src)) :input (if (string-match helm-ff-url-regexp initial-input) initial-input (expand-file-name initial-input)) :prompt prompt :candidate-number-limit candidate-number-limit :dim-prompt-on-update t :resume 'noresume :case-fold-search case-fold :default default :buffer buffer :full-frame nil :preselect preselect))) (or (cond ((and result (stringp result) (string= result "") "")) ((and result (stringp result) (file-equal-p result initial-input) helm-read-file-name-use-default-arg-behavior default) (if (listp default) (car default) default)) ((and result (listp result)) (mapcar #'expand-file-name result)) ((and result (file-directory-p result)) (file-name-as-directory (expand-file-name result))) (result (expand-file-name result))) (helm-mode--keyboard-quit)))) (defun helm-mode--default-filename (fname dir initial) (unless dir (setq dir default-directory)) (unless (file-name-absolute-p dir) (setq dir (expand-file-name dir))) (unless (or fname (consp fname)) (setq fname (expand-file-name (or initial buffer-file-name dir) dir))) (if (and fname (consp fname)) (setq fname (cl-loop for f in fname collect (if (file-name-absolute-p fname) (expand-file-name f (helm-mode-root-dir dir)) (expand-file-name fname dir)))) (if (file-name-absolute-p fname) (if (file-remote-p fname) fname (substitute-in-file-name (concat (helm-mode-root-dir dir) fname))) (expand-file-name fname dir)))) (defun helm-mode-root-dir (dir) (if (file-remote-p dir) (let* ((host (file-remote-p dir 'host)) (method (file-remote-p dir 'method)) (user (file-remote-p dir 'user))) (format "/%s:%s@%s:/" method user host)) "/")) (cl-defun helm--generic-read-file-name (prompt &optional dir default-filename mustmatch initial predicate) "Generic helm replacement of `read-file-name'. Don't use it directly, use instead `helm-read-file-name' in your programs." (let* ((init (or initial dir default-directory)) (helm-read-file-name-use-default-arg-behavior t) (current-command (or (helm-this-command) this-command)) (str-command (if current-command (helm-symbol-name current-command) "read-file-name")) (helm--file-completion-sources (cons str-command (remove str-command helm--file-completion-sources))) (buf-name (format "*helm-mode-%s*" str-command)) (entry (assq current-command helm-completing-read-handlers-alist)) (def-com (helm-mode--get-default-handler-for 'file entry)) (str-defcom (and def-com (helm-symbol-name def-com))) ;; Don't modify the original args list for emacs generic functions. (def-args (list prompt dir default-filename mustmatch initial predicate)) ;; Append the two extra args needed to set the buffer and source name ;; in helm specialized functions. (others-args (append def-args (list str-command buf-name))) (reading-directory (eq predicate 'file-directory-p)) (use-dialog (and (next-read-file-uses-dialog-p) ;; Graphical file dialogs can't handle ;; remote files. (not (file-remote-p init)) use-file-dialog)) helm-completion-mode-start-message ; Be quiet helm-completion-mode-quit-message ; Same here add-to-history fname) ;; Build `default-filename' with `dir'+`initial' when ;; `default-filename' is not specified. ;; See `read-file-name' docstring for more infos. (setq default-filename (helm-mode--default-filename default-filename dir initial)) ;; Some functions that normally call `completing-read' can switch ;; brutally to `read-file-name' (e.g find-tag), in this case ;; the helm specialized function will fail because it is build ;; for `completing-read', so set it to 'incompatible to be sure ;; we switch to `helm-read-file-name' and don't try to call it ;; with wrong number of args. (when (eq def-com 'ido) (setq def-com 'ido-read-file-name)) (when (and def-com (> (length (help-function-arglist def-com)) 8)) (setq def-com 'incompatible)) (unless (or (not entry) def-com) (cl-return-from helm--generic-read-file-name (unwind-protect (progn (helm-mode -1) (apply read-file-name-function def-args)) (helm-mode 1)))) ;; If we use now `read-file-name' or dialog we MUST turn off `helm-mode' ;; to avoid infinite recursion and CRASH. It will be reenabled on exit. (when (or (memq def-com '(read-file-name ido-read-file-name)) use-dialog (and (stringp str-defcom) (not (string-match "^helm" str-defcom)))) (helm-mode -1)) (unwind-protect (setq fname (cond (use-dialog (let ((dialog-mustmatch (not (memq mustmatch '(nil confirm confirm-after-completion))))) ;; Dialogs don't support a list of default fnames. (when (and default-filename (consp default-filename)) (setq default-filename (expand-file-name (car default-filename) init))) (setq add-to-history t) (x-file-dialog prompt init default-filename dialog-mustmatch reading-directory))) ;; A specialized function exists, run it ;; with the two extra args specific to helm. ;; Note that the helm handler should ensure ;; :initial-input is not nil i.e. Use init ;; which fallback to default-directory instead ;; of INITIAL. ((and def-com helm-mode (not (eq def-com 'ido-read-file-name)) (not (eq def-com 'incompatible)) ;; The entry in ;; `helm-completing-read-handlers-alist' is ;; a cons cell specifying a completing-read ;; and a read-file-name handler default ;; e.g. (foo (default default)). (not (eq def-com 'helm-read-file-name))) (apply def-com others-args)) (;; Def-com value is `ido-read-file-name' ;; run it with default args. (and def-com (eq def-com 'ido-read-file-name)) (ido-mode 1) (apply def-com def-args)) (;; Def-com value is `read-file-name' ;; run it with default args. (eq def-com 'read-file-name) (apply def-com def-args)) (t ; Fall back to classic `helm-read-file-name'. (helm-read-file-name prompt :name str-command :buffer buf-name :default default-filename ;; Helm handlers should always have a non nil INITIAL arg. :initial-input (if (string-match helm-ff-url-regexp init) init (if (file-name-absolute-p init) (if (file-remote-p init) init (substitute-in-file-name (concat (helm-mode-root-dir (or dir init)) init))) (expand-file-name init dir))) :alistp nil :nomark (null helm-comp-read-use-marked) :marked-candidates helm-comp-read-use-marked :must-match mustmatch :test predicate :noret reading-directory)))) (and ido-mode (ido-mode -1)) (helm-mode 1) ;; Same comment as in `helm--completing-read-default'. (setq this-command current-command)) (when add-to-history (add-to-history 'file-name-history (minibuffer-maybe-quote-filename fname))) (if (and ;; Using `read-directory-name'. reading-directory ;; `file-name-as-directory' return "./" when FNAME is ;; empty string. (not (string= fname ""))) (file-name-as-directory fname) fname))) ;; Read file name handler with history (Bug#1652) (defun helm-read-file-name-handler-1 (prompt dir default-filename mustmatch initial predicate name buffer) "A `read-file-name' handler with history. Can be added to `helm-completing-read-handlers-alist' for functions that need a `read-file-name' function with directory history. The `helm-find-files' history `helm-ff-history' is used here." (let ((helm-always-two-windows t) (helm-split-window-default-side (if (eq helm-split-window-default-side 'same) 'below helm-split-window-default-side)) helm-reuse-last-window-split-state ;; Helm handlers should always have a non nil INITIAL arg. (init (or initial dir default-directory))) (helm-read-file-name prompt :name name :history helm-ff-history :buffer buffer :default default-filename :initial-input (expand-file-name init dir) :alistp nil :must-match mustmatch :test predicate))) ;;; Completion in region and Helm style ;; (defun helm-mode--advice-lisp--local-variables (old--fn &rest args) (ignore-errors (apply old--fn args))) (defvar helm-completion--sorting-done nil "Flag that notifies the FCT if sorting has been done in completion function.") (defun helm-completion-in-region-sort-fn (candidates _source) "Default sort function for completion-in-region." (if helm-completion--sorting-done candidates (sort candidates 'helm-generic-sort-fn))) (defun helm-mode--completion-in-region-initial-input (str) "Highlight prefix in helm and helm-fuzzy `helm-completion-styles'." (if (memq helm-completion-style '(helm helm-fuzzy)) (propertize str 'read-only t 'face 'helm-mode-prefix 'rear-nonsticky t) str)) (defun helm-completion--decorate (comps afun afix category) "Decorate COMPS with function AFIX or AFUN. When CATEGORY is file or library remove dot files from COMPS. If both AFUN and AFIX are provided, AFIX takes precedence. When AFUN, AFIX are nil and CATEGORY is not file return COMPS unmodified." ;; Normally COMPS should be a list of ;; string but in some cases it is given as a list of strings containing a list ;; of string e.g. ("a" "b" "c" ("d" "e" "f")) ; This happen in rgrep ;; (bug#2607) and highlight-* fns (bug #2610), so ensure the list is flattened to ;; avoid e.g. wrong-type argument: stringp '("d" "e" "f") ;; FIXME: If this create a new bug with completion-in-region, flatten COMPS ;; directly in the caller i.e. helm-completing-read-default-1. (when (or afix afun (memq category '(file library))) (setq comps (helm-fast-remove-dups (helm-flatten-list comps) :test 'equal))) ;; Filter out dot files in file completion. ;; We were previously exiting directly without handling afix and afun, but ;; maybe some file completion tables have an afix or afun in their metadata so ;; let them a chance to run these functions if some. (when (memq category '(file library)) (setq comps (cl-loop for f in comps unless (string-match "\\`\\.\\{1,2\\}/\\'" f) collect f))) (cond (afix (let ((affixations (funcall afix comps))) (if (functionp affixations) (cl-loop for comp in comps for cand = (funcall affixations comp) when cand collect (cons (propertize (concat (nth 1 cand) ;prefix (nth 0 cand) ;comp (nth 2 cand)) ;suffix 'match-part (nth 0 cand)) comp)) (cl-loop for (comp prefix suffix) in affixations collect (cons (propertize (concat prefix comp suffix) 'match-part comp) comp))))) (afun ;; Add annotation at end of ;; candidate if needed, e.g. foo, this happen when ;; completing against a quoted symbol. (mapcar (lambda (s) (let ((ann (funcall afun s))) (or (helm-aand ann (propertize ann 'face 'helm-completions-annotations) (cons (concat s (propertize " " 'display it)) s)) s))) comps)) (t comps))) ;;;###autoload (defun helm-dynamic-completion (collection predicate &optional point metadata nomode styles) "Build a completion function for `helm-pattern' in COLLECTION. Only the elements of COLLECTION that satisfy PREDICATE are considered. Argument POINT is the same as in `completion-all-completions' and is meaningful only when using some kind of `completion-at-point'. The return value is a list of completions that may be sorted by the sort function provided by the completion-style in use (emacs-27 only), otherwise (emacs-26) the sort function has to be provided if needed either with an FCT function in source or by passing the sort function with METADATA E.g.: \\='((metadata (display-sort-function . foo))). Candidates can be modified by passing an affixation-function in METADATA. If you don't want the sort fn provided by style to kick in (emacs-27) you can use as metadata value the symbol `nosort'. Example: (helm :sources (helm-build-sync-source \"test\" :candidates (helm-dynamic-completion \\='(foo bar baz foab) \\='symbolp) :match-dynamic t) :buffer \"*helm test*\") When argument NOMODE is non nil don't use `completion-styles' as specified in `helm-completion-styles-alist' for specific modes. When STYLES is specified use these `completion-styles', see `helm--prepare-completion-styles'. Also `helm-completion-style' settings have no effect here, `emacs' being used inconditionally as value." (lambda () (let* (;; Force usage of emacs style otherwise ;; helm--prepare-completion-styles will reset ;; completion-styles to default value i.e. (basic partial ;; emacs22). (helm-completion-style 'emacs) (completion-styles (with-helm-current-buffer (helm--prepare-completion-styles nomode styles))) (completion-flex-nospace t) (nosort (eq metadata 'nosort)) (compsfn (lambda (str pred _action) (let* ((completion-ignore-case (helm-set-case-fold-search)) ;; Use a copy of metadata to avoid accumulation of ;; adjustment in metadata (This is not needed in ;; emacs-31+, it has been fixed in emacsbug ;; #74718). This also avoid the flex adjustment fn ;; reusing the previous sort fn. (md (copy-sequence metadata)) (comps (completion-all-completions str (if (functionp collection) (funcall collection str pred t) collection) pred (or point 0) (or (and (consp md) md) (setq metadata '(metadata))))) (last-data (last comps)) (sort-fn (unless nosort (completion-metadata-get md 'display-sort-function))) (affix (completion-metadata-get md 'affixation-function)) all) (when (cdr last-data) (setcdr last-data nil)) (setq all (if (and sort-fn (> (length str) 0)) (funcall sort-fn comps) comps)) (if affix (helm-completion--decorate all nil affix nil) all))))) ;; Ensure circular objects are removed. (complete-with-action t compsfn helm-pattern predicate)))) ;; Helm multi matching style (defun helm-completion-try-completion (string table pred point) "The try completion function for `completing-styles-alist'. Currently does nothing." ;; AFAIU the try-completions style functions ;; are here to check if what is at point is suitable for TABLE but ;; there is no way to pass a multiple pattern from what is at point ;; apart sending STRING in a minibuffer like helm does. Perhaps ;; minibuffer-complete should benefit of this but for now just do ;; nothing as this is used nowhere. It is anyway not clear what the ;; try-completions functions do in emacs so just do nothing for now. (ignore string table pred point)) (defun helm-completion-all-completions (string table pred point) "The all completions function for `completing-styles-alist'." (cl-multiple-value-bind (all _pattern prefix _suffix _carbounds) (helm-completion--multi-all-completions string table pred point) (when all (nconc all (length prefix))))) (defun helm-completion--multi-all-completions-1 (string collection &optional predicate) "Allow `all-completions' multi matching on its candidates." ;; Doing an initial call of all-completions on the first element of ;; STRING speedup completion and fix file completion when CAPF ;; returns relative paths to initial pattern (eshell and shell). (let* ((split (helm-mm-split-pattern string)) (fpat (or (car split) "")) (file-comp-p (or minibuffer-completing-file-name (eq (completion-metadata-get (completion-metadata string collection predicate) 'category) 'file))) (all (and file-comp-p (or (cdr split) (and (not (cdr split)) ;; Kickin when STRING is a simple string. ;; Handle as well "foo " (space at end). (not (string= fpat ""))) (string= string "")) (not (string-match "\\`!" fpat)) ;; all-completions should return nil if FPAT is a ;; regexp, it is what we expect. (all-completions fpat collection (lambda (x &optional _y) (let ((elm (if (listp x) (car x) x))) (funcall (or predicate #'identity) elm)))))) (pattern (helm-aand all (string-match " " string) ;; Returns the part of STRING after space ;; e.g. "foo bar baz" => "bar baz". (substring string (1+ it))))) (if (or (and all (not (cdr split))) (equal pattern "")) ; e.g. STRING == "foo ". all (all-completions "" (or all collection) (lambda (x &optional _y) ;; Second arg _y is needed when ;; COLLECTION is a hash-table (Bug#2231) ;; (C-x 8 RET). ;; Elements of COLLECTION may be ;; lists or alists, in this case consider the ;; car of element (Bug#2219 org-refile). (let ((elm (if (listp x) (car x) x))) ;; PREDICATE have been already called in ;; initial all-completions, no need to call ;; it a second time, thus ALL is now a list ;; of strings maybe not supported by ;; PREDICATE (e.g. symbols vs strings). (if (and predicate (null all)) (and (funcall predicate elm) ;; ALL is nil so use whole STRING ;; against COLLECTION. (helm-mm-match (helm-stringify elm) string)) (helm-mm-match (helm-stringify elm) (or (and all pattern) string))))))))) (defun helm-completion--multi-all-completions (string table pred point) "Collect completions from TABLE for helm completion style." (let* ((beforepoint (substring string 0 point)) (afterpoint (substring string point)) (bounds (completion-boundaries beforepoint table pred afterpoint)) (prefix (substring beforepoint 0 (car bounds))) (suffix (substring afterpoint (cdr bounds))) (all (helm-completion--multi-all-completions-1 ;; Using `regexp-quote' on STRING fixes bug#2355 but ;; breaks regexp matching in multi match, currently with ;; Helm-3.7.1 and emacs-27+ it seems using plain STRING ;; works for both so use it. ;;(regexp-quote string) string table pred))) (list all string prefix suffix point))) ;; The adjust-metadata functions run only in emacs-27, they are NOT ;; used otherwise. (defun helm-completion--adjust-metadata (metadata) (if (memq helm-completion-style '(helm helm-fuzzy)) metadata (let ((compose-helm-sort-fn (lambda (candidates) (sort candidates #'helm-generic-sort-fn)))) `(metadata (display-sort-function . ,compose-helm-sort-fn) (cycle-sort-function . ,compose-helm-sort-fn) ,@(cl-loop for (elm . val) in (cdr metadata) unless (memq elm '(display-sort-function cycle-sort-function)) collect (cons elm val)))))) (put 'helm 'completion--adjust-metadata 'helm-completion--adjust-metadata) ;; Helm-flex style. ;; This is more or less the same as emacs-27 flex style. (defun helm-flex-completion-try-completion (string table pred point) "The try completion function for `completing-styles-alist'." ;; It is needed here to make minibuffer-complete work in emacs-26, ;; e.g. with regular M-x. (unless (string-match-p " " string) (cl-multiple-value-bind (all pattern prefix suffix _carbounds) (helm-completion--flex-all-completions string table pred point) (when minibuffer-completing-file-name (setq all (completion-pcm--filename-try-filter all))) (completion-pcm--merge-try pattern all prefix suffix)))) (defun helm-flex-completion-all-completions (string table pred point) "The all completions function for `completing-styles-alist'." ;; FIXME: No need to bind all these value. (unless (string-match-p " " string) (cl-multiple-value-bind (all pattern prefix _suffix _carbounds) (helm-completion--flex-all-completions string table pred point #'helm-completion--flex-transform-pattern) (let ((regexp (completion-pcm--pattern->regex pattern 'group))) (when all (nconc (helm-flex-add-score-as-prop all regexp) (length prefix))))))) ;; Same as emacs-27 completion-substring--all-completions. (defun helm-completion--flex-all-completions (string table pred point &optional transform-pattern-fn) "Match the presumed substring STRING to the entries in TABLE. Respect PRED and POINT. The pattern used is a PCM-style substring pattern, but it will be massaged by TRANSFORM-PATTERN-FN, if that is non-nil." (let* ((beforepoint (substring string 0 point)) (afterpoint (substring string point)) (bounds (completion-boundaries beforepoint table pred afterpoint)) (suffix (substring afterpoint (cdr bounds))) (prefix (substring beforepoint 0 (car bounds))) (basic-pattern (completion-basic--pattern beforepoint afterpoint bounds)) (pattern (if (not (stringp (car basic-pattern))) basic-pattern (cons 'prefix basic-pattern))) (pattern (if transform-pattern-fn (funcall transform-pattern-fn pattern) pattern)) (all (completion-pcm--all-completions prefix pattern table pred))) (list all pattern prefix suffix (car bounds)))) (defun helm-completion-in-region--selection () (with-helm-buffer (setq helm-saved-selection (helm-get-selection nil 'withprop)))) ;; Completion-in-region-function (defvar helm--completing-region nil "[INTERNAL] flag let-bounded to nil when completing in region.") (defun helm--completion-in-region (origfun start end collection &optional predicate) "Helm replacement of `completion--in-region'. Can be used for `completion-in-region-function' by advicing it with an :around advice to allow passing the old `completion-in-region-function' value in ORIGFUN." (if (memq major-mode helm-mode-no-completion-in-region-in-modes) (funcall origfun start end collection predicate) (advice-add 'lisp--local-variables :around #'helm-mode--advice-lisp--local-variables) (let ((old--helm-completion-style helm-completion-style) (exit-fun (plist-get completion-extra-properties :exit-function)) ;; Always start with prefix to allow completing without ;; the need of inserting a space after cursor or ;; relaying on crap old completion-styles emacs22 which ;; add suffix after prefix. e.g. def|else. (initial-input (buffer-substring-no-properties start (point))) (current-command (or (helm-this-command) this-command ;; Some backends are async and ;; use a callback, in those ;; cases, we can't retrieve from ;; frames the last interactive ;; command, so fallback to ;; `last-command' which may be ;; the one that called the callback. last-command)) string) (helm-aif (cdr (or (assq major-mode helm-completion-styles-alist) (assq current-command helm-completion-styles-alist))) (customize-set-variable 'helm-completion-style (if (cdr-safe it) (car it) it))) ;; This hook force usage of the display part of candidate with ;; its properties, this is needed for lsp-mode in its ;; :exit-function see Bug#2265. (add-hook 'helm-before-action-hook 'helm-completion-in-region--selection) (unwind-protect (let* ((enable-recursive-minibuffers t) (completion-flex-nospace t) (helm--completing-region t) (completion-styles (helm--prepare-completion-styles)) (input (buffer-substring-no-properties start end)) (prefix (and (eq helm-completion-style 'emacs) initial-input)) (point (point)) (crm (eq current-command 'crm-complete)) (str-command (helm-symbol-name current-command)) (buf-name (format "*helm-mode-%s*" str-command)) ;; If we have a minibuffer use `minibuffer-completion-confirm' ;; otherwise assume we use `completion-at-point' and use `t'. (require-match (if crm minibuffer-completion-confirm t)) (metadata (completion-metadata input collection predicate)) ;; `completion-extra-properties' is let-bounded in `completion-at-point'. ;; `afun' is a closure to call against each string in `data'. ;; it provide the annotation info for each string. ;; e.g "foo" => "foo " where foo is a function. ;; See Bug#407. (afun (or (plist-get completion-extra-properties :annotation-function) (completion-metadata-get metadata 'annotation-function))) ;; Not sure if affixations are provided in ;; completion-in-region, try anyway never know. (afix (or (plist-get completion-extra-properties :affixation-function) (completion-metadata-get metadata 'affixation-function))) (docsig (plist-get completion-extra-properties :company-docsig)) (helm-truncate-lines (and docsig helm-popup-tip-mode)) (category (or (eq (completion-metadata-get metadata 'category) 'file) (eq (plist-get completion-extra-properties :category) 'file))) (file-comp-p (or (eq category 'file) (helm-guess-filename-at-point))) ;; `completion-all-completions' store the base-size in the last `cdr', ;; so data looks like this: '(a b c d . 0) and (last data) == (d . 0). base-size (compfn (lambda (str _predicate _action) (let* ((completion-lazy-hilit t) (completion-ignore-case (helm-set-case-fold-search)) ;; Use a copy of metadata to avoid accumulation of ;; adjustment in metadata (This is not needed in ;; emacs-31+, it has been fixed in emacsbug ;; #74718). This also avoid the flex adjustment fn ;; reusing the previous sort fn. (md (copy-sequence metadata)) (comps (completion-all-completions str ; This is helm-pattern collection predicate ;; Use prefix length at first call to ;; allow styles matching ;; "prefix*suffix" to kick in. (length (or prefix str)) md)) (last-data (last comps)) (bs (helm-aif (cdr last-data) (prog1 it ;; Remove the last element of ;; comps by side-effect. (setcdr last-data nil)) 0)) ;; Helm style sort fn is added to metadata only in ;; emacs-27 by adjustment , so in emacs-26 use ;; helm-generic-sort-fn which handle both helm and ;; helm-flex styles. When helm-completion-style is ;; helm or helm-fuzzy, sorting will be done later in ;; FCT. (sort-fn (and (eq helm-completion-style 'emacs) (or ;; Emacs-27+ (completion-metadata-get md 'display-sort-function) ;; Emacs-26 (lambda (candidates) (sort candidates #'helm-generic-sort-fn))))) all) ;; Reset prefix to allow using length of ;; helm-pattern on next calls (this avoid ;; args-out-of-range error). (and prefix (setq prefix nil)) ;; base-size needs to be set only once at ;; first call. (unless base-size (setq base-size bs)) (setq helm-completion--sorting-done (and sort-fn t)) (setq all (copy-sequence comps)) (if (and sort-fn (> (length str) 0)) (funcall sort-fn all) all)))) (data (if (memq helm-completion-style '(helm helm-fuzzy)) (funcall compfn input nil nil) compfn)) (result (if (stringp data) data (helm-comp-read ;; Completion-at-point and friends have no prompt, ;; we have a prompt only with CRM, in this case ;; reuse it. Handle the case where user switched out ;; of minibuffer, leaving an active minibuffer open. (or (and (minibufferp (current-buffer)) (minibuffer-prompt)) "Pattern: ") data :name str-command :nomark (null crm) :marked-candidates crm :initial-input (cond ((and file-comp-p (not (string-match "/\\'" initial-input))) (helm-mode--completion-in-region-initial-input (if (memq helm-completion-style '(helm helm-fuzzy)) (helm-basename initial-input) initial-input))) ((string-match "/\\'" initial-input) (and (eq helm-completion-style 'emacs) initial-input)) (t (helm-mode--completion-in-region-initial-input initial-input))) :buffer buf-name :fc-transformer ;; When afun afix and category are nil ;; helm-completion--decorate returns ;; candidates (COMPS) unmodified and ;; helm-completion-in-region-default-sort-fn returns ;; its candidates unmodified when sorting is already done. (append (list (lambda (candidates source) (helm-completion--decorate ;; Magit is disabling sorting in its ;; `magit-completing-read-multiple' by let binding ;; `helm-completion-in-region-default-sort-fn' to ;; nil to prevent modifying candidates order (bug #2689). (if helm-completion-in-region-default-sort-fn (funcall helm-completion-in-region-default-sort-fn candidates source) candidates) afun afix category))) '(helm-cr-default-transformer)) :popup-info docsig :match-dynamic (eq helm-completion-style 'emacs) :fuzzy (eq helm-completion-style 'helm-fuzzy) :exec-when-only-one t :keymap helm-comp-in-region-map :quit-when-no-cand (lambda () ;; Delay message to overwrite "Quit". (run-with-timer 0.01 nil (lambda () (message "[No matches]"))) t) ; exit minibuffer immediately. :must-match require-match)))) ;; `helm-completion-in-region--insert-result' is stripping ;; out properties on RESULT by side-effect (perhaps ;; `choose-completion-string'?) so make a copy of STRING ;; to not loose props. (setq string (copy-sequence result)) (helm-completion-in-region--insert-result result start point end base-size)) ;; Allow running extra property `:exit-function' (Bug#2265, ;; Bug#2356). Function is called with 'exact if the return value of ;; `try-completion' is a string ending with / (possibly a directory ;; Bug#2274), otherwise it is always called with 'finished. However it ;; is still not clear what to use, the documentation on this beeing ;; really bad (see bug#2646). (when (and (stringp string) exit-fun) (funcall exit-fun string (helm-acase (try-completion initial-input collection predicate) ((guard* (and (stringp it) (or (string-match "/\\'" it) ;; Fix bug#2669. (string-match "/\\'" string)))) 'exact) (t 'finished)))) (remove-hook 'helm-before-action-hook 'helm-completion-in-region--selection) (customize-set-variable 'helm-completion-style old--helm-completion-style) (setq helm-completion--sorting-done nil) (advice-remove 'lisp--local-variables #'helm-mode--advice-lisp--local-variables))))) (defvar helm-crm-default-separator "," "Default separator for `completing-read-multiple'. `crm-separator' will take precedence on this when it is a string composed of a single character. If used globally, it is a string composed of a single character, if let-bounded, it can be also nil or a symbol which mean no separator. Don't set this to a string composed of more than one character. Be sure to know what you are doing when modifying this.") (defun helm-completion-in-region--insert-result (result start point end base-size) (cond ((stringp result) ;; When RESULT have annotation, annotation is displayed ;; in it with a display property attached to a space ;; added at end of string, take care of removing this ;; space (Bug#2360). However keep RESULT intact to ;; pass it to `:exit-function' i.e. Don't store the ;; modified string in STRING. (choose-completion-string (replace-regexp-in-string " \\'" "" result) (current-buffer) (list (+ start base-size) point) completion-list-insert-choice-function) (when helm-completion-mark-suffix (run-with-idle-timer 0.01 nil (lambda () (helm-aand (+ (- (point) point) end) (and (> it (point)) it) (push-mark it t t)))))) ((consp result) ; crm. (let ((beg (+ start base-size)) (sep (or (and ;; If `crm-separator' is a string of length 1 ;; assume it can be used as separator (Bug#2298), ;; otherwise it is a regexp and use the value ;; it matches or default to "," if no match. (eq (length crm-separator) 1) crm-separator) (and (stringp crm-separator) (or (get-text-property 0 'separator crm-separator) (string-replace "[ \t]*" "" crm-separator))) helm-crm-default-separator))) ;; Try to find a default separator. If `crm-separator' is a ;; regexp use the string the regexp is matching. ;; If SEP is not a string, it have been probably bound to a ;; symbol or nil through `helm-crm-default-separator' that serve ;; as a flag to say "Please no separator" (Bug#2353 with ;; `magit-completing-read-multiple'). (if (stringp sep) (save-excursion (goto-char beg) (when (looking-back crm-separator (1- (point))) (setq sep (match-string 0)))) (setq sep nil)) (when (> emacs-major-version 29) (add-function :override (local 'completion-list-insert-choice-function) #'helm--crm-insert-fn)) (funcall completion-list-insert-choice-function beg end (mapconcat 'identity (append result '("")) sep)))) (t nil))) (defun helm--crm-insert-fn (_start _end choice) ;; Fix Emacs bug~76461. (let* ((beg (save-excursion (if (and (re-search-backward crm-separator nil t) ;; Matches end of prompt (:) when sep is ":". (not (field-at-pos (point)))) (1+ (point)) (minibuffer-prompt-end)))) (end (save-excursion (if (re-search-forward crm-separator nil t) (1- (point)) (point-max))))) (completion--replace beg end choice))) (defun helm-mode--disable-ido-maybe (&optional from-hook) (when (and (boundp 'ido-everywhere) ido-everywhere) (remove-function read-file-name-function #'ido-read-file-name) (remove-function read-buffer-function #'ido-read-buffer) (setq ido-everywhere nil) (if from-hook (user-error "Unable to turn on Ido-everywhere while Helm-mode is enabled") (user-error "Helm-mode enabled (Ido-everywhere is incompatible with Helm-mode, disabling it)")))) (defun helm-mode--ido-everywhere-hook () ;; Called only when user calls directly ido-everywhere ;; and helm-mode is enabled. (when helm-mode (helm-mode--disable-ido-maybe t))) ;;;###autoload (define-minor-mode helm-mode "Toggle generic helm completion. All functions in Emacs that use `completing-read', `read-file-name', `completion-in-region' and friends will use helm interface when this mode is turned on. However you can modify this behavior for functions of your choice with `helm-completing-read-handlers-alist'. Called with a positive arg, turn on unconditionally, with a negative arg turn off. You can toggle it with M-x `helm-mode'. About `ido-mode': DO NOT enable `ido-everywhere' when using `helm-mode'. Instead of using `ido-mode', add the commands where you want to use ido to `helm-completing-read-handlers-alist' with `ido' as value. Note: This mode is incompatible with Emacs23." :group 'helm-mode :global t :lighter helm-completion-mode-string (cl-assert (boundp 'completing-read-function) nil "`helm-mode' not available, upgrade to Emacs-24") (if helm-mode (progn (add-function :override completing-read-function #'helm--completing-read-default) (add-function :override read-file-name-function #'helm--generic-read-file-name) (add-function :override read-buffer-function #'helm--generic-read-buffer) (when helm-mode-handle-completion-in-region (add-function :around completion-in-region-function #'helm--completion-in-region)) ;; If user have enabled ido-everywhere BEFORE enabling ;; helm-mode disable it and warn user about its ;; incompatibility with helm-mode (Bug#2085). (helm-mode--disable-ido-maybe) ;; If ido-everywhere is not enabled yet anticipate and ;; disable it if user attempt to enable it while helm-mode ;; is running (Bug#2085). (add-hook 'ido-everywhere-hook #'helm-mode--ido-everywhere-hook) (when (fboundp 'ffap-read-file-or-url-internal) ;; `ffap-read-file-or-url-internal' have been removed in ;; emacs-27 and `ffap-read-file-or-url' is fixed, so no need ;; to advice it. (advice-add 'ffap-read-file-or-url :override #'helm-advice--ffap-read-file-or-url)) (advice-add 'read-buffer-to-switch :override #'helm-mode--read-buffer-to-switch) (helm-minibuffer-history-mode 1)) (progn (remove-function completing-read-function #'helm--completing-read-default) (remove-function read-file-name-function #'helm--generic-read-file-name) (remove-function read-buffer-function #'helm--generic-read-buffer) (remove-function completion-in-region-function #'helm--completion-in-region) (remove-hook 'ido-everywhere-hook #'helm-mode--ido-everywhere-hook) (when (fboundp 'ffap-read-file-or-url-internal) (advice-remove 'ffap-read-file-or-url #'helm-advice--ffap-read-file-or-url)) (advice-remove 'read-buffer-to-switch #'helm-mode--read-buffer-to-switch) (helm-minibuffer-history-mode -1)))) (provide 'helm-mode) ;;; helm-mode.el ends here helm-4.0.3/helm-multi-match.el000066400000000000000000000366031501106761700161500ustar00rootroot00000000000000;;; helm-multi-match.el --- Multiple regexp matching methods for helm -*- lexical-binding: t -*- ;; Original Author: rubikitch ;; Copyright (C) 2008 ~ 2011 rubikitch ;; Copyright (C) 2011 ~ 2025 Thierry Volpiatto ;; Author: Thierry Volpiatto ;; URL: http://github.com/emacs-helm/helm ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm-lib) (defgroup helm-multi-match nil "Helm multi match." :group 'helm) (defcustom helm-mm-matching-method 'multi3 "Matching method for helm match plugin. You can set here different methods to match candidates in helm. Here are the possible value of this symbol and their meaning: - multi1: Respect order, prefix of pattern must match. - multi2: Same but with partial match. - multi3: The best, multiple regexp match, allow negation. - multi3p: Same but prefix must match. Default is multi3, you should keep this for a better experience. Note that multi1 and multi3p are incompatible with fuzzy matching in file completion and by the way fuzzy matching will be disabled there when these options are used." :type '(radio :tag "Matching methods for helm" (const :tag "Multiple regexp 1 ordered with prefix match" multi1) (const :tag "Multiple regexp 2 ordered with partial match" multi2) (const :tag "Multiple regexp 3 matching no order, partial, best." multi3) (const :tag "Multiple regexp 3p matching with prefix match" multi3p)) :group 'helm-multi-match) ;; Internal (defvar helm-mm-default-match-functions '(helm-mm-exact-match helm-mm-match)) (defvar helm-mm-default-search-functions '(helm-mm-exact-search helm-mm-search)) ;;; Build regexps ;; ;; (defconst helm-mm-space-regexp "\\s\\\\s-" "Regexp to represent space itself in multiple regexp match.") (defun helm-mm-split-pattern (pattern &optional grep-space) "Split PATTERN if it contains spaces and return resulting list. If spaces in PATTERN are escaped, don't split at this place. i.e \"foo bar baz\"=> (\"foo\" \"bar\" \"baz\") but \"foo\\ bar baz\"=> (\"foo\\s-bar\" \"baz\"). If GREP-SPACE is used translate escaped space to \"\\s\" instead of \"\\s-\"." (split-string ;; Match spaces litteraly because candidate buffer syntax-table ;; doesn't understand "\s-" properly. (replace-regexp-in-string helm-mm-space-regexp (if grep-space "\\s" "\\s-") pattern nil t))) (defun helm-mm-1-make-regexp (pattern) "Replace spaces in PATTERN with \".*\"." (mapconcat 'identity (helm-mm-split-pattern pattern) ".*")) ;;; Exact match. ;; ;; ;; Internal. (defvar helm-mm--exact-pattern-str nil) (defvar helm-mm--exact-pattern-real nil) (defun helm-mm-exact-get-pattern (pattern) (unless (equal pattern helm-mm--exact-pattern-str) (setq helm-mm--exact-pattern-str pattern helm-mm--exact-pattern-real (concat "^" (regexp-quote pattern) "$"))) helm-mm--exact-pattern-real) (cl-defun helm-mm-exact-match (candidate &optional (pattern helm-pattern)) (if case-fold-search (string= (downcase candidate) (downcase pattern)) (string= candidate pattern))) (defun helm-mm-exact-search (pattern &rest _ignore) (helm-re-search-forward (helm-mm-exact-get-pattern pattern) nil t)) ;;; Prefix match ;; ;; ;; Internal (defvar helm-mm--prefix-pattern-str nil) (defvar helm-mm--prefix-pattern-real nil) (defun helm-mm-prefix-get-pattern (pattern) (unless (equal pattern helm-mm--prefix-pattern-str) (setq helm-mm--prefix-pattern-str pattern helm-mm--prefix-pattern-real (concat "\n" pattern))) helm-mm--prefix-pattern-real) (defun helm-mm-prefix-match (candidate &optional pattern) ;; In filename completion basename and basedir may be ;; quoted, unquote them for string comparison (Bug#1283). (setq pattern (replace-regexp-in-string "\\\\" "" (or pattern helm-pattern))) (let ((len (length pattern))) (and (<= len (length candidate)) (string= (substring candidate 0 len) pattern )))) (defun helm-mm-prefix-search (pattern &rest _ignore) (search-forward (helm-mm-prefix-get-pattern pattern) nil t)) ;;; Multiple regexp patterns 1 (order is preserved / prefix). ;; ;; ;; Internal (defvar helm-mm--1-pattern-str nil) (defvar helm-mm--1-pattern-real nil) (defun helm-mm-1-get-pattern (pattern) (unless (equal pattern helm-mm--1-pattern-str) (setq helm-mm--1-pattern-str pattern helm-mm--1-pattern-real (concat "^" (helm-mm-1-make-regexp pattern)))) helm-mm--1-pattern-real) (cl-defun helm-mm-1-match (candidate &optional (pattern helm-pattern)) (string-match (helm-mm-1-get-pattern pattern) candidate)) (defun helm-mm-1-search (pattern &rest _ignore) (re-search-forward (helm-mm-1-get-pattern pattern) nil t)) ;;; Multiple regexp patterns 2 (order is preserved / partial). ;; ;; ;; Internal (defvar helm-mm--2-pattern-str nil) (defvar helm-mm--2-pattern-real nil) (defun helm-mm-2-get-pattern (pattern) (unless (equal pattern helm-mm--2-pattern-str) (setq helm-mm--2-pattern-str pattern helm-mm--2-pattern-real (concat "^.*" (helm-mm-1-make-regexp pattern)))) helm-mm--2-pattern-real) (cl-defun helm-mm-2-match (candidate &optional (pattern helm-pattern)) (string-match (helm-mm-2-get-pattern pattern) candidate)) (defun helm-mm-2-search (pattern &rest _ignore) (re-search-forward (helm-mm-2-get-pattern pattern) nil t)) ;;; Multiple regexp patterns 3 (permutation). ;; ;; ;; Internal (defvar helm-mm--3-pattern-str nil) (defvar helm-mm--3-pattern-list nil) (defun helm-mm-3-get-patterns (pattern) "Return a list of predicate/regexp cons cells. E.g., ((identity . \"foo\") (not . \"bar\")). If PATTERN is unchanged, don't recompute PATTERN and return the previous value stored in `helm-mm--3-pattern-list'." (unless (equal pattern helm-mm--3-pattern-str) (setq helm-mm--3-pattern-str pattern helm-mm--3-pattern-list (helm-mm-3-get-patterns-internal pattern))) helm-mm--3-pattern-list) (defun helm-mm-3-get-patterns-internal (pattern) "Return a list of predicate/regexp cons cells. E.g., ((identity . \"foo\") (not . \"bar\"))." (unless (string= pattern "") (cl-loop for pat in (helm-mm-split-pattern pattern) collect (if (char-equal ?! (aref pat 0)) (cons 'not (substring pat 1)) (cons 'identity pat))))) (defun helm-mm-regexp-p (string) (string-match-p "[][*+^$.?]" string)) (defvar helm-mm--match-on-diacritics nil) (cl-defun helm-mm-3-match (candidate &optional (pattern helm-pattern)) "Check if PATTERN match CANDIDATE. When PATTERN contains a space, it is splitted and matching is done with the several resulting regexps against CANDIDATE. E.g., \"bar foo\" will match \"foobar\" and \"barfoo\". Argument PATTERN, a string, is transformed in a list of cons cell with `helm-mm-3-get-patterns' if it contains a space. E.g., \"foo bar\"=>((identity . \"foo\") (identity . \"bar\")). Then each predicate of cons cell(s) is called with the regexp of the same cons cell against CANDIDATE. I.e. (identity (string-match \"foo\" \"foo bar\")) => t." (let ((pat (helm-mm-3-get-patterns pattern))) (cl-loop for (predicate . regexp) in pat for re = (if (and helm-mm--match-on-diacritics (not (helm-mm-regexp-p regexp))) (char-fold-to-regexp regexp) regexp) always (funcall predicate (condition-case _err ;; FIXME: Probably do nothing when ;; using fuzzy leaving the job ;; to the fuzzy fn. (string-match re candidate) (invalid-regexp nil)))))) (defun helm-mm-3-search-base (pattern searchfn1 searchfn2) "Try to find PATTERN in `helm-buffer' with SEARCHFN1 and SEARCHFN2. This is the search function for `candidates-in-buffer' enabled sources. Use the same method as `helm-mm-3-match' except it search in buffer instead of matching on a string. i.e (identity (re-search-forward \"foo\" (pos-eol) t)) => t." (cl-loop with pat = (if (stringp pattern) (helm-mm-3-get-patterns pattern) pattern) with regex = (cdar pat) with regex1 = (if (and regex helm-mm--match-on-diacritics (not (helm-mm-regexp-p regex))) (char-fold-to-regexp regex) regex) when (eq (caar pat) 'not) return ;; Pass the job to `helm-search-match-part'. ;; We now forward-line from helm-search-from-candidate-buffer, see ;; comments about bug#2650 there. (list (pos-bol) (pos-eol)) while (condition-case _err (funcall searchfn1 (or regex1 "") nil t) (invalid-regexp nil)) for bol = (pos-bol) for eol = (pos-eol) if (cl-loop for (pred . str) in (cdr pat) for regexp = (if (and helm-mm--match-on-diacritics (not (helm-mm-regexp-p str))) (char-fold-to-regexp str) str) always (progn (goto-char bol) (funcall pred (condition-case _err (funcall searchfn2 regexp eol t) (invalid-regexp nil))))) do (helm-mm-3--search-move-forward bol eol) and return t else do (helm-mm-3--search-move-forward bol eol) finally return nil)) (defun helm-mm-3--search-move-forward (bol eol) "Move point forward for next search. Forward line on empty lines, otherwise goto eol." (if (eql bol eol) (forward-line 1) (goto-char eol))) (defun helm-mm-3-search (pattern &rest _ignore) (helm-mm-3-search-base pattern #'helm-re-search-forward #'helm-re-search-forward)) (defun helm-mm-3-search-on-diacritics (pattern &rest _ignore) (let ((helm-mm--match-on-diacritics t)) (helm-mm-3-search pattern))) ;;; mp-3 with migemo ;; Needs https://github.com/emacs-jp/migemo ;; (defvar helm-mm--previous-migemo-info nil "[Internal] Cache previous migemo query.") (make-local-variable 'helm-mm--previous-migemo-info) (declare-function migemo-get-pattern "ext:migemo.el") (declare-function migemo-search-pattern-get "ext:migemo.el") (define-minor-mode helm-migemo-mode "Enable migemo in helm. It will be available in the sources handling it, i.e. the sources which have the slot :migemo with non--nil value." :lighter " Hmio" :group 'helm :global t (cl-assert (featurep 'migemo) nil "No feature called migemo found, install migemo.el.")) (defun helm-mm-migemo-get-pattern (pattern) (let ((regex (migemo-get-pattern pattern))) (if (ignore-errors (string-match regex "") t) (concat regex "\\|" pattern) pattern))) (defun helm-mm-migemo-search-pattern-get (pattern) (let ((regex (migemo-search-pattern-get pattern))) (if (ignore-errors (string-match regex "") t) (concat regex "\\|" pattern) pattern))) (defun helm-mm-migemo-string-match (pattern str) "Migemo version of `string-match'." (unless (assoc pattern helm-mm--previous-migemo-info) (with-helm-buffer (setq helm-mm--previous-migemo-info (push (cons pattern (helm-mm-migemo-get-pattern pattern)) helm-mm--previous-migemo-info)))) (string-match (assoc-default pattern helm-mm--previous-migemo-info) str)) (defun helm-mm-diacritics-string-match (pattern str) "Check if PATTERN match STR ignoring diacritics. If PATTERN is a regexp (i.e. `helm-mm-regexp-p') use PATTERN unmodified, otherwise transform PATTERN with `char-fold-to-regexp'. This function is used to search match-part of candidate in in-buffer sources." (string-match (if (helm-mm-regexp-p pattern) pattern (char-fold-to-regexp pattern)) str)) (cl-defun helm-mm-3-migemo-match (candidate &optional (pattern helm-pattern)) (and helm-migemo-mode (cl-loop for (pred . re) in (helm-mm-3-get-patterns pattern) always (funcall pred (helm-mm-migemo-string-match re candidate))))) (defun helm-mm-migemo-forward (word &optional bound noerror count) (with-helm-buffer (unless (assoc word helm-mm--previous-migemo-info) (setq helm-mm--previous-migemo-info (push (cons word (if (delq 'ascii (find-charset-string word)) word (helm-mm-migemo-search-pattern-get word))) helm-mm--previous-migemo-info)))) (re-search-forward (assoc-default word helm-mm--previous-migemo-info) bound noerror count)) (defun helm-mm-3-migemo-search (pattern &rest _ignore) (and helm-migemo-mode (helm-mm-3-search-base pattern 'helm-mm-migemo-forward 'helm-mm-migemo-forward))) ;;; mp-3p- (multiple regexp pattern 3 with prefix search) ;; ;; (defun helm-mm-3p-match (candidate &optional pattern) "Check if PATTERN match CANDIDATE. Same as `helm-mm-3-match' but only for the cdr of patterns, the car of patterns must always match CANDIDATE prefix. E.g. \"bar foo baz\" will match \"barfoobaz\" or \"barbazfoo\" but not \"foobarbaz\" whereas `helm-mm-3-match' would match all." (let* ((pat (helm-mm-3-get-patterns (or pattern helm-pattern))) (first (car pat))) (and (funcall (car first) (helm-mm-prefix-match candidate (cdr first))) (cl-loop for (predicate . regexp) in (cdr pat) always (funcall predicate (string-match regexp candidate)))))) (defun helm-mm-3p-search (pattern &rest _ignore) (helm-mm-3-search-base pattern 'helm-mm-prefix-search 're-search-forward)) ;;; Generic multi-match/search functions ;; ;; (cl-defun helm-mm-match (candidate &optional (pattern helm-pattern)) "Call `helm-mm-matching-method' function against CANDIDATE." (let ((fun (cl-ecase helm-mm-matching-method (multi1 #'helm-mm-1-match) (multi2 #'helm-mm-2-match) (multi3 #'helm-mm-3-match) (multi3p #'helm-mm-3p-match)))) (funcall fun candidate pattern))) (cl-defun helm-mm-3-match-on-diacritics (candidate &optional (pattern helm-pattern)) "Same as `helm-mm-3-match' but match on diacritics if possible." (let ((helm-mm--match-on-diacritics t)) (helm-mm-match candidate pattern))) (defun helm-mm-search (pattern &rest _ignore) "Search for PATTERN with `helm-mm-matching-method' function." (let ((fun (cl-ecase helm-mm-matching-method (multi1 #'helm-mm-1-search) (multi2 #'helm-mm-2-search) (multi3 #'helm-mm-3-search) (multi3p #'helm-mm-3p-search)))) (funcall fun pattern))) (provide 'helm-multi-match) ;;; helm-multi-match.el ends here helm-4.0.3/helm-net.el000066400000000000000000000372671501106761700145210ustar00rootroot00000000000000;;; helm-net.el --- helm browse url and search web. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'url) (require 'xml) (require 'browse-url) (declare-function helm-comp-read "helm-mode") (defgroup helm-net nil "Net related applications and libraries for Helm." :group 'helm) (defcustom helm-google-suggest-default-browser-function nil "The browse url function you prefer to use with Google suggest. When nil, use the first browser function available See `helm-browse-url-default-browser-alist'." :group 'helm-net :type 'symbol) (defcustom helm-home-url "https://www.google.com" "Default url to use as home url." :group 'helm-net :type 'string) (defcustom helm-surfraw-default-browser-function nil "The browse url function you prefer to use with surfraw. When nil, fallback to `browse-url-browser-function'." :group 'helm-net :type 'symbol) (defcustom helm-google-suggest-url "https://encrypted.google.com/complete/search?output=toolbar&q=%s" "URL used for looking up Google suggestions. This is a format string, don't forget the `%s'." :type 'string :group 'helm-net) (defcustom helm-google-suggest-search-url "https://encrypted.google.com/search?ie=utf-8&oe=utf-8&q=%s" "URL used for Google searching. This is a format string, don't forget the `%s'." :type 'string :group 'helm-net) (defvaralias 'helm-google-suggest-use-curl-p 'helm-net-prefer-curl) (make-obsolete-variable 'helm-google-suggest-use-curl-p 'helm-net-prefer-curl "1.7.7") (defcustom helm-net-prefer-curl nil "When non--nil use CURL external program to fetch data. Otherwise `url-retrieve-synchronously' is used." :type 'boolean :group 'helm-net) (defcustom helm-surfraw-duckduckgo-url "https://duckduckgo.com/lite/?q=%s&kp=1" "The Duckduckgo url. This is a format string, don't forget the `%s'. If you have personal settings saved on duckduckgo you should have a personal url, see your settings on duckduckgo." :type 'string :group 'helm-net) (defcustom helm-search-suggest-action-wikipedia-url "https://en.wikipedia.org/wiki/Special:Search?search=%s" "The Wikipedia search url. This is a format string, don't forget the `%s'." :type 'string :group 'helm-net) (defcustom helm-search-suggest-action-youtube-url "https://www.youtube.com/results?aq=f&search_query=%s" "The Youtube search url. This is a format string, don't forget the `%s'." :type 'string :group 'helm-net) (defcustom helm-search-suggest-action-imdb-url "http://www.imdb.com/find?s=all&q=%s" "The IMDb search url. This is a format string, don't forget the `%s'." :type 'string :group 'helm-net) (defcustom helm-search-suggest-action-google-maps-url "https://maps.google.com/maps?f=q&source=s_q&q=%s" "The Google Maps search url. This is a format string, don't forget the `%s'." :type 'string :group 'helm-net) (defcustom helm-search-suggest-action-google-news-url "https://www.google.com/search?safe=off&prmd=nvlifd&source=lnms&tbs=nws:1&q=%s" "The Google News search url. This is a format string, don't forget the `%s'." :type 'string :group 'helm-net) (defcustom helm-google-suggest-actions (helm-make-actions "Google Search" 'helm-google-suggest-action "Wikipedia" (lambda (candidate) (helm-search-suggest-perform-additional-action helm-search-suggest-action-wikipedia-url candidate)) "Youtube" (lambda (candidate) (helm-search-suggest-perform-additional-action helm-search-suggest-action-youtube-url candidate)) "IMDb" (lambda (candidate) (helm-search-suggest-perform-additional-action helm-search-suggest-action-imdb-url candidate)) "Google Maps" (lambda (candidate) (helm-search-suggest-perform-additional-action helm-search-suggest-action-google-maps-url candidate)) "Google News" (lambda (candidate) (helm-search-suggest-perform-additional-action helm-search-suggest-action-google-news-url candidate))) "List of actions for google suggest sources." :group 'helm-net :type '(alist :key-type string :value-type function)) (defcustom helm-browse-url-firefox-new-window "--new-tab" "Allow choosing to browse url in new window or new tab. Can be \"--new-tab\" (default), \"--new-window\" or \"--private-window\"." :group 'helm-net :type '(radio (const :tag "New tab" "--new-tab") (const :tag "New window" "--new-window") (const :tag "New private window" "--private-window"))) (defcustom helm-net-curl-switches '("-s" "-L") "Arguments list passed to curl when using `helm-net-prefer-curl'." :group 'helm-net :type '(repeat string)) ;;; Additional actions for search suggestions ;; ;; ;; Internal (defvar helm-net-curl-log-file (expand-file-name "helm-curl.log" user-emacs-directory)) (defun helm-search-suggest-perform-additional-action (url query) "Perform the search via URL using QUERY as input." (browse-url (format url (url-hexify-string query)))) (defun helm-net--url-retrieve-sync (request parser) (if helm-net-prefer-curl (with-temp-buffer (apply #'call-process "curl" nil `(t ,helm-net-curl-log-file) nil request helm-net-curl-switches) (funcall parser)) (with-current-buffer (url-retrieve-synchronously request) (funcall parser)))) ;;; Google Suggestions ;; ;; (defun helm-google-suggest-parser () (cl-loop with result-alist = (xml-get-children (car (xml-parse-region (point-min) (point-max))) 'CompleteSuggestion) for i in result-alist collect (cdr (cl-caadr (assq 'suggestion i))))) (defun helm-google-suggest-fetch (input) "Fetch suggestions for INPUT from XML buffer." (let ((request (format helm-google-suggest-url (url-hexify-string input)))) (helm-net--url-retrieve-sync request #'helm-google-suggest-parser))) (defun helm-google-suggest-set-candidates (&optional request-prefix) "Set candidates with result and number of Google results found." (let ((suggestions (helm-google-suggest-fetch (or (and request-prefix (concat request-prefix " " helm-pattern)) helm-pattern)))) (if (member helm-pattern suggestions) suggestions ;; if there is no suggestion exactly matching the input then ;; prepend a Search on Google item to the list (append suggestions (list (cons (format "Search for '%s' on Google" helm-input) helm-input)))))) (defun helm-ggs-set-number-result (num) (if num (progn (and (numberp num) (setq num (number-to-string num))) (cl-loop for i in (reverse (split-string num "" t)) for count from 1 append (list i) into C when (= count 3) append (list ",") into C and do (setq count 0) finally return (replace-regexp-in-string "^," "" (mapconcat 'identity (reverse C) "")))) "?")) (defun helm-google-suggest-action (candidate) "Default action to jump to a Google suggested candidate." (let ((arg (format helm-google-suggest-search-url (url-hexify-string candidate)))) (helm-aif helm-google-suggest-default-browser-function (funcall it arg) (helm-browse-url arg)))) (defvar helm-google-suggest-default-function 'helm-google-suggest-set-candidates "Default function to use in `helm-google-suggest'.") (defvar helm-source-google-suggest (helm-build-sync-source "Google Suggest" :candidates (lambda () (funcall helm-google-suggest-default-function)) :action 'helm-google-suggest-actions :match-dynamic t :keymap helm-map :requires-pattern 3)) (defun helm-google-suggest-emacs-lisp () "Try to emacs lisp complete with Google suggestions." (helm-google-suggest-set-candidates "emacs lisp")) ;;; Web browser functions. ;; ;; ;; If default setting of `w3m-command' is not ;; what you want and you modify it, you will have to reeval ;; also `helm-browse-url-default-browser-alist'. (defvar helm-browse-url-chromium-program "chromium-browser") (defvar helm-browse-url-uzbl-program "uzbl-browser") (defvar helm-browse-url-nyxt-program "nyxt") (defvar helm-browse-url-conkeror-program "conkeror") (defvar helm-browse-url-opera-program "opera") (defvar helm-browse-url-w3m-program (or (and (boundp 'w3m-command) w3m-command) (executable-find "w3m"))) (defvar helm-browse-url-default-browser-alist '((helm-browse-url-w3m-program . w3m-browse-url) (browse-url-firefox-program . browse-url-firefox) (helm-browse-url-chromium-program . helm-browse-url-chromium) (helm-browse-url-conkeror-program . helm-browse-url-conkeror) (helm-browse-url-opera-program . helm-browse-url-opera) (helm-browse-url-uzbl-program . helm-browse-url-uzbl) (helm-browse-url-nyxt-program . helm-browse-url-nyxt) (browse-url-kde-program . browse-url-kde) (browse-url-gnome-moz-program . browse-url-gnome-moz) (browse-url-mozilla-program . browse-url-mozilla) (browse-url-galeon-program . browse-url-galeon) (browse-url-netscape-program . browse-url-netscape) (browse-url-xterm-program . browse-url-text-xterm) ("emacs" . eww-browse-url)) "Alist of (browse_url_variable . function) to try to find a suitable url browser.") (cl-defun helm-generic-browser (url cmd-name &rest args) "Browse URL with NAME browser." (let ((proc (concat cmd-name " " url))) (message "Starting %s..." cmd-name) (apply 'start-process proc nil cmd-name (append args (list url))) (set-process-sentinel (get-process proc) (lambda (process event) (when (string= event "finished\n") (message "%s process %s" process event)))))) ;;;###autoload (defun helm-browse-url-firefox (url &optional _ignore) "Same as `browse-url-firefox' but detach from Emacs. So when you quit Emacs you can keep your Firefox session open and not be prompted to kill the Firefox process. NOTE: Probably not supported on some systems (e.g., Windows)." (interactive (list (read-string "URL: " (browse-url-url-at-point)) nil)) (setq url (browse-url-encode-url url)) (let ((process-environment (browse-url-process-environment))) (call-process-shell-command (format "(%s %s %s &)" browse-url-firefox-program helm-browse-url-firefox-new-window (shell-quote-argument url))))) ;;;###autoload (defun helm-browse-url-opera (url &optional _ignore) "Browse URL with Opera browser and detach from Emacs. So when you quit Emacs you can keep your Opera session open and not be prompted to kill the Opera process. NOTE: Probably not supported on some systems (e.g., Windows)." (interactive (list (read-string "URL: " (browse-url-url-at-point)) nil)) (setq url (browse-url-encode-url url)) (let ((process-environment (browse-url-process-environment))) (call-process-shell-command (format "(%s %s &)" helm-browse-url-opera-program (shell-quote-argument url))))) ;;;###autoload (defun helm-browse-url-chromium (url &optional _ignore) "Browse URL with Google Chrome browser." (interactive "sURL: ") (helm-generic-browser url helm-browse-url-chromium-program)) ;;;###autoload (defun helm-browse-url-uzbl (url &optional _ignore) "Browse URL with uzbl browser." (interactive "sURL: ") (helm-generic-browser url helm-browse-url-uzbl-program "-u")) ;;;###autoload (defun helm-browse-url-conkeror (url &optional _ignore) "Browse URL with conkeror browser." (interactive "sURL: ") (helm-generic-browser url helm-browse-url-conkeror-program)) ;;;###autoload (defun helm-browse-url-nyxt (url &optional _ignore) "Browse URL with nyxt browser." (interactive "sURL: ") (helm-generic-browser url helm-browse-url-nyxt-program)) (defun helm-browse-url-default-browser (url &rest args) "Find the first available browser and ask it to load URL." (let ((default-browser-fn (cl-loop for (var . fn) in helm-browse-url-default-browser-alist for exe = (if (stringp var) var (and (boundp var) (symbol-value var))) thereis (and exe (executable-find exe) (fboundp fn) fn)))) (if default-browser-fn (apply default-browser-fn url args) (error "No usable browser found")))) (defun helm-browse-url (url &rest args) "Default command to browse URL." (if browse-url-browser-function (browse-url url args) (helm-browse-url-default-browser url args))) ;;; Surfraw ;; ;; Need external program surfraw. ;; ;; Internal (defvar helm-surfraw-engines-history nil) (defvar helm-surfraw-input-history nil) (defvar helm-surfraw--elvi-cache nil) (defun helm-build-elvi-list () "Return list of all engines and descriptions handled by surfraw." (or helm-surfraw--elvi-cache (setq helm-surfraw--elvi-cache (cdr (with-temp-buffer (call-process "surfraw" nil t nil "-elvi") (split-string (buffer-string) "\n")))))) ;;;###autoload (defun helm-surfraw (pattern engine) "Preconfigured `helm' to search PATTERN with search ENGINE." (interactive (list (let* ((default (if (use-region-p) (buffer-substring-no-properties (region-beginning) (region-end)) (thing-at-point 'symbol))) (prompt (if default (format "SearchFor (default %s): " default) "SearchFor: "))) (read-string prompt nil 'helm-surfraw-input-history default)) (helm-comp-read "Engine: " (helm-build-elvi-list) :must-match t :name "Surfraw Search Engines" :history 'helm-surfraw-engines-history))) (let* ((engine-nodesc (car (split-string engine))) (url (if (string= engine-nodesc "duckduckgo") ;; "sr duckduckgo -p foo" is broken, workaround. (format helm-surfraw-duckduckgo-url (url-hexify-string pattern)) (with-temp-buffer (apply 'call-process "surfraw" nil t nil (append (list engine-nodesc "-p") (split-string pattern))) (replace-regexp-in-string "\n" "" (buffer-string))))) (browse-url-browser-function (or helm-surfraw-default-browser-function browse-url-browser-function))) (if (string= engine-nodesc "W") (helm-browse-url helm-home-url) (helm-browse-url url)))) ;;;###autoload (defun helm-google-suggest () "Preconfigured `helm' for Google search with Google suggest." (interactive) (helm :sources 'helm-source-google-suggest :buffer "*helm google*")) (provide 'helm-net) ;;; helm-net.el ends here helm-4.0.3/helm-occur.el000066400000000000000000001174011501106761700150330ustar00rootroot00000000000000;;; helm-occur.el --- Incremental Occur for Helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'helm-utils) (declare-function helm-buffers-get-visible-buffers "helm-buffers") (declare-function helm-buffer-list "helm-buffers") (declare-function helm-grep-split-line "helm-grep") (declare-function helm-grep-highlight-match "helm-grep") (declare-function helm-grep-ag-1 "helm-grep") (declare-function helm-grep--ag-command "helm-grep") (declare-function helm-comp-read "helm-mode") (defvar helm-current-error) ;;; Internals ;; (defvar helm-source-occur nil "This will be the name of the source related to `current-buffer'. Don't use it as it value changes always.") (defvar helm-source-moccur nil "This is just a flag to add to `helm-sources-using-default-as-input'. Don't set it to any value, it will have no effect.") (defvar helm-occur--buffer-list nil) (defvar helm-occur--buffer-tick nil) (defvar helm-occur-history nil) (defvar helm-occur--search-buffer-regexp "\\`\\([0-9]*\\)\\s-\\(.*\\)\\'" "The regexp matching candidates in helm-occur candidate buffer.") (defvar helm-occur-mode--last-pattern nil) (defvar helm-occur--initial-pos 0) (defvar helm-occur-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "C-c o") 'helm-occur-run-goto-line-ow) (define-key map (kbd "C-c C-o") 'helm-occur-run-goto-line-of) (define-key map (kbd "C-x C-s") 'helm-occur-run-save-buffer) (define-key map (kbd "C-s") 'helm-run-occur-grep-ag-buffer-directory) map) "Keymap used in occur source.") (defgroup helm-occur nil "Regexp related Applications and libraries for Helm." :group 'helm) (defcustom helm-occur-actions '(("Go to Line" . helm-occur-goto-line) ("Goto line other window (C-u vertically)" . helm-occur-goto-line-ow) ("Goto line new frame" . helm-occur-goto-line-of) ("Save buffer" . helm-occur-save-results)) "Actions for helm-occur." :type '(alist :key-type string :value-type function)) (defcustom helm-occur-use-ioccur-style-keys nil "Similar to `helm-grep-use-ioccur-style-keys' but for multi occur. Note that if you define this variable with `setq' your change will have no effect, use customize instead." :type 'boolean :set (lambda (var val) (set var val) (if val (progn (define-key helm-occur-map (kbd "") 'helm-occur-right) (define-key helm-occur-map (kbd "") 'helm-occur-run-default-action)) (define-key helm-occur-map (kbd "") nil) (define-key helm-occur-map (kbd "") nil)))) (defcustom helm-occur-always-search-in-current nil "Helm multi occur always search in current buffer when non--nil." :type 'boolean) (defcustom helm-occur-truncate-lines t "Truncate lines in occur buffer when non nil." :type 'boolean) (defcustom helm-occur-auto-update-on-resume nil "Allow auto updating helm-occur buffer when outdated. noask => Always update without asking nil => Don't update but signal buffer needs update never => Never update and do not signal buffer needs update Any other non--nil value update after confirmation." :type '(radio :tag "Allow auto updating helm-occur buffer when outdated." (const :tag "Always update without asking" noask) (const :tag "Never update and do not signal buffer needs update" never) (const :tag "Don't update but signal buffer needs update" nil) (const :tag "Update after confirmation" t))) (defcustom helm-occur-candidate-number-limit 99999 "Value of `helm-candidate-number-limit' for helm-occur." :type 'integer) (defcustom helm-occur-buffer-substring-fn-for-modes '((mu4e-headers-mode . buffer-substring)) "Function used to display buffer contents per major-mode. Use this to display lines with their text properties in helm-occur buffer. Can be one of `buffer-substring' or `buffer-substring-no-properties'. See `helm-occur-buffer-substring-default-mode' to setup this globally. Note that when using `buffer-substring' initialization will be slower." :type '(alist :key-type (symbol :tag "Mode") :value-type (radio (const :tag "With text properties" buffer-substring) (const :tag "Without text properties" buffer-substring-no-properties)))) (defcustom helm-occur-buffer-substring-default-mode 'buffer-substring-no-properties "Function used to display buffer contents in helm-occur buffer. Default mode for major modes not defined in `helm-occur-buffer-substring-fn-for-modes'. Can be one of `buffer-substring' or `buffer-substring-no-properties'. Note that when using `buffer-substring' initialization will be slower. If buffer-substring, all buffers with the modes not defined in helm-occur-buffer-substring-fn-for-modes will be displayed with colors and properties in the helm-occur buffer" :type '(radio (const :tag "With text properties" buffer-substring) (const :tag "Without text properties" buffer-substring-no-properties))) (defcustom helm-occur-keep-closest-position t "When non nil select closest candidate from point after update. This happen only in `helm-source-occur' which is always related to `current-buffer'." :type 'boolean) (defcustom helm-occur-ignore-diacritics nil "When non nil helm-occur will ignore diacritics in patterns." :type 'boolean) (defcustom helm-occur-match-shorthands nil "Transform pattern according to `read-symbol-shorthands' when non nil." :type 'boolean) (defface helm-moccur-buffer `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "DarkTurquoise" :underline t)) "Face used to highlight occur buffer names.") (defface helm-resume-need-update `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :background "red")) "Face used to flash occur buffer when it needs update.") (defun helm-occur--select-closest-candidate () ;; Prevent error with `with-helm-window' when switching to help. (unless (or (not (get-buffer-window helm-buffer 'visible)) (string-equal helm-pattern "")) (with-helm-window (let ((lst '()) (name (helm-get-attr 'name helm-source-occur)) closest beg end) (while-no-input (goto-char (point-min)) (if (string= name "Helm occur") (setq beg (point) end (point-max)) (helm-awhile (helm-get-next-header-pos) (when (string= name (buffer-substring-no-properties (pos-bol) (pos-eol))) (forward-line 1) (setq beg (point) end (or (helm-get-next-header-pos) (point-max))) (cl-return)))) (save-excursion (when (and beg end) (goto-char beg) (while (re-search-forward "^[0-9]+" end t) (push (string-to-number (match-string 0)) lst)) (setq closest (helm-closest-number-in-list helm-occur--initial-pos lst)))) (when (and closest (re-search-forward (format "^%s" closest) end t)) (helm-mark-current-line) (goto-char (overlay-start helm-selection-overlay)))))))) ;;;###autoload (defun helm-occur () "Preconfigured helm for searching lines matching pattern in `current-buffer'. When `helm-source-occur' is member of `helm-sources-using-default-as-input' which is the default, symbol at point is searched at startup. When a region is marked search only in this region by narrowing. To search in multiples buffers start from one of the commands listing buffers (i.e. a helm command using `helm-source-buffers-list' like `helm-mini') and use the multi occur buffers action. This is the helm implementation that collect lines matching pattern like vanilla Emacs `occur' but have nothing to do with it, the search engine beeing completely different and also much faster." (interactive) (setq helm-source-occur (car (helm-occur-build-sources (list (current-buffer)) "Helm occur"))) (helm-set-local-variable 'helm-occur--buffer-list (list (current-buffer)) 'helm-occur--buffer-tick (list (buffer-chars-modified-tick (current-buffer)))) (helm-set-attr 'header-name (lambda (_name) (format "HO [%s]" (buffer-name helm-current-buffer))) helm-source-occur) (when helm-occur-keep-closest-position (setq helm-occur--initial-pos (line-number-at-pos)) (add-hook 'helm-after-update-hook 'helm-occur--select-closest-candidate)) (save-restriction (let ((helm-sources-using-default-as-input (unless (> (buffer-size) 2000000) helm-sources-using-default-as-input)) def pos) (when (use-region-p) ;; When user mark defun with `mark-defun' with intention of ;; using helm-occur on this region, it is relevant to use the ;; thing-at-point located at previous position which have been ;; pushed to `mark-ring', if it's within the active region. (let ((beg (region-beginning)) (end (region-end)) (prev-pos (car mark-ring))) (when (and prev-pos (>= prev-pos beg) (< prev-pos end)) (setq def (save-excursion (goto-char (setq pos prev-pos)) (helm-aif (thing-at-point 'symbol) (regexp-quote it))))) (narrow-to-region beg end))) (unwind-protect (helm :sources 'helm-source-occur :buffer "*helm occur*" :history 'helm-occur-history :default (or def (helm-aif (thing-at-point 'symbol) (regexp-quote it))) :preselect (and (memq 'helm-source-occur helm-sources-using-default-as-input) (format "^%d:" (line-number-at-pos (or pos (point))))) :truncate-lines helm-occur-truncate-lines) (deactivate-mark t) (remove-hook 'helm-after-update-hook 'helm-occur--select-closest-candidate))))) ;;;###autoload (defun helm-occur-visible-buffers () "Run helm-occur on all visible buffers in frame." (interactive) (require 'helm-buffers) (if (or (one-window-p) (region-active-p)) (call-interactively #'helm-occur) (let ((buffers (helm-buffers-get-visible-buffers))) (helm-multi-occur-1 (mapcar 'get-buffer buffers))))) (defun helm-occur-transformer (candidates source) "Return CANDIDATES prefixed with line number." (cl-loop with buf = (helm-get-attr 'buffer-name source) for c in candidates for bfname = (buffer-file-name (get-buffer buf)) for disp-linum = (when (string-match helm-occur--search-buffer-regexp c) (let ((linum (match-string 1 c)) (disp (match-string 2 c))) (list linum (format "%s:%s" (propertize linum 'face 'helm-grep-lineno 'help-echo bfname 'helm-grep-fname bfname) disp)))) for linum = (car disp-linum) for disp = (cadr disp-linum) when (and disp (not (string= disp ""))) collect (cons disp (string-to-number linum)))) (defvar helm-occur--gshorthands nil) (defun helm-occur-symbol-shorthands-pattern-transformer (pattern buffer gshorthands) "Maybe transform PATTERN to its `read-symbol-shorthands' counterpart in BUFFER. GSHORTHANDS is the concatenation of all `read-symbol-shorthands' value found in all buffers i.e. `buffer-list'. When GSHORTHANDS is nil use PATTERN unmodified." (if gshorthands (let* ((lshorthands (buffer-local-value 'read-symbol-shorthands buffer)) (prefix (cl-loop for (k . v) in gshorthands if (string-match (concat "\\`" k) pattern) return k else if (string-match (concat "\\`" v) pattern) return v)) (lgstr (cdr (or (assoc prefix gshorthands) (rassoc prefix gshorthands))))) (if (and lgstr lshorthands) (concat (car (rassoc lgstr lshorthands)) (replace-regexp-in-string prefix "" pattern)) pattern)) pattern)) (defclass helm-moccur-class (helm-source-in-buffer) ((buffer-name :initarg :buffer-name :initform nil) (moccur-buffers :initarg :moccur-buffers :initform nil) (find-file-target :initform #'helm-occur-quit-an-find-file-fn))) (defun helm-occur-build-sources (buffers &optional source-name) "Build sources for `helm-occur' for each buffer in BUFFERS list." (require 'helm-grep) (setq helm-occur--gshorthands nil) (and helm-occur-match-shorthands (setq helm-occur--gshorthands (cl-loop for b in (buffer-list) for rss = (buffer-local-value 'read-symbol-shorthands b) when rss append rss))) (let (sources) (dolist (buf buffers) (let ((bname (buffer-name buf))) (push (helm-make-source (or source-name bname) 'helm-moccur-class :header-name (lambda (name) (format "HO [%s]" (if (string= name "Helm occur") bname name))) :buffer-name bname :match-part (lambda (candidate) ;; The regexp should match what is in candidate buffer, ;; not what is displayed in helm-buffer e.g. "12 foo" ;; and not "12:foo". (when (string-match helm-occur--search-buffer-regexp candidate) (match-string 2 candidate))) :diacritics helm-occur-ignore-diacritics :search (lambda (pattern) (when (string-match "\\`\\^\\(.*\\)" pattern) (setq pattern (concat "^[0-9]*\\s-" (match-string 1 pattern)))) (condition-case _err (re-search-forward pattern nil t) (invalid-regexp nil))) :pattern-transformer (lambda (pattern) (helm-occur-symbol-shorthands-pattern-transformer pattern buf helm-occur--gshorthands)) :init (lambda () (with-current-buffer buf (let* ((bsfn (or (cdr (assq major-mode helm-occur-buffer-substring-fn-for-modes)) helm-occur-buffer-substring-default-mode)) (contents (funcall bsfn (point-min) (point-max)))) (helm-set-attr 'get-line bsfn) (with-current-buffer (helm-candidate-buffer 'global) (insert contents) (goto-char (point-min)) (let ((linum 1)) (insert (format "%s " linum)) (while (re-search-forward "\n" nil t) (cl-incf linum) (insert (format "%s " linum)))))))) :filtered-candidate-transformer 'helm-occur-transformer :help-message 'helm-moccur-help-message :nomark t :migemo t ;; Needed for resume. :history 'helm-occur-history :candidate-number-limit helm-occur-candidate-number-limit :action (append helm-occur-actions (helm-make-actions (lambda () (when helm-grep-ag-command (format "%s grep buffer directory" (upcase (helm-grep--ag-command))))) 'helm-occur-grep-ag-buffer-directory)) :requires-pattern 2 :follow 1 :group 'helm-occur :keymap helm-occur-map :resume 'helm-occur-resume-fn :moccur-buffers buffers) sources))) (nreverse sources))) (defun helm-multi-occur-1 (buffers &optional input default) "Run `helm-occur' on a list of buffers. Each buffer's result is displayed in a separated source. Arg INPUT if specified will be inserted as initial input in minibuffer. Arg DEFAULT if specified will be inserted in minibuffer with M-n. Arg INPUT takes precedence on DEFAULT if both are specified. If `helm-source-moccur' is member of `helm-sources-using-default-as-input' helm-occur will start immediately with DEFAULT as INPUT. Always prefer using DEFAULT instead of INPUT, they have the same effect but DEFAULT keep the minibuffer empty, allowing the user to write immediately without having to delete its contents before." (let* ((curbuf (current-buffer)) (bufs (if helm-occur-always-search-in-current (cons curbuf (remove curbuf buffers)) buffers)) (helm-sources-using-default-as-input (unless (cl-loop with total_size = 0 for b in bufs do (setq total_size (buffer-size b)) finally return (> total_size 2000000)) helm-sources-using-default-as-input)) (sources (helm-occur-build-sources bufs (and (eql curbuf (car bufs)) (not (cdr bufs)) "Helm occur"))) (helm-maybe-use-default-as-input (not (null (memq 'helm-source-moccur helm-sources-using-default-as-input))))) (helm-set-local-variable 'helm-occur--buffer-list bufs 'helm-occur--buffer-tick (cl-loop for b in bufs collect (buffer-chars-modified-tick (get-buffer b)))) (when (and helm-occur-always-search-in-current helm-occur-keep-closest-position) (setq helm-source-occur (cl-loop for s in sources when (eql helm-current-buffer (get-buffer (helm-get-attr 'buffer-name s))) return s)) (setq helm-occur--initial-pos (line-number-at-pos)) (add-hook 'helm-after-update-hook 'helm-occur--select-closest-candidate)) (unwind-protect (helm :sources sources :buffer "*helm moccur*" :history 'helm-occur-history :default (or default (helm-aif (thing-at-point 'symbol) (regexp-quote it))) :input input :truncate-lines helm-occur-truncate-lines) (remove-hook 'helm-after-update-hook 'helm-occur--select-closest-candidate)))) ;;; Actions ;; (cl-defun helm-occur-action (lineno &optional (method (quote buffer))) "Jump to line number LINENO with METHOD. METHOD can be one of buffer, buffer-other-window, buffer-other-frame." (require 'helm-grep) (let ((buf (if (eq major-mode 'helm-occur-mode) (get-text-property (point) 'buffer-name) (helm-get-attr 'buffer-name))) (split-pat (helm-mm-split-pattern helm-input))) (cl-case method (buffer (switch-to-buffer buf)) (buffer-other-window (helm-window-show-buffers (list buf) t)) (buffer-other-frame (switch-to-buffer-other-frame buf))) (with-current-buffer buf (helm-goto-line lineno) ;; Move point to the nearest matching regexp from bol. (cl-loop for str in split-pat for reg = (helm-occur-symbol-shorthands-pattern-transformer str (get-buffer buf) helm-occur--gshorthands) when (save-excursion (condition-case _err (if helm-migemo-mode (helm-mm-migemo-forward reg (pos-eol) t) (re-search-forward reg (pos-eol) t)) (invalid-regexp nil))) collect (match-beginning 0) into pos-ls finally (when pos-ls (goto-char (apply #'min pos-ls))))))) (defun helm-occur-goto-line (candidate) "From multi occur, switch to buffer and CANDIDATE line." (helm-occur-action candidate 'buffer)) (defun helm-occur-goto-line-ow (candidate) "Go to CANDIDATE line in other window. Same as `helm-occur-goto-line' but go in other window." (helm-occur-action candidate 'buffer-other-window)) (defun helm-occur-goto-line-of (candidate) "Go to CANDIDATE line in new frame. Same as `helm-occur-goto-line' but go in new frame." (helm-occur-action candidate 'buffer-other-frame)) (helm-make-command-from-action helm-occur-run-goto-line-ow "Run goto line other window action from `helm-occur'." 'helm-occur-goto-line-ow) (helm-make-command-from-action helm-occur-run-goto-line-of "Run goto line new frame action from `helm-occur'." 'helm-occur-goto-line-of) (helm-make-command-from-action helm-occur-run-default-action "Goto matching line from helm-occur buffer." 'helm-occur-goto-line) (helm-make-command-from-action helm-occur-run-save-buffer "Run moccur save results action from `helm-moccur'." 'helm-occur-save-results) (defun helm-occur-right () "`helm-occur' action for right arrow. This is used when `helm-occur-use-ioccur-style-keys' is enabled. If follow is enabled (default) go to next source, otherwise execute persistent action." (interactive) (if (helm-aand (helm-get-attr 'follow) (> it 0)) (helm-next-source) (helm-execute-persistent-action))) (put 'helm-occur-right 'helm-only t) (defun helm-occur-quit-an-find-file-fn (source) (let* ((sel (helm-get-selection nil nil source)) (occur-fname (helm-aand (numberp sel) (helm-get-attr 'buffer-name) (buffer-file-name (get-buffer it))))) (when (and occur-fname (file-exists-p occur-fname)) (expand-file-name occur-fname)))) (defun helm-occur-grep-ag-buffer-directory (_candidate) "Start helm-grep-ag in the `default-directory' of currently searched buffer." (let* ((src (with-helm-buffer ;; Search from current source or fallback to the first ;; source if helm-buffer is empty, if only one source ;; we are right in either cases. (or (helm-get-current-source) (car helm-sources)))) (buf (helm-get-attr 'buffer-name src)) (directory (with-current-buffer buf default-directory)) (input helm-pattern)) (helm-grep-ag-1 directory nil input))) (helm-make-command-from-action helm-run-occur-grep-ag-buffer-directory "Ag grep buffer directory." 'helm-occur-grep-ag-buffer-directory) ;;; helm-occur-mode ;; ;; (defvar helm-occur-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "RET") 'helm-occur-mode-goto-line) (define-key map (kbd "C-o") 'helm-occur-mode-goto-line-ow) (define-key map (kbd "") 'helm-occur-mode-goto-line-ow-forward) (define-key map (kbd "") 'helm-occur-mode-goto-line-ow-backward) (define-key map (kbd "") 'helm-gm-next-file) (define-key map (kbd "") 'helm-gm-precedent-file) (define-key map (kbd "M-n") 'helm-occur-mode-goto-line-ow-forward) (define-key map (kbd "M-p") 'helm-occur-mode-goto-line-ow-backward) (define-key map (kbd "M-N") 'helm-gm-next-file) (define-key map (kbd "M-P") 'helm-gm-precedent-file) (define-key map (kbd "C-c b") 'helm-occur-mode-resume-session) map)) (defun helm-occur-mode-goto-line () (interactive) (setq next-error-last-buffer (current-buffer)) (setq-local helm-current-error (point-marker)) (helm-aif (get-text-property (point) 'helm-realvalue) (progn (helm-occur-goto-line it) (helm-match-line-cleanup-pulse)))) (defun helm-occur-mode-goto-line-ow () (interactive) (setq next-error-last-buffer (current-buffer)) (setq-local helm-current-error (point-marker)) (helm-aif (get-text-property (point) 'helm-realvalue) (progn (helm-occur-goto-line-ow it) (helm-match-line-cleanup-pulse)))) (defun helm-occur-mode-goto-line-ow-forward-1 (arg) (condition-case nil (progn (when (or (eq last-command 'helm-occur-mode-goto-line-ow-forward) (eq last-command 'helm-occur-mode-goto-line-ow-backward)) (forward-line arg)) (save-selected-window (helm-occur-mode-goto-line-ow) (recenter))) (error nil))) (defun helm-occur-mode-goto-line-ow-forward (arg) (interactive "p") (helm-occur-mode-goto-line-ow-forward-1 arg)) (defun helm-occur-mode-goto-line-ow-backward (arg) (interactive "p") (helm-occur-mode-goto-line-ow-forward-1 (- arg))) (defun helm-occur-save-results (_candidate) "Save helm moccur results in a `helm-moccur-mode' buffer." (let ((buf "*hmoccur*") new-buf) (when (get-buffer buf) (setq new-buf (helm-read-string "OccurBufferName: " buf)) (cl-loop for b in (helm-buffer-list) when (and (string= new-buf b) (not (y-or-n-p (format "Buffer `%s' already exists overwrite? " new-buf)))) do (setq new-buf (helm-read-string "OccurBufferName: " "*hmoccur "))) (setq buf new-buf)) (with-current-buffer (get-buffer-create buf) (kill-all-local-variables) (setq buffer-read-only t) (buffer-disable-undo) (let ((inhibit-read-only t) (map (make-sparse-keymap)) buf-name) (erase-buffer) (insert "-*- mode: helm-occur -*-\n\n" (format "Occur Results for `%s':\n\n" helm-input)) (save-excursion (insert (with-current-buffer helm-buffer (goto-char (point-min)) (forward-line 1) (buffer-substring (point) (point-max))))) (save-excursion (forward-line -2) (while (not (eobp)) (if (helm-pos-header-line-p) (let ((beg (pos-bol)) (end (pos-eol))) (set-text-properties beg (1+ end) nil) (delete-region (1- beg) end)) (helm-aif (setq buf-name (assoc-default 'buffer-name (get-text-property (point) 'helm-cur-source))) (progn (insert (propertize (concat it ":") 'face 'helm-moccur-buffer 'helm-realvalue (get-text-property (point) 'helm-realvalue))) (add-text-properties (pos-bol) (pos-eol) `(buffer-name ,buf-name)) (add-text-properties (pos-bol) (pos-eol) `(keymap ,map help-echo ,(concat (buffer-file-name (get-buffer buf-name)) "\nmouse-1: set point\nmouse-2: jump to selection") mouse-face highlight invisible nil)) (define-key map [mouse-1] 'mouse-set-point) (define-key map [mouse-2] 'helm-occur-mode-mouse-goto-line) (define-key map [mouse-3] 'ignore)))) (forward-line 1)))) (buffer-enable-undo) (helm-occur-mode)) (pop-to-buffer buf) (setq next-error-last-buffer (get-buffer buf)) (message "Helm occur Results saved in `%s' buffer" buf))) (defun helm-occur-mode-mouse-goto-line (event) (interactive "e") (let* ((window (posn-window (event-end event))) (pos (posn-point (event-end event)))) (with-selected-window window (when (eq major-mode 'helm-occur-mode) (goto-char pos) (helm-occur-mode-goto-line))))) (put 'helm-moccur-mode-mouse-goto-line 'helm-only t) (defun helm-occur-mode-resume-session () (interactive) (cl-assert (eq major-mode 'helm-occur-mode) nil "Helm command called in wrong context") (helm-multi-occur-1 helm-occur--buffer-list helm-occur-mode--last-pattern)) (defun helm-occur-buffer-substring-with-linums () "Return current-buffer contents as a string with all lines numbered. The property \\='buffer-name is added to the whole string." (let ((bufstr (buffer-substring-no-properties (point-min) (point-max))) (bufname (buffer-name))) (with-temp-buffer (save-excursion (insert bufstr)) (let ((linum 1)) (insert (format "%s " linum)) (while (re-search-forward "\n" nil t) (cl-incf linum) (insert (format "%s " linum))) (add-text-properties (point-min) (point-max) `(buffer-name ,bufname))) (buffer-string)))) (defun helm-occur-mode--revert-buffer-function (&optional _ignore-auto _noconfirm) "The `revert-buffer-function' for `helm-occur-mode'." (goto-char (point-min)) (let (pattern) (when (re-search-forward "^Occur Results for `\\(.*\\)'" nil t) (setq pattern (match-string 1)) (forward-line 0) (when (re-search-forward "^$" nil t) (forward-line 1)) (let ((inhibit-read-only t) (buffer (current-buffer)) (buflst helm-occur--buffer-list)) (delete-region (point) (point-max)) (message "Reverting buffer...") (save-excursion (with-temp-buffer (insert "\n" (cl-loop for buf in buflst for bufstr = (or (and (buffer-live-p (get-buffer buf)) (with-current-buffer buf (helm-occur-buffer-substring-with-linums))) "") concat bufstr) "\n") (goto-char (point-min)) (cl-loop with linum with mpart ;; Bind helm-pattern used by `helm-grep-split-line'. with helm-pattern = pattern while (helm-mm-search pattern) ; point is at eol. ;; Calculate line number (linum) and extract real ;; part of line (mpart). do (when (save-excursion ;; `helm-mm-search' puts point at eol. (forward-line 0) (re-search-forward "^\\([0-9]*\\)\\s-\\{1\\}\\(.*\\)$" (pos-eol) t)) (setq linum (string-to-number (match-string 1)) mpart (match-string 2))) ;; Match part after line number. when (and mpart (helm-mm-match mpart pattern)) for line = (format "%s:%d:%s" (get-text-property (point) 'buffer-name) linum mpart) when line do (with-current-buffer buffer (insert (propertize (car (helm-occur-filter-one-by-one line)) 'helm-realvalue linum) "\n")))) (when (fboundp 'wgrep-cleanup-overlays) (wgrep-cleanup-overlays (point-min) (point-max))) (message "Reverting buffer done") (when executing-kbd-macro (sit-for 1))))))) (defun helm-occur-filter-one-by-one (candidate) "`filter-one-by-one' function for `helm-source-moccur'." (require 'helm-grep) (let* ((split (helm-grep-split-line candidate)) (buf (car split)) (lineno (nth 1 split)) (str (nth 2 split)) (bfname (buffer-file-name (get-buffer buf)))) (cons (concat (propertize buf 'face 'helm-moccur-buffer 'help-echo bfname 'helm-grep-fname bfname 'buffer-name buf) ":" (propertize lineno 'face 'helm-grep-lineno) ":" (helm-grep-highlight-match str)) candidate))) (define-derived-mode helm-occur-mode special-mode "helm-moccur" "Major mode to provide actions in helm moccur saved buffer. Special commands: \\{helm-occur-mode-map}" (set (make-local-variable 'helm-occur--buffer-list) (with-helm-buffer helm-occur--buffer-list)) (set (make-local-variable 'revert-buffer-function) #'helm-occur-mode--revert-buffer-function) (set (make-local-variable 'helm-occur-mode--last-pattern) helm-input) (set (make-local-variable 'next-error-function) #'helm-occur-next-error) (set (make-local-variable 'helm-current-error) nil)) (put 'helm-moccur-mode 'helm-only t) (defun helm-occur-next-error (&optional argp reset) "Goto ARGP position from a `helm-occur-mode' buffer. RESET non-nil means rewind to the first match. This is the `next-error-function' for `helm-occur-mode'." (interactive "p") (goto-char (cond (reset (point-min)) ((and (< argp 0) helm-current-error) (line-beginning-position)) ((and (> argp 0) helm-current-error) (line-end-position)) ((point)))) (let ((fun (if (> argp 0) #'next-single-property-change #'previous-single-property-change))) (helm-aif (funcall fun (point) 'buffer-name) (progn (goto-char it) (forward-line 0) ;; `helm-current-error' is set in ;; `helm-occur-mode-goto-line'. (helm-occur-mode-goto-line)) (user-error "No more matches")))) ;;; Resume ;; (defun helm-occur-resume-fn () (with-helm-buffer (let (new-tick-ls buffer-is-modified) (set (make-local-variable 'helm-occur--buffer-list) (cl-loop for b in helm-occur--buffer-list when (buffer-live-p (get-buffer b)) collect b)) (setq buffer-is-modified (/= (length helm-occur--buffer-list) (length (helm-get-attr 'moccur-buffers)))) (helm-set-attr 'moccur-buffers helm-occur--buffer-list) (setq new-tick-ls (cl-loop for b in helm-occur--buffer-list collect (buffer-chars-modified-tick (get-buffer b)))) (when buffer-is-modified (setq helm-occur--buffer-tick new-tick-ls)) (cl-assert (> (length helm-occur--buffer-list) 0) nil "helm-resume error: helm-(m)occur buffer list is empty") (unless (eq helm-occur-auto-update-on-resume 'never) (when (or buffer-is-modified (cl-loop for b in helm-occur--buffer-list for new-tick = (buffer-chars-modified-tick (get-buffer b)) for tick in helm-occur--buffer-tick thereis (/= tick new-tick))) (helm-aif helm-occur-auto-update-on-resume (when (or (eq it 'noask) (y-or-n-p "Helm (m)occur Buffer outdated, update? ")) (run-with-idle-timer 0.1 nil (lambda () (with-helm-buffer (helm-force-update) (message "Helm (m)occur Buffer have been udated") (sit-for 1) (message nil)))) (unless buffer-is-modified (setq helm-occur--buffer-tick new-tick-ls))) (run-with-idle-timer 0.1 nil (lambda () (with-helm-buffer (let ((ov (make-overlay (save-excursion (goto-char (point-min)) (forward-line 1) (point)) (point-max)))) (overlay-put ov 'face 'helm-resume-need-update) (sit-for 0) (delete-overlay ov) (message "[Helm occur Buffer outdated (C-c C-u to update)]"))))) (unless buffer-is-modified (with-helm-after-update-hook (setq helm-occur--buffer-tick new-tick-ls) (message "Helm (m)occur Buffer have been udated"))))))))) ;;; Helm occur from isearch ;; ;;;###autoload (defun helm-occur-from-isearch () "Invoke `helm-occur' from isearch. To use this bind it to a key in `isearch-mode-map'." (interactive) (let ((input (if isearch-regexp isearch-string (regexp-quote isearch-string))) (bufs (list (current-buffer))) ;; Use `helm-occur-always-search-in-current' as a flag for ;; `helm-occur--select-closest-candidate'. (helm-occur-always-search-in-current t)) (isearch-exit) (helm-multi-occur-1 bufs input))) ;;;###autoload (defun helm-multi-occur-from-isearch () "Invoke `helm-multi-occur' from isearch. With a prefix arg, reverse the behavior of `helm-moccur-always-search-in-current'. The prefix arg can be set before calling `helm-multi-occur-from-isearch' or during the buffer selection. To use this bind it to a key in `isearch-mode-map'." (interactive) (let (buf-list helm-moccur-always-search-in-current (input (if isearch-regexp isearch-string (regexp-quote isearch-string)))) (isearch-exit) (setq buf-list (mapcar 'get-buffer (helm-comp-read "Buffers: " (helm-buffer-list) :name "Occur in buffer(s)" :marked-candidates t))) (setq helm-moccur-always-search-in-current (if (or current-prefix-arg helm-current-prefix-arg) (not helm-moccur-always-search-in-current) helm-moccur-always-search-in-current)) (helm-multi-occur-1 buf-list input))) (provide 'helm-occur) ;;; helm-occur.el ends here helm-4.0.3/helm-packages.el000066400000000000000000000504271501106761700155020ustar00rootroot00000000000000;;; helm-packages.el --- helm interface to manage packages -*- lexical-binding: t; -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'package) (require 'finder) (require 'helm-utils) ; For with-helm-display-marked-candidates. (require 'async-package) (declare-function dired-async-mode-line-message "ext:dired-async.el") (defgroup helm-packages nil "Helm interface for package.el." :group 'helm) (defclass helm-packages-class (helm-source-in-buffer) ((coerce :initform #'helm-symbolify) (find-file-target :initform #'helm-packages-quit-an-find-file) (filtered-candidate-transformer :initform `(helm-packages-transformer ,(lambda (candidates _source) (sort candidates #'helm-generic-sort-fn)))) (update :initform #'helm-packages--refresh-contents)) "A class to define `helm-packages' sources.") (defcustom helm-packages-async t "Install packages async when non nil." :type 'boolean) (defcustom helm-package-install-upgrade-built-in (bound-and-true-p package-install-upgrade-built-in) "Allow upgrading builtin packages when non nil." :type 'boolean) (defcustom helm-packages-isolate-fn (if (fboundp 'package-isolate) #'package-isolate #'helm-packages-isolate-1) "The function to isolate package. `package-isolate' is available only in emacs-30+." :type 'function) ;;; Actions ;; ;; (defun helm-packages-upgrade (_candidate) "Helm action for upgrading marked packages." (let ((mkd (helm-marked-candidates)) (error-file (expand-file-name "helm-packages-upgrade-error.txt" temporary-file-directory))) (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar #'symbol-name mkd) (when (y-or-n-p (format "Upgrade %s packages? " (length mkd))) (if helm-packages-async (async-package-do-action 'upgrade mkd error-file) (mapc #'package-upgrade mkd)))))) (defun helm-packages-describe (candidate) "Helm action for describing package CANDIDATE." (describe-package candidate)) (defun helm-packages-visit-homepage (candidate) "Helm action for visiting package CANDIDATE home page." (let* ((id (package-get-descriptor candidate)) (name (package-desc-name id)) (extras (package-desc-extras id)) (url (and (listp extras) (cdr-safe (assoc :url extras))))) (if (stringp url) (browse-url url) (message "Package %s has no homepage" (propertize (symbol-name name) 'face 'font-lock-keyword-face))))) (defun helm-packages-package-reinstall (_candidate) "Helm action for reinstalling marked packages." (let ((mkd (helm-marked-candidates)) (error-file (expand-file-name "helm-packages-reinstall-error.txt" temporary-file-directory))) (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar #'symbol-name mkd) (when (y-or-n-p (format "Reinstall %s packages? " (length mkd))) (if helm-packages-async (async-package-do-action 'reinstall mkd error-file) (mapc #'package-reinstall mkd)))))) (defun helm-packages-delete-1 (packages &optional force) "Run `package-delete' on PACKAGES. If FORCE is non nil force deleting packages." (mapc (lambda (x) (package-delete (package-get-descriptor x) force)) packages)) (defun helm-packages-uninstall (_candidate) "Helm action for uninstalling marked packages. Unlike `helm-packages-delete' this will refuse to delete packages when they are needed by others packages as dependencies." (let ((mkd (helm-marked-candidates))) (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar #'symbol-name mkd) (when (y-or-n-p (format "Uninstall %s packages? " (length mkd))) (helm-packages-delete-1 mkd))))) (defun helm-packages-delete (_candidate) "Helm action for deleting marked packages. Unlike `helm-packages-uninstall' this delete packages even when they are needed as dependencies." (let ((mkd (helm-marked-candidates))) (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar #'symbol-name mkd) (when (y-or-n-p (format "Delete %s packages? " (length mkd))) (helm-packages-delete-1 mkd 'force))))) (defun helm-packages-recompile (_candidate) "Helm action for recompiling marked packages." (let ((mkd (helm-marked-candidates))) (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar #'symbol-name mkd) (when (y-or-n-p (format "Recompile %s packages? " (length mkd))) (mapc #'package-recompile mkd))))) (defun helm-packages-install--sync (packages) (condition-case err (mapc #'package-install packages) (error "%S:\n Please refresh package list before installing" err))) (defun helm-packages-install (_candidate) "Helm action for installing marked packages." (let ((mkd (helm-marked-candidates)) (error-file (expand-file-name "helm-packages-install-error.txt" temporary-file-directory))) (with-helm-display-marked-candidates helm-marked-buffer-name (mapcar #'symbol-name mkd) (when (y-or-n-p (format "Install %s packages? " (length mkd))) (if helm-packages-async (async-package-do-action 'install mkd error-file) (helm-packages-install--sync mkd)))))) (defun helm-packages--get-deps (pkg) "Recursively find PKG dependencies." (let ((desc (cadr (assq pkg package-archive-contents)))) (when desc (cl-loop for req in (package-desc-reqs desc) ; (foo (1 2 3)) for sym = (car req) nconc (cons sym (helm-packages--get-deps sym)) into pkgs finally return (helm-fast-remove-dups pkgs))))) (defun helm-packages-isolate-1 (packages &optional _ignore) "Start an Emacs with only PACKAGES loaded. Arg PACKAGES is a list of strings." (let* ((name (concat "package-isolate-" (mapconcat #'identity packages "_"))) (deps (cl-loop for p in packages for sym = (intern p) nconc (helm-packages--get-deps sym)))) (apply #'start-process name nil (list (expand-file-name invocation-name invocation-directory) "-Q" "--debug-init" (format "--eval=%S" `(progn (require 'package) (setq package-load-list ',(append (mapcar (lambda (p) (list (intern p) t)) packages) (mapcar (lambda (p) (list p t)) deps)) package-user-dir ,package-user-dir package-directory-list ',package-directory-list) (package-initialize))))))) (defun helm-packages-isolate (_candidate) "Start a new Emacs with only marked packages loaded." (let* ((mkd (helm-marked-candidates)) (pkg-names (mapcar #'symbol-name mkd))) (with-helm-display-marked-candidates helm-marked-buffer-name pkg-names (when (y-or-n-p "Start a new Emacs with only package(s)? ") (funcall helm-packages-isolate-fn pkg-names helm-current-prefix-arg))))) (defun helm-packages-quit-an-find-file (source) "`find-file-target' function for `helm-packages'." (let* ((sel (helm-get-selection nil nil source)) (pkg (package-get-descriptor (intern sel)))) (if (and pkg (package-installed-p pkg)) (expand-file-name (package-desc-dir pkg)) package-user-dir))) ;;; Transformers ;; ;; (defun helm-packages-transformer (candidates _source) "Transformer function for `helm-packages'." (cl-loop with lgst_arch = (cl-loop for (arch . _) in package-archives maximize (length arch)) for c in candidates for sym = (intern-soft c) for archive = (assq sym package-archive-contents) for id = (package-get-descriptor sym) for provider = (if archive (package-desc-archive (cadr archive)) (and (assq sym package--builtins) "emacs")) for status = (if id (package-desc-status id) (and (assq sym package--builtins) "Built-in")) for version = (if id (mapconcat #'number-to-string (package-desc-version id) ".") (or (helm-aand (assq sym package--builtins) (aref (cdr it) 0) (package-version-join it)) "---")) for description = (if id (package-desc-summary id) (helm-aand (assq sym package--builtins) (aref (cdr it) 2))) for disp = (format "%s%s%s%s%s%s%s%s%s" ;; Package name. (propertize c 'face (helm-acase status ("dependency" 'font-lock-type-face) ("disabled" 'default) (t 'font-lock-keyword-face)) 'match-part c) ;; Separator. (helm-make-separator c) ;; Package status. (propertize (or status "") 'face (helm-acase status ("dependency" 'bold-italic) ("disabled" 'font-lock-property-name-face) (t 'default))) ;; Separator. (helm-make-separator status 10) ;; Package provider. (or provider "") ;; Separator. (helm-make-separator provider lgst_arch) ;; Package version. (or version "") ;; Separator. (helm-make-separator version 20) ;; Package description. (if description (propertize description 'face 'font-lock-warning-face) "")) collect (cons disp c))) (defun helm-packages-transformer-1 (candidates _source) "Transformer function for `helm-packages' upgrade and delete sources." (cl-loop for c in candidates collect (cons (propertize c 'face 'font-lock-keyword-face) c))) (defvar helm-packages--updated nil) (defun helm-packages--refresh-contents () (unless helm-packages--updated (package-refresh-contents)) (helm-set-local-variable 'helm-packages--updated t)) (defun helm-finder--list-matches (key) (let* ((id (intern key)) (built-in (gethash id finder-keywords-hash)) (exts (cl-loop for p in package-archive-contents for sym = (car p) when (package--has-keyword-p (package-get-descriptor sym) (list key)) collect sym))) (unless (or exts built-in) (error "No packages matching key `%s'" key)) (nconc exts built-in))) (defun helm-finder-packages-from-keyword (candidate) (if (string-match "\\.el$" candidate) (finder-commentary candidate) (helm :sources (helm-make-source "packages" 'helm-packages-class :header-name (lambda (name) (format "%s (%s)" name candidate)) :init (lambda () (helm-init-candidates-in-buffer 'global (helm-fast-remove-dups (helm-finder--list-matches candidate)))) :filtered-candidate-transformer `(helm-packages-transformer ,(lambda (candidates _source) (sort candidates #'helm-generic-sort-fn))) :action-transformer (lambda (actions candidate) (if (package-installed-p candidate) actions (append actions '(("Install packages(s)" . helm-packages-install))))) :action '(("Describe package" . helm-packages-describe) ("Visit homepage" . helm-packages-visit-homepage))) :buffer "*helm finder results*"))) (defun helm-package--upgradeable-packages (&optional include-builtins) ;; Initialize the package system to get the list of package ;; symbols for completion. (package--archives-initialize) (let ((pkgs (if include-builtins (append package-alist (cl-loop for (sym . vec) in package--builtins when (not (assq sym package-alist)) nconc (list (list sym (package--from-builtin (cons sym vec)))))) package-alist))) (cl-loop for (sym desc) in pkgs for pkg = (assq sym package-archive-contents) for cversion = (and pkg (package-desc-version desc)) for available = (and pkg (not (package-disabled-p sym cversion)) pkg) ;; Exclude packages installed with package-vc (issue#2692). when (and available (or (and include-builtins (not cversion)) (and cversion (version-list-< cversion (package-desc-version (cadr available)))))) collect sym))) ;;;###autoload (defun helm-packages (&optional arg) "Helm interface to manage packages. With a prefix arg ARG refresh package list. When installing or upgrading ensure to refresh the package list to avoid errors with outdated packages no more availables." (interactive "P") (package-initialize) (when arg (helm-packages--refresh-contents)) (let ((upgrades (helm-package--upgradeable-packages helm-package-install-upgrade-built-in)) (removables (package--removable-packages))) (helm :sources (list (helm-make-source "Availables for upgrade" 'helm-packages-class :init (lambda () (helm-init-candidates-in-buffer 'global upgrades)) :filtered-candidate-transformer #'helm-packages-transformer-1 :action '(("Upgrade package(s)" . helm-packages-upgrade))) (helm-make-source "Packages to delete" 'helm-packages-class :init (lambda () (helm-init-candidates-in-buffer 'global removables)) :filtered-candidate-transformer #'helm-packages-transformer-1 :action '(("Delete package(s)" . helm-packages-delete))) (helm-make-source "Installed packages" 'helm-packages-class :init (lambda () (helm-init-candidates-in-buffer 'global (mapcar #'car package-alist))) :action '(("Describe package" . helm-packages-describe) ("Visit homepage" . helm-packages-visit-homepage) ("Reinstall package(s)" . helm-packages-package-reinstall) ("Recompile package(s)" . helm-packages-recompile) ("Uninstall package(s)" . helm-packages-uninstall) ("Isolate package(s)" . helm-packages-isolate))) (helm-make-source "Available external packages" 'helm-packages-class :data (cl-loop for p in package-archive-contents for sym = (car p) for id = (package-get-descriptor sym) for status = (package-desc-status id) unless (or (and id (member status '("installed" "dependency" "source"))) (and id (assoc sym package--builtins))) nconc (list (car p))) :action '(("Describe package" . helm-packages-describe) ("Visit homepage" . helm-packages-visit-homepage) ("Install packages(s)" . helm-packages-install))) (helm-make-source "Available built-in packages" 'helm-packages-class :data (cl-loop for p in package--builtins ;; Show only builtins that are available as ;; well on (m)elpa. Other builtins don't ;; have a package-descriptor, the format is ;; (sym . [version reqs summary]). when (package-desc-p (package-get-descriptor (car p))) collect (car p)) :action '(("Describe package" . helm-packages-describe) ("Visit homepage" . helm-packages-visit-homepage) ("Install packages(s)" . helm-packages-install)))) :buffer "*helm packages*"))) ;;;###autoload (defun helm-finder (&optional arg) "Helm interface to find packages by keywords with `finder'. To have more actions on packages, use `helm-packages'." (interactive "P") (when arg (package-refresh-contents)) (package-initialize) ; needed to feed package-archive-contents. (helm :sources (helm-build-in-buffer-source "helm finder" :data (cl-loop for p in package-archive-contents for sym = (car p) for desc = (package-get-descriptor sym) nconc (copy-sequence (package-desc--keywords desc)) into keywords finally return (helm-fast-remove-dups keywords :test 'equal)) :filtered-candidate-transformer (list (lambda (candidates _source) (cl-loop for cand in candidates for desc = (or (assoc-default (intern-soft cand) finder-known-keywords) cand) for sep = (helm-make-separator cand) for disp = (helm-aand (propertize desc 'face 'font-lock-warning-face) (propertize " " 'display (concat sep it)) (concat cand it)) collect (cons disp cand))) (lambda (candidates _source) (sort candidates #'helm-generic-sort-fn))) :action (helm-make-actions "Packages from keyword" 'helm-finder-packages-from-keyword) :persistent-action 'ignore :persistent-help "Do nothing") :buffer "*helm finder*")) (provide 'helm-packages) ;;; helm-packages.el ends here helm-4.0.3/helm-regexp.el000066400000000000000000000105201501106761700152040ustar00rootroot00000000000000;;; helm-regexp.el --- In buffer regexp searching and replacement for helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'helm-utils) (declare-function helm-mm-split-pattern "helm-multi-match") (defgroup helm-regexp nil "Regexp related Applications and libraries for Helm." :group 'helm) ;; History vars (defvar helm-build-regexp-history nil) (defun helm-query-replace-regexp (_candidate) "Query replace regexp from `helm-regexp'. With a prefix arg replace only matches surrounded by word boundaries, i.e. don't replace inside a word, regexp is surrounded with \\bregexp\\b." (let ((regexp helm-input)) (apply 'query-replace-regexp (helm-query-replace-args regexp)))) (defun helm-kill-regexp-as-sexp (_candidate) "Kill regexp in a format usable in lisp code." (helm-regexp-kill-new (prin1-to-string helm-input))) (defun helm-kill-regexp (_candidate) "Kill regexp as it is in `helm-pattern'." (helm-regexp-kill-new helm-input)) (defun helm-query-replace-args (regexp) "Create arguments of `query-replace-regexp' action in `helm-regexp'." (let ((region-only (helm-region-active-p))) (list regexp (query-replace-read-to regexp (format "Query replace %sregexp %s" (if helm-current-prefix-arg "word " "") (if region-only "in region " "")) t) helm-current-prefix-arg (when region-only (region-beginning)) (when region-only (region-end))))) (defvar helm-source-regexp (helm-build-in-buffer-source "Regexp Builder" :init (lambda () (helm-init-candidates-in-buffer 'global (with-temp-buffer (insert-buffer-substring helm-current-buffer) (buffer-string)))) :get-line #'helm-regexp-get-line :persistent-action #'helm-regexp-persistent-action :persistent-help "Show this line" :multiline t :multimatch nil :requires-pattern 2 :group 'helm-regexp :mode-line "Press TAB to select action." :action '(("Kill Regexp as sexp" . helm-kill-regexp-as-sexp) ("Query Replace Regexp (C-u Not inside word.)" . helm-query-replace-regexp) ("Kill Regexp" . helm-kill-regexp)))) (defun helm-regexp-get-line (s e) (let ((matches (match-data)) (line (buffer-substring s e))) (propertize (cl-loop with ln = (format "%5d: %s" (1- (line-number-at-pos s)) line) for i from 0 to (1- (/ (length matches) 2)) if (match-string i) concat (format "\n%s%s'%s'" (make-string 10 ? ) (format "Group %d: " i) it) into ln1 finally return (concat ln ln1)) 'helm-realvalue s))) (defun helm-regexp-persistent-action (pt) (helm-goto-char pt) (helm-highlight-current-line)) (defun helm-regexp-kill-new (input) (kill-new (substring-no-properties input)) (message "Killed: %s" input)) ;;; Predefined commands ;; ;; ;;;###autoload (defun helm-regexp () "Preconfigured helm to build regexps. `query-replace-regexp' can be run from there against found regexp." (interactive) (save-restriction (when (and (helm-region-active-p) ;; Don't narrow to region if buffer is already narrowed. (not (helm-current-buffer-narrowed-p (current-buffer)))) (narrow-to-region (region-beginning) (region-end))) (helm :sources helm-source-regexp :buffer "*helm regexp*" :prompt "Regexp: " :history 'helm-build-regexp-history))) (provide 'helm-regexp) ;;; helm-regexp.el ends here helm-4.0.3/helm-ring.el000066400000000000000000000627141501106761700146650ustar00rootroot00000000000000;;; helm-ring.el --- kill-ring, mark-ring, and register browsers for helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-utils) (require 'helm-help) (require 'helm-elisp) (declare-function undo-tree-restore-state-from-register "ext:undo-tree.el" (register)) (declare-function kmacro--keys "kmacro.el") (declare-function frameset-register-p "frameset") (defgroup helm-ring nil "Ring related Applications and libraries for Helm." :group 'helm) (defcustom helm-kill-ring-threshold 3 "Minimum length of a candidate to be listed by `helm-source-kill-ring'." :type 'integer :group 'helm-ring) (defcustom helm-kill-ring-max-offset 400 "Max number of chars displayed per candidate in kill-ring browser. When `t', don't truncate candidate, show all. By default it is approximatively the number of bits contained in five lines of 80 chars each, i.e. 80*5. Note that if you set this to nil multiline will be disabled, i.e. you will not have separators between candidates any more." :type '(choice (const :tag "Disabled" t) (integer :tag "Max candidate offset")) :group 'helm-ring) (defcustom helm-kill-ring-actions '(("Yank marked" . helm-kill-ring-action-yank) ("Delete marked" . helm-kill-ring-action-delete) ("Search from candidate" . helm-kill-ring-search-from-string)) "List of actions for kill ring source." :group 'helm-ring :type '(alist :key-type string :value-type function)) (defcustom helm-kill-ring-separator "\n" "The separator used to separate marked candidates when yanking." :group 'helm-ring :type 'string) (defcustom helm-register-max-offset 160 "Max size of string register entries before truncating." :group 'helm-ring :type 'integer) ;;; Kill ring ;; ;; (defvar helm-kill-ring-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "M-y") 'helm-next-line) (define-key map (kbd "M-u") 'helm-previous-line) (define-key map (kbd "M-D") 'helm-kill-ring-delete) (define-key map (kbd "C-s") 'helm-kill-ring-run-search-from-string) (define-key map (kbd "C-]") 'helm-kill-ring-toggle-truncated) (define-key map (kbd "C-c C-k") 'helm-kill-ring-kill-selection) (define-key map (kbd "C-c d") 'helm-kill-ring-run-persistent-delete) map) "Keymap for `helm-show-kill-ring'.") (defvar helm-source-kill-ring (helm-build-sync-source "Kill Ring" :init (lambda () (helm-set-attr 'last-command last-command) (helm-set-attr 'multiline helm-kill-ring-max-offset)) :candidates #'helm-kill-ring-candidates :filtered-candidate-transformer #'helm-kill-ring-transformer :action 'helm-kill-ring-actions :persistent-action 'ignore :help-message 'helm-kill-ring-help-message :persistent-help "DoNothing" :keymap helm-kill-ring-map :migemo t :multiline 'helm-kill-ring-max-offset :group 'helm-ring) "Source for browse and insert contents of kill-ring.") (defun helm-kill-ring-candidates () (cl-loop with cands = (helm-fast-remove-dups kill-ring :test 'equal) for kill in (if (eq (helm-get-attr 'last-command) 'yank) (cdr cands) cands) unless (or (< (length kill) helm-kill-ring-threshold) (string-match "\\`[\n[:blank:]]+\\'" kill)) collect kill)) (defun helm-kill-ring-transformer (candidates _source) "Ensure CANDIDATES are not read-only." (cl-loop for i in candidates when (get-text-property 0 'read-only i) do (set-text-properties 0 (length i) '(read-only nil) i) collect i)) (defvar helm-kill-ring--truncated-flag nil) (defun helm-kill-ring-toggle-truncated () "Toggle truncated view of candidates in helm kill-ring browser." (interactive) (with-helm-alive-p (setq helm-kill-ring--truncated-flag (not helm-kill-ring--truncated-flag)) (let* ((cur-cand (helm-get-selection)) (presel-fn (lambda () (helm-kill-ring--preselect-fn cur-cand))) helm-display-source-at-screen-top) (helm-set-attr 'multiline (if helm-kill-ring--truncated-flag 15000000 helm-kill-ring-max-offset)) (helm-update presel-fn)))) (put 'helm-kill-ring-toggle-truncated 'helm-only t) (defun helm-kill-ring-kill-selection () "Store the real value of candidate in kill-ring. Same as `helm-kill-selection-and-quit' called with a prefix arg." (interactive) (helm-kill-selection-and-quit t)) (put 'helm-kill-ring-kill-selection 'helm-only t) (defun helm-kill-ring--preselect-fn (candidate) "Internal, used to preselect CANDIDATE when toggling truncated view." ;; Preselection by regexp may not work if candidate is huge, so walk ;; the helm buffer until selection is on CANDIDATE. (helm-awhile (condition-case-unless-debug nil (and (not (helm-pos-header-line-p)) (helm-get-selection)) (error nil)) (if (string= it candidate) (cl-return) (helm-next-line)))) (defun helm-kill-ring-action-yank (_str) "Insert concatenated marked candidates in current-buffer. When two prefix args are given prompt to choose separator, otherwise use `helm-kill-ring-separator' as default." (let ((marked (helm-marked-candidates)) (sep (if (equal helm-current-prefix-arg '(16)) (read-string "Separator: ") helm-kill-ring-separator))) (helm-kill-ring-action-yank-1 (cl-loop for c in (butlast marked) concat (concat c sep) into str finally return (concat str (car (last marked))))))) (defun helm-kill-ring-action-yank-1 (str) "Insert STR in `kill-ring' and set STR to the head. When called with a prefix arg, point and mark are exchanged without activating region. If this action is executed just after `yank', replace with STR as yanked string." (let ((yank-fn (lambda (&optional before yank-pop) (insert-for-yank str) ;; Set the window start back where it was in ;; the yank command, if possible. (when yank-pop (set-window-start (selected-window) yank-window-start t)) (when (or (equal helm-current-prefix-arg '(4)) before) ;; Same as exchange-point-and-mark but without ;; activating region. (goto-char (prog1 (mark t) (set-marker (mark-marker) (point) helm-current-buffer))))))) ;; Prevent inserting and saving highlighted items. (set-text-properties 0 (length str) nil str) (with-helm-current-buffer (unwind-protect (progn (setq kill-ring (delete str kill-ring)) ;; Adding a `delete-selection' property ;; to `helm-kill-ring-action' is not working ;; because `this-command' will be `helm-maybe-exit-minibuffer', ;; so use this workaround (Bug#1520). (when (and (region-active-p) delete-selection-mode) (delete-region (region-beginning) (region-end))) (if (not (eq (helm-get-attr 'last-command helm-source-kill-ring) 'yank)) (progn ;; Ensure mark is at beginning of inserted text. (push-mark) ;; When yanking in a helm minibuffer we need a small ;; delay to detect the mark in previous minibuffer. [1] (run-with-timer 0.01 nil yank-fn)) ;; from `yank-pop' (let ((inhibit-read-only t) (before (< (point) (mark t)))) (if before (funcall (or yank-undo-function 'delete-region) (point) (mark t)) (funcall (or yank-undo-function 'delete-region) (mark t) (point))) (setq yank-undo-function nil) (set-marker (mark-marker) (point) helm-current-buffer) ;; Same as [1] but use the same mark and point as in ;; the initial yank according to BEFORE even if no ;; prefix arg is given. (run-with-timer 0.01 nil yank-fn before 'pop)))) (kill-new str))))) (define-obsolete-function-alias 'helm-kill-ring-action 'helm-kill-ring-action-yank "2.4.0") (defun helm-kill-ring-search-from-string (candidate) (let ((str (car (split-string candidate "\n")))) (helm-multi-occur-1 (list (current-buffer)) (regexp-quote (substring-no-properties str))))) (helm-make-command-from-action helm-kill-ring-run-search-from-string "Run helm-occur from kill ring." 'helm-kill-ring-search-from-string) (defun helm-kill-ring-action-delete (_candidate) "Delete marked candidates from `kill-ring'." (cl-loop for c in (helm-marked-candidates) do (setq kill-ring (delete c kill-ring)))) (defun helm-kill-ring-persistent-delete (_candidate) (unwind-protect (cl-loop for c in (helm-marked-candidates) do (progn (helm-preselect (format "^%s" (regexp-quote c))) (setq kill-ring (delete c kill-ring)) (helm-delete-current-selection) (helm--remove-marked-and-update-mode-line c))) (with-helm-buffer (setq helm-marked-candidates nil helm-visible-mark-overlays nil)) (helm-force-update (helm-aif (helm-get-selection nil t) (regexp-quote it))))) (helm-make-persistent-command-from-action helm-kill-ring-run-persistent-delete "Delete current candidate without quitting." 'quick-delete 'helm-kill-ring-persistent-delete) (helm-make-command-from-action helm-kill-ring-delete "Delete marked candidates from `kill-ring'." 'helm-kill-ring-action-delete) ;;;; ;; DO NOT use these sources with other sources use ;; the commands `helm-mark-ring', `helm-global-mark-ring' or ;; `helm-all-mark-rings' instead. (defun helm-mark-ring-line-string-at-pos (pos) "Return line string at position POS." (save-excursion (goto-char pos) (forward-line 0) (let ((line (car (split-string (thing-at-point 'line) "[\n\r]")))) (remove-text-properties 0 (length line) '(read-only) line) (if (string= "" line) "" line)))) (defun helm-mark-ring-get-candidates () (with-helm-current-buffer (cl-loop with marks = (if (mark t) (cons (mark-marker) mark-ring) mark-ring) for marker in marks with max-line-number = (line-number-at-pos (point-max)) with width = (length (number-to-string max-line-number)) for m = (format (concat "%" (number-to-string width) "d: %s") (line-number-at-pos marker) (helm-mark-ring-line-string-at-pos marker)) unless (and recip (assoc m recip)) collect (cons m marker) into recip finally return recip))) (defun helm-mark-ring-default-action (candidate) (let ((target (copy-marker candidate))) (helm-aif (marker-buffer candidate) (progn (switch-to-buffer it) (with-selected-window (get-buffer-window it) (unless helm-in-persistent-action (helm-log-run-hook "helm-mark-ring-default-action" 'helm-goto-line-before-hook)) (helm-match-line-cleanup) (unless helm-yank-point (setq helm-yank-point (point))) (helm-goto-char target) (helm-highlight-current-line))) ;; marker points to no buffer, no need to dereference it, just ;; delete it. (setq mark-ring (delete target mark-ring)) (error "Marker points to no buffer")))) (defvar helm-source-mark-ring (helm-build-sync-source "mark-ring" :candidates #'helm-mark-ring-get-candidates :action '(("Goto line" . helm-mark-ring-default-action)) :persistent-help "Show this line" :group 'helm-ring)) ;;; Global-mark-ring (defvar helm-source-global-mark-ring (helm-build-sync-source "global-mark-ring" :candidates #'helm-global-mark-ring-get-candidates :action '(("Goto line" . helm-mark-ring-default-action)) :persistent-help "Show this line" :group 'helm-ring)) (defun helm-global-mark-ring-format-buffer (marker) (with-current-buffer (marker-buffer marker) (goto-char marker) (forward-line 0) (let ((line (helm-acase (thing-at-point 'line) ((guard* (and (stringp it) (not (string-match-p "\\`\n?\\'" it)))) (car (split-string it "[\n\r]"))) (t "")))) (remove-text-properties 0 (length line) '(read-only) line) (format "%7d:%s: %s" (line-number-at-pos) (marker-buffer marker) line)))) (defun helm-global-mark-ring-get-candidates () (let ((marks global-mark-ring)) (when marks (cl-loop for marker in marks for mb = (marker-buffer marker) for gm = (unless (or (string-match "^ " (format "%s" mb)) (null mb)) (helm-global-mark-ring-format-buffer marker)) when (and gm (not (assoc gm recip))) collect (cons gm marker) into recip finally return recip)))) ;;;; ;;; Insert from register (defvar helm-source-register (helm-build-sync-source "Registers" :candidates #'helm-register-candidates :action-transformer #'helm-register-action-transformer :persistent-help "" :multiline t :action '(("Delete Register(s)" . (lambda (_candidate) (cl-loop for candidate in (helm-marked-candidates) for register = (car candidate) do (setq register-alist (delq (assoc register register-alist) register-alist)))))) :group 'helm-ring) "See (info \"(emacs)Registers\")") (defun helm-register-candidates () "Collecting register contents and appropriate commands." (require 'frameset) (cl-loop for (char . rval) in register-alist for key = (single-key-description char) for e27 = (registerv-p rval) for val = (if e27 ; emacs-27 (registerv-data rval) rval) for string-actions = (cond ((numberp val) (list (int-to-string val) 'insert-register 'increment-register)) ((markerp val) (let ((buf (marker-buffer val))) (if (null buf) (list "a marker in no buffer") (list (concat "a buffer position:" (buffer-name buf) ", position " (int-to-string (marker-position val))) 'jump-to-register 'insert-register)))) ((and (consp val) (window-configuration-p (car val))) (list (if (fboundp 'describe-register-1) (describe-register-1 char) "window configuration.") 'jump-to-register)) ((and (vectorp val) (fboundp 'undo-tree-register-data-p) (undo-tree-register-data-p (if e27 val (elt val 1)))) (list "Undo-tree entry." 'undo-tree-restore-state-from-register)) ((or (and (vectorp val) (eq 'registerv (aref val 0))) (and (consp val) (frame-configuration-p (car val))) (or (frame-configuration-p val) (frameset-register-p val))) (list (if (fboundp 'describe-register-1) (describe-register-1 char) "Frame configuration") 'jump-to-register)) ((and (consp val) (eq (car val) 'file)) (list (concat "file:" (prin1-to-string (cdr val)) ".") 'jump-to-register)) ((and (consp val) (eq (car val) 'buffer)) (list (concat "buffer:" (prin1-to-string (cdr val)) ".") 'jump-to-register)) ((and (consp val) (eq (car val) 'file-query)) (list (concat "file:a file-query reference: file " (car (cdr val)) ", position " (int-to-string (car (cdr (cdr val)))) ".") 'jump-to-register)) ((consp val) (let ((lines (format "%4d" (length val)))) (list (format "%s: %s\n" lines (truncate-string-to-width (mapconcat 'identity (list (car val)) "^J") (- (window-width) 15))) 'insert-register))) ((stringp val) (list (concat (substring-no-properties val 0 (min (length val) helm-register-max-offset)) (if (> (length val) helm-register-max-offset) "[...]" "")) 'insert-register 'kill-new 'append-to-register 'prepend-to-register))) unless (null string-actions) ; Fix Bug#1107. collect (cons (format "Register %3s:\n %s" key (car string-actions)) (cons char (cdr string-actions))))) (defun helm-register-action-transformer (actions register-and-functions) "Decide actions by the contents of register." (cl-loop with func-actions = '((insert-register "Insert Register" . (lambda (c) (insert-register (car c)))) (kill-new "Kill Register" . (lambda (c) (with-temp-buffer (insert-register (car c)) (kill-new (buffer-string))))) (jump-to-register "Jump to Register" . (lambda (c) (jump-to-register (car c)))) (append-to-register "Append Region to Register" . (lambda (c) (append-to-register (car c) (region-beginning) (region-end)))) (prepend-to-register "Prepend Region to Register" . (lambda (c) (prepend-to-register (car c) (region-beginning) (region-end)))) (increment-register "Increment Prefix Arg to Register" . (lambda (c) (increment-register helm-current-prefix-arg (car c)))) (undo-tree-restore-state-from-register "Restore Undo-tree register" . (lambda (c) (and (fboundp 'undo-tree-restore-state-from-register) (undo-tree-restore-state-from-register (car c)))))) for func in (cdr register-and-functions) when (assq func func-actions) collect (cdr it) into transformer-actions finally return (append transformer-actions actions))) ;;;###autoload (defun helm-mark-ring () "Preconfigured `helm' for `helm-source-mark-ring'." (interactive) (helm :sources 'helm-source-mark-ring :resume 'noresume :buffer "*helm mark*")) ;;;###autoload (defun helm-global-mark-ring () "Preconfigured `helm' for `helm-source-global-mark-ring'." (interactive) (helm :sources 'helm-source-global-mark-ring :resume 'noresume :buffer "*helm global mark*")) ;;;###autoload (defun helm-all-mark-rings () "Preconfigured `helm' for mark rings. Source used are `helm-source-global-mark-ring' and `helm-source-mark-ring'." (interactive) (helm :sources '(helm-source-mark-ring helm-source-global-mark-ring) :resume 'noresume :buffer "*helm mark ring*")) ;;;###autoload (defun helm-register () "Preconfigured `helm' for Emacs registers." (interactive) (helm :sources 'helm-source-register :resume 'noresume :buffer "*helm register*")) ;;;###autoload (defun helm-show-kill-ring () "Preconfigured `helm' for `kill-ring'. It is drop-in replacement of `yank-pop'. First call open the kill-ring browser, next calls move to next line." (interactive) (setq helm-kill-ring--truncated-flag nil) (let ((enable-recursive-minibuffers t)) (helm :sources helm-source-kill-ring :buffer "*helm kill ring*" ;; :display-source-at-screen-top nil :resume 'noresume :allow-nest t))) ;;;###autoload (defun helm-execute-kmacro () "Preconfigured helm for keyboard macros. Define your macros with `f3' and `f4'. See (info \"(emacs) Keyboard Macros\") for detailed infos." (interactive) (let ((helm-quit-if-no-candidate (lambda () (message "No kbd macro has been defined")))) (helm :sources (helm-build-sync-source "Kmacro" :candidates (lambda () (delq nil (helm-fast-remove-dups (cons (kmacro-ring-head) kmacro-ring) :test 'equal))) :multiline t :candidate-transformer (lambda (candidates) (cl-loop for c in candidates for keys = (if (functionp c) ;; Emacs-29+ (Oclosure). (kmacro--keys c) ;; Emacs-28 and below (list). (car c)) collect (propertize (help-key-description keys nil) 'helm-realvalue c))) :persistent-action 'ignore :persistent-help "Do nothing" :help-message 'helm-kmacro-help-message :action (helm-make-actions "Execute kmacro (`C-u ' to execute times)" 'helm-kbd-macro-execute "Concat marked macros" 'helm-kbd-macro-concat-macros "Delete marked macros" 'helm-kbd-macro-delete-macro "Edit marked macro" 'helm-kbd-macro-edit-macro "Insert kbd macro" 'helm-kbd-macro-insert-macro) :group 'helm-ring) :buffer "*helm kmacro*"))) (defun helm-kbd-macro-make-current (candidate) "Make CANDIDATE macro the current one." (setq kmacro-ring (delete candidate kmacro-ring)) (kmacro-push-ring) (kmacro-split-ring-element candidate)) (defun helm-kbd-macro-insert-macro (candidate) "Insert macro at point in `helm-current-buffer'." (let ((desc (read-string "Describe macro briefly: ")) name key) (while (fboundp (setq name (intern (read-string "New name for macro: ")))) (message "Symbol `%s' already exists, choose another name" name) (sit-for 1.5)) (helm-kbd-macro-make-current candidate) (kmacro-name-last-macro name) (when (y-or-n-p "Bind macro to a new key?") (helm-awhile (key-binding (setq key (read-key-sequence-vector "Bind macro to key: "))) (message "`%s' already run command `%s', choose another one" (help-key-description key nil) it) (sit-for 1.5)) (global-set-key key name)) (with-helm-current-buffer (insert (format ";; %s%s\n" desc (and key (format " (bound to `%s')" (help-key-description key nil))))) (insert-kbd-macro name (not (null key)))))) (defun helm-kbd-macro-execute (candidate) ;; Move candidate on top of list for next use. (helm-kbd-macro-make-current candidate) (kmacro-exec-ring-item candidate helm-current-prefix-arg)) (defun helm-kbd-macro-concat-macros (_candidate) (let ((mkd (helm-marked-candidates))) (when (cdr mkd) (kmacro-push-ring) (setq last-kbd-macro (cl-loop for km in mkd for keys = (if (functionp km) (kmacro--keys km) (helm-acase (car km) ((guard* (vectorp it)) it) ((guard* (stringp it)) (kmacro--to-vector it)))) vconcat keys))))) (defun helm-kbd-macro-delete-macro (_candidate) (let ((mkd (helm-marked-candidates)) (head (kmacro-ring-head))) (cl-loop for km in mkd do (setq kmacro-ring (delete km kmacro-ring))) (when (member head mkd) (kmacro-delete-ring-head)))) (defun helm-kbd-macro-edit-macro (candidate) (kmacro-push-ring) (setq kmacro-ring (delete candidate kmacro-ring)) (kmacro-split-ring-element candidate) (kmacro-edit-macro)) (provide 'helm-ring) ;;; helm-ring.el ends here helm-4.0.3/helm-semantic.el000066400000000000000000000221411501106761700155170ustar00rootroot00000000000000;;; helm-semantic.el --- Helm interface for Semantic -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2017 Daniel Hackney ;; 2012 ~ 2023 Thierry Volpiatto ;; Author: Daniel Hackney ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Uses `candidates-in-buffer' for speed. ;;; Code: (require 'cl-lib) (require 'semantic) (require 'helm-help) (require 'helm-imenu) (declare-function pulse-momentary-highlight-one-line "pulse.el" (point &optional face)) (defgroup helm-semantic nil "Semantic tags related libraries and applications for helm." :group 'helm) (defcustom helm-semantic-display-style '((python-mode . semantic-format-tag-summarize) (c-mode . semantic-format-tag-concise-prototype-c-mode) (emacs-lisp-mode . semantic-format-tag-abbreviate-emacs-lisp-mode)) "Function to present a semantic tag according to `major-mode'. It is an alist where the `car' of each element is a `major-mode' and the `cdr' a `semantic-format-tag-*' function. If no function is found for current `major-mode', fall back to `semantic-format-tag-summarize' default function. You can have more or less informations depending of the `semantic-format-tag-*' function you choose. All the supported functions are prefixed with \"semantic-format-tag-\", you have completion on these functions with `C-M i' in the customize interface." :group 'helm-semantic :type '(alist :key-type symbol :value-type symbol)) ;;; keymap (defvar helm-semantic-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) map)) (defcustom helm-semantic-lynx-style-map nil "Use Arrow keys to jump to occurences." :group 'helm-semantic :type 'boolean :set (lambda (var val) (set var val) (if val (progn (define-key helm-semantic-map (kbd "") 'helm-execute-persistent-action) (define-key helm-semantic-map (kbd "") 'helm-maybe-exit-minibuffer)) (define-key helm-semantic-map (kbd "") nil) (define-key helm-semantic-map (kbd "") nil)))) ;; Internals vars (defvar helm-semantic--tags-cache nil) (defun helm-semantic--fetch-candidates (tags depth &optional class) "Write the contents of TAGS to the current buffer." (let ((class class) cur-type (stylefn (or (with-helm-current-buffer (assoc-default major-mode helm-semantic-display-style)) #'semantic-format-tag-summarize))) (dolist (tag tags) (when (listp tag) (cl-case (setq cur-type (semantic-tag-class tag)) ((function variable type) (let ((spaces (make-string (* depth 2) ?\s)) (type-p (eq cur-type 'type))) (unless (and (> depth 0) (not type-p)) (setq class nil)) (insert (if (and class (not type-p)) (format "%s%s(%s) " spaces (if (< depth 2) "" "├►") class) spaces) ;; Save the tag for later (propertize (funcall stylefn tag nil t) 'semantic-tag tag) "\n") (and type-p (setq class (car tag))) ;; Recurse to children (unless (eq cur-type 'function) (helm-semantic--fetch-candidates (semantic-tag-components tag) (1+ depth) class)))) ;; Don't do anything with packages or includes for now ((package include) (insert (propertize (funcall stylefn tag nil t) 'semantic-tag tag) "\n") ) ;; Catch-all (t)))))) (defun helm-semantic-default-action (_candidate &optional persistent) ;; By default, helm doesn't pass on the text properties of the selection. ;; Fix this. (helm-log-run-hook "helm-semantic-default-action" 'helm-goto-line-before-hook) (with-current-buffer helm-buffer (when (looking-at " ") (goto-char (next-single-property-change (pos-bol) 'semantic-tag nil (pos-eol)))) (let ((tag (get-text-property (point) 'semantic-tag))) (semantic-go-to-tag tag) (unless persistent (pulse-momentary-highlight-one-line (point)))))) (defun helm-semantic--maybe-set-needs-update () (with-helm-current-buffer (when (semantic-parse-tree-needs-update-p) (semantic-parse-tree-set-needs-update)))) (defvar helm-source-semantic nil) (defclass helm-semantic-source (helm-source-in-buffer) ((init :initform (lambda () (helm-semantic--maybe-set-needs-update) (setq helm-semantic--tags-cache (semantic-fetch-tags)) (with-current-buffer (helm-candidate-buffer 'global) (let ((major-mode (with-helm-current-buffer major-mode))) (helm-semantic--fetch-candidates helm-semantic--tags-cache 0))))) (get-line :initform 'buffer-substring) (persistent-help :initform "Show this entry") (keymap :initform 'helm-semantic-map) (help-message :initform 'helm-semantic-help-message) (persistent-action :initform (lambda (elm) (helm-semantic-default-action elm t) (helm-highlight-current-line))) (action :initform 'helm-semantic-default-action))) (defcustom helm-semantic-fuzzy-match nil "Enable fuzzy matching in `helm-source-semantic'." :group 'helm-semantic :type 'boolean :set (lambda (var val) (set var val) (setq helm-source-semantic (helm-make-source "Semantic Tags" 'helm-semantic-source :fuzzy-match helm-semantic-fuzzy-match)))) ;;;###autoload (defun helm-semantic (arg) "Preconfigured `helm' for `semantic'. If ARG is supplied, pre-select symbol at point instead of current." (interactive "P") (let ((tag (helm-aif (car (semantic-current-tag-parent)) (let ((curtag (car (semantic-current-tag)))) (if (string= it curtag) (format "\\_<%s\\_>" curtag) (cons (format "\\_<%s\\_>" it) (format "\\_<%s\\_>" curtag)))) (format "\\_<%s\\_>" (car (semantic-current-tag))))) (helm-highlight-matches-around-point-max-lines 'never)) (unless helm-source-semantic (setq helm-source-semantic (helm-make-source "Semantic Tags" 'helm-semantic-source :fuzzy-match helm-semantic-fuzzy-match))) (helm :sources 'helm-source-semantic :candidate-number-limit 9999 :preselect (if arg (thing-at-point 'symbol) tag) :buffer "*helm semantic*"))) ;;;###autoload (defun helm-semantic-or-imenu (arg) "Preconfigured helm for `semantic' or `imenu'. If ARG is supplied, pre-select symbol at point instead of current semantic tag in scope. If `semantic-mode' is active in the current buffer, then use semantic for generating tags, otherwise fall back to `imenu'. Fill in the symbol at point by default." (interactive "P") (unless helm-source-semantic (setq helm-source-semantic (helm-make-source "Semantic Tags" 'helm-semantic-source :fuzzy-match helm-semantic-fuzzy-match))) (unless helm-source-imenu (setq helm-source-imenu (helm-make-source "Imenu" 'helm-imenu-source :fuzzy-match helm-imenu-fuzzy-match))) (let* ((source (if (semantic-active-p) 'helm-source-semantic 'helm-source-imenu)) (helm-highlight-matches-around-point-max-lines 'never) (imenu-p (eq source 'helm-source-imenu)) (imenu-auto-rescan imenu-p) (str (thing-at-point 'symbol)) (helm-execute-action-at-once-if-one (and imenu-p helm-imenu-execute-action-at-once-if-one)) (tag (helm-aif (car (semantic-current-tag-parent)) (let ((curtag (car (semantic-current-tag)))) (if (string= it curtag) (format "\\_<%s\\_>" curtag) (cons (format "\\_<%s\\_>" it) (format "\\_<%s\\_>" curtag)))) (format "\\_<%s\\_>" (car (semantic-current-tag)))))) (helm :sources source :candidate-number-limit 9999 :default (and imenu-p (list (concat "\\_<" (and str (regexp-quote str)) "\\_>") str)) :preselect (if (or arg imenu-p) str tag) :buffer "*helm semantic/imenu*"))) (provide 'helm-semantic) ;;; helm-semantic.el ends here helm-4.0.3/helm-source.el000066400000000000000000001436531501106761700152300ustar00rootroot00000000000000;;; helm-source.el --- Helm source creation. -*- lexical-binding: t -*- ;; Copyright (C) 2015 ~ 2025 Thierry Volpiatto ;; Author: Thierry Volpiatto ;; URL: http://github.com/emacs-helm/helm ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Interface to create helm sources easily. ;; Currently the eieo objects are transformed in alist for compatibility. ;; In the future this package should allow creating source as eieo objects ;; without conversion to alist, teaching helm to read such a structure. ;; The compatibility with alists would be kept. ;;; Code: (require 'cl-lib) (require 'eieio) (require 'helm-lib) (defvar helm-fuzzy-sort-fn) (defvar helm-fuzzy-match-fn) (defvar helm-fuzzy-search-fn) (declare-function helm-init-candidates-in-buffer "helm-core.el") (declare-function helm-interpret-value "helm-core.el") (declare-function helm-fuzzy-highlight-matches "helm-core.el") (declare-function helm-marked-candidates "helm-core.el") ;;; Advice Emacs fn ;; Make Classes's docstrings more readable by removing the attempts to align ;; unuseful stuff and add newline for separating slot documentation, as well ;; slots are in bold characters. (defun helm-source--cl--print-table (&rest args) "Advice for `cl--print-table' to make readable class slots docstrings." (let ((format "%s\n\n Initform=%s\n\n%s")) (dolist (row (cadr args)) (setcar row (propertize (car row) 'face 'bold)) (setcdr row (nthcdr 1 (cdr row))) (insert "\n* " (apply #'format format row) "\n")))) (cl-defgeneric helm--setup-source (source) "Prepare slots and handle slot errors before creating a helm source.") (cl-defgeneric helm-setup-user-source (source) "Allow users modifying slots in SOURCE just before creation.") ;;; Classes for sources ;; ;; (defclass helm-source () ((name :initarg :name :initform nil :custom string :documentation " The name of the source. A string which is also the heading which appears above the list of matches from the source. Must be unique.") (header-name :initarg :header-name :initform nil :custom function :documentation " A function returning the display string of the header. Its argument is the name of the source. This attribute is useful to add an additional information with the source name. It doesn't modify the name of the source.") (init :initarg :init :initform nil :custom function :documentation " Function called with no parameters when helm is started. It is useful for collecting current state information which can be used to create the list of candidates later. Initialization of `candidates-in-buffer' is done here with `helm-init-candidates-in-buffer'.") (candidates :initarg :candidates :initform nil :custom (choice function list) :documentation " Specifies how to retrieve candidates from the source. It can either be a variable name, a function called with no parameters or the actual list of candidates. Do NOT use this for asynchronous sources, use `candidates-process' instead. The list must be a list whose members are strings, symbols or (DISPLAY . REAL) pairs. In case of (DISPLAY . REAL) pairs, the DISPLAY string is shown in the Helm buffer, but the REAL one is used as action argument when the candidate is selected. This allows a more readable presentation for candidates which would otherwise be, for example, too long or have a common part shared with other candidates which can be safely replaced with an abbreviated string for display purposes. Note that if the (DISPLAY . REAL) form is used then pattern matching is done on the displayed string, not on the real value. This function, generally should not compute candidates according to `helm-pattern' which defeat all the Helm's matching mechanism i.e. multiple pattern matching and/or fuzzy matching. If you want to do so, use :match-dynamic slot to be sure matching occur only in :candidates function and there is no conflict with other match functions.") (update :initarg :update :initform nil :custom function :documentation " Function called with no parameters before :init function when `helm-force-update' is called.") (cleanup :initarg :cleanup :initform nil :custom function :documentation " Function called with no parameters when *helm* buffer is closed. It is useful for killing unneeded candidates buffer. Note that the function is executed BEFORE performing action.") (keymap :initarg :keymap :initform 'helm-map :custom sexp :documentation " Specific keymap for this source. default value is `helm-map'.") (action :initarg :action :initform 'identity :custom (alist :key-type string :value-type function) :documentation " An alist of (DISPLAY . FUNCTION) pairs, a variable name or a function. FUNCTION is called with one parameter: the selected candidate. An action other than the default can be chosen from this list of actions for the currently selected candidate (by default with TAB). The DISPLAY string is shown in the completions buffer and the FUNCTION is invoked when an action is selected. The first action of the list is the default. You should use `helm-make-actions' to build this alist easily.") (persistent-action :initarg :persistent-action :initform nil :custom function :documentation " Can be a either a Function called with one parameter (the selected candidate) or a cons cell where first element is this same function and second element a symbol (e.g never-split) that inform `helm-execute-persistent-action' to not split his window to execute this persistent action. Example: (defun foo-persistent-action (candidate) (do-something candidate)) :persistent-action \\='(foo-persistent-action . never-split) ; Don't split or :persistent-action \\='foo-persistent-action ; Split When specifying :persistent-action by slot directly, foo-persistent-action will be executed without quitting helm when hitting `C-j'. Note that other persistent actions can be defined using other bindings than `C-j' by simply defining an interactive function bound to a key in the keymap source. The function should create a new attribute in source before calling `helm-execute-persistent-action' on this attribute. Example: (defun helm-ff-persistent-delete () \"Delete current candidate without quitting.\" (interactive) (with-helm-alive-p (helm-set-attr \\='quick-delete \\='(helm-ff-quick-delete . never-split)) (helm-execute-persistent-action \\='quick-delete))) This function is then bound in `helm-find-files-map'.") (persistent-action-if :initarg :persistent-action-if :initform nil :custom function :documentation " Similar from persistent action but it is a function that should return an object suitable for persistent action when called , i.e. a function or a cons cell. Example: (defun foo-persistent-action (candidate) (cond (something ;; Don't split helm-window. (cons (lambda (_ignore) (do-something candidate)) \\='no-split)) ;; Split helm-window. (something-else (lambda (_ignore) (do-something-else candidate))))) :persistent-action-if \\='foo-persistent-action Here when hitting `C-j' one of the lambda's will be executed depending on something or something-else condition, splitting or not splitting as needed. See `helm-find-files-persistent-action-if' definition as another example.") (persistent-help :initarg :persistent-help :initform nil :custom string :documentation " A string to explain persistent-action of this source. It is a facility to display what persistent action does in header-line, once your source is loaded don't use it directly, it will have no effect, use instead `header-line' attribute. It also accepts a function or a variable name. It will be displayed in `header-line' or in `minibuffer' depending of value of `helm-echo-input-in-header-line' and `helm-display-header-line'.") (help-message :initarg :help-message :initform nil :custom (choice string function) :documentation " Help message for this source. If not present, `helm-help-message' value will be used.") (multiline :initarg :multiline :initform nil :custom (choice boolean integer) :documentation " Allow multiline candidates. When non-nil candidates will be separated by `helm-candidate-separator'. You can customize the color of this separator with `helm-separator' face. Value of multiline can be an integer which specify the maximum size of the multiline string to display, if multiline string is longer than this value it will be truncated.") (requires-pattern :initarg :requires-pattern :initform 0 :custom integer :documentation " If present matches from the source are shown only if the pattern is not empty. Optionally, it can have an integer parameter specifying the required length of input which is useful in case of sources with lots of candidates.") (candidate-transformer :initarg :candidate-transformer :initform nil :custom (choice function list) :documentation " It's a function or a list of functions called with one argument when the completion list from the source is built. The argument is the list of candidates retrieved from the source. The function should return a transformed list of candidates which will be used for the current completion. If it is a list of functions, it calls each function sequentially. This can be used to transform or remove items from the list of candidates. Note that `candidates' is run already, so the given transformer function should also be able to handle candidates with (DISPLAY . REAL) format.") (filtered-candidate-transformer :initarg :filtered-candidate-transformer :initform nil :custom (choice function list) :documentation " It has the same format as `candidate-transformer', except the function is called with two parameters: the candidate list and the source. This transformer is run on the candidate list which is already filtered by the current pattern. While `candidate-transformer' is run only once, it is run every time the input pattern is changed. It can be used to transform the candidate list dynamically, for example, based on the current pattern. In some cases it may also be more efficent to perform candidate transformation here, instead of with `candidate-transformer' even if this transformation is done every time the pattern is changed. For example, if a candidate set is very large then `candidate-transformer' transforms every candidate while only some of them will currently be displayed due to the limit imposed by `helm-candidate-number-limit'. Note that `candidates' and `candidate-transformer' is run already, so the given transformer function should also be able to handle candidates with (DISPLAY . REAL) format.") (filter-one-by-one :initarg :filter-one-by-one :initform nil :custom (choice function list) :documentation " A transformer function that treat candidates one by one. It is called with one arg the candidate. It is faster than `filtered-candidate-transformer' or `candidate-transformer', but should be used only in sources that recompute constantly their candidates, e.g `helm-source-find-files'. Filtering happen early and candidates are treated one by one instead of re-looping on the whole list. If used with `filtered-candidate-transformer' or `candidate-transformer' these functions should treat the candidates transformed by the `filter-one-by-one' function in consequence.") (display-to-real :initarg :display-to-real :initform nil :custom function :documentation " Transform the selected candidate when passing it to action. Function called with one parameter, the selected candidate. Avoid recomputing all candidates with candidate-transformer or filtered-candidate-transformer to give a new value to REAL, instead the selected candidate is transformed only when passing it to action. This works (and make sense) only with plain string candidates, it will NOT work when candidate is a cons cell, in this case the real value of candidate will be used. Example: (helm :sources (helm-build-sync-source \"test\" :candidates \\='(a b c d e) :display-to-real (lambda (c) (concat c \":modified by d-t-r\"))) :buffer \"*helm test*\") Note that this is NOT a transformer, so the display will not be modified by this function.") (real-to-display :initarg :real-to-display :initform nil :custom function :documentation " Recompute all candidates computed previously with other transformers. Function called with one parameter, the selected candidate. The real value of candidates will be shown in display and of course be used by action. Example: (helm :sources (helm-build-sync-source \"test\" :candidates \\='((\"foo\" . 1) (\"bar\" . 2) (\"baz\". 3)) :real-to-display (lambda (c) (format \"%s\" (1+ c)))) :buffer \"*helm test*\") Mostly deprecated, kept only for backward compatibility.") (marked-with-props :initarg :marked-with-props :initform nil :custom (choice boolean symbol) :documentation " Get candidates with their properties in `helm-marked-candidates'. Allow using the FORCE-DISPLAY-PART of `helm-get-selection' in marked candidates, use t or \\='withprop to pass it to `helm-get-selection'.") (action-transformer :initarg :action-transformer :initform nil :custom (choice function list) :documentation " It's a function or a list of functions called with two arguments when the action list from the source is assembled. The first argument is the list of actions, the second is the current selection. If it is a list of functions, it calls each function sequentially. The function should return a transformed action list. This can be used to customize the list of actions based on the currently selected candidate.") (pattern-transformer :initarg :pattern-transformer :initform nil :custom (choice function list) :documentation " It's a function or a list of functions called with one argument before computing matches. Its argument is `helm-pattern'. Functions should return transformed `helm-pattern'. It is useful to change interpretation of `helm-pattern'.") (candidate-number-limit :initarg :candidate-number-limit :initform nil :custom integer :documentation " Override `helm-candidate-number-limit' only for this source.") (volatile :initarg :volatile :initform nil :custom boolean :documentation " Indicates the source assembles the candidate list dynamically, so it shouldn't be cached within a single Helm invocation. It is only applicable to synchronous sources, because asynchronous sources are not cached.") (match :initarg :match :initform nil :custom (choice function list) :documentation " List of functions called with one parameter: a candidate. The function should return non-nil if the candidate matches the current pattern (see variable `helm-pattern'). When using `candidates-in-buffer' its default value is `identity' and don't have to be changed, use the `search' slot instead. This attribute allows the source to override the default pattern matching based on `string-match'. It can be used, for example, to implement a source for file names and do the pattern matching on the basename of files, since it's more likely one is typing part of the basename when searching for a file, instead of some string anywhere else in its path. If the list contains more than one function then the list of matching candidates from the source is constructed by appending the results after invoking the first function on all the potential candidates, then the next function, and so on. The matching candidates supplied by the first function appear first in the list of results and then results from the other functions, respectively. This attribute has no effect for asynchronous sources (see attribute `candidates'), and sources using `match-dynamic' since they perform pattern matching themselves. Note that FUZZY-MATCH slot will overhide value of this slot.") (diacritics :initarg :diacritics :initform nil :custom boolean :documentation " Ignore diacritics when searching.") (match-on-real :initarg :match-on-real :initform nil :custom boolean :documentation " Match the real value of candidates when non nil.") (fuzzy-match :initarg :fuzzy-match :initform nil :custom boolean :documentation " Enable fuzzy matching in this source. This will overwrite settings in MATCH slot, and for sources built with child class `helm-source-in-buffer' the SEARCH slot. This also add a `filtered-candidate-transformer' function to sort candidates (see `helm-fuzzy-sort-fn') according to the score of each candidate which is computed with `helm-fuzzy-default-score-fn'. This is an easy way of enabling fuzzy matching, but you can use the MATCH or SEARCH slots yourself if you want something more elaborated, mixing different type of match (See `helm-source-buffers' class for example), you will have in this case to provide as well a sort fn in `filtered-candidate-transformer' yourself. This attribute is not supported for asynchronous sources since they perform pattern matching themselves.") (redisplay :initarg :redisplay :initform 'identity :custom (choice list function) :documentation " A function or a list of functions to apply to current list of candidates when redisplaying buffer with `helm-redisplay-buffer'. This is only interesting for modifying and redisplaying the whole list of candidates in async sources. It uses `identity' by default for when async sources are mixed with normal sources, in this case these normal sources are not modified and redisplayed as they are.") (nomark :initarg :nomark :initform nil :custom boolean :documentation " Don't allow marking candidates when this attribute is present.") (nohighlight :initarg :nohighlight :initform nil :custom boolean :documentation " Disable highlighting matches in this source. This will disable generic highlighting of matches, but some specialized highlighting can be done from elsewhere, i.e from `filtered-candidate-transformer' or `filter-one-by-one' slots. So use this to either disable completely highlighting in your source, or to disable highlighting and use a specialized highlighting matches function for this source. Remember that this function should run AFTER all filter functions if those filter functions are modifying face properties, though it is possible to avoid this by using new `add-face-text-property' in your filter functions.") (allow-dups :initarg :allow-dups :initform nil :custom boolean :documentation " Allow helm collecting duplicates candidates.") (history :initarg :history :initform nil :custom symbol :documentation " Allow passing history variable to helm from source. It should be a quoted symbol. Passing the history variable here have no effect so add it also in the `helm' call with the :history keyword. The main point of adding the variable here is to make it available when resuming.") (coerce :initarg :coerce :initform nil :custom function :documentation " It's a function called with one argument: the selected candidate. This function is intended for type convertion. In normal case, the selected candidate (string) is passed to action function. If coerce function is specified, it is called just before action function. Example: converting string to symbol (coerce . intern)") (mode-line :initarg :mode-line :initform nil :custom (choice string sexp) :documentation " Source local `helm-mode-line-string' (included in `mode-line-format'). It accepts also variable/function name.") (header-line :initarg :header-line :initform nil :custom (choice string function) :documentation " Source local `header-line-format'. It will be displayed in `header-line' or in `minibuffer' depending of value of `helm-echo-input-in-header-line' and `helm-display-header-line'. It accepts also variable/function name.") (resume :initarg :resume :initform nil :custom function :documentation " Function called with no parameters at end of initialization when `helm-resume' is started. If this function try to do something against `helm-buffer', (e.g updating, searching etc...) probably you should run it in a timer to ensure `helm-buffer' is ready.") (follow :initarg :follow :initform nil :custom integer :documentation " Enable `helm-follow-mode' for this source only. With a value of 1 enable, a value of -1 or nil disable the mode, value set to \\='never prevent using `helm-follow-mode' in this source. See `helm-follow-mode' for more infos.") (follow-delay :initarg :follow-delay :initform nil :custom integer :documentation " `helm-follow-mode' will execute persistent-action after this delay. Otherwise value of `helm-follow-input-idle-delay' is used if non--nil, If none of these are found fallback to `helm-input-idle-delay'.") (multimatch :initarg :multimatch :initform t :custom boolean :documentation " Use the multi-match algorithm when non-nil. I.e Allow specifying multiple patterns separated by spaces. When a pattern is prefixed by \"!\" the negation of this pattern is used, i.e match anything but this pattern. It is the standard way of matching in helm and is enabled by default. It can be used with fuzzy-matching enabled, but as soon helm detect a space, each pattern will match by regexp and will not be fuzzy.") (match-part :initarg :match-part :initform nil :custom function :documentation " Allow matching only one part of candidate. If source contain match-part attribute, match is computed only on part of candidate returned by the call of function provided by this attribute. The function should have one arg, candidate, and return only a specific part of candidate. On async sources, as matching is done by the backend, this have no effect apart for highlighting matches.") (before-init-hook :initarg :before-init-hook :initform nil :custom symbol :documentation " A local hook that run at beginning of initilization of this source. i.e Before the creation of `helm-buffer'. Should be a variable (a symbol) bound to a list of functions or a single function (see `run-hooks' documentation). Even better is to use `add-hook' to feed this variable. Usage of an anonymous function, or a list of functions is still supported but not recommended.") (after-init-hook :initarg :after-init-hook :initform nil :custom symbol :documentation " A local hook that run at end of initilization of this source. i.e After the creation of `helm-buffer'. Should be a variable (a symbol) bound to a list of functions or a single function (see `run-hooks' documentation). Even better is to use `add-hook' to feed this variable. Usage of an anonymous function, or a list of functions is still supported but not recommended.") (delayed :initarg :delayed :initform nil :custom (choice null integer) :documentation " This slot have no more effect and is just kept for backward compatibility. Please don't use it.") (must-match :initarg :must-match :initform nil :custom symbol :documentation " Same as `completing-read' require-match arg. Possible values are: - `t' which prevent exiting with an empty helm-buffer i.e. no matches. - `confirm' which ask for confirmation i.e. need to press a second time RET. - `nil' is the default and is doing nothing i.e. returns nil when pressing RET with an empty helm-buffer. - Any other non nil values e.g. `ignore' allow exiting with minibuffer contents as candidate value (in this case helm-buffer is empty).") (find-file-target :initarg :find-file-target :initform nil :custom function :documentation " Determine the target file when running `helm-quit-and-find-file'. It is a function called with one arg SOURCE.") (group :initarg :group :initform 'helm :custom symbol :documentation " The current source group, default to `helm' when not specified.") (popup-info :initarg :popup-info :initform nil :custom function :documentation " A function that show infos in a popup on the selected candidate. This happen when `helm-popup-tip-mode' is enabled. The function is called on candidate.") (all-marked :initarg :all-marked :initform nil :custom boolean :documentation " When non nil display marked candidates from all sources in mode-line.")) "Main interface to define helm sources." :abstract t) (defclass helm-source-sync (helm-source) ((candidates :initform '("ERROR: You must specify the `candidates' slot, either with a list or a function")) (migemo :initarg :migemo :initform nil :custom boolean :documentation " Enable migemo. When multimatch is disabled, you can give the symbol \\='nomultimatch as value to force not using generic migemo matching function. In this case you have to provide your own migemo matching funtion that kick in when `helm-migemo-mode' is enabled. Otherwise it will be available for this source once `helm-migemo-mode' is enabled when non-nil.") (match-strict :initarg :match-strict :initform nil :custom function :documentation " When specifying a match function within a source and helm-multi-match is enabled, the result of all matching functions will be concatened, which in some cases is not what is wanted. When using `match-strict' only this or these functions will be used. You can specify those functions as a list of functions or a single symbol function. NOTE: This have the same effect as using :MULTIMATCH nil.") (match-dynamic :initarg :match-dynamic :initform nil :custom boolean :documentation " Disable all helm matching functions when non nil. The :candidates function in this case is in charge of fetching candidates dynamically according to `helm-pattern'. If you want to make your :candidates function working with `completion-styles' use the function `helm-dynamic-completion'. Note that :volatile is automatically enabled when using this, so no need to specify it.")) "Use this class to make helm sources using a list of candidates. This list should be given as a normal list, a variable handling a list or a function returning a list. Matching is done basically with `string-match' against each candidate.") (defclass helm-source-async (helm-source) ((candidates-process :initarg :candidates-process :initform nil :custom function :documentation " This attribute is used to define a process as candidate. The function called with no arguments must return a process i.e. `processp', it use typically `start-process' or `make-process', see (info \"(elisp) Asynchronous Processes\"). NOTE: When building the source at runtime you can give directly a process as value, otherwise wrap the process call into a function. The process buffer should be nil, otherwise, if you use `helm-buffer' give to the process a sentinel.") (multimatch :initform nil)) "Use this class to define a helm source calling an external process. The external process is called typically in a `start-process' call to be asynchronous. Note that using multiples asynchronous sources is not fully working, expect weird behavior if you try this. The :candidates slot is not allowed even if described because this class inherit from `helm-source'.") (defclass helm-source-in-buffer (helm-source) ((init :initform 'helm-default-init-source-in-buffer-function) (data :initarg :data :initform nil :custom (choice list string) :documentation " A string, a list or a buffer that will be used to feed the `helm-candidates-buffer'. This data will be passed in a function added to the init slot and the buffer will be build with `helm-init-candidates-in-buffer' or directly with `helm-candidates-buffer' if data is a buffer. This is an easy and fast method to build a `candidates-in-buffer' source.") (migemo :initarg :migemo :initform nil :custom boolean :documentation " Enable migemo. When multimatch is disabled, you can give the symbol \\='nomultimatch as value to force not using generic migemo matching function. In this case you have to provide your own migemo matching funtion that kick in when `helm-migemo-mode' is enabled. Otherwise it will be available for this source once `helm-migemo-mode' is enabled when non-nil.") (candidates :initform 'helm-candidates-in-buffer) (volatile :initform t) (match :initform '(identity)) (get-line :initarg :get-line :initform 'buffer-substring-no-properties :custom function :documentation " A function like `buffer-substring-no-properties' or `buffer-substring'. This function converts region from point at line-beginning and point at line-end in the `helm-candidate-buffer' to a string which will be displayed in the `helm-buffer', it takes two args BEG and END. By default, `helm-candidates-in-buffer' uses `buffer-substring-no-properties' which does no conversion and doesn't carry text properties.") (search :initarg :search :initform '(helm-candidates-in-buffer-search-default-fn) :custom (choice function list) :documentation " List of functions like `re-search-forward' or `search-forward'. Buffer search function used by `helm-candidates-in-buffer'. By default, `helm-candidates-in-buffer' uses `re-search-forward'. The function should take one arg PATTERN. If your search function needs to handle negation like multimatch, this function should returns in such case a cons cell of two integers defining the beg and end positions to match in the line previously matched by `re-search-forward' or similar, and move point to next line (See how the `helm-mm-3-search-base' and `helm-fuzzy-search' functions are working). NOTE: FUZZY-MATCH slot will overhide value of this slot.") (search-strict :initarg :search-strict :initform nil :custom function :documentation " When specifying a search function within a source and helm-multi-match is enabled, the result of all searching functions will be concatened, which in some cases is not what is wanted. When using `search-strict' only this or these functions will be used. You can specify those functions as a list of functions or a single symbol function. NOTE: This have the same effect as using a nil value for :MULTIMATCH slot.")) "Use this source to make helm sources storing candidates inside a buffer. The buffer storing candidates is generated by `helm-candidate-buffer' function and all search are done in this buffer, results are transfered to the `helm-buffer' when done. Contrarily to `helm-source-sync' candidates are matched using a function like `re-search-forward' (see below documentation of `:search' slot) which makes the search much faster than matching candidates one by one. If you want to add search functions to your sources, don't use `:match' which will raise an error, but `:search'. See `helm-candidates-in-buffer' for more infos.") (defclass helm-source-dummy (helm-source) ((candidates :initform '("dummy")) (filtered-candidate-transformer :initform (lambda (_candidates _source) (list helm-pattern))) (multimatch :initform nil) (accept-empty :initarg :accept-empty :initform t :custom boolean :documentation " Allow exiting with an empty string. You should keep the default value.") (match :initform 'identity) (volatile :initform t))) (defclass helm-source-in-file (helm-source-in-buffer) ((init :initform (lambda () (let ((file (helm-get-attr 'candidates-file)) (count 1)) (with-current-buffer (helm-candidate-buffer 'global) (insert-file-contents file) (goto-char (point-min)) (when (helm-get-attr 'linum) (while (not (eobp)) (add-text-properties (pos-bol) (pos-eol) `(helm-linum ,count)) (cl-incf count) (forward-line 1))))))) (get-line :initform #'buffer-substring) (candidates-file :initarg :candidates-file :initform nil :custom string :documentation " The file used to fetch candidates.") (linum :initarg :linum :initform nil :documentation " Store line number in each candidate when non nil. Line number is stored in `helm-linum' text property.")) "The contents of the FILE will be used as candidates in buffer.") ;;; Error functions ;; ;; (defun helm-default-init-source-in-buffer-function () (helm-init-candidates-in-buffer 'global '("ERROR: No buffer handling your data, use either the `init' slot or the `data' slot."))) ;;; Internal Builder functions. ;; ;; (defun helm--create-source (object) "[INTERNAL] Build a helm source from OBJECT. Where OBJECT is an instance of an eieio class." (cl-loop for sd in (eieio-class-slots (eieio-object-class object)) for s = (eieio-slot-descriptor-name sd) for slot-val = (slot-value object s) when slot-val collect (cons s slot-val))) (defun helm-make-source (name class &rest args) "Build a `helm' source named NAME with ARGS for CLASS. Argument NAME is a string which define the source name, so no need to use the keyword :name in your source, NAME will be used instead. Argument CLASS is a symbol defining an eieio class object. Arguments ARGS are keyword value pairs as defined in CLASS." (declare (indent 2)) (let ((source (apply #'make-instance class :name name args))) (helm--setup-source source) (helm-setup-user-source source) (helm--create-source source))) (defun helm-make-type (class &rest args) (let ((source (apply #'make-instance class args))) (setf (slot-value source 'name) nil) (helm--setup-source source) (helm--create-source source))) (make-obsolete 'helm-make-type 'helm-make-source "4.0.3") (defvar helm-mm-default-search-functions) (defvar helm-mm-default-match-functions) (defun helm-source-mm-get-search-or-match-fns (source method) "Prepare match or search functions for class SOURCE. Argument METHOD is the matching method used by SOURCE either `match' or `search'." (let* ((diacritics (slot-value source 'diacritics)) (defmatch (helm-aif (slot-value source 'match) (helm-mklist it))) (defmatch-strict (helm-aif (and (eq method 'match) (slot-value source 'match-strict)) (helm-mklist it))) (defsearch (helm-aif (and (eq method 'search) (slot-value source 'search)) (helm-mklist it))) (defsearch-strict (helm-aif (and (eq method 'search-strict) (slot-value source 'search-strict)) (helm-mklist it))) (migemo (slot-value source 'migemo))) (cl-case method (match (cond (defmatch-strict) ((and migemo diacritics) (append (list 'helm-mm-exact-match 'helm-mm-3-match-on-diacritics) defmatch '(helm-mm-3-migemo-match))) (migemo (append helm-mm-default-match-functions defmatch '(helm-mm-3-migemo-match))) (diacritics (delq nil `(helm-mm-exact-match ,@defmatch helm-mm-3-match-on-diacritics))) (defmatch (append helm-mm-default-match-functions defmatch)) (t helm-mm-default-match-functions))) (search (cond (defsearch-strict) ((and migemo diacritics) (append '(helm-mm-exact-search) defsearch '(helm-mm-3-migemo-search helm-mm-3-search-on-diacritics))) (migemo (append helm-mm-default-search-functions defsearch '(helm-mm-3-migemo-search))) (diacritics (delq nil `(helm-mm-exact-search ,@defsearch helm-mm-3-search-on-diacritics))) (defsearch (append helm-mm-default-search-functions defsearch)) (t helm-mm-default-search-functions)))))) ;;; Modifiers ;; (cl-defun helm-source-add-action-to-source-if (name fn source predicate &optional (index 4)) "Same as `helm-add-action-to-source-if' but for SOURCE defined as eieio object. You can use this inside a `helm--setup-source' method for a SOURCE defined as an eieio class." (let* ((actions (slot-value source 'action)) (action-transformers (slot-value source 'action-transformer)) (new-action (list (cons name fn))) (transformer (lambda (actions _candidate) (let ((candidate (car (helm-marked-candidates)))) (cond ((funcall predicate candidate) (helm-append-at-nth actions new-action index)) (t actions)))))) (cond ((functionp actions) (setf (slot-value source 'action) (list (cons "Default action" actions)))) ((listp actions) (setf (slot-value source 'action) (helm-interpret-value actions source)))) (when (or (symbolp action-transformers) (functionp action-transformers)) (setq action-transformers (list action-transformers))) (setf (slot-value source 'action-transformer) (delq nil (append (list transformer) action-transformers))))) ;;; Methods to build sources. ;; ;; (defun helm-source--persistent-help-string (value source) "Format `persistent-help' VALUE in SOURCE. Argument VALUE can be a string, a variable or a function." (substitute-command-keys (format "\\\\[helm-execute-persistent-action]: %s (keeping session)" (helm-aif value (helm-interpret-value value source) (slot-value source 'header-line))))) (defun helm-source--header-line (source) "Compute a default header line for SOURCE. The header line is based on one of `persistent-action-if', `persistent-action', or `action' (in this order of precedence)." (substitute-command-keys (concat "\\\\[helm-execute-persistent-action]: " (helm-acond ((slot-value source 'persistent-action-if) (helm-symbol-name it)) ((or (slot-value source 'persistent-action) (slot-value source 'action)) (cond ((and (symbolp it) (functionp it) (eq it 'identity)) "Do Nothing") ((and (symbolp it) (boundp it) (listp (symbol-value it)) (stringp (caar (symbol-value it)))) (caar (symbol-value it))) ((or (symbolp it) (functionp it)) (helm-symbol-name it)) ((listp it) (let ((action (car it))) ;; It comes from :action ("foo" . function). (if (stringp (car action)) (car action) ;; It comes from :persistent-action ;; (function . 'nosplit) Fix Bug#788. (if (or (symbolp action) (functionp action)) (helm-symbol-name action))))) (t ""))) (t "")) " (keeping session)"))) (cl-defmethod helm--setup-source ((_source helm-source))) (cl-defmethod helm--setup-source :before ((source helm-source)) (unless (slot-value source 'group) (setf (slot-value source 'group) 'helm)) (when (slot-value source 'delayed) (warn "Deprecated usage of helm `delayed' slot in `%s'" (slot-value source 'name))) (helm-aif (slot-value source 'keymap) (let* ((map (if (symbolp it) (symbol-value it) it)) (must-match-map (when (slot-value source 'must-match) (let ((map (make-sparse-keymap))) (define-key map (kbd "RET") 'helm-confirm-and-exit-minibuffer) map))) (loc-map (if must-match-map (make-composed-keymap must-match-map map) map))) (setf (slot-value source 'keymap) loc-map))) (helm-aif (slot-value source 'persistent-help) (setf (slot-value source 'header-line) (helm-source--persistent-help-string it source)) (setf (slot-value source 'header-line) (helm-source--header-line source))) (when (slot-value source 'fuzzy-match) (cl-assert helm-fuzzy-sort-fn nil "Wrong type argument functionp: nil") (setf (slot-value source 'filtered-candidate-transformer) (helm-aif (slot-value source 'filtered-candidate-transformer) (append (helm-mklist it) (list helm-fuzzy-sort-fn)) (list helm-fuzzy-sort-fn)))) (unless (slot-value source 'nohighlight) (setf (slot-value source 'filtered-candidate-transformer) (helm-aif (slot-value source 'filtered-candidate-transformer) (append (helm-mklist it) (list #'helm-fuzzy-highlight-matches)) (list #'helm-fuzzy-highlight-matches)))) (when (numberp (helm-interpret-value (slot-value source 'multiline))) (setf (slot-value source 'filtered-candidate-transformer) (helm-aif (slot-value source 'filtered-candidate-transformer) (append (helm-mklist it) (list #'helm-multiline-transformer)) (list #'helm-multiline-transformer)))) (helm-aif (slot-value source 'requires-pattern) (let ((val (if (symbolp it) (symbol-value it) it))) (setf (slot-value source 'requires-pattern) val))) ;; Warn when hooks are defined as something else as a symbol i.e. a lambda or ;; a list, if a function an error will raise later anyway when this function ;; is called with `run-hooks'. (let ((sname (slot-value source 'name))) (helm-aif (slot-value source 'before-init-hook) (when (or (and (functionp it) (not (symbolp it))) (consp it)) (warn "Helm source `%s': before-init-hook Should be defined as a symbol" sname))) (helm-aif (slot-value source 'after-init-hook) (when (or (and (functionp it) (not (symbolp it))) (consp it)) (warn "Helm source `%s': after-init-hook Should be defined as a symbol" sname))))) (cl-defmethod helm-setup-user-source ((_source helm-source))) (cl-defmethod helm--setup-source ((source helm-source-sync)) (when (slot-value source 'fuzzy-match) (helm-aif (slot-value source 'match) (setf (slot-value source 'match) (append (helm-mklist it) (list helm-fuzzy-match-fn))) (setf (slot-value source 'match) helm-fuzzy-match-fn))) (when (slot-value source 'multimatch) (setf (slot-value source 'match) (helm-source-mm-get-search-or-match-fns source 'match))) (helm-aif (and (null (slot-value source 'multimatch)) (slot-value source 'migemo)) (unless (eq it 'nomultimatch) ; Use own migemo fn. (setf (slot-value source 'match) (append (helm-mklist (slot-value source 'match)) '(helm-mm-3-migemo-match))))) (when (slot-value source 'match-dynamic) (setf (slot-value source 'match) 'identity) (setf (slot-value source 'match-part) nil) (setf (slot-value source 'multimatch) nil) (setf (slot-value source 'fuzzy-match) nil) (setf (slot-value source 'volatile) t))) (cl-defmethod helm--setup-source ((source helm-source-in-buffer)) (cl-assert (eq (slot-value source 'candidates) 'helm-candidates-in-buffer) nil (format "Wrong usage of `candidates' attr in `%s' use `data' or `init' instead" (slot-value source 'name))) (let ((cur-init (slot-value source 'init))) (helm-aif (slot-value source 'data) (setf (slot-value source 'init) (delq nil (list (and (null (eq 'helm-default-init-source-in-buffer-function cur-init)) cur-init) (lambda () (helm-init-candidates-in-buffer 'global (cond ((functionp it) (funcall it)) ((and (bufferp it) (buffer-live-p it)) (with-current-buffer it (buffer-string))) (t it))))))))) (when (slot-value source 'fuzzy-match) (helm-aif (slot-value source 'search) (setf (slot-value source 'search) (append (helm-mklist it) (list helm-fuzzy-search-fn))) (setf (slot-value source 'search) (list helm-fuzzy-search-fn)))) (when (slot-value source 'multimatch) (setf (slot-value source 'search) (helm-source-mm-get-search-or-match-fns source 'search))) (helm-aif (and (null (slot-value source 'multimatch)) (slot-value source 'migemo)) (unless (eq it 'nomultimatch) (setf (slot-value source 'search) (append (helm-mklist (slot-value source 'search)) '(helm-mm-3-migemo-search))))) (let ((mtc (slot-value source 'match))) (cl-assert (or (equal '(identity) mtc) (eq 'identity mtc)) nil "Invalid slot value for `match'") (cl-assert (eq (slot-value source 'volatile) t) nil "Invalid slot value for `volatile'"))) (cl-defmethod helm--setup-source ((source helm-source-async)) (cl-assert (null (slot-value source 'candidates)) nil "Incorrect use of `candidates' use `candidates-process' instead") (cl-assert (null (slot-value source 'multimatch)) nil "`multimatch' not allowed in async sources.") (cl-assert (null (slot-value source 'fuzzy-match)) nil "`fuzzy-match' not supported in async sources.")) (cl-defmethod helm--setup-source ((source helm-source-dummy)) (let ((mtc (slot-value source 'match))) (cl-assert (or (equal '(identity) mtc) (eq 'identity mtc)) nil "Invalid slot value for `match'") (cl-assert (eq (slot-value source 'volatile) t) nil "Invalid slot value for `volatile'") (cl-assert (equal (slot-value source 'candidates) '("dummy")) nil "Invalid slot value for `candidates'") (cl-assert (eq (slot-value source 'accept-empty) t) nil "Invalid slot value for `accept-empty'"))) ;;; User functions ;; ;; Sources (defmacro helm-build-sync-source (name &rest args) "Build a synchronous helm source with name NAME. Args ARGS are keywords provided by `helm-source-sync'." (declare (indent 1)) `(helm-make-source ,name 'helm-source-sync ,@args)) (defmacro helm-build-async-source (name &rest args) "Build a asynchronous helm source with name NAME. Args ARGS are keywords provided by `helm-source-async'." (declare (indent 1)) `(helm-make-source ,name 'helm-source-async ,@args)) (defmacro helm-build-in-buffer-source (name &rest args) "Build a helm source with name NAME using `candidates-in-buffer' method. Args ARGS are keywords provided by `helm-source-in-buffer'." (declare (indent 1)) `(helm-make-source ,name 'helm-source-in-buffer ,@args)) (defmacro helm-build-dummy-source (name &rest args) "Build a helm source with name NAME using `dummy' method. Args ARGS are keywords provided by `helm-source-dummy'." (declare (indent 1)) `(helm-make-source ,name 'helm-source-dummy ,@args)) (defmacro helm-build-in-file-source (name file &rest args) "Build a helm source with NAME name using `candidates-in-files' method. Arg FILE is a filename, the contents of this file will be used as candidates in buffer. Args ARGS are keywords provided by `helm-source-in-file'." (declare (indent 2)) `(helm-make-source ,name 'helm-source-in-file :candidates-file ,file ,@args)) (provide 'helm-source) ;;; helm-source.el ends here helm-4.0.3/helm-sys.el000066400000000000000000000427011501106761700145360ustar00rootroot00000000000000;;; helm-sys.el --- System related functions for helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'helm-utils) (defgroup helm-sys nil "System related helm library." :group 'helm) (defface helm-top-columns `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit helm-header)) "Face for helm help string in minibuffer." :group 'helm-sys) (defcustom helm-top-command (cl-case system-type (darwin "env COLUMNS=%s ps -axo pid,user,pri,nice,ucomm,tty,start_time,vsz,%%cpu,%%mem,etime,command") (t "env COLUMNS=%s top -b -n 1")) "Top command used to display output of top. A format string where %s will be replaced with `frame-width'. To use top command, a version supporting batch mode (-b option) is needed. On Mac OSX top command doesn't support this, so the ps command is used instead by default. Normally top command output have 12 columns, but in some versions you may have less than this, so you can either customize top to use 12 columns with the interactives f and W commands of top, or modify `helm-top-sort-columns-alist' to fit with the number of columns your top command is using. If you modify ps command be sure that pid comes in first and \"env COLUMNS=%s\" is specified at beginning of command. Ensure also that no elements contain spaces (e.g., use start_time and not start). Same as for top: you can customize `helm-top-sort-columns-alist' to make sort commands working properly according to your settings." :group 'helm-sys :type 'string) (defcustom helm-top-sort-columns-alist '((com . 11) (mem . 9) (cpu . 8) (user . 1)) "Allow defining which column to use when sorting output of top/ps command. Only com, mem, cpu and user are sorted, so no need to put something else there,it will have no effect. Note that column numbers are counted from zero, i.e. column 1 is the nth 0 column." :group 'helm-sys :type '(alist :key-type symbol :value-type (integer :tag "Column number"))) (defcustom helm-top-poll-delay 1.5 "Helm top poll after this delay when `helm-top-poll-mode' is enabled. The minimal delay allowed is 1.5, if less than this helm-top will use 1.5." :group 'helm-sys :type 'float) (defcustom helm-top-poll-delay-post-command 1.0 "Helm top stop polling during this delay. This delay is added to `helm-top-poll-delay' after Emacs stops being idle." :group 'helm-sys :type 'float) (defcustom helm-top-poll-preselection 'linum "Stay on same line or follow candidate when `helm-top-poll' updates display. Possible values are \\='candidate or \\='linum. This affects also sorting functions in the same way." :group'helm-sys :type '(radio :tag "Preferred preselection action for helm-top" (const :tag "Follow candidate" candidate) (const :tag "Stay on same line" linum))) ;;; Top (process) ;; ;; (defvar helm-top-sort-fn nil) (defvar helm-top-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "M-P") 'helm-top-run-sort-by-cpu) (define-key map (kbd "M-C") 'helm-top-run-sort-by-com) (define-key map (kbd "M-M") 'helm-top-run-sort-by-mem) (define-key map (kbd "M-U") 'helm-top-run-sort-by-user) map)) (defvar helm-top-after-init-hook nil "Local hook for helm-top.") (defvar helm-top--poll-timer nil) (defun helm-top-poll (&optional no-update delay) (when helm-top--poll-timer (cancel-timer helm-top--poll-timer)) (condition-case nil (progn (when (and (helm--alive-p) (null no-update) (null helm-suspend-update-flag)) ;; Fix quitting while process is running ;; by binding `with-local-quit' in init function ;; Bug#1521. (helm-force-update (cl-ecase helm-top-poll-preselection (candidate (replace-regexp-in-string "[0-9]+" "[0-9]+" (regexp-quote (helm-get-selection nil t)))) (linum `(lambda () (goto-char (point-min)) (forward-line ,(helm-candidate-number-at-point))))))) (setq helm-top--poll-timer (run-with-idle-timer (helm-aif (current-idle-time) (time-add it (seconds-to-time (or delay (helm-top--poll-delay)))) (or delay (helm-top--poll-delay))) nil 'helm-top-poll))) (quit (cancel-timer helm-top--poll-timer)))) (defun helm-top--poll-delay () (max 1.5 helm-top-poll-delay)) (defun helm-top-poll-no-update () (helm-top-poll t (+ (helm-top--poll-delay) helm-top-poll-delay-post-command))) (defun helm-top-initialize-poll-hooks () ;; When Emacs is idle during say 20s ;; the idle timer will run in 20+1.5 s. ;; This is fine when Emacs stays idle, because the next timer ;; will run at 21.5+1.5 etc... so the display will be updated ;; at every 1.5 seconds. ;; But as soon as emacs looses its idleness, the next update ;; will occur at say 21+1.5 s, so we have to reinitialize ;; the timer at 0+1.5. (add-hook 'post-command-hook 'helm-top-poll-no-update) (add-hook 'focus-in-hook 'helm-top-poll-no-update)) ;;;###autoload (define-minor-mode helm-top-poll-mode "Refresh automatically helm top buffer once enabled." :group 'helm-top :global t (if helm-top-poll-mode (progn (add-hook 'helm-top-after-init-hook 'helm-top-poll-no-update) (add-hook 'helm-top-after-init-hook 'helm-top-initialize-poll-hooks)) (remove-hook 'helm-top-after-init-hook 'helm-top-poll-no-update) (remove-hook 'helm-top-after-init-hook 'helm-top-initialize-poll-hooks))) (defvar helm-source-top (helm-build-in-buffer-source "Top" :header-name (lambda (name) (concat name (if helm-top-poll-mode " (auto updating)" " (Press C-c C-u to refresh)"))) :init #'helm-top-init :after-init-hook 'helm-top-after-init-hook :cleanup (lambda () (when helm-top--poll-timer (cancel-timer helm-top--poll-timer)) (remove-hook 'post-command-hook 'helm-top-poll-no-update) (remove-hook 'focus-in-hook 'helm-top-poll-no-update)) :display-to-real #'helm-top-display-to-real :persistent-action '(helm-top-sh-persistent-action . never-split) :persistent-help "SIGTERM" :help-message 'helm-top-help-message :mode-line 'helm-top-mode-line :follow 'never :keymap helm-top-map :filtered-candidate-transformer #'helm-top-sort-transformer :action-transformer #'helm-top-action-transformer :group 'helm-sys)) (defvar helm-top--line nil) (defun helm-top-transformer (candidates _source) "Transformer for `helm-top'. Return empty string for non--valid candidates." (cl-loop for disp in candidates collect (cond ((string-match "^ *[0-9]+" disp) disp) ((string-match "^ *PID" disp) (setq helm-top--line (cons (propertize disp 'face 'helm-top-columns) ""))) (t (cons disp ""))) into lst finally return (or (member helm-top--line lst) (cons helm-top--line lst)))) (defun helm-top--skip-top-line () (let ((src (helm-get-current-source))) (when (helm-aand (assoc-default 'name src) (string= it "Top") (helm-get-selection nil t src) (string-match-p "^ *PID" it)) (helm-next-line)))) (defun helm-top-action-transformer (actions _candidate) "Action transformer for `top'. Show actions only on line starting by a PID." (let ((disp (helm-get-selection nil t))) (cond ((string-match "\\` *[0-9]+" disp) `(("kill (SIGTERM)" . ,(lambda (_pid) (helm-top-sh "TERM" (helm-top--marked-pids)))) ("kill (SIGKILL)" . ,(lambda (_pid) (helm-top-sh "KILL" (helm-top--marked-pids)))) ("kill (SIGINT)" . ,(lambda (_pid) (helm-top-sh "INT" (helm-top--marked-pids)))) ("kill (Choose signal)" . ,(lambda (_pid) (let ((pids (helm-top--marked-pids))) (helm-top-sh (helm-comp-read (format "Kill %d pids with signal: " (length pids)) '("ALRM" "HUP" "INT" "KILL" "PIPE" "POLL" "PROF" "TERM" "USR1" "USR2" "VTALRM" "STKFLT" "PWR" "WINCH" "CHLD" "URG" "TSTP" "TTIN" "TTOU" "STOP" "CONT" "ABRT" "FPE" "ILL" "QUIT" "SEGV" "TRAP" "SYS" "EMT" "BUS" "XCPU" "XFSZ") :must-match t) pids)))))) (t actions)))) (defun helm-top--marked-pids () (helm-remove-if-not-match "\\`[0-9]+\\'" (helm-marked-candidates))) (defun helm-top-sh (sig pids) "Run kill shell command with signal SIG on PIDS for `helm-top'." (message "kill -%s %s exited with status %s" sig (mapconcat 'identity pids " ") (apply #'call-process "kill" nil nil nil (format "-%s" sig) pids))) (defun helm-top-sh-persistent-action (pid) (helm-top-sh "TERM" (list pid)) (helm-delete-current-selection)) (defun helm-top-init () "Insert output of top command in candidate buffer." (with-local-quit (unless helm-top-sort-fn (helm-top-set-mode-line "CPU")) (with-current-buffer (helm-candidate-buffer 'global) (call-process-shell-command (format helm-top-command (frame-width)) nil (current-buffer))))) (defun helm-top-display-to-real (line) "Return pid only from LINE." (car (split-string line))) ;; Sort top command (defun helm-top-set-mode-line (str) (if (string-match "Sort:\\[\\(.*\\)\\] " helm-top-mode-line) (setq helm-top-mode-line (replace-match str nil nil helm-top-mode-line 1)) (setq helm-top-mode-line (concat (format "Sort:[%s] " str) helm-top-mode-line)))) (defun helm-top-sort-transformer (candidates source) (helm-top-transformer (if helm-top-sort-fn (cl-loop for c in candidates if (string-match "^ *[0-9]+" c) collect c into pid-cands else collect c into header-cands finally return (append header-cands (sort pid-cands helm-top-sort-fn))) candidates) source)) (defun helm-top-sort-by-com (s1 s2) (let* ((split-1 (split-string s1)) (split-2 (split-string s2)) (col (cdr (assq 'com helm-top-sort-columns-alist))) (com-1 (nth col split-1)) (com-2 (nth col split-2))) (string< com-1 com-2))) (defun helm-top-sort-by-mem (s1 s2) (let* ((split-1 (split-string s1)) (split-2 (split-string s2)) (col (cdr (assq 'mem helm-top-sort-columns-alist))) (mem-1 (string-to-number (nth col split-1))) (mem-2 (string-to-number (nth col split-2)))) (> mem-1 mem-2))) (defun helm-top-sort-by-cpu (s1 s2) (let* ((split-1 (split-string s1)) (split-2 (split-string s2)) (col (cdr (assq 'cpu helm-top-sort-columns-alist))) (cpu-1 (string-to-number (nth col split-1))) (cpu-2 (string-to-number (nth col split-2)))) (> cpu-1 cpu-2))) (defun helm-top-sort-by-user (s1 s2) (let* ((split-1 (split-string s1)) (split-2 (split-string s2)) (col (cdr (assq 'user helm-top-sort-columns-alist))) (user-1 (nth col split-1)) (user-2 (nth col split-2))) (string< user-1 user-2))) (defun helm-top--preselect-fn () (if (eq helm-top-poll-preselection 'linum) `(lambda () (goto-char (point-min)) (forward-line ,(helm-candidate-number-at-point))) (replace-regexp-in-string "[0-9]+" "[0-9]+" (regexp-quote (helm-get-selection nil t))))) (defun helm-top-run-sort-by-com () (interactive) (helm-top-set-mode-line "COM") (setq helm-top-sort-fn 'helm-top-sort-by-com) (helm-update (helm-top--preselect-fn))) (defun helm-top-run-sort-by-cpu () (interactive) (helm-top-set-mode-line "CPU") ;; Force sorting by CPU even if some versions of top are using by ;; default CPU sorting (Bug#1908). (setq helm-top-sort-fn 'helm-top-sort-by-cpu) (helm-update (helm-top--preselect-fn))) (defun helm-top-run-sort-by-mem () (interactive) (helm-top-set-mode-line "MEM") (setq helm-top-sort-fn 'helm-top-sort-by-mem) (helm-update (helm-top--preselect-fn))) (defun helm-top-run-sort-by-user () (interactive) (helm-top-set-mode-line "USER") (setq helm-top-sort-fn 'helm-top-sort-by-user) (helm-update (helm-top--preselect-fn))) ;;; X RandR resolution change ;; ;; ;;; FIXME I do not care multi-display. (defun helm-xrandr-info () "Return a pair with current X screen number and current X display name." (with-temp-buffer (call-process "xrandr" nil (current-buffer) nil "--current") (let (screen output) (goto-char (point-min)) (save-excursion (when (re-search-forward "\\(^Screen \\)\\([0-9]\\):" nil t) (setq screen (match-string 2)))) (when (re-search-forward "^\\(.*\\) connected" nil t) (setq output (match-string 1))) (list screen output)))) (defun helm-xrandr-screen () "Return current X screen number." (car (helm-xrandr-info))) (defun helm-xrandr-output () "Return current X display name." (cadr (helm-xrandr-info))) (defvar helm-source-xrandr-change-resolution (helm-build-sync-source "Change Resolution" :candidates (lambda () (with-temp-buffer (call-process "xrandr" nil (current-buffer) nil "--screen" (helm-xrandr-screen) "-q") (goto-char 1) (cl-loop while (re-search-forward " \\([0-9]+x[0-9]+\\)" nil t) for mode = (match-string 1) unless (member mode modes) collect mode into modes finally return modes))) :action (helm-make-actions "Change Resolution" (lambda (mode) (call-process "xrandr" nil nil nil "--screen" (helm-xrandr-screen) "--output" (helm-xrandr-output) "--mode" mode))))) ;;; Emacs process ;; ;; (defvar helm-source-emacs-process (helm-build-sync-source "Emacs Process" :init (lambda () (let (tabulated-list-use-header-line) (list-processes--refresh))) :candidates (lambda () (mapcar #'process-name (process-list))) :candidate-transformer (lambda (candidates) (cl-loop for c in candidates for command = (mapconcat 'identity (process-command (get-process c)) " ") if (and command (not (string= command ""))) collect (cons (concat c " --> " (mapconcat 'identity (process-command (get-process c)) " ")) c) else collect c)) :multiline t :persistent-action (lambda (elm) (delete-process (get-process elm)) (helm-delete-current-selection)) :persistent-help "Kill Process" :action (helm-make-actions "Kill Process" (lambda (_elm) (cl-loop for p in (helm-marked-candidates) do (delete-process (get-process p))))))) ;;;###autoload (defun helm-top (&optional arg) "Preconfigured `helm' for top command. When prefix arg ARG is non nil toggle auto updating mode `helm-top-poll-mode'." (interactive "P") (when arg (helm-top-poll-mode 'toggle)) (add-hook 'helm-after-update-hook 'helm-top--skip-top-line) (unwind-protect (helm :sources 'helm-source-top :buffer "*helm top*" :full-frame t :candidate-number-limit 9999 :preselect "^\\s-*[0-9]+" :truncate-lines helm-show-action-window-other-window) (remove-hook 'helm-after-update-hook 'helm-top--skip-top-line))) ;;;###autoload (defun helm-list-emacs-process () "Preconfigured `helm' for Emacs process." (interactive) (helm :sources 'helm-source-emacs-process :truncate-lines t :buffer "*helm process*")) ;;;###autoload (defun helm-xrandr-set () "Preconfigured helm for xrandr." (interactive) (helm :sources 'helm-source-xrandr-change-resolution :buffer "*helm xrandr*")) (provide 'helm-sys) ;;; helm-sys.el ends here helm-4.0.3/helm-tags.el000066400000000000000000000316151501106761700146600ustar00rootroot00000000000000;;; helm-tags.el --- Helm for Etags. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (require 'helm-utils) (require 'helm-grep) (defvar helm-etags-fuzzy-match) (declare-function xref-push-marker-stack "xref") (defgroup helm-tags nil "Tags related Applications and libraries for Helm." :group 'helm) (defcustom helm-etags-tag-file-name "TAGS" "Etags tag file name." :type 'string) (defcustom helm-etags-tag-file-search-limit 10 "The limit level of directory to search tag file. Don't search tag file deeply if outside this value." :type 'number) (defcustom helm-etags-match-part-only 'tag "Allow choosing the tag part of CANDIDATE in `helm-source-etags-select'. A tag looks like this: filename: (defun foo You can choose matching against the tag part (i.e \"(defun foo\"), or against the whole candidate (i.e \"(filename:5:(defun foo\")." :type '(choice (const :tag "Match only tag" tag) (const :tag "Match all file+tag" all))) (defcustom helm-etags-execute-action-at-once-if-one t "Whether to jump straight to the selected tag if there's only one match." :type 'boolean) (defgroup helm-tags-faces nil "Customize the appearance of helm-tags faces." :prefix "helm-" :group 'helm-tags :group 'helm-faces) (defface helm-etags-file `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :foreground "Lightgoldenrod4" :underline t)) "Face used to highlight etags filenames." :group 'helm-tags-faces) ;;; Etags ;; ;; (defun helm-etags-find-file (candidate) "Find file CANDIDATE from helm etags buffer." (helm-etags-action-goto 'find-file candidate)) (defun helm-etags-find-file-other-window (candidate) "Find file other window from helm etags buffer." (helm-etags-action-goto 'find-file-other-window candidate)) (defun helm-etags-find-file-other-frame (candidate) "Find file other frame from helm etags buffer." (helm-etags-action-goto 'find-file-other-frame candidate)) (helm-make-command-from-action helm-etags-run-switch-other-window "Run switch to other window action from `helm-source-etags-select'." 'helm-etags-find-file-other-window) (helm-make-command-from-action helm-etags-run-switch-other-frame "Run switch to other frame action from `helm-source-etags-select'." 'helm-etags-find-file-other-frame) (defvar helm-etags-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "M-") 'helm-goto-next-file) (define-key map (kbd "M-") 'helm-goto-precedent-file) (define-key map (kbd "C-c o") 'helm-etags-run-switch-other-window) (define-key map (kbd "C-c C-o") 'helm-etags-run-switch-other-frame) map) "Keymap used in Etags.") (defvar helm-etags-mtime-alist nil "Store the last modification time of etags files here.") (defvar helm-etags-cache (make-hash-table :test 'equal) "Cache content of etags files used here for faster access.") (defun helm-etags-get-tag-file (&optional directory) "Return the path of etags file if found in DIRECTORY. Look recursively in parents directorys for a `helm-etags-tag-file-name' file." ;; Get tag file from `default-directory' or upper directory. (let ((current-dir (helm-etags-find-tag-file-directory (or directory default-directory)))) ;; Return nil if not find tag file. (when current-dir (expand-file-name helm-etags-tag-file-name current-dir)))) (defun helm-etags-all-tag-files () "Find Etags files. Return files from the following sources: 1) An automatically located file in the parent directories, by `helm-etags-get-tag-file'. 2) `tags-file-name', which is commonly set by `find-tag' command. 3) `tags-table-list' which is commonly set by `visit-tags-table' command." (helm-fast-remove-dups (delq nil (append (list (helm-etags-get-tag-file) tags-file-name) tags-table-list)) :test 'equal)) (defun helm-etags-find-tag-file-directory (current-dir) "Try to find the directory containing tag file. If not found in CURRENT-DIR search in upper directory." (let ((file-exists? (lambda (dir) (let ((tag-path (expand-file-name helm-etags-tag-file-name dir))) (and (stringp tag-path) (file-regular-p tag-path) (file-readable-p tag-path)))))) (cl-loop with count = 0 until (funcall file-exists? current-dir) ;; Return nil if outside the value of ;; `helm-etags-tag-file-search-limit'. if (= count helm-etags-tag-file-search-limit) do (cl-return nil) ;; Or search upper directories. else do (cl-incf count) (setq current-dir (expand-file-name (concat current-dir "../"))) finally return current-dir))) (defun helm-etags-get-header-name (_x) "Create header name for this helm etags session." (concat "Etags in " (with-helm-current-buffer (helm-etags-get-tag-file)))) (defun helm-etags-create-buffer (file) "Create the `helm-buffer' based on contents of etags tag FILE." (let* (max (split (with-temp-buffer (insert-file-contents file) (prog1 (split-string (buffer-string) "\n" 'omit-nulls) (setq max (line-number-at-pos (point-max)))))) (progress-reporter (make-progress-reporter "Loading tag file..." 0 max))) (cl-loop with fname with cand for i in split for count from 0 for elm = (unless (string-match "^\x0c" i) ;; "^L" (helm-aif (string-match "\177" i) ;; "^?" (substring i 0 it) i)) for linum = (when (string-match "[0-9]+,?[0-9]*$" i) (car (split-string (match-string 0 i) ","))) do (cond ((and elm (string-match "^\\([^,]+\\),[0-9]+$" elm)) (setq fname (propertize (match-string 1 elm) 'face 'helm-etags-file))) (elm (setq cand (format "%s:%s:%s" fname linum elm))) (t (setq cand nil))) when cand do (progn (insert (propertize (concat cand "\n") 'linum linum)) (progress-reporter-update progress-reporter count))))) (defun helm-etags-init () "Feed `helm-buffer' using `helm-etags-cache' or tag file. If there is no entry in cache, create one." (let ((tagfiles (helm-etags-all-tag-files))) (when tagfiles (with-current-buffer (helm-candidate-buffer 'global) (dolist (f tagfiles) (helm-aif (gethash f helm-etags-cache) ;; An entry is present in cache, insert it. (insert it) ;; No entry, create a new buffer using content of tag file (slower). (helm-etags-create-buffer f) ;; Store content of buffer in cache. (puthash f (buffer-string) helm-etags-cache) ;; Store or set the last modification of tag file. (helm-aif (assoc f helm-etags-mtime-alist) ;; If an entry exists modify it. (setcdr it (helm-etags-mtime f)) ;; No entry create a new one. (cl-pushnew (cons f (helm-etags-mtime f)) helm-etags-mtime-alist :test 'equal)))))))) (defvar helm-source-etags-select nil "Helm source for Etags.") (defun helm-etags-build-source () (helm-build-in-buffer-source "Etags" :header-name 'helm-etags-get-header-name :init 'helm-etags-init :get-line 'buffer-substring :match-part (lambda (candidate) ;; Match only the tag part of CANDIDATE ;; and not the filename. (cl-case helm-etags-match-part-only (tag (cl-caddr (helm-grep-split-line candidate))) (t candidate))) :fuzzy-match helm-etags-fuzzy-match :help-message 'helm-etags-help-message :keymap helm-etags-map :action '(("Go to tag" . helm-etags-find-file) ("Go to tag in other window" . helm-etags-find-file-other-window) ("Go to tag in other frame" . helm-etags-find-file-other-frame)) :group 'helm-tags :persistent-help "Go to line" :persistent-action (lambda (candidate) (helm-etags-action-goto 'find-file candidate) (helm-highlight-current-line)))) (defcustom helm-etags-fuzzy-match nil "Use fuzzy matching in `helm-etags-select'." :group 'helm-tags :type 'boolean :set (lambda (var val) (set var val) (setq helm-source-etags-select (helm-etags-build-source)))) (defsubst helm-etags--file-from-tag (fname) (cl-loop for ext in (cons "" (remove "" tags-compression-info-list)) for file = (concat fname ext) when (file-exists-p file) return file)) (defun helm-etags-action-goto (switcher candidate) "Helm default action to jump to an etags entry in other window." (require 'etags) (deactivate-mark t) (helm-log-run-hook "helm-etags-action-goto " 'helm-goto-line-before-hook) (let* ((split (helm-grep-split-line candidate)) (fname (cl-loop for tagf being the hash-keys of helm-etags-cache for f = (expand-file-name (car split) (file-name-directory tagf)) ;; Try to find an existing file, possibly compressed. when (helm-etags--file-from-tag f) return it)) (elm (cl-caddr split)) (linum (string-to-number (cadr split)))) (if (null fname) (error "file %s not found" fname) (xref-push-marker-stack) (funcall switcher fname) (helm-goto-line linum t) (when (search-forward elm nil t) (goto-char (match-beginning 0)))))) (defun helm-etags-mtime (file) "Last modification time of etags tag FILE." (cadr (nth 5 (file-attributes file)))) (defun helm-etags-file-modified-p (file) "Check if tag FILE have been modified in this session. If FILE is nil return nil." (let ((last-modif (and file (assoc-default file helm-etags-mtime-alist)))) (and last-modif (/= last-modif (helm-etags-mtime file))))) ;;;###autoload (defun helm-etags-select (reinit) "Preconfigured helm for etags. If called with a prefix argument REINIT or if any of the tag files have been modified, reinitialize cache. This function aggregates three sources of tag files: 1) An automatically located file in the parent directories, by `helm-etags-get-tag-file'. 2) `tags-file-name', which is commonly set by `find-tag' command. 3) `tags-table-list' which is commonly set by `visit-tags-table' command." (interactive "P") (let ((tag-files (helm-etags-all-tag-files)) (helm-execute-action-at-once-if-one helm-etags-execute-action-at-once-if-one) (str (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) (thing-at-point 'symbol)))) (cl-assert (cl-loop for f in tag-files thereis (file-exists-p f)) nil "No TAGS file found") (cl-loop for k being the hash-keys of helm-etags-cache unless (member k tag-files) do (remhash k helm-etags-cache)) (mapc (lambda (f) (when (or (equal reinit '(4)) (and helm-etags-mtime-alist (helm-etags-file-modified-p f))) (remhash f helm-etags-cache))) tag-files) (unless helm-source-etags-select (setq helm-source-etags-select (helm-etags-build-source))) (helm :sources 'helm-source-etags-select :keymap helm-etags-map :default (and (stringp str) (if (or helm-etags-fuzzy-match (and (eq major-mode 'haskell-mode) (string-match "[']\\'" str))) str (list (concat "\\_<" str "\\_>") str))) :buffer "*helm etags*"))) (provide 'helm-tags) ;;; helm-tags.el ends here helm-4.0.3/helm-types.el000066400000000000000000000345671501106761700150770ustar00rootroot00000000000000;;; helm-types.el --- Helm types classes and methods. -*- lexical-binding: t -*- ;; Copyright (C) 2015 ~ 2025 Thierry Volpiatto ;; Author: Thierry Volpiatto ;; URL: http://github.com/emacs-helm/helm ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'eieio) (eval-when-compile (require 'helm-source)) (defvar helm-map) (defvar helm-mode-line-string) (defvar helm-bookmark-map) (declare-function helm-make-actions "helm-lib") (declare-function helm-ediff-marked-buffers "helm-buffers") (declare-function helm-make-type "helm-source") ;; Files (defclass helm-type-file (helm-source) ((completing-file-name :initarg :completing-file-name :initform t :documentation "Flag to notify we are completing filenames.")) "A class to define helm type file.") (cl-defmethod helm-source-get-action-from-type ((object helm-type-file)) (slot-value object 'action)) (defun helm-actions-from-type-file () (let ((source (make-instance 'helm-type-file))) (helm--setup-source source) (helm-source-get-action-from-type source))) (defvar helm-generic-files-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-map) (define-key map (kbd "C-]") 'helm-ff-run-toggle-basename) (define-key map (kbd "C-s") 'helm-ff-run-grep) (define-key map (kbd "M-g s") 'helm-ff-run-grep) (define-key map (kbd "M-g z") 'helm-ff-run-zgrep) (define-key map (kbd "M-g p") 'helm-ff-run-pdfgrep) (define-key map (kbd "M-R") 'helm-ff-run-rename-file) (define-key map (kbd "M-C") 'helm-ff-run-copy-file) (define-key map (kbd "M-B") 'helm-ff-run-byte-compile-file) (define-key map (kbd "M-L") 'helm-ff-run-load-file) (define-key map (kbd "M-S") 'helm-ff-run-symlink-file) (define-key map (kbd "M-H") 'helm-ff-run-hardlink-file) (define-key map (kbd "M-D") 'helm-ff-run-delete-file) (define-key map (kbd "C-=") 'helm-ff-run-ediff-file) (define-key map (kbd "C-c =") 'helm-ff-run-ediff-merge-file) (define-key map (kbd "C-c o") 'helm-ff-run-switch-other-window) (define-key map (kbd "C-c r") 'helm-ff-run-find-file-as-root) (define-key map (kbd "C-c C-o") 'helm-ff-run-switch-other-frame) (define-key map (kbd "M-i") 'helm-ff-properties-persistent) (define-key map (kbd "C-c C-x") 'helm-ff-run-open-file-externally) (define-key map (kbd "C-c X") 'helm-ff-run-open-file-with-default-tool) (define-key map (kbd "C-c @") 'helm-ff-run-insert-org-link) (define-key map (kbd "C-x C-q") 'helm-ff-run-marked-files-in-dired) (define-key map (kbd "C-c C-a") 'helm-ff-run-mail-attach-files) map) "Generic Keymap for files.") (defcustom helm-type-file-actions (helm-make-actions "Find file" 'helm-find-file-or-marked "Find file as root" 'helm-find-file-as-root "Find file other window" 'helm-find-files-other-window "Find file other frame" 'find-file-other-frame "Open dired in file's directory" 'helm-open-dired "Attach file(s) to mail buffer `C-c C-a'" 'helm-ff-mail-attach-files "Marked files in dired" 'helm-marked-files-in-dired "Grep File(s) `C-u recurse'" 'helm-find-files-grep "Zgrep File(s) `C-u Recurse'" 'helm-ff-zgrep "Pdfgrep File(s)" 'helm-ff-pdfgrep "Insert as org link" 'helm-files-insert-as-org-link "Checksum File" 'helm-ff-checksum "Ediff File" 'helm-find-files-ediff-files "Ediff Merge File" 'helm-find-files-ediff-merge-files "View file" 'view-file "Insert file" 'insert-file "Add marked files to file-cache" 'helm-ff-cache-add-file "Delete file(s)" 'helm-ff-delete-files "Copy file(s) `M-C, C-u to follow'" 'helm-find-files-copy "Rename file(s) `M-R, C-u to follow'" 'helm-find-files-rename "Symlink files(s) `M-S, C-u to follow'" 'helm-find-files-symlink "Relsymlink file(s) `C-u to follow'" 'helm-find-files-relsymlink "Hardlink file(s) `M-H, C-u to follow'" 'helm-find-files-hardlink "Open file externally (C-u to choose)" 'helm-open-file-externally "Open file with default tool" 'helm-open-file-with-default-tool "Find file in hex dump" 'hexl-find-file) "Default actions for type files." :group 'helm-files :type '(alist :key-type string :value-type function)) (cl-defmethod helm--setup-source ((_source helm-type-file))) (cl-defmethod helm--setup-source :before ((source helm-type-file)) (setf (slot-value source 'action) 'helm-type-file-actions) (setf (slot-value source 'persistent-help) "Show this file") (setf (slot-value source 'action-transformer) '(helm-transform-file-load-el helm-transform-file-browse-url helm-transform-file-cache)) (setf (slot-value source 'candidate-transformer) '(helm-skip-boring-files helm-w32-pathname-transformer)) (setf (slot-value source 'filtered-candidate-transformer) 'helm-highlight-files) (setf (slot-value source 'help-message) 'helm-generic-file-help-message) (setf (slot-value source 'mode-line) (list "File(s)" helm-mode-line-string)) (setf (slot-value source 'keymap) helm-generic-files-map) (setf (slot-value source 'group) 'helm-files)) ;; Bookmarks (defclass helm-type-bookmark (helm-source) () "A class to define type bookmarks.") (defcustom helm-type-bookmark-actions (helm-make-actions "Jump to bookmark" 'helm-bookmark-jump "Jump to BM other window" 'helm-bookmark-jump-other-window "Jump to BM other frame" 'helm-bookmark-jump-other-frame "Jump to BM other tab" 'helm-bookmark-jump-other-tab "Show bookmark annotation" 'helm-bookmark-show-annotation "Bookmark edit annotation" 'bookmark-edit-annotation "Delete marked bookmark(s)" 'helm-delete-marked-bookmarks "Edit Bookmark" 'helm-bookmark-edit-bookmark "Reset Bookmark to current pos" 'bookmark-set "Rename marked bookmark(s)" 'helm-bookmark-rename-marked "Relocate bookmark" 'bookmark-relocate) "Default actions for type bookmarks." :group 'helm-bookmark :type '(alist :key-type string :value-type function)) (cl-defmethod helm-source-get-action-from-type ((object helm-type-bookmark)) (slot-value object 'action)) (cl-defmethod helm--setup-source ((_source helm-type-bookmark))) (cl-defmethod helm--setup-source :before ((source helm-type-bookmark)) (setf (slot-value source 'action) 'helm-type-bookmark-actions) (setf (slot-value source 'keymap) helm-bookmark-map) (setf (slot-value source 'mode-line) (list "Bookmark(s)" helm-mode-line-string)) (setf (slot-value source 'help-message) 'helm-bookmark-help-message) (setf (slot-value source 'migemo) t) (setf (slot-value source 'follow) 'never) (setf (slot-value source 'group) 'helm-bookmark)) ;; Buffers (defclass helm-type-buffer (helm-source) () "A class to define type buffer.") (defcustom helm-type-buffer-actions (helm-make-actions "Switch to buffer(s)" 'helm-buffer-switch-buffers "Switch to buffer(s) other window `C-c o'" 'helm-buffer-switch-buffers-other-window "Switch to buffer(s) other frame `C-c C-o'" 'helm-buffer-switch-to-buffer-other-frame "Raise buffer frame maybe" 'helm-buffers-maybe-raise-buffer-frame (lambda () (and (fboundp 'tab-bar-mode) "Switch to buffer(s) other tab `C-c C-t'")) 'helm-buffers-switch-buffers-in-tab "Switch to buffer at line number" 'helm-switch-to-buffer-at-linum "Browse project `C-x C-d'" 'helm-buffers-browse-project "Switch to shell" 'helm-buffer-switch-to-shell "Query replace regexp `C-M-%'" 'helm-buffer-query-replace-regexp "Query replace `M-%'" 'helm-buffer-query-replace "View buffer" 'view-buffer "Display buffer" 'display-buffer "Rename buffer `M-R'" 'helm-buffers-rename-buffer "Grep buffer(s) `M-g s' (C-u grep all buffers)" 'helm-zgrep-buffers "Multi occur buffer(s) `C-s (C-u search also in current)'" 'helm-multi-occur-as-action "Revert buffer(s) `M-G'" 'helm-revert-marked-buffers "Insert buffer" 'insert-buffer "Kill buffer(s) `M-D'" 'helm-kill-marked-buffers "Diff with file `C-='" 'diff-buffer-with-file "Ediff Marked buffers `C-c ='" 'helm-ediff-marked-buffers "Ediff Merge marked buffers `M-='" (lambda (candidate) (helm-ediff-marked-buffers candidate t))) "Default actions for type buffers." :group 'helm-buffers :type '(alist :key-type string :value-type function)) (cl-defmethod helm-source-get-action-from-type ((object helm-type-buffer)) (slot-value object 'action)) (cl-defmethod helm--setup-source ((_source helm-type-buffer))) (cl-defmethod helm--setup-source :before ((source helm-type-buffer)) (setf (slot-value source 'action) 'helm-type-buffer-actions) (setf (slot-value source 'persistent-help) "Show this buffer") (setf (slot-value source 'mode-line) ;; Use default-value of `helm-mode-line-string' in case user ;; starts with a helm buffer as current-buffer otherwise the ;; local value of this helm buffer is used (bug#1517, bug#2377). (list "Buffer(s)" (default-value 'helm-mode-line-string))) (setf (slot-value source 'filtered-candidate-transformer) '(helm-skip-boring-buffers helm-buffers-sort-transformer helm-highlight-buffers)) (setf (slot-value source 'group) 'helm-buffers)) ;; Functions (defclass helm-type-function (helm-source) () "A class to define helm type function.") (defcustom helm-type-function-actions (helm-make-actions "Describe function" 'helm-describe-function "Find function (C-u for source)" 'helm-find-function "Info lookup" 'helm-info-lookup-symbol "Debug on entry" 'debug-on-entry "Cancel debug on entry" 'cancel-debug-on-entry "Trace function" 'trace-function "Trace function (background)" 'trace-function-background "Untrace function" 'untrace-function) "Default actions for type functions." :group 'helm-elisp ;; Use symbol as value type because some functions may not be ;; autoloaded (like untrace-function). :type '(alist :key-type string :value-type symbol)) (cl-defmethod helm-source-get-action-from-type ((object helm-type-function)) (slot-value object 'action)) (defun helm-actions-from-type-function () (let ((source (make-instance 'helm-type-function))) (helm--setup-source source) (helm-source-get-action-from-type source))) (cl-defmethod helm--setup-source ((_source helm-type-function))) (cl-defmethod helm--setup-source :before ((source helm-type-function)) (setf (slot-value source 'action) 'helm-type-function-actions) (setf (slot-value source 'action-transformer) 'helm-transform-function-call-interactively) (setf (slot-value source 'candidate-transformer) 'helm-mark-interactive-functions) (setf (slot-value source 'coerce) 'helm-symbolify)) ;; Commands (defclass helm-type-command (helm-source) () "A class to define helm type command.") (defun helm-actions-from-type-command () (let ((source (make-instance 'helm-type-command))) (helm--setup-source source) (helm-source-get-action-from-type source))) (defcustom helm-type-command-actions (append (helm-make-actions "Execute command" 'helm-M-x-execute-command) (symbol-value (helm-actions-from-type-function))) "Default actions for type command." :group 'helm-command :type '(alist :key-type string :value-type symbol)) (cl-defmethod helm--setup-source ((_source helm-type-command))) (cl-defmethod helm--setup-source :before ((source helm-type-command)) (setf (slot-value source 'action) 'helm-type-command-actions) (setf (slot-value source 'coerce) 'helm-symbolify) (setf (slot-value source 'persistent-action) 'helm-M-x-persistent-action) (setf (slot-value source 'persistent-help) "Describe this command") (setf (slot-value source 'group) 'helm-command)) ;; Timers (defclass helm-type-timers (helm-source) () "A class to define helm type timers.") (defcustom helm-type-timers-actions `(("Cancel Timer" . ,(lambda (_timer) (let ((mkd (helm-marked-candidates))) (cl-loop for timer in mkd do (cancel-timer timer))))) ("Describe Function" . ,(lambda (tm) (describe-function (timer--function tm)))) ("Find Function" . ,(lambda (tm) (helm-aif (timer--function tm) (if (or (byte-code-function-p it) (helm-subr-native-elisp-p it)) (message "Can't find anonymous function `%s'" it) (find-function it)))))) "Default actions for type timers." :group 'helm-elisp :type '(alist :key-type string :value-type function)) (cl-defmethod helm--setup-source ((_source helm-type-timers))) (cl-defmethod helm--setup-source :before ((source helm-type-timers)) (setf (slot-value source 'action) 'helm-type-timers-actions) (setf (slot-value source 'persistent-action) (lambda (tm) (describe-function (timer--function tm)))) (setf (slot-value source 'persistent-help) "Describe Function") (setf (slot-value source 'group) 'helm-elisp)) ;; Builders. (defun helm-build-type-file () (helm-make-type 'helm-type-file)) (make-obsolete 'helm-build-type-file 'helm-make-source "4.0.3") (defun helm-build-type-function () (helm-make-type 'helm-type-function)) (make-obsolete 'helm-build-type-function 'helm-make-source "4.0.3") (defun helm-build-type-command () (helm-make-type 'helm-type-command)) (make-obsolete 'helm-build-type-command 'helm-make-source "4.0.3") (provide 'helm-types) ;;; helm-types.el ends here helm-4.0.3/helm-utils.el000066400000000000000000001462651501106761700150720ustar00rootroot00000000000000;;; helm-utils.el --- Utilities Functions for helm. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'cl-lib) (require 'helm) (require 'helm-help) (declare-function helm-find-files-1 "helm-files" (fname &optional preselect)) (declare-function helm-grep-split-line "helm-grep" (line)) (declare-function markdown-show-entry "ext:markdown-mode.el") (declare-function outline-show-subtree "outline") (declare-function org-reveal "org") (declare-function hs-show-block "hideshow.el") (declare-function hs-show-all "hideshow.el") (declare-function tab-bar-tabs "tab-bar") (declare-function tab-bar-select-tab "tab-bar") (declare-function dired-goto-file "dired") (declare-function bookmark-get-filename "bookmark") (declare-function package-installed-p "package") (declare-function package-desc-dir "package") (defvar hs-minor-mode) (defvar hs-show-hook) (defvar org-directory) (defvar winner-boring-buffers) (defvar bookmark-alist) (defvar dired-buffers) (defvar helm-show-completion-overlay) (defvar helm-buffers-maybe-switch-to-tab) (defvar helm-ff-transformer-show-only-basename) (defvar helm-popup-tip-mode) (defvar helm-ff-last-expanded-candidate-regexp) (defvar helm-mode-find-file-target-alist) (defgroup helm-utils nil "Utilities routines for Helm." :group 'helm) (defcustom helm-su-or-sudo "sudo" "What command to use for root access." :type 'string :group 'helm-utils) (defcustom helm-default-kbsize 1024.0 "Default Kbsize to use for showing files size. It is a float, usually 1024.0 but could be 1000.0 on some systems." :group 'helm-utils :type 'float) (define-obsolete-variable-alias 'helm-highlight-number-lines-around-point 'helm-highlight-matches-around-point-max-lines "20160119") (defcustom helm-highlight-matches-around-point-max-lines '(15 . 15) "Number of lines around point where matched items are highlighted. Possible value are: - A cons cell (x . y) Match x lines before point and y lines after point. - An integer Positive means this number lines after point. Negative means this number lines before point. A zero value means highlight only inside matched lines. - The symbol never Means do not highlight matched items. " :group 'helm-utils :type '(choice (cons (integer :tag "Match before") (integer :tag "Match after")) (const :tag "Match in line only" 0) (integer :tag "Match after or before (+/-)") (const :tag "Never match" never))) (defcustom helm-highlight-only-all-matches nil "Highlight only when all items match on the line when non nil. See `helm-highlight-current-line'." :group 'helm-utils :type 'boolean) (defcustom helm-buffers-to-resize-on-pa nil "A list of helm buffers where the helm-window should be reduced on PA. Where PA means persistent action." :group 'helm-utils :type '(repeat (choice string))) (defcustom helm-resize-on-pa-text-height 12 "The size of the helm-window when resizing on persistent action." :group 'helm-utils :type 'integer) (defcustom helm-html-decode-entities-function #'helm-html-decode-entities-string "Function used to decode HTML entities in HTML bookmarks. Helm comes by default with `helm-html-decode-entities-string', if you need something more sophisticated you can use `w3m-decode-entities-string' if available. In Emacs itself org-entities seem broken and `xml-substitute-numeric-entities' supports only numeric entities." :group 'helm-utils :type 'function) (defvar helm-goto-line-before-hook '(helm-save-current-pos-to-mark-ring) "Run before jumping to line. This hook runs when jumping from `helm-goto-line', `helm-etags-default-action', `helm-imenu-default-action' and `helm-mark-ring-default-action' itself. This allows you to retrieve a previous position after using the different helm tools for searching or retrieving a position (etags, grep, gid, (m)occur etc...). By default positions are added to `mark-ring'. You can also add to register by using (or adding) `helm-save-pos-to-register-before-jump' instead. In this case last position is added to the register `helm-save-pos-before-jump-register'. Note that when using a register only one position is saved globally to registers whereas when using `mark-ring' all positions are saved locally in each buffer.") (defvar helm-save-pos-before-jump-register ?_ "The register where `helm-save-pos-to-register-before-jump' saves position.") (defconst helm-html-entities-alist '((""" . 34) ;; " (">" . 62) ;; > ("<" . 60) ;; < ("&" . 38) ;; & ("€" . 8364) ;; € ("Ÿ" . 89) ;; Y ("¡" . 161) ;; ¡ ("¢" . 162) ;; ¢ ("£" . 163) ;; £ ("¤" . 164) ;; ¤ ("¥" . 165) ;; ¥ ("¦" . 166) ;; ¦ ("§" . 167) ;; § ("¨" . 32) ;; SPC (" " . 160) ;;   (non breaking space) ("©" . 169) ;; © ("ª" . 97) ;; a ("«" . 171) ;; « ("¬" . 172) ;; ¬ ("&masr;" . 174) ;; ® ("°" . 176) ;; ° ("±" . 177) ;; ± ("²" . 50) ;; 2 ("³" . 51) ;; 3 ("´" . 39) ;; ' ("µ" . 956) ;; μ ("¶" . 182) ;; ¶ ("·" . 183) ;; · ("¸" . 32) ;; SPC ("¹" . 49) ;; 1 ("º" . 111) ;; o ("»" . 187) ;; » ("¼" . 49) ;; 1 ("½" . 49) ;; 1 ("¾" . 51) ;; 3 ("¿" . 191) ;; ¿ ("À" . 192) ;; À ("Á" . 193) ;; Á ("Â" . 194) ;; Â ("Ã" . 195) ;; Ã ("Ä" . 196) ;; Ä ("Å" . 197) ;; Å ("&Aelig" . 198) ;; Æ ("Ç" . 199) ;; Ç ("È" . 200) ;; È ("É" . 201) ;; É ("Ê" . 202) ;; Ê ("Ë" . 203) ;; Ë ("Ì" . 204) ;; Ì ("Í" . 205) ;; Í ("Î" . 206) ;; Î ("Ï" . 207) ;; Ï ("ð" . 208) ;; Ð ("Ñ" . 209) ;; Ñ ("Ò" . 210) ;; Ò ("Ó" . 211) ;; Ó ("Ô" . 212) ;; Ô ("Õ" . 213) ;; Õ ("Ö" . 214) ;; Ö ("×" . 215) ;; × ("Ø" . 216) ;; Ø ("Ù" . 217) ;; Ù ("Ú" . 218) ;; Ú ("Û" . 219) ;; Û ("Ü" . 220) ;; Ü ("Ý" . 221) ;; Ý ("þ" . 222) ;; Þ ("ß" . 223) ;; ß ("à" . 224) ;; à ("á" . 225) ;; á ("â" . 226) ;; â ("ã" . 227) ;; ã ("ä" . 228) ;; ä ("å" . 229) ;; å ("æ" . 230) ;; æ ("ç" . 231) ;; ç ("è" . 232) ;; è ("é" . 233) ;; é ("ê" . 234) ;; ê ("ë" . 235) ;; ë ("ì" . 236) ;; ì ("í" . 237) ;; í ("î" . 238) ;; î ("ï" . 239) ;; ï ("ð" . 240) ;; ð ("ñ" . 241) ;; ñ ("ò" . 242) ;; ò ("ó" . 243) ;; ó ("ô" . 244) ;; ô ("õ" . 245) ;; õ ("ö" . 246) ;; ö ("÷" . 247) ;; ÷ ("ø" . 248) ;; ø ("ù" . 249) ;; ù ("ú" . 250) ;; ú ("û" . 251) ;; û ("ü" . 252) ;; ü ("ý" . 253) ;; ý ("þ" . 254) ;; þ ("ÿ" . 255) ;; ÿ ("®" . 174) ;; ® ("­" . 173)) ;; ­ "Table of html character entities and values. See https://www.freeformatter.com/html-entities.html") (defvar helm-find-many-files-after-hook nil "Hook that runs at end of `helm-find-many-files'.") (defvar helm-marked-buffer-name "*helm marked*") ;;; Faces. ;; (defface helm-selection-line `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit highlight :distant-foreground "black")) "Face used in the `helm-current-buffer' when jumping to a candidate." :group 'helm-faces) (defface helm-match-item `((t ,@(and (>= emacs-major-version 27) '(:extend t)) :inherit isearch)) "Face used to highlight the item matched in a selected line." :group 'helm-faces) ;;; Utils functions ;; ;; (defcustom helm-window-prefer-horizontal-split nil "Maybe switch to other window vertically when non nil. Possible values are t, nil and `decide'. When t switch vertically. When nil switch horizontally. When `decide' try to guess if it is possible to switch vertically according to the setting of `split-width-threshold' and the size of the window from where splitting is done. Note that when using `decide' and `split-width-threshold' is nil, the behavior is the same as with a nil value." :group 'helm-utils :type '(choice (const :tag "Split window vertically" t) (const :tag "Split window horizontally" nil) (symbol :tag "Guess how to split window" 'decide))) (defcustom helm-window-show-buffers-function #'helm-window-decide-split-fn "The default function to use when opening several buffers at once. It is typically used to rearrange windows." :group 'helm-utils :type '(choice (function :tag "Decide how to split according to number of candidates" helm-window-decide-split-fn) (function :tag "Split windows vertically or horizontally" helm-window-default-split-fn) (function :tag "Split in alternate windows" helm-window-alternate-split-fn) (function :tag "Split windows in mosaic" helm-window-mosaic-fn))) (defun helm-window-show-buffers (buffers &optional other-window) "Show BUFFERS. With more than one buffer marked switch to these buffers in separate windows. If OTHER-WINDOW is non-nil, keep current buffer and switch to other buffers in separate windows. If a prefix arg is given split windows vertically." (let ((initial-ow-fn (if (cdr (window-list)) #'switch-to-buffer-other-window #'helm-window-other-window))) (if (cdr buffers) (funcall helm-window-show-buffers-function buffers (and other-window initial-ow-fn)) (if other-window (funcall initial-ow-fn (car buffers)) (helm-buffers-switch-to-buffer-or-tab (car buffers)))))) (defvar tab-bar-tab-name-function) (declare-function tab-bar-switch-to-tab "tab-bar.el") (declare-function tab-bar-tab-name-all "tab-bar.el") (defun helm-buffers-maybe-switch-to-buffer-in-tab (buffer fallback-fn) "Switch to BUFFER in its tab if possible, otherwise call FALLBACK-FN." (let* (;; Normally `helm-buffers-maybe-switch-to-tab' custom set function ;; bounded `tab-bar-tab-name-function' to `tab-bar-tab-name-all' ;; but in case user bounded this with setq ensure it works ;; at least partially by let-bounding it here. (tab-bar-tab-name-function #'tab-bar-tab-name-all) (tabs (tab-bar-tabs)) (tab-names (mapcar (lambda (tab) (cdr (assq 'name tab))) tabs)) (bname (buffer-name (get-buffer buffer))) (tab (helm-buffers--get-tab-from-name bname tabs))) (if (helm-buffers--buffer-in-tab-p bname tab-names) (progn (tab-bar-switch-to-tab (alist-get 'name tab)) (select-window (get-buffer-window bname))) (funcall fallback-fn buffer)))) (defun helm-buffers-switch-to-buffer-or-tab (buffer) "Switch to BUFFER in its tab if some." (if (and (fboundp 'tab-bar-mode) helm-buffers-maybe-switch-to-tab tab-bar-mode) (helm-buffers-maybe-switch-to-buffer-in-tab buffer #'switch-to-buffer) (switch-to-buffer buffer))) (defun helm-buffers--get-tab-from-name (tab-name tabs) "Return tab from TABS when it contains TAB-NAME." (cl-loop for tab in tabs when (member tab-name (split-string (cdr (assq 'name tab)) ", " t)) return tab)) (defun helm-buffers-switch-buffers-in-tab-1 (buffers) "Display BUFFERS in a new tab. If only one buffer in BUFFERS, try to switch to it in its tab if some, otherwise, display it in a new tab. When a double prefix arg is given (C-u C-u), display each buffer of BUFFERS in new tabs. When a single buffer is given, a single prefix arg will force displaying buffer in a new tab even if it is already present in a tab. When there is more than one buffer in BUFFERS a single prefix arg display the buffers vertically according to `helm-window-show-buffers-function'." (when (fboundp 'switch-to-buffer-other-tab) (if (cdr buffers) (if (equal helm-current-prefix-arg '(16)) (cl-loop for b in buffers do (switch-to-buffer-other-tab b)) (switch-to-buffer-other-tab (car buffers)) (helm-window-show-buffers buffers)) (if helm-current-prefix-arg (switch-to-buffer-other-tab (car buffers)) (helm-buffers-maybe-switch-to-buffer-in-tab (car buffers) #'switch-to-buffer-other-tab))))) (defun helm--get-tab-names () (let ((tab-bar-tab-name-function #'tab-bar-tab-name-all)) (mapcar (lambda (tab) (cdr (assq 'name tab))) (tab-bar-tabs)))) (defun helm-buffers--buffer-in-tab-p (buffer-name &optional tab-names) "Check if BUFFER-NAME is in TAB-NAMES list." (cl-loop for name in (or tab-names (helm--get-tab-names)) ;; Buf names are separated with "," in TAB-NAMES ;; e.g. '("tab-bar.el" "*scratch*, helm-buffers.el"). thereis (member buffer-name (split-string name ", " t)))) (defun helm-window-decide-split-fn (candidates &optional other-window-fn) "Try to find the best split window fn according to the number of CANDIDATES." (let ((fn (cond (;; 4 or more. (>= (length candidates) 4) #'helm-window-mosaic-fn) ;; 3 only. ((= (length candidates) 3) #'helm-window-alternate-split-fn) ;; 2 or less. (t #'helm-window-default-split-fn)))) (funcall fn candidates other-window-fn))) (defun helm-window-default-split-fn (candidates &optional other-window-fn) "Split windows in one direction and balance them. Direction can be controlled via `helm-window-prefer-horizontal-split'. If a prefix arg is given split windows the other direction. This function is suitable for `helm-window-show-buffers-function'." (if other-window-fn (funcall other-window-fn (car candidates)) (switch-to-buffer (car candidates))) (save-selected-window (cl-loop with nosplit for b in (cdr candidates) when nosplit return (message "Too many buffers to visit simultaneously") do (condition-case _err (helm-window-other-window b 'balance) (error (setq nosplit t) nil))))) (defun helm-window-alternate-split-fn (candidates &optional other-window-fn) "Split windows horizontally and vertically in alternate fashion. Direction can be controlled via `helm-window-prefer-horizontal-split'. If a prefix arg is given split windows the other direction. This function is suitable for `helm-window-show-buffers-function'." (if other-window-fn (funcall other-window-fn (car candidates)) (switch-to-buffer (car candidates))) (let (right-side) (save-selected-window (cl-loop with nosplit for b in (cdr candidates) when nosplit return (message "Too many buffers to visit simultaneously") do (condition-case _err (let ((helm-current-prefix-arg right-side)) (helm-window-other-window b) (setq right-side (not right-side))) (error (setq nosplit t) nil)))))) (defun helm-window-mosaic-fn (candidates &optional other-window-fn) "Make an as-square-as-possible window mosaic of the CANDIDATES buffers. If rectangular, the long side is in the direction given by `helm-window-prefer-horizontal-split': if non-nil, it is horizontal, vertical otherwise. If OTHER-WINDOW-FN is non-nil, current windows are included in the mosaic. This function is suitable for `helm-window-show-buffers-function'." (when other-window-fn (setq candidates (append (mapcar 'window-buffer (window-list)) candidates))) (delete-other-windows) (let* ((helm-window-prefer-horizontal-split (if (eq helm-window-prefer-horizontal-split 'decide) (and (numberp split-width-threshold) (>= (window-width (selected-window)) split-width-threshold)) helm-window-prefer-horizontal-split)) mosaic-length-tile-count mosaic-width-tile-count mosaic-length-tile-size mosaic-width-tile-size next-window) ;; If 4 tiles, make 2x2 mosaic. ;; If 5-6 tiles, make 2x3 mosaic with direction depending on `helm-window-prefer-horizontal-split'. ;; If 7-9 tiles, make 3x3 mosaic. And so on. (setq mosaic-length-tile-count (ceiling (sqrt (length candidates)))) (setq mosaic-width-tile-count (if (<= (length candidates) (* mosaic-length-tile-count (1- mosaic-length-tile-count))) (1- mosaic-length-tile-count) mosaic-length-tile-count)) ;; We lower-bound the tile size, otherwise the function would ;; fail during the first inner split. ;; There is consequently no need to check for errors when ;; splitting. (let ((frame-mosaic-length-direction-size (frame-height)) (frame-mosaic-width-direction-size (frame-width)) (window-mosaic-length-direction-min-size window-min-height) (window-mosaic-width-direction-min-size window-min-width)) (if helm-window-prefer-horizontal-split (setq frame-mosaic-length-direction-size (frame-width) frame-mosaic-width-direction-size (frame-height) window-mosaic-length-direction-min-size window-min-width window-mosaic-width-direction-min-size window-min-height)) (setq mosaic-length-tile-size (max (/ frame-mosaic-length-direction-size mosaic-length-tile-count) window-mosaic-length-direction-min-size) mosaic-width-tile-size (max (/ frame-mosaic-width-direction-size mosaic-width-tile-count) window-mosaic-width-direction-min-size)) ;; Shorten `candidates' to `max-tiles' elements. (let ((max-tiles (* (/ frame-mosaic-length-direction-size mosaic-length-tile-size) (/ frame-mosaic-width-direction-size mosaic-width-tile-size)))) (when (> (length candidates) max-tiles) (message "Too many buffers to visit simultaneously") (setcdr (nthcdr (- max-tiles 1) candidates) nil)))) ;; Make the mosaic. (while candidates (when (> (length candidates) mosaic-length-tile-count) (setq next-window (split-window nil mosaic-width-tile-size (not helm-window-prefer-horizontal-split)))) (switch-to-buffer (pop candidates)) (dotimes (_ (min (1- mosaic-length-tile-count) (length candidates))) (select-window (split-window nil mosaic-length-tile-size helm-window-prefer-horizontal-split)) (switch-to-buffer (pop candidates))) (when next-window (select-window next-window))))) (defun helm-window-other-window (buffer-or-name &optional balance) "Switch to BUFFER-OR-NAME in other window. Direction can be controlled via `helm-window-prefer-horizontal-split'. If a prefix arg is given split windows the other direction. When argument BALANCE is provided `balance-windows'." (let* ((helm-window-prefer-horizontal-split (if (eq helm-window-prefer-horizontal-split 'decide) (and (numberp split-width-threshold) (>= (window-width (selected-window)) split-width-threshold)) helm-window-prefer-horizontal-split)) (right-side (if helm-window-prefer-horizontal-split (not helm-current-prefix-arg) helm-current-prefix-arg))) (select-window (split-window nil nil right-side)) (and balance (balance-windows)) (switch-to-buffer buffer-or-name))) (cl-defun helm-current-buffer-narrowed-p (&optional (buffer helm-current-buffer)) "Check if BUFFER is narrowed. Default is `helm-current-buffer'." (with-current-buffer buffer (let ((beg (point-min)) (end (point-max)) (total (buffer-size))) (or (/= beg 1) (/= end (1+ total)))))) (defun helm-goto-char (loc) "Go to char, revealing if necessary." (goto-char loc) (let ((fn (cond ((eq major-mode 'org-mode) ;; On some old Emacs versions org may not be loaded. (require 'org) #'org-reveal) ((and (boundp 'outline-minor-mode) outline-minor-mode) #'outline-show-subtree) ((and (boundp 'hs-minor-mode) hs-minor-mode) #'hs-show-all) ((and (boundp 'markdown-mode-map) (derived-mode-p 'markdown-mode)) #'markdown-show-entry))) (hs-show-hook (list (lambda () (goto-char loc))))) ;; outline may fail in some conditions e.g. with markdown enabled ;; (Bug#1919). (condition-case-unless-debug nil (and fn (funcall fn)) (error nil)))) (defun helm-goto-line (lineno &optional noanim) "Goto LINENO opening only outline headline if needed. Animation is used unless NOANIM is non--nil." (helm-log-run-hook "helm-goto-line" 'helm-goto-line-before-hook) (helm-match-line-cleanup) (unless helm-alive-p (with-helm-current-buffer (unless helm-yank-point (setq helm-yank-point (point))))) (goto-char (point-min)) (helm-goto-char (pos-bol lineno)) (unless noanim (helm-highlight-current-line))) (defun helm-save-pos-to-register-before-jump () "Save current buffer position to `helm-save-pos-before-jump-register'. To use this add it to `helm-goto-line-before-hook'." (point-to-register helm-save-pos-before-jump-register)) (defun helm-save-current-pos-to-mark-ring () "Save current buffer position to mark ring. To use this add it to `helm-goto-line-before-hook'." (set-marker (mark-marker) (point)) (push-mark (point) 'nomsg)) (defun helm-displaying-source-names () "Return the list of sources name for this helm session." (with-current-buffer helm-buffer (goto-char (point-min)) (cl-loop with pos while (setq pos (next-single-property-change (point) 'helm-header)) do (goto-char pos) collect (buffer-substring-no-properties (pos-bol)(pos-eol)) do (forward-line 1)))) (defun helm-handle-winner-boring-buffers () "Add `helm-buffer' to `winner-boring-buffers' when quitting/exiting helm. Add this function to `helm-cleanup-hook' when you don't want to see helm buffers after running winner-undo/redo." (require 'winner) (cl-pushnew helm-buffer winner-boring-buffers :test 'equal)) (add-hook 'helm-cleanup-hook #'helm-handle-winner-boring-buffers) (defun helm-quit-and-find-file () "Drop into `helm-find-files' from `helm'. If current selection is a buffer or a file, `helm-find-files' from its directory." (interactive) (with-helm-alive-p (require 'helm-grep) (require 'helm-elisp) (require 'bookmark) ; For bookmark-alist (let ((src (helm-get-current-source))) (helm-run-after-exit (lambda (f) ;; Ensure specifics `helm-execute-action-at-once-if-one' ;; fns don't run here. (let (helm-execute-action-at-once-if-one helm-actions-inherit-frame-settings) ; use this-command (if (file-exists-p f) (helm-find-files-1 (file-name-directory f) (format helm-ff-last-expanded-candidate-regexp (regexp-quote (if helm-ff-transformer-show-only-basename (helm-basename f) f)))) (helm-find-files-1 f)))) (helm--quit-and-find-file-default-file src))))) (put 'helm-quit-and-find-file 'helm-only t) (defun helm--quit-and-find-file-default-file (source) (let ((target-fn (or (helm-get-attr 'find-file-target source) (assoc-default (helm-get-attr 'name source) helm-mode-find-file-target-alist)))) ;; target-fn function may return nil, in this case fallback to default. (helm-aif (and target-fn (funcall target-fn source)) it (let* ((sel (helm-get-selection nil nil source)) (default-preselection (or (helm-default-directory) (buffer-file-name helm-current-buffer) default-directory))) (cond ((and (stringp sel) (or (file-remote-p sel) (file-exists-p sel))) (expand-file-name sel)) ;; Url. ((and (stringp sel) helm--url-regexp (string-match helm--url-regexp sel)) sel) ;; Exit brutally from a `with-helm-show-completion' ((and helm-show-completion-overlay (overlayp helm-show-completion-overlay)) (delete-overlay helm-show-completion-overlay) (remove-hook 'helm-move-selection-after-hook 'helm-show-completion) (expand-file-name default-preselection)) ;; Default. (t (expand-file-name default-preselection))))))) (defun helm-generic-sort-fn (s1 s2) "Sort predicate function for helm candidates. Args S1 and S2 can be single or (display . real) candidates, that is sorting is done against real value of candidate." (let* ((qpattern (regexp-quote helm-pattern)) (reg1 (concat "\\_<" qpattern "\\_>")) (reg2 (concat "\\_<" qpattern)) (reg3 helm-pattern) (split (helm-remove-if-match "\\`!" (helm-mm-split-pattern helm-pattern))) (str1 (if (consp s1) (cdr s1) s1)) (str2 (if (consp s2) (cdr s2) s2)) (score (lambda (str r1 r2 r3 lst) (condition-case nil (+ (if (string-match (concat "\\`" qpattern) str) 1 0) (cond ((string-match r1 str) 5) ((and (string-match " " qpattern) (car lst) (string-match (concat "\\_<" (regexp-quote (car lst))) str) (cl-loop for r in (cdr lst) always (string-match r str))) 4) ((and (string-match " " qpattern) (cl-loop for r in lst always (string-match r str))) 3) ((string-match r2 str) 2) ((string-match r3 str) 1) (t 0))) (invalid-regexp 0)))) (sc1 (get-text-property 0 'completion-score str1)) (sc2 (get-text-property 0 'completion-score str2)) (sc3 (if sc1 0 (funcall score str1 reg1 reg2 reg3 split))) (sc4 (if sc2 0 (funcall score str2 reg1 reg2 reg3 split)))) (cond ((and sc1 sc2) ; helm-flex style. (> sc1 sc2)) ((or (zerop (string-width qpattern)) (and (zerop sc3) (zerop sc4))) (string-lessp str1 str2)) ((= sc3 sc4) (< (length str1) (length str2))) (t (> sc3 sc4))))) (cl-defun helm-file-human-size (size &optional (kbsize helm-default-kbsize)) "Return a string showing SIZE of a file in human readable form. SIZE can be an integer or a float depending on it's value. `file-attributes' will take care of that to avoid overflow error. KBSIZE is a floating point number, defaulting to `helm-default-kbsize'." (cl-loop with result = (cons "B" size) for i in '("k" "M" "G" "T" "P" "E" "Z" "Y") while (>= (cdr result) kbsize) do (setq result (cons i (/ (cdr result) kbsize))) finally return (helm-acase (car result) ("B" (format "%s" size)) (t (format "%.1f%s" (cdr result) it))))) (defun helm-directory-size (directory &optional recursive human) "Return the resulting size of the sum of all files in DIRECTORY. If RECURSIVE is non nil return the size of all files in DIRECTORY and its subdirectories. With arg HUMAN format the size in a human readable format,see `helm-file-human-size'." (cl-loop with files = (if recursive (helm-walk-directory directory :path 'full :directories t) (directory-files directory t directory-files-no-dot-files-regexp)) for file in files sum (nth 7 (file-attributes file)) into total finally return (if human (helm-file-human-size total) total))) (defun helm-format-time-string (time) "Format the time value TIME. If year of TIME is inferior to current year use \"%b %e %Y\" otherwise use \"%b %e %H:%M\"." (let ((current-year (nth 5 (decode-time))) (year (nth 5 (decode-time time))) (fmt-opt '("%b %e %H:%M" "%b %e %Y"))) (if (< year current-year) (format-time-string (nth 1 fmt-opt) time) (format-time-string (nth 0 fmt-opt) time)))) (cl-defun helm-file-attributes (file &key type links uid gid access-time modif-time status size mode gid-change inode device-num dired human-size mode-type mode-owner mode-group mode-other octal (string t)) "Return `file-attributes' elements of FILE separately according to key value. Availables keys are: - TYPE: Same as nth 0 `files-attributes' if STRING is nil otherwise return either symlink, directory or file (default). - LINKS: See nth 1 `files-attributes'. - UID: See nth 2 `files-attributes'. - GID: See nth 3 `files-attributes'. - ACCESS-TIME: See nth 4 `files-attributes', however format time when STRING is non--nil (the default). - MODIF-TIME: See nth 5 `files-attributes', same as above. - STATUS: See nth 6 `files-attributes', same as above. - SIZE: See nth 7 `files-attributes'. - MODE: See nth 8 `files-attributes'. - GID-CHANGE: See nth 9 `files-attributes'. - INODE: See nth 10 `files-attributes'. - DEVICE-NUM: See nth 11 `files-attributes'. - DIRED: A line similar to what \\='ls -l' return. - HUMAN-SIZE: The size in human form, see `helm-file-human-size'. - MODE-TYPE, mode-owner,mode-group, mode-other: Split what nth 8 `files-attributes' return in four categories. - OCTAL: The octal value of MODE-OWNER+MODE-GROUP+MODE-OTHER. - STRING: When non--nil (default) `helm-file-attributes' return more friendly values. If you want the same behavior as `files-attributes' , \(but with return values in proplist) use a nil value for STRING. However when STRING is non--nil, time and type value are different from what you have in `file-attributes'." (helm-aif (file-attributes file string) (let* ((all (cl-destructuring-bind (type links uid gid access-time modif-time status size mode gid-change inode device-num) it (list :type (if string (cond ((stringp type) "symlink") ; fname (type "directory") ; t (t "file")) ; nil type) :links links :uid uid :gid gid :access-time (if string (helm-format-time-string access-time) access-time) :modif-time (if string (helm-format-time-string modif-time) modif-time) :status (if string (format-time-string "%Y-%m-%d %R" status) status) :size size :mode mode :gid-change gid-change :inode inode :device-num device-num))) (perms (cl-getf all :mode)) (modes (helm-split-mode-file-attributes perms nil t))) (cond (type (cl-getf all :type)) (links (cl-getf all :links)) (uid (cl-getf all :uid)) (gid (cl-getf all :gid)) (access-time (cl-getf all :access-time)) (modif-time (cl-getf all :modif-time)) (status (cl-getf all :status)) (size (cl-getf all :size)) (mode perms) (gid-change (cl-getf all :gid-change)) (inode (cl-getf all :inode)) (device-num (cl-getf all :device-num)) (dired (helm-file-attributes-dired-line all human-size octal)) (human-size (helm-file-human-size (cl-getf all :size))) (mode-type (cl-getf modes :mode-type)) (mode-owner (cl-getf modes :user)) (mode-group (cl-getf modes :group)) (mode-other (cl-getf modes :other)) (octal (cl-getf modes :octal)) (t (append all modes)))))) (defun helm-file-attributes-dired-line (all &optional human-size show-octal) (format "%s %s %s:%s %s %s" (helm-split-mode-file-attributes (cl-getf all :mode) t show-octal) (number-to-string (cl-getf all :links)) (cl-getf all :uid) (cl-getf all :gid) (if human-size (helm-file-human-size (cl-getf all :size)) (int-to-string (cl-getf all :size))) (cl-getf all :modif-time))) (defun helm-split-mode-file-attributes (modes &optional string show-octal) "Split MODES in a list of modes. MODES is same as what (nth 8 (file-attributes \"foo\")) would return." (if (string-match "\\`\\(.\\)\\(...\\)\\(...\\)\\(...\\)\\'" modes) (let* ((type (match-string 1 modes)) (user (match-string 2 modes)) (group (match-string 3 modes)) (other (match-string 4 modes)) (octal (and show-octal (helm-ff-numeric-permissions (list user group other))))) (if string (mapconcat 'identity (list type user group other (or octal "")) "") (list :mode-type type :user user :group group :other other :octal octal))) (error "Wrong modes specification for %s" modes))) (defun helm-ff-numeric-permissions (perms) "Return the numeric representation of PERMS. PERMS is the list of permissions for owner, group and others." ;; `file-modes-symbolic-to-number' interpret its MODES argument as what would ;; result when calling such mode on a file with chmod, BTW we have to remove ;; all "-" like read-file-modes does. (helm-aand (listp perms) (apply 'format "u=%s,g=%s,o=%s" perms) (replace-regexp-in-string "-" "" it) (format "%o" (file-modes-symbolic-to-number it)))) (defun helm-format-columns-of-files (files) "Same as `dired-format-columns-of-files'. Inlined here for compatibility." (let ((beg (point))) (completion--insert-strings files) (put-text-property beg (point) 'mouse-face nil))) (defmacro with-helm-display-marked-candidates (buffer-or-name candidates &rest body) (declare (indent 0) (debug t)) (helm-with-gensyms (buffer window winconf) `(let* ((,buffer (temp-buffer-window-setup ,buffer-or-name)) (,winconf helm-last-frame-or-window-configuration) (helm-always-two-windows t) (helm-split-window-default-side (if (eq helm-split-window-default-side 'same) 'below helm-split-window-default-side)) helm-split-window-inside-p helm-reuse-last-window-split-state ,window) (with-current-buffer ,buffer (helm-format-columns-of-files ,candidates)) (unwind-protect (with-selected-window (setq ,window (temp-buffer-window-show ,buffer '(display-buffer-below-selected (window-height . fit-window-to-buffer)))) (progn ,@body)) (quit-window 'kill ,window) (and ,winconf (set-window-configuration ,winconf)))))) ;;; Persistent Action Helpers ;; ;; ;; Internal (defvar helm-match-line-overlay nil) (defvar helm--match-item-overlays nil) (cl-defun helm-highlight-current-line (&optional start end buf face) "Highlight current line and all matching items around it. The number of lines around matched line where the matching items are highlighted are defined by `helm-highlight-matches-around-point-max-lines'. When the variable `helm-highlight-only-all-matches' is non nil only the lines containing all matches (in case of multi match) are highlighted. Optional arguments START, END and FACE are only here for debugging purpose." (let* ((start (or start (line-beginning-position))) (end (or end (1+ (line-end-position)))) start-match end-match (args (list start end buf)) (case-fold-search (if helm-alive-p (helm-set-case-fold-search) case-fold-search))) ;; Highlight the current line. (if (not helm-match-line-overlay) (setq helm-match-line-overlay (apply 'make-overlay args)) (apply 'move-overlay helm-match-line-overlay args)) (overlay-put helm-match-line-overlay 'face (or face 'helm-selection-line)) ;; Now highlight matches only if we are in helm session, we are ;; maybe coming from helm-grep-mode or helm-moccur-mode buffers. (when helm-alive-p (helm-acase helm-highlight-matches-around-point-max-lines ;; Next 2 clauses must precede others otherwise ;; `helm-highlight-matches-around-point-max-lines' is ;; compared as a number by other clauses and return an error. (never (cl-return-from helm-highlight-current-line)) ((dst* (x . y)) (setq start-match (save-excursion (forward-line (- x)) (pos-bol)) end-match (save-excursion (forward-line y) (pos-bol)))) ((guard* (or (null it) (zerop it))) (setq start-match start end-match end)) ((guard* (< it 0)) (setq start-match (save-excursion (forward-line it) (pos-bol)) end-match start)) ((guard* (> it 0)) (setq start-match start end-match (save-excursion (forward-line it) (pos-bol))))) (catch 'empty-line (let* ((regex-list (helm-remove-if-match "\\`!" (helm-mm-split-pattern (if (with-helm-buffer ;; Needed for highlighting AG matches. (assq 'pcre (helm-get-current-source))) (helm--translate-pcre-to-elisp helm-input) helm-input)))) (num-regex (length regex-list))) (save-excursion (goto-char start-match) (while (< (point) end-match) (let* ((start-line (line-beginning-position)) (end-line (line-end-position)) all-matches) (dolist (r regex-list) (let ((match-list '())) (save-excursion (goto-char start-line) (while (condition-case _err (and (not (= start-line end-line)) (if helm-migemo-mode (helm-mm-migemo-forward r end-line t) (re-search-forward r end-line t))) (invalid-regexp nil)) (let ((s (match-beginning 0)) (e (match-end 0))) (if (= s e) (throw 'empty-line nil) (push (cons s e) match-list))))) (when match-list (push match-list all-matches)))) (when (and all-matches (or (not helm-highlight-only-all-matches) (eql (length all-matches) num-regex))) (cl-loop for ml in all-matches do (cl-loop for (s . e) in ml for ov = (make-overlay s e) do (progn (push ov helm--match-item-overlays) (overlay-put ov 'face 'helm-match-item) (overlay-put ov 'priority 1)))))) (forward-line 1)))))) (recenter))) (defun helm--translate-pcre-to-elisp (regexp) "Should translate pcre REGEXP to elisp regexp. Assume regexp is a pcre based regexp." (with-temp-buffer (insert " " regexp " ") (goto-char (point-min)) (save-excursion ;; match (){}| unquoted (helm-awhile (and (re-search-forward "\\([(){}|]\\)" nil t) (match-string 1)) (let ((pos (match-beginning 1))) (if (eql (char-before pos) ?\\) (delete-region pos (1- pos)) (replace-match (concat "\\" it) t t nil 1))))) ;; match \s or \S (helm-awhile (and (re-search-forward "\\S\\?\\(\\s\\[sS]\\)[^-]" nil t) (match-string 1)) (replace-match (concat it "-") t t nil 1)) (buffer-substring (1+ (point-min)) (1- (point-max))))) (defun helm-match-line-cleanup () (when helm-match-line-overlay (delete-overlay helm-match-line-overlay) (setq helm-match-line-overlay nil)) (when helm--match-item-overlays (mapc 'delete-overlay helm--match-item-overlays))) (defun helm-match-line-cleanup-maybe () (when (helm-empty-buffer-p) (helm-match-line-cleanup))) (defun helm-match-line-update () (when helm--match-item-overlays (mapc 'delete-overlay helm--match-item-overlays)) (when helm-match-line-overlay (delete-overlay helm-match-line-overlay) (helm-highlight-current-line))) (defun helm-persistent-autoresize-hook () (when (and helm-buffers-to-resize-on-pa (member helm-buffer helm-buffers-to-resize-on-pa) (eq helm-split-window-state 'vertical)) (set-window-text-height (helm-window) helm-resize-on-pa-text-height))) (defun helm-match-line-cleanup-pulse () (run-with-timer 0.3 nil #'helm-match-line-cleanup)) (add-hook 'helm-after-update-hook 'helm-match-line-cleanup-maybe) (add-hook 'helm-after-persistent-action-hook 'helm-persistent-autoresize-hook) (add-hook 'helm-cleanup-hook 'helm-match-line-cleanup) (add-hook 'helm-after-action-hook 'helm-match-line-cleanup-pulse) (add-hook 'helm-after-persistent-action-hook 'helm-match-line-update) ;;; Popup-info ;; (defvar helm--show-help-echo-timer nil) (defvar helm--maybe-show-help-echo-overlay nil) (defface helm-tooltip '((((type tty pc)) :background "yellow" :foreground "black") (t :background "Goldenrod" :foreground "black")) "Face used by `helm-tooltip-show'." :group 'helm-grep-faces) (defun helm-cancel-help-echo-timer () (when helm--show-help-echo-timer (cancel-timer helm--show-help-echo-timer) (setq helm--show-help-echo-timer nil)) (when helm--maybe-show-help-echo-overlay (delete-overlay helm--maybe-show-help-echo-overlay) (setq helm--maybe-show-help-echo-overlay nil))) (defun helm-tooltip-show (text pos) "Display TEXT at POS in an overlay." (setq helm--maybe-show-help-echo-overlay (make-overlay pos (1+ pos))) (overlay-put helm--maybe-show-help-echo-overlay 'display (propertize (concat text "\n") 'face 'helm-tooltip))) (defun helm-maybe-show-help-echo () (when helm--show-help-echo-timer (cancel-timer helm--show-help-echo-timer) (setq helm--show-help-echo-timer nil)) (when helm--maybe-show-help-echo-overlay (delete-overlay helm--maybe-show-help-echo-overlay)) (let* ((src (helm-get-current-source)) (popup-info-fn (assoc-default 'popup-info src))) (when (and helm-alive-p helm-popup-tip-mode popup-info-fn) (setq helm--show-help-echo-timer (run-with-idle-timer 1 nil (lambda () ;; We may have an error (wrong-type-argument window-live-p nil) ;; when switching to help window, the error may occur in the ;; small lap of time where the helm-window is deleted and the ;; help buffer not already displayed. (ignore-error wrong-type-argument (save-selected-window (with-helm-window (let ((pos (save-excursion (end-of-visual-line) (point))) (str (and popup-info-fn (funcall popup-info-fn (helm-get-selection))))) (when (and str (not (string= str ""))) (helm-tooltip-show (concat " " str) pos)))))))))))) ;;;###autoload (define-minor-mode helm-popup-tip-mode "Show additional informations in a popup tip at end of line. When the mode is enabled, popup showup when the source has a `popup-info' attribute which define a specific function for this source to fetch infos on candidate." :global t (if helm-popup-tip-mode (progn (add-hook 'helm-move-selection-after-hook 'helm-maybe-show-help-echo) (add-hook 'helm-help-mode-after-hook 'helm-maybe-show-help-echo) (add-hook 'helm-cleanup-hook 'helm-cancel-help-echo-timer)) (remove-hook 'helm-move-selection-after-hook 'helm-maybe-show-help-echo) (remove-hook 'helm-help-mode-after-hook 'helm-maybe-show-help-echo) (remove-hook 'helm-cleanup-hook 'helm-cancel-help-echo-timer))) (defun helm-open-file-with-default-tool (file) "Open FILE with the default tool on this platform." (let (process-connection-type) (if (eq system-type 'windows-nt) (helm-w32-shell-execute-open-file file) (start-process "helm-open-file-with-default-tool" nil (helm-acase system-type (gnu/linux "xdg-open") ((darwin macos) "open") (cygwin "cygstart")) file)))) (defun helm-open-dired (file) "Open a dired buffer in FILE's directory. If FILE is a directory, open this directory." (require 'dired) (if (file-directory-p file) (dired file) (dired (file-name-directory file)) (dired-goto-file file))) (defun helm-find-file-as-root (candidate) (let* ((buf (helm-basename candidate)) (host (file-remote-p candidate 'host)) (remote-path (format "/%s:%s:%s" helm-su-or-sudo (or host "") (expand-file-name (if host (file-remote-p candidate 'localname) candidate)))) non-essential) (if (buffer-live-p (get-buffer buf)) (progn (set-buffer buf) (find-alternate-file remote-path)) (find-file remote-path)))) (defun helm-find-many-files (_ignore) "Simple action that run `find-file' on marked candidates. Run `helm-find-many-files-after-hook' at end." (let ((helm--reading-passwd-or-string t)) (mapc 'find-file (helm-marked-candidates)) (helm-log-run-hook "helm-find-many-files" 'helm-find-many-files-after-hook))) (defun helm-read-repeat-string (prompt &optional count) "Prompt as many time PROMPT is not empty. If COUNT is non--nil add a number after each prompt. Return the list of strings entered in each prompt." (cl-loop with prt = prompt with elm while (not (string= elm "")) for n from 1 do (when count (setq prt (format "%s (%s): " (replace-regexp-in-string ": " "" prompt) (int-to-string n)))) collect (setq elm (helm-read-string prt)) into lis finally return (remove "" lis))) (defun helm-html-bookmarks-to-alist (file url-regexp bmk-regexp) "Parse HTML bookmark FILE and return an alist with (title . url) as elements." (let (bookmarks-alist url title) (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) (while (re-search-forward "href=\\|^ *
. ;;; Commentary: ;;; Code: (require 'helm-for-files) ;;; List of files gleaned from every dired buffer ;; ;; (defvar dired-buffers) (defvar directory-files-no-dot-files-regexp) (defun helm-files-in-all-dired-candidates () "Return a list of files from live `dired' buffers." (save-excursion (cl-loop for (f . b) in dired-buffers when (buffer-live-p b) append (let ((dir (with-current-buffer b dired-directory))) (if (listp dir) (cdr dir) (directory-files f t directory-files-no-dot-files-regexp)))))) ;; (dired '("~/" "~/.emacs.d/.emacs-custom.el" "~/.emacs.d/.emacs.bmk")) (defclass helm-files-dired-source (helm-source-sync helm-type-file) ((candidates :initform #'helm-files-in-all-dired-candidates))) (defvar helm-source-files-in-all-dired (helm-make-source "Files in all dired buffer." 'helm-files-dired-source)) ;;; session.el files ;; ;; session (http://emacs-session.sourceforge.net/) is an alternative to ;; recentf that saves recent file history and much more. (defvar session-file-alist) (defclass helm-source-session-class (helm-source-sync) ((candidates :initform (lambda () (cl-delete-if-not (lambda (f) (or (string-match helm-tramp-file-name-regexp f) (file-exists-p f))) (mapcar 'car session-file-alist)))) (keymap :initform 'helm-generic-files-map) (help-message :initform 'helm-generic-file-help-message) (action :initform 'helm-type-file-actions))) (defvar helm-source-session nil "File list from emacs-session.") (defcustom helm-session-fuzzy-match nil "Enable fuzzy matching in `helm-source-session' when non--nil." :group 'helm-files :type 'boolean :set (lambda (var val) (set var val) (setq helm-source-session (helm-make-source "Session" 'helm-source-session-class :fuzzy-match val)))) ;;; External searching file tools. ;; ;; Tracker desktop search (defun helm-source-tracker-transformer (candidates _source) "Return file names from tracker CANDIDATES." ;; loop through tracker candidates selecting out file:// lines ;; then select part after file:// and url decode to get straight filenames (cl-loop for cand in candidates when (and (stringp cand) (string-match "\\`[[:space:]]*file://\\(.*\\)" cand)) collect (url-unhex-string (match-string 1 cand)))) (defvar helm-source-tracker-search (helm-build-async-source "Tracker Search" :candidates-process (lambda () ;; the tracker-search command has been deprecated, now invoke via tracker ;; also, disable the contextual snippets which we don't currently use (start-process "tracker-search-process" nil "tracker" "search" "--disable-snippets" "--disable-color" "--limit=512" helm-pattern)) ;; new simplified transformer of tracker search results :filtered-candidate-transformer #'helm-source-tracker-transformer ;;(multiline) ; https://github.com/emacs-helm/helm/issues/529 :keymap helm-generic-files-map :action 'helm-type-file-actions :action-transformer '(helm-transform-file-load-el helm-transform-file-browse-url) :requires-pattern 3) "Source for the Tracker desktop search engine.") ;; Spotlight (MacOS X desktop search) (defclass helm-mac-spotlight-source (helm-source-async helm-type-file) ((candidates-process :initform (lambda () (start-process "mdfind-process" nil "mdfind" helm-pattern))) (requires-pattern :initform 3))) (defvar helm-source-mac-spotlight (helm-make-source "mdfind" 'helm-mac-spotlight-source) "Source for retrieving files via Spotlight's command line utility mdfind.") (provide 'helm-x-files) ;;; helm-x-files.el ends here helm-4.0.3/helm-x-icons.el000066400000000000000000000166321501106761700153040ustar00rootroot00000000000000;;; helm-x-icons.el --- Provide compatibility between icons providers -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2025 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm-lib) (defgroup helm-x-icons nil "Compatibility functions between icons provider packages." :group 'helm) (defcustom helm-x-icons-provider 'all-the-icons "The icons provider package. The currently supported providers are `all-the-icons' and `nerd-icons'. Do not use setq to set this variable but customize." :type '(choice (const :tag "Use `all-the-icons' package" all-the-icons) (const :tag "Use `nerd-icons' package" nerd-icons)) :set (lambda (var val) (when val (require val nil t)) (set var val))) (defun helm-x-icons-match-to-alist (file type) "Match FILE against an entry in ALIST using `string-match-p'. Supported TYPE are ext, regexp, mode, url and dir." (cl-loop with alist = (helm-x-icons-resolve-alist type) for (elm . rest) in alist for target = (if (eq type 'url) file (helm-basename file)) when (string-match-p (helm-stringify elm) target) return rest)) (defun helm-x-icons-resolve-alist (type) "Return the icon alist corresponding to TYPE. The returned alist is computed according to `helm-x-icons-provider'." (let* ((provider-name (symbol-name helm-x-icons-provider)) (alist-name (helm-acase type (ext "extension-icon-alist") (regexp "regexp-icon-alist") (dir "dir-icon-alist") (url "url-alist") (mode "mode-icon-alist")))) (symbol-value (intern-soft (concat provider-name "-" alist-name))))) (defun helm-x-icons-icon-for-file (&rest args) "Compatibility function for `*-icon-for-file'." (let ((fn (helm-acase helm-x-icons-provider (all-the-icons 'all-the-icons-icon-for-file) (nerd-icons 'nerd-icons-icon-for-file)))) (when fn (apply fn args)))) (defvar helm-x-icons-nerd-icons-compat-alist '(("file-symlink-directory" . (nerd-icons-codicon . "nf-cod-file_symlink_directory")) ("file-directory" . (nerd-icons-sucicon . "nf-custom-folder_oct")) ("star" . (nerd-icons-mdicon . "nf-md-star")) ("mail-read" . (nerd-icons-codicon . "nf-cod-mail_read")) ("info" . (nerd-icons-faicon . "nf-fa-info")) ("link-external" . (nerd-icons-faicon . "nf-fa-external_link")) ("mail" . (nerd-icons-mdicon . "nf-md-email")) ("note_add" . (nerd-icons-codicon . "nf-cod-new_file")) ("create_new_folder" . (nerd-icons-codicon . "nf-cod-new_folder")) ("firefox" . (nerd-icons-faicon . "nf-fa-firefox")) ("globe" . (nerd-icons-faicon . "nf-fa-globe")) ("man-page" . (nerd-icons-octicon . "nf-oct-command_palette")) ("crop" . (nerd-icons-faicon . "nf-fa-crop")) ("package" . (nerd-icons-octicon . "nf-oct-package")) ("color_lens" . (nerd-icons-mdicon . "nf-md-color_lens")) ("cube" . (nerd-icons-mdicon . "nf-md-cube")) ("three-bars" . (nerd-icons-codicon . "nf-code-three_bars")) ("cog" . (nerd-icons-mdicon . "nf-md-cog")) ("lightning" . (nerd-icons-mdicon . "nf-md-lightning_bolt")) ("file" . (nerd-icons-mdicon . "nf-md-file")) ("folder" . (nerd-icons-mdicon . "nf-md-folder")) ("key" . (nerd-icons-faicon . "nf-fa-key")) ("angle-double-right" . (nerd-icons-faicon . "nf-fa-angle-double-right")) ("calculator" . (nerd-icons-mdicon . "nf-md-calculator")) ("book" . (nerd-icons-codicon . "nf-cod-book")) ("border_style" . (nerd-icons-mdicon . "nf-md-border_style")) ("text_fields" . (nerd-icons-mdicon . "nf-md-text")) ("code" . (nerd-icons-faicon . "nf-fa-code")) ("bar-chart" . (nerd-icons-faicon . "nf-fa-bar_chart")) ("clone" . (nerd-icons-faicon . "nf-fa-clone"))) "The `nerd-icons' counterpart for icon names. The `helm-x-icons-generic' function uses this alist to find `nerd-icons' functions and names to display icons. To each icon added here, its all-the-icons counterpart have to be added in `helm-x-icons-all-the-icons-compat-alist'.") (defvar helm-x-icons-all-the-icons-compat-alist '(("file-symlink-directory" . (all-the-icons-octicon . "file-symlink-directory")) ("file-directory" . (all-the-icons-octicon . "file-directory")) ("star" . (all-the-icons-octicon . "star")) ("mail-read" . (all-the-icons-octicon . "mail-read")) ("info" . (all-the-icons-octicon . "info")) ("link-external" . (all-the-icons-octicon . "link-external")) ("mail" . (all-the-icons-octicon . "mail")) ("note_add" . (all-the-icons-material . "note_add")) ("create_new_folder" . (all-the-icons-material . "create_new_folder")) ("firefox" . (all-the-icons-faicon . "firefox")) ("globe" . (all-the-icons-faicon . "globe")) ("man-page" . (all-the-icons-fileicon . "man-page")) ("crop" . (all-the-icons-material . "crop")) ("package" . (all-the-icons-octicon . "package")) ("color_lens" . (all-the-icons-material . "color_lens")) ("cube" . (all-the-icons-faicon . "cube")) ("three-bars" . (all-the-icons-octicon . "three-bars")) ("cog" . (all-the-icons-faicon . "cog")) ("lightning" . (all-the-icons-wicon . "lightning")) ("file" . (all-the-icons-faicon . "file")) ("folder" . (all-the-icons-faicon . "folder")) ("key" . (all-the-icons-octicon . "key")) ("angle-double-right" . (all-the-icons-faicon . "angle-double-right")) ("calculator" . (all-the-icons-faicon . "calculator")) ("book" . (all-the-icons-octicon . "book")) ("border_style" . (all-the-icons-material . "border_style")) ("text_fields" . (all-the-icons-material . "text_fields")) ("code" . (all-the-icons-material . "code")) ("bar-chart" . (all-the-icons-faicon . "bar-chart")) ("clone" . (all-the-icons-faicon . "clone"))) "The `all-the-icons' counterpart for icon names. The `helm-x-icons-generic' function uses this alist to find `all-the-icons' functions and names to display icons. To each icon added here, its all-the-icons counterpart have to be added in `helm-x-icons-nerd-icons-compat-alist'.") (defun helm-x-icons-generic (icon-name &rest args) "Compatibility function for icons. Run an `all-the-icons' or `nerd-icons' function according to `helm-x-icons-provider'and ICON-NAME. Functions and icon names are found in `helm-x-icons-all-the-icons-compat-alist' and `helm-x-icons-nerd-icons-compat-alist'." (let (fn) (helm-acase helm-x-icons-provider (nerd-icons (helm-acase (assoc-default icon-name helm-x-icons-nerd-icons-compat-alist) ((dst* (sym . name)) (setq fn sym icon-name name)))) (all-the-icons (helm-acase (assoc-default icon-name helm-x-icons-all-the-icons-compat-alist) ((dst* (sym . name)) (setq fn sym icon-name name))))) (when fn (apply fn icon-name args)))) (provide 'helm-x-icons) ;;; helm-x-icons.el ends here helm-4.0.3/helm.el000066400000000000000000000040761501106761700137250ustar00rootroot00000000000000;;; helm.el --- Helm is an Emacs incremental and narrowing framework -*- lexical-binding: t -*- ;; Copyright (C) 2007 Tamas Patrovics ;; 2008 ~ 2011 rubikitch ;; 2011 ~ 2025 Thierry Volpiatto ;; This is a fork of anything.el wrote by Tamas Patrovics. ;; Authors of anything.el: Tamas Patrovics ;; rubikitch ;; Thierry Volpiatto ;; Author: Thierry Volpiatto ;; Version: 4.0.3 ;; URL: https://emacs-helm.github.io/helm/ ;; Package-Requires: ((helm-core "4.0.3") (wfnames "1.2")) ;; Keywords: helm, convenience, files, buffers, grep, completion, lisp, matching, tools, help ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This is just a wrapper for helm-core.el and a place holder we ;; currently use only to hold the package's metadata in the header. ;;; Code: (require 'helm-core) (require 'helm-global-bindings) ;; Build info sources and commands once called (bug #2608). We need to autoload ;; only these commands which are bound in helm-global-bindings, if we add more ;; helm-info* commands to helm-global-bindings we will have to autoload them ;; here. Requiring helm-info here instead will make recursive require to helm so ;; don't do that. (autoload 'helm-info-emacs "helm-info" nil t) (autoload 'helm-info-gnus "helm-info" nil t) (autoload 'helm-info-at-point "helm-info" nil t) (provide 'helm) ;;; helm.el ends here helm-4.0.3/images/000077500000000000000000000000001501106761700137145ustar00rootroot00000000000000helm-4.0.3/images/patreon-25x.png000066400000000000000000000174001501106761700165100ustar00rootroot00000000000000PNG  IHDR7H!gAMA asRGB cHRMz&u0`:pQ<bKGDC pHYs  IDATxyU;{!LB'@ CdQ(qu`DqQqCA@ dtvv^JSU޺Q'U_=PXT}1>@50;[2`9 kf6v2dȕrH@Hps 0HF"&Dlo9\`";2ː!CUAJ}f;x 4g$!CEj{AV.U_;'Mis}u@%_o iQ3Ln,p(u'74e׷`ȭx@sFn`@)_6688{5 52dx(" 7ƶKOp,zƆmU3dx7/bCl6.~6)gȐ r30p3 m#r 4`3Z+w*3ː݁RC Ci<5`)ckZc0 %L6xW w܀jZ-j ^ |4<ĞBg5mj$;w4" 3ː"6 I[ n`;$7[us6ŽCD{:(`2ՙ!CE$ͩ ˆm- m=;  5(x8"8<4m}klIE 2.x@#:ǁ#GƀZ' "M]Sw͐!C?Ss&Y(87IA.^e+4&y4 e4&2.gI|#[jJn9:[5 6zB 5'd@%{m624vH܃R#Pd[vD?̵A)kU$ oقJ>ݏ΄d~ P`!"I0!sǣE wyW"'g:r0EW=B#X> Gh, zPg]sOS}7"m(X/ӗf*qf?+D#Y>zy;&RsscV"b5M=_I׈&3zCq!ʁ-6y: FA(RxQXLD+yeȜІƤ]> D}"!?ysgPbTcl.!Vb"R+hȾ Kdap _< ;Q>x1`:IˎC8bۄ{͒\NWv.=uĭ])*&aO Ul{Bl >  TVHf`D}rt;ޫ~о܄)XfWQ; َ'1 7J?<҈ .Wb dQ(Dt{"M6V;nlM ;Pځ~K[9JPUU݀Vf("4 @ahQ-x alJTw><^[WC (06E >dDn ->e4#vˡi nn0ܸ>=9.'ml' d+O6C-mjȕ7->,ՠ@ҙJz6҆I*iԦ)0VG5$ͳD[ǒN SD2Q4 @;hb#<:P}"ZR솜"| dGloȸl]m 44c\[_M2b*DqM-ӐSȇv~yע]HDd=:FjsD0E#FԧO#"@޼ױO''݀ki<ZzF<`5Gb6$Ӡ TEi))h~UcCIdH ͟RHm\vt64AҮ@DZ]>~!Dj]D@keM)4c!6s#->i%]qV~c1M[qt 4vE:@kGc,WL{ GV E ܀mj`.m@ՓO cw;E4*M2U9 ^B?gdԱQQƼb7J*/U oZ=vyo6!Nt'~CMaMOې9%9dOZ@d+VqIp|bM7,@cj0DnO-b~BA%*3%0{L; bZVP22h|eۂu,qKaDk葳rI9#+іʄQ؅G1$r.xp;-MXl+pRصy0yLV%͝6FmNR܌ocR pnf/vѰ a?Im\4d="0#sH)F%".dz/ Xː;*fE4f!A y -*T_mo/+m 0*&sϴ4SH`+&4r6ƠB  ^mįYtV!M'[y5T#Vb؁|c'H/缎է+pvJ$^뽼 QFE B d`vQHYAr<S.aJ8Nmyѝ6٨mdk/֥1b6 ".03OVQ]B}-TkNCڸY6 O򨈄$)U%278alOǒEq_XE(AO $=/' [MEҀGɰ/`!ڦu_CGa5[1'P2|m(lLL6E2p}vփ){_BzqI [4L F A4p8 i`( 6uaKD|]Q-㻗M$? 76TSlTFYOLмordo䶁.D{V;Hc"˴U`_;c OdU&Vk J;EL*Mt-L(C`_L286fŠ2ǓAVA(4խ~JT7:||?U5>QELYqx׮UrANmr "(=9qĻ.q ĸ-5z 1x Kv..m&_;c*C*qT?JLɘC0޾mG"-f}qIPxZUg? rim(>;وMQ8cG4ZPVۨlR`'7qTTWNKȖt( #^6'CjJ"8خAQ. =c&xcS 2*9ga#\MEuδz ;g1i.Lz}ZJySY&^{h!TbC7x wzI;"OE;pQuNEZJhKj4~@ hioȮx-0ЃkDv<;Vݼ3/DxtydtTeiF%5 U/w%m. 1q8FDjVPO Ut>@#cPg$24e}ş$ޫ`\ȡjw!4v]!{ݯ׌saHb0_~Ч{ э؈\*&]5 Q;i"ݍΌںyph4C)~9:Nu魊A{48>{~ ] NMA aڭiNAdFrMBO7ڭͻ(ȁނ>-*7;W~̮wV䬊:nZ,{]i_'&ykm^5,쁫4 nvҒ#kt#H&棴ž dhF΍K"Ahҝ4e^b{V,E{~^"y6W'q^1r捹{(c!|0"mW;F0r&erۊJ7lZJ=Nz`k5UtF*Ôf&ztTcO4H)X@ Y%~?/oUqVOpI$+ |㭧WmnP@s2V(JޖCI7`N<5yҪ/Iߧ`N2=j;:xK$ |kW/ H3}:Pǹ@@aq9wbJфI`}in 4྆Uҫ|w> fª5Gm/ӰBFx 2Ur%wЏ^Kl5Q'݆>!feimCޢ(em.O@tm6#BN1ۙԮCKA@1乾 yPg_0*P?k;UmIs<=#jpiD}hG6pPKں萚G6l4݌[{BٶQ i-3l.w7;"ṫhE~4%$ EVO9Ӵ! fmBGЁg!tq?9D>-ˡ ^X6mNٌ e9. A)gw[o--5oi,,b(Or<](۴ 2 D[џ.Ƌ/!C~-"Oyl.ː!C:fmCBY %|T!ȶ27Yr}P*I^mhψ-C " U ";H~fj*3t9Д[ Mn9LQcCMS(P<rt*u2dnrjN mly%oFYA@Y0e02?WV-`mFl2ov vfd*T \Fb{%ہ9ʗ!CzҡP5F_keخBabڧheȐ!}[ͨ1Il&2"ːW?[]Ҫ6e }H*)^ȣ1w "Ȃs3dx"mr\- F+Bu:2Rːݍ-mC^gQQbfȐ"mr+ǖ ^A娗l!C.U%{%Ep^:D^ Q8m"2d.ŏtq%tEXtdate:create2017-03-02T09:43:41+01:00}b%tEXtdate:modify2017-02-14T12:50:59+01:00IENDB`helm-4.0.3/images/screenshot002.png000066400000000000000000055364131501106761700170420ustar00rootroot00000000000000PNG  IHDR)vsBIT|d IDATxyܯUYﳷ l@T81ĩB-Ĭf~̲rBSsj9qD@4xQ0A`?{qð7 [z^gZ>K^W9I$I$I$I$I$I$I$<p$I$I$I$I$I$I$o{WC$I$I$I$I$I$I$I]}I$I$I$I$I$I$I$!$I$I$I$I$I$I$I )'I$I$I$I$I$I$I\OH8I$I$I$I$I$I$IzB I$I$I$I$I$I$I$RN$I$I$I$I$I$I$Pw $I$I$I$I$I$I$IouNz3uwp^_CǑ>ۯM$I$I$I$I$I$I$IY/%gWRyᕇ<$I$I$I$I$I$I$I 8<Om>[ub7pN$I$I$I$I$I$I$9«&\੧ -p$I$I$I$I$I$I$uK>+WK3Wz,I$I$I$}W<>e;iY=93?g?˭o}kv}s$I$I$IZ~~vS˓>M>unׂWL/w|;5ᱏ},w.g'|2C;!*ii{UF7箇>[1<ٗ;?x?p8s$I$I$IZۅ|s$Bټ;{ޠ˗O8!y;l7k C|3yы^o$I$I$9OiJ\ɏlW4=bO3/t=C94{ooԩs曾92Ǟ~,{wcnOy_9z_~;8t;O} o&sv_nt[龉rEbN$I$I$Ik-kpV竞oҾG>k$SM$I$I$>Gw%?~{nxe+E/+x;z#\ @k 7%gؽoƯҽ8/Ox+߁bol>[{3yƟ~=镼?¶n|s~dwy7gpOv'iՉ*/}|}<|`;G=hv,I$I$I$u[6z(;wW /n7 =//vs $I$I$IyN熚۾W8nY^t{⢋tzq-o9gwW8p\ߞj{ ^mv!Cz}+qܞC/?N?|_>Gv;xc_<"8?#nO~%I7I$I$I])𮸶z^_xW.x]ڦ'ދg;)p$I$I$IAIȓ.~}}Q?s;9sپB$ {^$I$I$I$I$I$I$I'dt$I$I$I$I$I$I$$I$I$I$I$I$I$I'$I$I$I$I$I$I$Ir=!$I$I$I$I$I$I$I )'I$I$I$I$I$I$I\OH8I$I$I$I$I$I$IzB I$I$I$I$I$I$I$RN$I$I$I$I$I$I$ w}WD$I$I$I\|#吏pۭ~ Dps@C}Ff b( "R(7";Z0/QDRpk sZQ`w;ZL"Hq@h7D3ÁC- (qQ˄L*t7\a鎊`R` hP0!4spq*)uJQǻ EynԂ ꆙS(y։I"EJA87HAq3rrje!hشy[Q;&CUpQAq0@>7(R@Z8IiީuA AQ@Uq`v[GJ3p1x8} yF7mƶ^H)N*b;M; -띢JQAcqo-x&#  L;F-5Ɖu7՘pPu^ ?-N)񬌸fjMq-L ZPy7X\D0sAI%~.hY0K8Jo2)c,jUywcn(T*1WY^Q-[cgB)u[GKEEqs㗸:Ιtsjk)1Z }Dkw z)(UnF t1 㺼(HGpc,튊sq#Uղ T9ƆcvJ]̋X Z$<^ sXX1C)b(4T@ }\&ZbnZ㢎봊bq4IĻw#00w}\Zq0_Z07&)` t1o\P 91Pb=2hZ98V09(sh1`Z0hX+PPzXx0cq_]*cpw(}y}FKŬQ`m5\("(MDszSjTqg]L5 c/w5xv"c.03Dbu\/QtNM5܉uZP]0,X-q_kY:)>v({Z'[c{3zgX;9k>*FBWm)gR*J f3G )N]K 2-&xkj ۢB4 ;̥E KGJgcCW ݍ9? z+!`X;z]dTB"kȥzBiY< Q qI43(掊c@ .!:s" faT+wE !N!Uѭ2FSAD2X_ycr㳥@UƳ11 !jGky:JcP≒ὖk !O]<~➈(= t2-ņ.ABknT*Z$lҤGQ(B*P&:1C>E@H(W1-ƀ(Z㇘+!-tGYCD'2CR'th]6 5]~\;sPb^ZQDeNÄ2h.c+6  oq^#୳m. ;C@tI$2zŚGspRX bn,[JPбƢ,f}`rBsjYz3)Zq%or}IdgK1NXG-\n7zXY-q p!^:R*gT46;,u aF=&>z8c#pe VbtkoplY!`i aԠs8h5n26ۼuw!:6B:*¤%Ѭ#q7>fgp^d9AWTKKafBp9pU*qUa$C(e5OYt_R4dpuLí3`֨>!rR:3*0b}:wtD k -XC1 fepo-QE^7L_98zl`(&Zsh(c5[[ǼQkEZBDm%[ߡq9HUÀ^- t>g8ęےl.:.6C0X"yjgCpDnV$2fexHp5Cd>=sH8ܘ c!0cͭ $5Ŗm1ޫḟ1C'f0>R%Ǩec bqA8;-N:1pU %t%\"w"R0!~6!bEAǹ8㎺[bhQPd, {,(C6u s= { Ѵwz5u]8Uj 70Q,@1~}8Gdbx1 !nጏxbS7pZlFPk,NrQQǜ.#,.pbcrLXa4i r8}K-D4͉1ȩVD*Y`j!:ZE2XKXkXKdxՑExyuѝ:ށ(jr=/K 741XKUYw 3t m0>ִuqzc-Vn34++C܇r6ASk֚E2XgBiT&a|[q1L HA(*J8rYt P5汉GS51Zh(HXI%b=\n [.x( -F TU<%.n߹OcJQNz_cէC>[G?Ǯ>kĕ]Wח3yy(7sg-[x;~sksyyҩ0oz+#R\uR8/QUre[ߊWl{2\3vەK$[DUqs2-~^{ԩbò7 f<=n)ᐼx^GB]2S5]46<02cbSBŊ!6= @Je675+Gp 6\T? N7.6piTAbcVJr2FULBah6 L !aB̦i;JH6c^V,#x8C| 8\AێG*^Vc5D{s[C`& Q, pN#Su7ֵjٍ3F)D)e6ff("&Dp4GD/ᠶҬe-gN6""wg6hbWLQ>7\+u-"!׺λg͐2!RLiU =&m+v 4'BX\`R(B RGk\ӢL*XB] G乇\%Bⱄ8.!*KhDDk, !j(Zj-fMhB@+ˆnẋ wF[-"eHWbNy9ȆZE(YԉZֈbjD*"xbLh wZt1PEW+f=y{Z5ı!8Wj)Lu85H"!ZX+qӐ8޻8.JwN|2QJ$ !0 pڈUZ[$DxLup2(0$( wrY(HT#v,Lb[ 2b/v(YЪBUҀzi-&ZbwVdku97bח@6}}+Sp>DXhww(.Q82( r)fd!2F!*YmLeRe[CE(s*qL%-TN ʈ/"Q|׍;E`2푲@4xE#Qe}fϘ>q;i֍$ɮj Ͻ8-?O7r{rOhҾyNx6??<"NbCΓ}lMygq:7߁(+O~t 8_}/]jݲ7G<)p]c\[peױjG8UF7!yq?=/WOxm:W+ܮf\>/Myk}޻g|QvƼ}x?qOz2 _•]G{z>0^_s&|MCMv8xa`Wܿ$II#ڲذASG԰4M᮱p-A(k@o{a\(Apř|IZZec=ćz}8IGkD*f!k6XJ̅!܁LuA)ʦ5* h !64ܘF G4Ei=6p .-#٭33Qk6ӐYKI@&J}ld3Jip3;&BO7I+Ն8iUD).̽cuWDLwY"v9Zj##!HE<NZ(` ē*K>D!^: NξrHwbSDz\@($h 4-! qRGD826Eˆ`[C`r_!%_қј~y!#2uވp腈z CUª羅 ^CRCP{[{4zt!R%d s/@<)cC FOmXR2"c^'6D* w8l2тiC 1\Q#Z7DeFRN;-e vg S)Lcm^%k;Ԏ5L~0b]ǐ|$y(fwi>8ūV豞9qFB-GX ,Z> Dq8ji̍~z0G8.#nG ~1wg1MT YV,RR+F8!^Nmqp͔&c͊hi⑐!HX߀"1H`Bxq}=k$rU+fQ}(rQxUR"kAb$D(Ż#YFH^֍F,(P N^;,Ś)'(m*:Y=bXzrwp)`v[/ gfiZG4"C8JeZḼE1DE\kGA|DP.&Do]hoQ ^$ >\Xޢ 2M4w-E?n5d^am-1gQ -z7:sD ¢.XTטYzgsgQj"-oy=(&½Zdm{z<91^DY!<좵֣pk()c,V kSZ+$ W(Zt֦`*swubQj+RKeVḷU$Iv_o/lڴ^G>уGE={ =\~3~#qu]ַwe~v7ӎȵ9Nw;:v6;2NY Ӟ|q9>_".Ç>e/x.qWx_sٕxs?y<kt~\mLib]GOTtĎ52MkhUTg#juj B{ aʑEx%\8ӈUf{$z4x#x=?MC@ui14[-%n!@{ibF̰#jQJL.ӧt S+"p/i%4!d9-X{fp -X.gEYur}-BΤ>7ʴX#{t#J-2(aU:a^hBDqX)}#a7\E 6DXF/Tq\mmN#1c GC@W>%2"nިћgn-",'^"#ؙz7tU2zU_;ܺ:h$ bJxn}p舛.CpZeĒFz/nU  U82OHk=WzEm;p1\}n]oDyH ׷a}TՍs5z;/jE-Rw1GZdAj"2=w4{iAsx'E(6 ]CW=K"- 5>,||NA"ŇEyI)8~UX6#z$ yQ]ױ}y((Qbļ! V#MN)Q+0UѫV>cjCJ%ޟekJkubǼpjs_\W"։yaH~"Nko=Q G =K A[(Ugk DIm|JdqyS~g=Eb'z6Nw昣^«FN9tcwq;Or&WR~7%{kx|׃~{O+ ˟8^yx V.xbc6DvdSke{q;ܞ+{>;s}I䇏I ͢](`|8RxDoo|HbtSG42cafqQ&*V%EŭS@JlP1-#rSThppشt 1[]L@g#Z/Js҈@evcb-0Q Yr҄aJk%R#[Dؖ"R`8*x9C0T Lc9pfh>p%!ީl3e¥oUis'p iw!nL5Ns6dMb6>b_AGof-DV[kzQ iՃ*-D,`p[˼ qe8V%"k1\{fTCL!쮬!)N)f7m!2wl1|ه[FUT*͍p~8XUt#6Ӑpyiz! aRC<J1|" 7q ՊHeD<"1UA"eMjxEcU3;fqR>"#Z>faJ\D|eٖcD}\xc {\c_Gf{$]ƚSd@AI"ւ^.1~tz&#BX D$ߪa~%wgc:9?!u.mz ϧqig9A|tG/)='rO2c9e/SwgN?`X\s9A|o[)'-osw?.|wl{Wzwtؑqq {?~O>}l9 v◾!gW?&ַoz+§>>1;(o|搟9mo>|}>w*ˮ}Y_*+^r8Y[dMQ:tt] f=Ww^UvdꪰƎ>gx#qoG>7i>p;Ϝ̿}>:W+ nw9?{8K~M ;z܎Y'}4$3޽$WtRPcZL%]F7pʖd]ލ>kk _4tTlD3K})7Pbr3Hub-u*ϱ),I:v"y՚lNGkD=OuH Q@ G;@;#uCdED_}9a GiS Z~71)R(6z w֍ac JDGù#V;~4g"!Rƺ,#,C[WPѵ\Y9ڼ0E4s G[^S\KwD1*n+6aP a 癎{0}o9!:^;0G>CP1GsQt%EG?Opm{ ':u8f-0)"X4\}(/#2y#z!8]B׈SӄKr+C$\ۣ5 H7yua;D1GoH8e$ h#PQb +͌nyh,CN?.QP"®x%37!b"[裮e+rc)k*D!9G(fLN>Z<찚CŅ:ns 7ZG\Ԍxgh(аh!(8BF'敍"9y|Qb QPk]Qc=IdWrwyO_vG%= _n??~;pipe9އO46WzϞկ^]N|G?:(7ؼ#ַs韻gg^p㑿8.wyëfn>>CH??s}-'}#\pw>'?}~Xǯ\]|/k֫=Q89Ϣϝƙ;W:vkc|EBPXsaʛ "S)c31\r3q.b)?"Bp^I1-V=ecԆ R=k7\%׈ QAkB _al YR5NyfqRB`]br)Yy"5}>.2u v|_)>5CphT5>c8=p+yeWU?k[Bz }HFQCNiN%v@ QN / 4>ʏS=H>4!$sZs?\V@nU*g~>${9YkSkPCo<b@[f0nS2r=QO@#Zd ⭋Mwfy4dtH2sK3a8hafpj3[ٺt jzӼ.S%FS Ahb-)JK/:b2K$P[ +f DF".=U)AFeTf $:I9ޑy p6 \gPtGO * H|8)`iF :S68J)SDa06N',4Q pR7ebÁRؠlՂu[@jK0&w5so5f С3Sr=Mg=: ={g+^8^R+(ens)\cZsf犘- GH6H:u IDAT!CPL5389_t.% G"\P=os= }`[+[GEp-ERFHgg獂GgpClBo&XSR|w6 R`nmWC7269zx' 8Pka^x 6\if1i>,WgXBCJ ,khZ2a3WN~64c/b" &G, ;;ٔaM 2ϥXOi4qmR%h83m9hc[7,k/Dkٰ`9GvLhptQk:"gei,I9퍮b>5@h] E ܗA EHYa.ւ3ʜ`sָ罵Y ♳K:BXK6$_4sxTÌtfճAgtI vԦ6@@_R?/ qF%8z7S??qᔏxNN/y/xYZ.8G?)x=p.ڍgم-#'~d1Ox279[^':?[lyы\G? '> 8ҕp [} <,8/߯sݗqzc'j_o'q u[/}pCo+ny{. ׿{kyN{=.~(cgx?.<&wo?K?[ {ٗ|X_ Wo;>nP.w}~N;{֭n~3Y_/~߾NorWNm?A7)i phU3EEtgkPxvw)B}F)iᦺ7ŵӕވp}8aD?`] F=_  $ x:z3{4B!GjRȤH0Ė2.Mg bƜtR+x%r\S sΙSNfc齳 ޘWKd5R F'7zC3wJB{G u) tZ֠HڐX5:W")b)yO}Wn;Y;yӅEĚl0"GzfJDKCut #P*Hs6PСCm¹%ƽdBqx6jXφ 6aXbj88ꪘ\3Aeי-ِ2jKޗ DExKWp#tZKz6EZ{3HRҀ!dvwmG Sx٠B:'.<1/"P9`85s~(r+3D*1DZ"5fFwD6G`NC6 IULhjàtCo+C#8q6ϴޡF䂖N|0؈aKgGgf1+0f4^th w;׷[>C*3C>$a½{8ZMy%EnFIl2's8QkyZ^U z>d),Fkb8X<0[!.@>򻊰AK`5SEV %ydDuF~:Z!S:E<حeJ6ې`gPX1D kM~GsϦ@4-Ȇ\m !@ՊGsM-"WDn} A)* @1/zo*[1QC|oԦίg\cwsi?h|;~y~?{2~aWP<.~;:^~O}G>Tk|v> R>SNõy^s;~_U8#`pw‰'-==7}lS38Sw<<έۭ~ 'E4?q띯4O~"ݹg&7Ƨ>s^XO4N?{1]{"d#?DQ:d'<Uw6 "C#0m:rtĮz:m"xY-Wc&&9,P)P3)˜ 3)NP X،_,dDZdn$xoGx|̌j 1BҖTu41*(帟;3)EK.ƵT֫lX!MmqW36IpZdQ( l^( لV aA DoDKU}Q)FD)IE1wfG(y3Ґk@GAV0Q넶n x@(f 0;{ WHpXa؄d)9s π;.s0#3E\bMTzmL?kZm=|gᅭ{^^猏}>>83I}qы\d81ǽ_C~{_igv.{KGgy~w{:.~Ck.L/~(NG}_{SO=YΏy7ZW:.vы{{s|<9׿v;-p]=0y83q}⎷9t̎Ж߰tEEB}_~"ێiho&Zr|y$"+\pPe3[6BL9~+)g֤Y_ f%'F`H[Ф5N@t̫03ش kSf7VaAa5$dG{bP:Pfu[ѡcKg0zP.p\LY6AFB5M96= ?q[,׾bbl`IӐ\igl~y7MY~v=}{g{;\_Swv'?ރ_x11O8vGaҹigi~pCw&v:N/;?y<qѿwn[n{o|or9taϝk\?5uW*m|3Ϻ\Ώy7uoz3J)xOWuگ;owejc(aO138VAg8ŝ!Q16ɝyA )l i*06(RБrGf~qb6j2 d$8NMfAK: 6T*Dl×5DO }a=*@gH"k[F,(ˀ5ҙ0v_ 3](1ʒY"api K踇{tx" E8y^#:)Pt ǭ7ttJ1nS8mtNߕ٧\zPR/)Zo( m#ꮙ)b `2,q¦B4o@ȜT &O'\$ ]o!Pևs;׀lqЙY̝KXZc7ccxt\$ f"%p )9b!7t!p-k<^l))F[{cW`)wZ Z)AJM״dĶm+DfH60x"B!(G g$;1!4|/t+9߹Fn0C8O`[NQ.命H\{f[r͌plMNzDn]u!ͱntG~k\b򤆪@k=ʼZȘgBH@C09J61 iX`@AB-bto6MР'>s˝﫤Z̫5h]ݑ |%Q%zj@n s pI ;$טy y\j5[VC 6'޽V6hA@}8jPI`f"a|mM[Xv 9`ٝ^P}b;vY@(7(g,e Ǻu~O `lb"@6 I4f'mjSkp]w˝32:W/}!O9\Ѓ]wVpsϻF7 <)O' n}[kk εz(>On T8?R?r~ߎ唽淾u_5!v.ɼ|߸>y(>&&~z׾^"{zW|6Mn!|u|=uqzQWUp/6^z_Nw~{|'g=/^7mxɟ=׿εpF"C(C jACa(8lAs# ZF0ұKWp⠫*(Q㈠ EBH\J$:SH2B5'W~򆛍n4AisG); 4a SDDu80~Hq_K[#ݵ͑U: D wҌkta8C`Ct,d#EEh jJ;M oHJ˂cV e>k"S,"Ř-*dLSDn\ JUӁ)9IkSZsM.L+ṙ7⤝XR&qNNwf$z]ULQ 1xj]!Qۈ/̵M:N=R^[G'f!-ISs{;ގy݉Oak3z+olA#ytR%3yo%D82|d\ (R"[ 隂pt cVT16;t xo`CET[{]YAZY^c,5잍-8ߓY5p^+ƃm]’tp`$„RHU-Y"#27_]s%3F5ERʂ970?@A`uk3) sW&dCp:{ockʫ=sc,=paA>:!ײ IDATs5G '.kI`Vf#ァxZ*GUcĂߓ8(2<:PQZ&1&ixP]\S-r%Z(AcZ""3@KIa`׮]o3th-6Wk1I(e)0%3\zx3FZѢg. j8oj\(zAJ7>II Qfl󻫒JU@W$gk,+y?6Mg'?>{oǯw]\^w8dz$~_S8=籓q8q '}gopk_ w˝?lmmn~W"n?OYRr^wcx̱O>/xkg> /x3p$~斷s)'ǻuvm-qy̫7 p{38gN,"Oxރ~8=>}Ծs^Ky§ѻA7y?w;'`Nj'?ף$|G^r||z|x%.]׽L<8Ћz׿8~׹5nS8׿q*Nޣyee;|pߺ חMmjS?\ALQ$0a%vT !Cl 0t "VFw![ ZXtxh1l :>`0Ρb̬2>(}AF΍"[ӹtۉ Fp SpZNבts^{Kk8M tp 7#-lvy/%%.u~m^cN'1%yKC NuwHU ãcDswV*¸-ˋV+3,Eo;^Ōj;J`tv4gF J3CFB ]%kFA1灩X,=aeɮ)KZB<N6`ntKe꒙) (ȚPVpc#itB@<˱4Q\ηnmh|.'%RDEd7UbU7k{KT #~tsN۾NYB',see42\RaFl-(5M,k.{=׌tw. p,F E.tvUAY;'"}m"hm(=tܧļMazPs$V8gw^ X1x<Όבmly5C~{;| uZE|ΎlkjF9C卤2j`.`c(T|sfč[H\{ꯒM\{~ȲRӚyوcfLM`ΕCC2?=wGo/9lq,t7xag`op67w7ڬEo5݄ 8N7Db-ETklh ^ch5 +?uFn5 +^TQJE1E8 EI2(MTHjQA)fHWH-$4(`fx`M3w1F3y>9.IN!]9ƖPl @QQT`&gԦ~j@/=翀zqX1|/x1q逮r+sv|>/}+7OyV1{oӞ\bÑ䉏.vQ|ϞMt{q/w ^Wcsƻq(f=:w+i {}[,ai~p&߼ؽڍw8L 7!/_8Q~ ~뻧=x#~s|݁\Og>OG)} Ngc׿;Mweyox9:?֫Ծ)_"'aG~C/~'Nߞ53?+ʗwo~wǷqnϷ=^>_x.;~3nox#|#GKq8o,cݻv^SMmꇯbH)VE CzBUstLQhN@)5@igtG&XU+'hVYδ@8z*1Yϙ MN!JbKq!RcEyDtv'&jxft 7M %Yw4tenb,9(զ|Ώ(⠘̭kl @mq9p]K(p$s@" - e"{KǪP:6A_QwBt1MEJ7]fX2kόK`C<@%Ȅ%aA +N{"MZ973ᚸikY#}p6@NTK!w`;"2jރ9݃*9vV!ڗyۂS1n|HrJq.."| N\l3+-"3UUH!D9sWá v.A@Z"[RDBD?ռ[{d!S +%h*\K4}#f6S:dV5 udaS`E;38\}f5 fC^ϒb+(8"~WMR3<.NXkd@k!sy^v 3k=W GpX19̄fɱ&ט2/s ѠxꅴSi=Bw`57Xe6)P%Et![oUYϒ %%Ҧ6Wr<>/@<_<\җƝp~$tGw oG<1;؇)8 Mn=W^ʗ?YL0 Lqk_/yO>cm֗MmjS?v>~=ҙDVVV 4ҽ$)XbS0CcJE- 4!R0a5Jn?׹ZC4?nuޚ}-Z{nVSD0`f*1y+ t (&C&o5h=$:ԊPQyU\}q<;L,tGk&P8IunyM#s$S)u2L˽sJ`%75ETt!]QjtxO)yAli%u{.3zb#/x-c7W7NtnBGqDҞ5UVIwVFi:ہVPJjt{660±QKɦm{[s6 ,.:72k);떙BAObpުMKbpBmA C5Hi !IɆ4DNqlP()Fw&ֹ7bXSMOcAUZϰZ19#yo9Fz jz86DG C6< EgqȂ*Aw(;(Gi+m`^۾lcӳKL89\ɚD[s`.&,=19?+v2<^qvXsBd#}_1D0*JKHLLj +tXdǧ >dC8޲d;SK`3C){'*uf<ht#[6m낦k:+9v<ڲQLLؐc$рl K'x"ƑpD \[ =פ!+E21M| 7{pW +[yp$K伢BdU"zQ 6߁[ wn}:1U>;|rl (H՘5;VLuqI,|ah@+;vՂuwm9[2U^*P0)\u6.@OW-=P0X+lZ R={/֘f3Z3VkR'wIr$xZpLlH쳱eƊD'v_B T:ۊcQ@R7>lljS:b‡/"nWKpßq>MmjSP7OBDWy>Me=ا_>g?`^Uk+^|؈3?ua^g;>0q#Mm@k[[%(* X%&NmuCb|CRպA-QtGklAAE S-f BYfD1 `Vn 祥ٍ2Òdfifx 7mS$"8a锭F(]Z mhNT %Rݙk\,H8,(6Qu :pB#PD'HJ -K)ԉR#8zw3VYͪPғ ЋۛHg:;j:#]k"Ri;V[UT08b͆yw*ǚ{b5 1*{%Ś5bPK@*$̔XH>ĵT_: i)(ogit) D"10"]ylp@:XEʗ76!$gtm2J e3%Q:"9NVomBqtzGB*z/9'Z_d ]7yMHQu;U-8wB\5ڒ]le:D4sh<TX$tܗN5Mt A2s\BwP{!|0"JKjއ-Af !ci I3;A[=NR/w: !3ɹx cWMCurJ^}[i.K0pڲӀ(K7jtasm,<Qx/fVLh6(1r[͜l&Ѽ_̠#:]bg`P#!7bQkZVQbyWI.ۘ^|Hfr~"k<:"d%,/ULjIvgÇ r6 Zs瞠5)ả6Ov B!=mjS?ҵ_Y-gxvM[xz+~Ʃ>MmjSP7 Ϝ|tś:xdp^XjgҽY:FRm^W5\"MotCwGVeSE 4HǙ 7 `nքg]SheVJI$t)8 ܈} PEZ,MiJDb+dEJGT;)=jc\5J.Hcv@S ss=$?oTtpx]*ZS^{Q$(n(0ύyN*O! ʱAoy@k )8x D?3#aWD>*C)F=6u,Ofrug+E@V]tvD0N wt H_U1MA7Q.cl:(ئtΊrLts'*y<Ԭ/k"ץH`XsKHuʵQ8HD>zX%܊3:|fmgHI{z("u6HlL[}5Co5¬\{F*rl7@B4ʲO E Tw@`-N0K5xI$& 'bIa߫h3ʬzDrX':-( &t~njS?uЗ%q+_ ?SX86: կrZⲩMmjS:XϷK_Rx1Goq3q8q׾ۿyλԦ6MvwLXV2%K4b2k N¼;wǖM•3_Nfy|D/:[E\Z pF1tk m=s#:# |\W fN_04^Q!Uz,Ub=|L5y UÔ35A<_=܋9xC8b֤h@U:`"s3$PM@qG"Dho)"t<#]]g]MQ"4ݎ{`dU@4)܁¥;=c#P 9 IDATDP(<\`q1Hq * >8֝X""5iEzO DC*Cmγ"Ak3V+D*PtvnzK:CtfkZCzc3טm‹<|p[5PD@[[w%AJ :E|wQI6wE8s,&a# b 2UplzZ{u9#A@>u#~\%{F7Ta:1e5(kHqK,K(]lTgWpr97G{[ftA7G%¦5{}Ԧ6uչ!o~sԸ՗|_9؇MmjS|ƩyԦ6ٹɵWv0Q1)]-:VbUC 81=Z}nBl, nq <Ӛ;f`[m;tylʪҽAAW;EF a%e 3 IUБgh ) P(Rvt6q2_r/L9 "@@3繩<ƑJgq3 fM+-uG"Z'z Lw:@H4/B*t`\==toۢNa8)RvsEFleuGӁG@͍gKac`u5f=1 [PPXDwc;@l z/@?w5K0hJ 0Ax|eC*)CRER4j2m\W7.cXI3lԐo8kUuݛ9-λ1ƜTUWuuw:nc;DKXƎq$@ n@%Hl.H;qNld+8s3|w:uV]~1\kxu@[ČkYqg"^yZ3ts ܯfI"B5ϲ9aFJeYvp{kiMy~ׂJ:\'<⁽.AG?Zư@T&0 e6톤|> RyH y>M=wHƻj;ꪫꪫꪫϵ|/@9qq"MhǁGW2' n1L_Pmd N{K{ ֝HP#R:ZGVĆs-nfáX܀gp`,Oq*'$sL؎DUkP hZ ot]/eX"+H+ՒT5UB53[Ab@U2J&pCO:U 3?@6Z)(mk5UaWJJ:(+B%`'5ȚTx=Ucgx:JpЕÃV1'C`ۀAHnvZ');w n # A3drogm;yu7a|~1 r3ry+NЛ1b AE13Ck5TiBhMlRC{q( fF=RœZW չfsLI6=|$։8;kFMS69᪻ ǡa@7 πlf9&n ]2"̌tM) H4˹Җ`zx#%h/ U39_VjϾf6[^j64*b#QBmGߨv11dk5¡5BM}s  ZM G`q|#ս-Dcs{T9fm >cW*zrMyZzȈbD3m㙾"0@|f5U,,jøꪫꪫꪫ* R6 |h5sEE^"Laˤr*{3r=v&AC@땠 ƤZ_csE{BOH!3^Hx*~P +Y׬Al8 UFeKpeL j<Qf ,w`Eba3#.Q_M/sLy*S3A`F-ڂ2b3KZV+S{SmG[T f>]K9f ]hXP妶aRuG2-"3-ڠ(J% 6C{UVWkg(^IlyX lȱC+U#t.t8lx,KJUZw&lYpt2 V% t) xM]u_T Z)7P,HM4_TWPQ>/u\Ljy>])Ͻd Ԙsʵ8{V,[vT'LjGJu^#o9ZkC[B9>ט@j[P'm{1D04ud/$΋&q<5SH.xͥHbшyVgG5uf1_HrLjH5hiEi,B\¨v=<Άjlxַl9Wj52yS>+q=fA0 פl+N zA_^j1f*c Jʦ1|EkFh[`g#htIyiiOHXP1DS&N_ltJWjl{N{fX06HLlFw}#OG߱+SJ?$3U1q ⚒ϐl []wfl~ ,Y@Lluh =#HsxE+TGFtł"&]i5*&K#k l<14P뚅W]uUW]uUW]uUW]E?gXc+ ijn*ur,,x,/ں8nm8JV6M8B%3U[R87 XTvne`fT &$Rw͢ hRJyM9#Eqhí8:7̙NAfr@UMsڕ[*i+iBn{K[ghf&4Hh-I/2`-Bh_퐝*r*1MU`'Sce9Ѵ%n+\`O4lYlx@KsB1s}*?/J3H;R&#튲E}iՒJ,pXۊel #"~m8e ÆʶvTa%Q @#*GMj-ۺ Z`g"ymgِB!al P ײ ˋc 햟4Ec[ WKgA=M)-N W:s6}m]u^ˀ[I1TV-C!9&D/paI=_͘[ 3`͗N'b;cV= 0cWVP dg"!+/.7vXR\m_סV^M *N<5Dly=[y6+ǵ);jD>'?oyҶ؝Js y;Vc{m{q &ǵCX.w8-RsCjg88!)ܙ= `$<|vؓjV6&)u R6z0W6 |+xgpwǛ{ZӮ\U)AsmM,llhs! Z09. )XG^GGYwF;PXKWʆUV¦)g*h1k$L0qƉ|Gm>G3ܬHw72p_)֠@(Ơp// zKPm&t`TFӱҮϪ.|UW]uUW]uUW]uUzO8eb1sMB 2CV43Tp V~ P  e,G5P> (JW gw M#<-$+"б*Zn"7܌oͬ _8O7X34Ƥ"*>D R$:p[ pϽVӗLѻeFá(YZv64XHT*L{਀VF d?l=AV*thbM `̱z ΄HH< {sHP&MEն MaZ~D'\rfTQ9r.Z=% [ d-J.{fRU:iVB570kT[u&/?T|ygI%KK[Jr (h?O)#" = vzRa ,k{t9 IDATP("7\@ ( Jw^r}ЎZ5^q&n_F3olRm*Z:$-rɟSrM^d+ı&@c yamp>#ȐEmM۪ȦǼs"3+W6M; }y(̫tkR8"aR[5n ڻMYW㇊Vދ=d͜cd~p*I1Vk*Ѭ%Cw ( ݩUPT%wJY2w~pd P7s@til¨&ȩeMlN"+֪իuXoѤ)߯N8iarmm#Vc/*0c6; HS16TX8>e| Oq8'ZW@]as񷿉_淾_{K~o _g~|1Oo??Cmn%›O>~>F?|[<:܁K _OşĿ?򋿀~3~&zk$UW]uUW]uUW]uU/*'R0UU;ea Pۊ.KX NK>U&̂]4v` /lS٤]B<0ϩlWAkTI'kRT F_i׬"< 2v*S _dH4D 6It*u !vMߴ]aGڣNukA_h4r13Aplg EMm/Ȏ`e=y-A`7KWMU+ ^sz c :|muhwu6@26&9#3wtڛ{kŏ؜C~]&󋵘 \/a_YȂR[ꆁ3[@9:Һ"n>ϵ`y +}r TCŜ>^WNnuʆ++s06&ll*pY=!9v>ۘRuX3Al,J%D8Z}[ M k1 oOx?:Q*nyT -\ }\M+7] 8}8ApifÑY.G̮Efk@y"HůzpĮqPK;};B`H7$:LLg[Vl`;lD<3^|&) FZj&9\#)vt81PQ,Rq'X |9Ϫ'k@ CkO~.ڦ"z:~~ A>ۚaΑ3=0׉Bo`īWh_3!?%?ɛ;_*~' _ә0o~ͬ_V _"yz}c+?xc?1N.C~Ev;wpox=|+_kWZǷ?*} xO_uUW]uUW]uUW]u9|՝_ &X8[NT m2O6E( =^S\0EL1qJ/S| ^=a@׎n9i * ޹I?3":P2G*Wg8 s+!KT4 s%7}]sҢЌmgTcqWR]5;ݨ^hX!{|``aĶg;`Zp̸ɫ:&Q^֮˝Jxd xbo-+E7, 晧@viGƾƕ3JH['tMp=c)׵Pfv2W)a#ҵ9xZtqqEkQr}l((3 e]_.rhNZ9ߒ@8Q" ڷ`s K ϙ五/˱UUDٟ} :βJR93e%}sPݗs- Z ӭ,P\P@sC0PTXyo i+ٻkǾi+wpKR· 9a 9^5/fsZ-5|NQ 73%lL^z*}-C @l"#,sCgޟoq"-xYBȆE[tn06t&6Ø\9(⽚j0D|̪~aX36ƤEGYcYiA-"<@*f4 &56b8Z_=sqhc}!^ِ J|7$gt ہ5ML r6Q>4W]ugT6)| j cN8Z -:z#h[ܬ!ϱЬY)eWs8t;>1<'<7?o_ŏŗ+Kdt|/} vjiv܊'A_m~kbx񫉯I<Sp7#?C{K~? ڞxČꪫꪫꪫ+0+1,k} Tj'*ܴ f"pT֦}^A)ΐ5Gʯ]J("I% -\c_AK:3J&i^xA -|#2UܼfB B1,PAiHo "tރU`65*iG\G S)Zj.& B9NZQ xF^lTj$`y(HǾu*E0y i'+H[ݲEk(P4m -ˬ:kR6sĔ``۾*]<ƪ9[*ބ/ʛF;_;/hjSc EGշjĀ="}uxCK;fz^ jr֚Uy[׹ sctQ ]1Y#$U)Tqߩur"s}/XלyZ s[,u~ !Z*9cC飵mNj`3y]͘_體᠓B੊/hF84A*mК[-ZK%MsmO@UVG7_- {lfyE;i%aQfm ZWt)yAwUvְes;P K3cx56QcTj>'s |K:V@wwX>DŽMa"+y܌\i !pHӱ}&`C4zU4QMCy?(P[9NLشFZGKE` X`ېk2l`¡&"T0#]}u;AcQD \uUm}Oxoz8kMBo\,-|n+w_C h:a~ sT\< fid5 _=oW7~p>c?oO$*+|\ pZ@RmD?x*3Wt/N;7}wӏ~Ow[c.Ͽ'wꪫꪫꪫ%qTs^#C"^S}~0TEY4 s,fPcd^gT $7Ωqx4lfKAϦHOMhpޘZw[BGZc&HXhnr Rh@09B1w[xCjZs-n içht(FvA0`ÞM :w92 Z0)c@5opx&@W۪R*@kûY#L֭} *Lakc3VkD"7K[Rz 7=UR'%Ղ(&TAYcʱWʂ,p[pF{ecT?atH?X ,0}m7-`Oi5h^u&Eo(7 qմ8K`LAM8" \Ve^+֘wvڠqT{]oZR LgSg._icS-muy&I0TLw3Rwi)sdkG;p.:*p̦`pi@Q|r((4Wu(\pW6`smeǂ4 G*l\'u"UaL2%i-fJpŜ14Feu"g8y}oua 1F;de#2+Xp"g0yb,ɵ"A?w͆B+x;s`r ٮ곮 y>i 9;a|·A0 ˃ fV6/ZܞnxPId'D O'N:L^cO0Cf;|~_w o}WO?OGǫW7fl{?ǫ0`ć{Zd\1'T9>0;x ~g{|_~[ķ>OgO |~UW]uUW]uUW]u-`rNup ծjlpfL $A dZ0unBhc -u_XOrHk WܬaT-*_b8WaTGI"pī~g*Djr#ٳ* 0Sw㜄JLm,hK,ˀM-hPa2#ĸBz7ZDGU0 S)LdZwRUAX) y=U[0˽* *sM5D)nA4N;Rc &=b|A(hZv"HVިզ\c!a;W`$hRZF3;'s:T^+b'y\@ZωuPH-8rU%XS~VչթP mKֳ+TP/u$|8PM63N`)hA_ZֿfcV*%7JEC1K,>Mȅb6ʪ[{"k2>_J-uODHKD -s Z-_i)i]$(H☫o [RUOy< >cm͞w/nH0-qHsOb3/welh_}imD̡.5ZTL\/K9k\6{R-+q^x49H5&j ӊXsnU",(^qpQzw%gj W3 Ry׼sLg˞ϭ Vu}؃D'@^{;SqP%Z1IEF0\w^ZXB7sl4"ЏϺ5ZX#}n¦=MZtǂF6ՔsnyWײ7Kd+pf`Mg|>я'>4T6FU.vi3Xjh_T?Z0.Ikll Lh٘j\eN&2":1c!$ fpUW}V=`H_bڜ_:nנq4X.I>}d^E  Z s.8N4Y(^>xs Opj'10њ|f__}?iT7w ?wm| ś]˭c΅-| _w_X~>_O>—_}? _~19sUW]uUW]uUW]Ϣ˙:/햛sj8RVDIE7(X8Z'P+7eM *{4ӡI"Օ+`>Zjd6;=H`'Pf3`J.@/$S3f#=Ԣ-5 Am=;T065P!BK^ n !İ`P#}-B)bA#atrCBTGqs#2mu v~eSmZ,n<Eߛ9ZB.8Z_\J& jh}*lFeA5^5%TX"S Ο }\=_3߲i~.XGοԑfTUj:p|l 'S@kkB nrFv|}yR}#v[,lNʋL׹s3k:O$Z+ IDATZ#؄RH8J[L-8qAxq~ƶ.@U&"xPk>A-sb7k청^{`O*a yxu3UƆ%3)ح _@*wTA6iZ\'zҌsGB:Ɔ2>)IpZ9a#!">޷rw=+{aMݬ??Zc0,<;D ㉿dSȀ逪vZØ &}6%eEX]% vJ'|o͛7@Oջi8?F$" ^|M|6kGt ?S<ï} __ǧ?K_">޻_h7~om5ӷ8ZWCNOu4kn.]G9ww^ꪫꪫꪫHMv$奛Mabv#X': 1s*NC*+p9{ 'LтB=x:HxKLfu7D2{lw|Tnͺ{/ HH]"@(b"8nݭ(Vv1^gʡZjaڋh:-t w"Q}XK4o {vbt>'3 4MM6t !0X_|R^?CE]o؟B+ek ݏ,|ÌLbe/<?_TϘ t `Sy皸 MO #+4e ݾ'tUW 4 %_r^$qc% Oǁ;z~A~ 0fIvA9'60yNFLk)1~GX`r5:6ǸyJȺ}т#94A#AMD1E`LyNj:$䚝k3{5TF*' D'iլߥv>\*. |rKqj6}|},P(iXN6./koO"̊Jy`l`jlo}̭y?Eo;Zoބ:ꨣ:ꨣ:ꨣfҚq3V7+8)O*bTRQ%S%(sI{05\ojQl[r Z3'7 I sB3o4١1^5T HmR HJ!'߅wڙa}NWAUwy‡TH:fÀbc#IL0P~{Yc*&8+%˼ bD/RUB 166\j#dGq >Άj]sC6ج&ksmlҵE܏AQ4 %fC hmVEpyɴ7鳙&sM ̽~k{O(in;v#3cewLtlV#ןF].PkH- ȵ^!L;`ד8QG}2}8*u.4 G/|}P-3a2+1v} t:ɣI'4i''T<ܳ@ s<|o]<}{U\_]?y{m;- 0*1eWHF%%Aa 1yEڨZ6*'Lͮjl,@Uh c }4>`JB,m3u0Z#P 3}5wIrUhL)!Ƽ+̧j y7,1V-59& TǏ19_cf4#, T$gp5ռf3t5Y<^5EMU AT]Lc"vΦ`,o$6@@5RϸLXh{kTT8J6a+3t6\i͵&*rd '#v Q{(P93}i͛^јi<*kf{P՝4Ì ޱպXYc,{cua?Ou*gݙܓ[ݠJpw1F6\ׄž@(:nhM= .c 6t fxtMJXJo9Y7VSTOHyb=Dedmf<偑y&D8FwZ҆3ӽk֠N{lX=wy)- GSX0 BpB[CYsi#Lkl)m1F 8ǽ*| T)GG3izkjEE1Zgf3`T]Uhs¡03K2a}Qv?vWN7@kghQPjŴĩ]Y@>Bݱm?Tj>37`G9X[_=+w\m+/S ??|O~g>yK_+?POg>{_|{wE{>moop?gÛog}=|e?/wq{S o~P4tl?ƏxϾ𑕙sQGuQGuQGu[S@*DwbT>HE< u恾jT(U!pZ#cx*_ $4`7(ZkFWlŰwUJf1c\!?K´Y̾uUh8oזhvo zG&TNKث` K]7s1'd$9jTlے`"Xb<55A&ĿBmM8ed脬9$P{Fp]*eAIYNpٕ{ft=EYrCh~AbA`ZbН !5.X8g3#xX>{*f'F*^gNC9ssRFgO`Q5HyT9E-mP !LG@ f>3TjF jH#dcBw_`gsI0k5qrC6  9**] }R #ZRҗ&u}6U*d3%ՈKe 9& 2.=9*S9H;kH *5kNA0{_kT9c(" ΉB?kRr8dP+y S.ll)2z#i.K-ʽKL#F1]_5-̄T&~cc:dY TIG~kX8\uߢJ`gsLP :Nl3 9lk\%sm&ys%l4f95cFD^ &lX9*k5AbpZׄ{6{5Ox~h}E2Z *ܣ!lЉzxجy+F/0iӧ7T yEUUBnz~2 ٌ'EsfJ|.XQG!|(\ 3ـ8oi`vؤ<`>z"rC\aNLo]Yy"}QJ?mᨧk޽+|Oۧog'|S?Db?x;_ _?Goa3g:{- pGuQGuQGuQG)XA0&JX{Zz <`6KHpN6PyZ 7ZH{eDI`PY\f Y~m`evF+PV#v)y`y0>z^W$Tx#0T0Ԋj0"<нs#LWDGGЧ=-lH';U`o,]"^e!,";ǀ!VО>{hG 7- .¦ Vs&)%5Zk< > >P$3ݭ >1\E-9Ci_0#c4> [>m|3١hŇ%yCi)ێtFw}ɟ[o_޿{xٗ_mkg>/_~E|Ͽ|?n~8wG@u{/^īxՏ㣯|peco;UB;kZ6r$Pj*K1#E9Fz2%ƹ5K ݬVh0sw\31`۶hjV3h`R{`36%wz6|=(j n+`-F6<3dfs\:ꨣ>z_ `CGN~cu:僤3NPZGv)Fwၭ<~)> |&P=> Janyqux񏽊s;|ק'P+>{ =x /|//Ä1R>x|e^`{tBpu}>~_| 77?/?%|6zOo=x@SIܼ:?'_}Z>pQGuQGuQGudPSeT@1- R#)ipQWj޴(Rࢀ27,C* !麕)D*QPZ,*Ram  v91w*ВOW1P | fn|ĩLԁj`M[Ӣ߱BjyPK0FGrR&,Z`yxZGjBX0%K1暂gp@>yoi9`H D?H`6YVƽ}(KjT&4g6gjDu% zQ>OsR5|sllk#υ"mP3~HeՍN]9΃u0ӄj,I0e^TMp+y<9º Z멈Ryw#UY#RT1ufӒssΆ `)'duKuT6Uxkf[\֎^\T߶#s*pД @z|K~ɫj:J mz/b-E,lYP{*%m=bUS->TeΦ uz?—|w{DAw6Zwm.'T^y)"GB$U| goqt6Di}l'xF sL'TN poyvz(Ucj,*zBe]_j݂.{k&XM0XVtEX]M^iQ\ ra%(KSr9ݑ*`В 2cve3 ߙUnAX̒VZvds@'.fsgv|f}@͈6(u pMW1 [ Ox@݁Z FQ Ǫr3|s\4Oɐ z-fC@ę-Vj6 3έbv{"+PضQHt5f[QGa\_‡v_WE,*o(f0Nnز)1On@cTN})ȇzztu)T7O ?7q{>?StƵ/~W.M[{wƿ`S%y~H*0[yO~B}c#>_gE|_ãw+~tuE7> >Oi\?<:ꨣ:ꨣ:ꨣ^B5[ZC6B;V*- }`sG"L -kAϴs$hS@Eʫg q3 iXچ6Z2g'0a 0TfTH*UXA IA`SCH[Ub[ 1tKM{d~{>t`t Oeߛ3G<&8 JDRuK-dZJdB^@ <\ T.wh#8>h! DE8"@9gg*פFdu6pnL"!Ј`uflL5:3^EB8:׺ȦVOeJ5 V*3zj J5ג.zfM˺!@eI_YĈ@u&6," =9ǀRTc;\Zx5xkkʝ`c5`}U?W>",  mνTXjHZ @HUp햜͆;ª.匒sR~-JWi=oTT zkKWۆY#DX)c+utZ{c3[eT xZ+S-m!Նx3~3p=cOow|o;(RLYE=лK%a'+c@LA5=zu; y ~ox+y xnw:ꨣ:ꨣ:ꨣZy ߑ< /U3C^`"4pw B Hub RQLK AAKPك,}('~v 8+PDqZC_j&̓n%hU!8FlfjT 4eA ~HABkV@+D6fc hI%-VP*0͘*Z[9bRt3K:QE)l.W ra3*G;DsB2QBl*#APJO(9KFe8IJ&t,_(UeZ*DΜm*}sOڶz&ST :<*Njj( [:҂t IHq`3BOHE8Hii+ y@"gcS58-;VT;N#ܹT7J6,^^r֋~*iЃEjH]SQ\RY'y֧ FJ^C6.8Ω,!ȹ3Ք':9S5c5tb9x>+?zm{gD@63TOf`ިG.=t]&^t5h#MWTNH6tikTAklRiE-Lm`QeO=zM&kRװ_aՍE5miK=E-/,'1-sN\mkN̼_Gׇ9r]TƯu#Tε~K6n6c\TXc ]ָM V*WqL4W9sM[ePVemˆ<ٔf+3~QR1kfwpNcF:Mu:Sflj+cQN1EM1ZKj:52,zkA5 Eq{>k:*8>%`^JGT#ґqN*D:( Lټ3`Q{[alp\y61ҵMkt@>ܱ߳U !T0]"4-|X% ơL:ëmG73[][1‡nP"ᙿSDa>?(ֹAP ?й;Z~>>5SѰN(u7#WO5o} ?xݻ3#䥏|\;Fo` 2p> j<@x!|{b‹8e+jsk?yO*i(D; 1BE"VJ;i PSKىA5$8$Mu:MVG"/ B?*&Xc'<~ lk3!C_o QTy DӺ{Q aT!E |wTi xe4_y| 6bUf (3w})ȩt`-HE *Q"S7M[ME)b'HHl4-}\y2-iL$ЛjvJeFDZMnYe L*ы3va9aX5קi'=P*=Λ;)R3J*g&lalAf92Xqá(b9F[o^%;Ѕ9%oZYUu!OCT w073Ȇ1R><9%-›1(f{,ipT x;Kq莩>ϵ`l(]KиX@}mZϵѝ 9t|(fCΈyF 193|$l>gFn;$vfOKrHI-}/2yV`Pα ^IkڗJe"G x׼Jh:ZZ> W࿡Zbq )D 6-T8\s.u;Z> (̢31:cQ`x#UCkt5OWX*:ІbK%1h^+7SIqp@@ ! T-DlT>j(Dк{"d3Õ>{q]c Z-b7Ym>3+ s6%=Z``8ꨣ>z_A n zoP)*mGl >6p{sPXg vt!oq@ AkDby ,Uq}X3+/ cŗ^@ ӛ6ÿ>#ha5n@ E8ܳo|kxt{[;9)Nx,g}=gx=|K";|P:ꨣ:ꨣ:;EM OXPL6 >UBeڼZ-u6toV[g -G 8 8dR B<<\Պpfʩܻ9#2E- ZxP,Zh@E%P#Z".f( -E[E NE3+@X'р[,e"ruZ6*{|2dJzZpk>& *Jk۩RpL!⩄K%0kOUvK:^ffSh1Ǻ`i?5vSqH@̠KSHe z ’u5=>|JOu+0xA0Z9&/$@z0wfN`1A_V$Sņ3dMhB;ՈKpOup)de: ϖNL`$,pڌq9W:ƴMK8fٕjl `#ZPUH)} jXv$ץDBw+l>H瘹&85sTgfqxZM!\|ڰ.;r]̟;t&X*֜Wil9Be|ev)ljÞ\߫fK<|ِ]@ֆ ,3#} IDATx6̵{ZN :RbHBU5>T,:^=)urös@[6YDǥh uۘ1.:w Tn Un9 >@}{ksZ)<>CVp=!(_oI6\b v01脾3f>_Eos`xVhkgʝ^t8iAF71NBѩr CTsOG>PN'FkD> %dA-7[VtZyx͋¯}G^`a{fh+)B-q*~Ɏ/}^=ᨣp}Áy/;&>xW<`nyȮN"$K>T6*ۆ/ն N9)W?A:ꨣ:ꨣ:ꨣb5mZ0Rۇ;GZѨ+< })U$+"F(s1@\O Qc,w% 3g zFǩ~O?3%bNUrZY)r@f^EpJFe8oLTܣv.q2-F:$c;DUTHl=[þc2n!"2]ܱ]]#;%.UAt6p lrݦ3#">sͳR+RQ+pI(_XzlrO9׍ Bm5w,EJ6* {m٠q}7{JsYy̬QIZ&.e1ab"ĸn*нC4 `6y}D`4T|l؊0*ZV$3bWG8:8p5g`E[ :ìl8ag(;W`3b&8;?,[c 'x<Оc;'%Jw${iY.cB ;k>`XA6I4.Eu=BFjf;%sLShh9hoeOUf2YlL#F@pnҸ 0F_ af8#Z`P԰CUCN\N»/7 _ ; #*ĘSm|0c>(УCkYTţ:í]?^x.0- NmS)1*ڻ>N[Z )=~<*±N\FꊖVKp}NWՂyw3[v:a;]cgxt7-777=x[o`qpٹ8;HT-|P sEXKYNզ.tK5Frλ S&̘["w6+l^Iu4qY{ev6@L1* _1RJBmSWYrlVH;p8_c&5Ksk^@XY5wtȴ05Fr cs:91"~f;9x ӶaRa.¦,3@ '1. 8sp5^",0^U Em) =d51\N 5=ofΩA{}ni{TKYjX GMU>3[Be) ;^?h@Ʀ +wP-8Z_9&l$B>r;U uf$RLF[[fYeO"?זsjsVSG(Qr`ɶlI`vy1~?ypyCFb 9(,[%QDl6o{ήZkCo[c<6` Bm):{'):)aa:63pF>O#$W}kf3hDǬUl2%FMg]~!yO "ERnRTq:rϡ,orOFL9Sg!ZHˬ:ŵT ХW0% (FU>W' 8z9b|ұ<@{fO!iu (q>{$|"+,Aw+UX= $bpN'5%@6tCah:'|W7ev)FwpwZ+HZS|AC  {:oP.54wp3>#!r D?32q6>#$OCT"out]&׊;jH9s?=10Ph!ː16 33Ɗ37. nM0=(&2v:EqZ)rwczjRrR*0&g La}i!YNۊsT A58N:1`aW\u5Ja['ZAERȕzOb^k.,KP@n̶w/H {| FTwt|Y {nrV jkmCuJEƶ mh`[73uCVܞN(,K*}U,Ld:=:X1F~?ꨣ>Xj"C7^cq:p>?@M|.{,7|[S6}`]U_P3/|`I$I^'o, >zd77>z_^*?5a]7<_7*ݷ/} 3|K?}_KO'/ }9 +*>1zǖ Χ"?K/fI|twCGuQGuQGuQG]˗ҌHf4މ3R]6:*^@eFb,9i$rIs6DXWvrbZ v1ÐHY68>r!15Fxq:G`X? u &azZs5͛W8ctQHc&:w5D/bGD`)#HΝH!栣z6M{f (2 'gb݁@SSDg1RcH.4~VKyܙ #rPdp "9@)O# }Hg,7w:-A%2w85 j0W,Axi@)w M?QPY "&q_\˲3r ` jT&vXtm]?gq[aִdNvalNǠc]W"_{GI9 -J7Y뛚μt , mP~=Qh]N-05RQ3:2O*t$@7=p*S][=*GtN7!m͍u_g9o c;(4ѩBUOO\4!pt:S t"a(t80sOAl)z>yf𥐲}0d:?["4e^*dpt | .N{H;t/gY0)cP5&m->N"gJa3O^fف=޵3}:QN+VKC@M8ushlp9ps ¡9Dޱ,>B6Qƈԅx| {~vwF"+#Jc'/̸GdX4ZahKr") FLrv1bG/W7pY7&cOEhűe\vaZ61ia y|9-pҀpXcfZ7Grui={,aq5^Ѓ3ȸWŒ4`8$E"08Ponx.݁~F"VƕpS+R 8P64?_ |;]q⨣$c!uGh݅"1qrYr ,[^Pӎ(9K1Oe`|Ü_Ko|q!=_o~ [Z~ w,pчk+[] #o|_7/~Kqn>w&~W?Ocy5oWoxS[g3ʫY*6ܭ?QGuQGuQGuj2'T9}c;$RɁغRWS!nZr[1SD9`y'/hf虹X^?WDmʥ TA$0u )t5.{r9O'Wǩ#a Q H 9IfrUc3~ʆ[󵴊ZDH ,(-1 r POME oi";R܃߳%J-FgUe˖Rvq2M!}ś 'MB2bEi?Wk֕Iw}Erљ*N]E{X7k S@ZufOTp Cfkp#s_y.ax:"K kH9).b|ŝC~e4Kp19Ʀia m57Q;yNU IDAT)"@OLv ėmO6_yXKI$ڑSN>.LmSa~׈Eҕ*83!!y0:34S9%ݿ^3Bw̑;H'+bm1oۦUmF ~ #3en/Swڙ֔9:i/8G?kߘ{鏔S_XFҵ50Zҵ3P&xl]YSD⣻ ]0C Sw =9( c 'D986p%H0 FĞujy/i\h3b.icE;qb>R6z]0MCA@!ww0s\},ٺ]cFB5:g8u͢*N1$3Z"#Q[E$5J Ņn.@J%Rә?tn}y3 J"yGu'[?Ժࣧ?8QJCO+GI j~s{gy7X &鲮xW'N'fdiض)@ ˯ᅢ7跱?!'|#T낧~/?g pf羀pl霰N|o-?量@3ګÍxulxs_@?Ϟç<nnNVqSNxtlGuQGuQGuQTt9 署Nam ވ#RwT-tf6+̾+U :RKh^Q@c:/hVcK`l@@ "LnEf*5  ;\ W6\VX-Xdž!'FgGz2QM.=c#pZb< 3Ssj6|-A}cH(l|c[!fwI6<GJ{s81TW*qD11P8APʭV6̶T^MC==iYآ-XJa4$lE:˞QjÖ:Nˉ&ٔ7Ӂ㜅`0/;rtLǣD"#S\5#zy  S,_;BӁi$nZ$Otq]q1 w pBlDqSPO[v ]·ceT]OBcF}a˼.@rixr}Ό M1fQwmf2'|fY+0CiN $4cLr35ᘐkt]5چj;/,rik]U=Q^')!40{Ɏ=t^9S9L 9CҼ\;vgڣMT| V (qm]"ւ^ޏv_E{kecwfpkE}# ZYHf-~ 13zt[ Wk@jRLT}hˉ^ﱜNNR\&}ЖӞ㝥"D1}Y`Q `T0Pv@pF0Б- 1& 3=(j04Xͫh4Nz>䙈؄?6:ꓪ%KönxSo.O, -?h8ˏࣧPO7xp~cC+wxxxs}jqe:ꨣ:ꨣ:ꨣډVhx&'D&PKCG ,ak(q 03bs$AF"H 2L[۝nmniD3n+Ȱ+ E+6Ы9eiNˊd&jmtOpɑV`i(j.]2 39=cAe k0d(N̫,vQw#=蠖s 64V`."a[wCW!O["ItضA|qBΖHrzqc9qT=hh#83CM]T 6bHM_$xVIpœf¦^ 3SM9LZ^k]b鬇:69h&Kr zl _]]r¹֞we!jľR+Idl:NȈ)~⚷ }q]hJ)lVwR2ϋrIe4g@C.rs=P U-yD u W.($DSOjz֝=c鎻NWx^] x@|tlC9p}C+C-,Dΐiw&BZ)Fc +B'YR%Uk3;L*E #(DnHsgVt]ӅЫkaړQ"REݒU+<\S|{;p'xHhD :Ds[V U _,5^BѯU.0$rĞ#)C{M.p&.8@cG|L%ϑIܾVc5dwkøqPm+υ 8v]33~bQ2s݈8 DwEq64ڏQ Z%!\H{7v r_xA5=sgvtuDDZmHs,m?@РNVdguX4`t H]d?SZ;袥Ԃm]Rѷ ;QǶ!DKɱ!Gg,A-§s^C d۾UгV` Phzk>Xwg>ennޡ $/|x?;17 mYpZN+)ض>dKezawq2W47ߺ~Ƀ܃xm~iᅨomϧSG6<>з|1}Yk'+_?7ƺLj=|wPӆ~ꫯq{< ,~OQKCGuQGuQGuQGZ|3[́(h Zbj~#peav.7͜Nݤ(Uh9p7\.+3JpɁ0J; :^ ƱD0ΈKM;fAvz==( V)åbl2CHdDA9ylH'!w+ZH n4R+Z)?7o –~jǎe_&jY_D$$s72;!(T\1BD13)qM)g2of:;KkcG;}N\x Wc>fH)֋-Dm0 DY: "WϋJw+LbIr8! 5hR+3/Q|]p>H+6õ*~k.I'3{&P+%{G8N/㞾1O}"sе~zDvE1Pvr@ň2"S֚hr ]Z-s}Oq*M"T>) R=o([GX |#Ӂޗ2#%;uQh},g>SB 5bp[0b?޻j haa[>YUkÙFȇ,X J)8NX;,*?'qwY5[/m|/忄x[R3 YCujc#xGm|oW|C<{|ƶr~X~py~7*ۂr~/{;XhnX/ R[s݀C8}xg8R ^zP!B+r؋481!$>v>ꨣ>Xc#W>ă((+WH/G&J WwL5}0eFZ׋Z z_nηDZh g~izSxSѧ7Ix X w[dz ~çO3<׿oyA i]k8oJg>Xp7q#'Sve=`F+NxW~:ꨣ:ꨣ:YlNpB)_-|{w$JTwT&lQLF$J*jB-[%"˵h}$kձmV*]x#c]7xipp7itUѥx,0 &d 71C(_/®XZF3؀@]dS<񸣑%b@0Se3fBB+vD4gN;r-cw%MܱHy dz΃O_ZoJ1P=,)5Ky@ΤrY=bBPGJd5.'ˢFPQA7QџbuD0+|KiNlb_a6݃^p0`m:u\.Ǝ1x \};qS*&*U.o)Q8lmt7mgvM9?˞_< .$A6絚9BtSн ^4Ꮅ̀XF 3 &}=O؃sp84:JֵulcrۼB۝-;>L77M)pƎ}yݒYK'/T~" lc܀ASpG 6Y0&|Ņ-9].'%vrwf }ycbxSdF9ϦMh*+5c]ֹ'tr%]0x >Ek Q#9 ٯx9 AGj+=(hfރМT|_[}l:߫Qo"hdO ξ9@W/_s]ZĔSӡ]uXZC5:b'|M/ Ff-x}GJV^"|ѱm ^ X̼nLWt}kx@ZFF(\fo ωh#t/<9H|r9h*mr0Gs[DPeRRn^l>{c 7i֭oD*3f`޳{bfph!Nyf<ϭ5W3nӑӠR1`lt}ocخGu'SK >z84n->/Uu,bXN/'ڂ Omf6:z)E8ihG8]Fc2򿉟WKg~]C\[C?縬uE:'49:z˲૿p}ܽO{|5 n7<ԏ>ه8~ciib{[L*?yC^GuQGuQGuQG@n,mAPQ5mx#2[69 csyiaߕktr( :X6 _Dz.ۀYé6VfbZK?lCLX03YA+߮棥D[ȼ=:蜾6gnn~)sZ5xcsAΌG +T>c%ݤ^l %fF %&Txb^06r=qD R>8@% D)k8ю͊\E t@E5ݲs @Lmi\]y3Y_CBM'$F} "3M_ALd lOn V V 9PYѰ#( IDATD':34M"{<&'~:1tŎD\3st}sdrnSD5ffjӥf0m0 E{#c:ʕKqaX%D04e}K"Ik'0kקq+>RSvk~޹,5PC!+kU&5:'_Sp>tn}!u}:}NUNAlc]!b:790af$[LԲל]d,c3A*LM 9 &ʾmh^ef\㎡&^wE4] Zg;-V ϡ! _\Hmq~y? ~MА=ofH n} `#Z]ZEcVA91}rkw UI.lC.QK8I[M_v8w awN5xa .o|́`:W"?u7'mӐc]WdbCH} Q'z^Exe:qBx2Hcny\M=|ьQ](Z1<&p%ط{cPς33}@uwy{s;84p;[tls=ꨣ>Xp]nR)$̜('/OKcqļ1MED/={du3Y.YIe~Y,NEf|>2mlkJxX?_/_<׎}xœMzgѳJA]<7o\G={y =ո-[Q{@SA+wc~KvQGuQGuQGuԿZA׈92F%y,&3nZ#slZ5u3pwc ]Wi"ܰ, `u%\qx-$|*[)-bzoџ&OULjmFPіp8^aלRLD:N'yIcAsbc߇aqGs L{~m鉔DG`4R=>̎VB3ޡu'^KM<ߜq:_igv,E◾/ϡ3ji8eYP 777@WT _=aeض 0#[/ŕ{e|Hn[ cKsϣ pE1g>:x饗oɳ$B- tFY2$noN ( m9#$*>pYѳW<Ə}UTL`%N5,`L2 zZ5b 8llOj0]K33(6jŶv(-p͉{R+r@l̸M5S+=p )[:QnEkX䦝>qI{bzQ6; g21+6Z=g0O"s(a :G/XSd *ZJ1:ӁHϱ jf^jGc&4)s}-4Ѓy6~AG,~Lf];_ ]fvR7| X&&jA"S̙tOG7 k|)B\Z'8 nB'*CӍ93b݁x'%3UbO6`þւV9w1WG!QqU㵓6! ztĎջζ/:ZK὾J;䦜ْS4ntEI0={%Vh$c!b]p\̉ ؇A8s]ğaGÎI*0,> *p T Bw ]Fw ŸN϶AxfuDaO';]gֶpM{r`Wy= s:^,P<=c/8/Ɯ&°ˈ@4 pEa_K7t3۶"v y1%H'z߈7|!eiUC=gQ.K}O (bgW52Iw`P" "-ebu~,붢)[|椓,xh7pd 9AYiksC *nyjZZe]I.#]9a  /z_)hEY $hJgt뙾`7zl]_p1\'q5dme`V6:(Kmn88h`Duܜoy,O;࣎~+(@) |o眢Y_z)_Ek$ԥh$77g\9Irӗ3Z}|7bkŶ0+s`\Z+=}x,x[8o'.C=>#qrZZ oѿ+=},`];/h `_Fdtj#x؞=>O{у' oɷ/G?QGuQGuQGuԋ,k&1͊T`KFxffRm\U8`>Y*sЌM\F?HuB/"7 ]&F|*B܆$4>r>~PD51,3ȁ`h"EPtrjJT&qnH4a *|'ͦuD<1БִOK-*r`0liVlcђc1llħnjd|o~1fx FلbcY j42ckE"1$>m~W^ow &@V;-jeޠđ'uf6z-:`e5(LKdBSQ6)L,t Hx.a-B"vODLG\2)4%eyO$+}jљM{;2QZ#6;bwcrȡ=sw1 C+\a9!庮 $pu:I1va+#2[PH;~ݐ0cω&>YjMĶvv"L{U,.VIsp)~sO)ri›OYub:>sP >3P'" {T]-ruʵh&ts<&܅}a읎O+.vŧ>\.*fȭ*3Ҁǹm > KToZwŔO1FsK-!a* K9E.$sNuzb;߯muaðIԄcϴ輻zʁ1aFA)] s5bqwMW(t8X}0sfqض7VYsNB!=?(Stk-i[NX]UC,c(iH ޑv%':,F}r8- W>h+}HPQ\giVc.X^PFO 5l s V H5\(>vT [4q2  >W qiq+|5t_y o:[Dz(+?ҫoS(^zGp71CʫxG _g\.#z࣏>Z ۺ~pjz7QH0!Յ/c` >k7 6n-'=}m]=?WGh !@C_.xxs8<,ɇ"?Jk>>?ꨣ:ꨣ:ꨣ:j5B9qJ6` m9v1ef}p :Cbu 7@""\̨-C"Kp@qZ;DVz4j+hMySʉf.p[9vnDZas|6lf'D%T "^\uHlXib\eQ㙈)\>}%8Y(nN%Dw{.AWG]\e} rZCf.ql; .q ,g2 SE)nr%Yt1lj$]d]8Q¶']1k\I)JA'P 3lwGBLfIރ+ @kbt?sH%iE=k:QڵC$ļO@bHpe#3׻`b)(;Ob҉'0I]25ߚBwcg| h؁M /FmsbH¾n W =όC Mqg!Z8ԖEkpLM\5,H=gu~GDAbcjIxwkp>O? |H9pw 6Vۛ{Ѓ/>_g=d霁D3D>ocrm ?ևnN ~'x+ܝ;!'xWgy];Jylxwq>?,qsj8Ǔs`?^}9 _<|ϽxﭷKݟʽֵuk]Z׺ֵukP[)mSTo33Z-p0,lX# e1ʖ22@AJRz Ѐ۔'Z.0ye&JI@1Ua6Hy\X4`՘@83s +Dbϔ;FyFrvui0M| V X/zjbIYV.^=auiޙ7ǀCGN#0NmeԠͭ%UsZ&348t8>_ ,T֎1 ]3!R_$c2r =]bVupfD8)IĵB;3ivEU@Oڅo/>'j`H*qXtْj%ak1 !@6"[j"fF5e sr"kfSK qXzA*7Vỏ5qP0deLhN DCO{S=a!{N)B -'"/IPHO`/(?#e_*āU 5tF R/`- "a]" V\}XO\%#>VVdQy0j=' 6 VɌK>MyHVfg﫥 G`s"lOX@> >oő}3E&4ToG:cz; DI VzkM v9s@mG4ÑI621bc2yy~9G nT*w:aի><_>u9FNTwwgֵS Et㡁FBʊ+R {8>/<K/ʺǗO3Cۆ9i 83%dLoW^} 3_Œ*c"$KKx3<| |}|'K; }?v!cyCno'={x ^~> ~ H|OQo}-kxO_Z׺ֵuk]Z׺ֵT8Kqhdhk 030i kYP Aq35H H]X;NmebNP1~(W ͫ2`OJS)C$P3NdNz/)Kxo}?G0vDHɌ[+jӜl#NGR;rpPZ1)\)sLΤ+IZQR g4*n;0!.BVe 0?FK111;@5htF04ar : IZ_xRnYJn f2 IDATer(^ HlU`3 SwdkRlq`&ULM3;)KY{"0Ze.ԋ],R5_S)=2sef.ЋThŞRK.fF=iArbS2x͔A *fP z-ڧ ,W3cpXRJ7NF-yg0_~,ש4]ose @L!1 ]D"-l $LNKa~:rpIqvZE-y-\+ICV)e+3)eT{朁DR I *ڲc0mXljI(\/`iJ}\\w SL%-V+D"WRLqϕZi`;jf:]\ d^s&{\_J)vߙ-2익b c,0p_WЗ3R>t6H+ctQl>'yHGNbvC ,f$YNp}vRNJq}:L`T>vs u[ wV醻s)j)C$,KA2IBZ6 _¸Ú[jhsw y]O'> i[zM2|LV"(b#& LnnП?N &؋,7](% _$ETd< DiJ 2NHl=y3NTLYUO5VK]:i{o\Z_xL8pQHb=h2k0/:(x2sxd?p6tgp wOwdv:m3~~[lۆ=Ԕ3ij|3=ܻw/{ ;o:pw_vs{0'28?;ީ|~;/cm_ֵֽ۟uk]Z׺ֵukP3VsDMdvLZ9p`[}"x/*k^28-^7f.śrIRv0<)PV L=sDqXTMDYsR|1e}D*^Jj!gJO*YlF9mX`^e<Z*- Tw!8北V)QrT`2VA.NT\RkyqE0?r;]@ZD l!NiՍ,JeMȎRAÑXdHH[9c`"q!}^r¥T3é5̽c,op*EnRu""OYr@xV`TO+d\D9d(y(#c / /Xh h%ID懺YX(2P{\ɡ}1(a)\He—Ei$r@)%`𵱬٧bimtpuXTKlyJ)Ys3{V 4R[սP!Z*mn}@IpbW~p @o\K%'1${xakP] VwXV;l`@JyWLRpu? #4&{XfR-E!Կ#c7s V+?V``Sj}V)-3I€a?J1\[󻵆"WSz wxDuG P. `{^\N*ɶ0 ]hm' l<"訍9a]aN!KAN[eBtmE{FQKFqc͇" 2MJ|JD1H t݈91" cq\|>+4SUzJm,&䵦J~>Xgz E5%jfUFXǯ(R\/rW1Sz{ -as?V &sJ|F6H@ ضg&RNtU53wI m#ڛFOA1`isJV1LDI>CT^Zk*|k]Z?Zy|)w̤5yH9 nmpW/-{'Gc=Kܻq->?_<Œox0+Ӷ!ٳ;|=Ç/pXN|_W_i KI:ܻŻoGw;CL^ik]Z׺ֵuk]Z׺j{ VfNe:m玁 l(.H, ҋQ*@3**̚),PZEJr!&aŒj~eRbeYff- fԶژq y,P iyu;̊x9}tLh|˜NPVdC|Ͷ,Cݱ`>3\*CSifDMj.V| *z ꕕi5毦%NK-V^wDM HІXuM1w/h Y۲v?zP0%[n 7X%ƙ3U L=[y:+~dަT|}.0!ƼV$}&CGLYBJ:Uu 3Y8hՊ P6 du'Brq7s-E(e)s\M@ۑDBkxj/0ntI\ҕQ r5Xy|̞\ xIXg.`Io3`#U&I{Ue1fmN䲥h#?)F'LRۅ$u^$%t"x?^n\} VcૅYzR &)[׼mUN)~>3 XKInRZ-ewl\`hGbk I($_ºT&p[e79pX?­t{J9v"g!D 1a1'cپ9 yaV܍~b"f8 j1ʦ3Y] 1r*0G }vV129n}¼jLKޔǡN.\ECNn+m@9`EYOT6f??`BCd %ZpZ:B99"jIXjH=fHHi,pTyQkTƱ\ݏ0sH5X ŊY6`?@m2pd.%sX!=A-_3fBm 9m)AKCY ;ST]ʣ1- ,u_aS3RFERj`)_PѵZOhsb@j,C95ߠRN e([;g(גmh@[lhMI Vz);gUf^G臷YQAvۋMR(g.g=KqaoN@ΩRDݖ3fZ3_y+ւj=bTP Ld 뀈aEV?@1&椵%A7FGwKs]쉗ԃ\ٱj8vFܤuF=l n@і_k`yypt]rֽl*M+[v˾DDZFԲXbKTct"$Jc3ET3m:nN<Rt͒{2Xt0ǐb\4zv!Gxvf/:9~ag;&w~={~D(8 ( 19{6BeiTuvF0ZE0K|k]gW_ ^ߍkȻ?'mQ֓V(?TˆĽ6=Ë+ ;|q>? aжRj;a7 ??scqMƁ=@Zw{am;/kœ;"N[ŃxoG??y،%cL{ǫ/ÃW^KcKK|ֵuk]Z׺ֵukWN " 4fR7d&njC3#n@V0+30scA3}' L hm V^"!?5G 5$Ne3e8!kA$L8Y kRC2Ó:4-fLف MJ,9 1g`3J0_rj@S;e2t 9icR C +Mhmpݫp˖xY?/VSY]nz.S@V28$P @ښm\l7hஹl: 4c Za@WK>[qg})fG_־ IFh 8rP¦C!qdb].w9v K"l/(U S H7 3IM[ӕoToJ@"AbϝؒVzٺn=.eIOH}Xa"9?d+nB$TCԊ< r_Rrļ#팀)Ջ _.R>^eIKE" .je-],uB.`c*~&sla|\ Gu-TKYi)T1+V@-q(|E"V*L5@}_$H4sJ7O[[zmA8ɼ Xk>U2W "иZ׺϶b52x|J8y N7_kE:lx.6UG߅w`O1Aگ\ՓY1ig%K1iud>y =seY{f} ?/ps|/cn/;yǓ-}1<Ïc|GTuk]Z׺ֵuk]KT]IuBf[U5Bcf0'(Wꇪ$XVؕ"㄂H98)11"##!墾FPJ8lPF08yf99^ k!LxލR f @j!_eq7l4H4@UliJĶ."5!m֜NbT6'T!SS})8  y,; -,H&Pfy-E9,m~e}fjh1 Sܵ~xqfؑ:c Ĉ/D@0ծ l!aS~%_iѾq\e!^tW~53g(.P}J͚K5͖m^ ڀ3xva"%ԶZжUjϋE!g&[@z0a Pj@d~|1NfNH~Ա.Xz_I\\J֗@ CEZ3^Kβ\hYNPl ,[9ý籆)( uxIb=j|7Zv/(ƜڨMjѕ )PA0ڔ)LD$ Zc99G"#H>/Vc-өȞ3c-<3JzZE,!|LNߧf;ERI]777=/ZӘg*е8I7ZQ7FP9QSĠ HaB *,1ѴWgp9qwwG"9:, SO<ͮcD@+M'IdT+N0r%};wX =:NV19TsxL:ڜ<3-|ؗc#&2&HH0dمG"MV6Z׺϶_O`z(&?Ivk},R,d ӜtxK|;O0tλW_=/?['VeN˨w|o㍷A;sBXv*-@ _|G_ބg(KkpYFޞ{7՗7^ųgw8T?O?Ǐ? v\muk]Z׺ֵuk]^Ә%X5"0 rk!f @7Fn1PKN 7wL*äTp//#s"瘴 @ʲ`smYUwL#؜Dlyn8ŨJ1#pe͘ ȳTu,l$Xwx"J@nA DV!!0Z fPxpлJx/ԡD)̂-y!w Ba7%L>I hlB)0|'@ IDATHEWRK{TY R?9k-dk@Z;,3AKϐ;[;Ky_ W"Wey ])_6ִn&8\ T;wWi?Kmf|1PwYvW b-Ki jK5 ՞Z*L9ސRHqRB vRSpS ةSRb_$Dz}5ݛ)3kxV_YfT;O[V_DKH,K͎딀2Ee/eD<|̤Ey8T pGL,yET'f-%Cr|F731 CT!ݘ9kg7dqcԢ5OzL&~HPRvH8ʰ%wF=`NĘh `Z^%EwY+=7ީRa3F_hVyi-쳦bϪ[)< 8LY$ L^~>0R}?ρJ+/Y!|JUlM?Sq=)ɍB"D& ̐:s.|yù4Yc)cqN{WL]7Wy0R9Jm  *Qonq+ 7pT] o󒕂+_]U&m3 EZ~q C"؍*^,mD*q*^*Zkz]ew=WksZdW":ǎ#vB$űޕ)?'ji@HZCMȦh}.l507{L?o X8lȜ(Φvk{;|sfAԪ='?>F/6}w߃ׁO`3nC;x%N<_|?}?~~Ǐ/!g]'N Ϟ|wy 4|O~ xᅪG쳇x G_~́_{S/gk]Z׺ֵuk]Z׺ZYB f.E\" >d'KwC`YL*J)>@,KDNTo0+T9 e4놽!L`VLTQU.WOLN֮wڞfN U,A褚T-D)0 9,Z:#0+|G[P8 ਥHuX6u)p8-M掹!T !aK#-o @E-TJ1{T.,k7%V亡=1Z֨Dj Fph,#U 0Z4*k080s̘@ReK昺@5P9\\..׀IE(='㳪e˙YI|V(b9UIťFB9|.Jͷxaڧpj&UJ4cRo&@Sब7ry>٨l+n_ U:u=p @Sk -TQ@3UY PZ@y9*Պ~@x)ʁa7)$"hGV*{qnȴJtN#p'. OT&]j)V{,zFBFҌ NQ$.W`ZWZ  8'8mi;`/J _N[:#' @s TI9 T\S`L غgjX}"`,7mus* y?2[/>ْ0H1R& i-&H,]YϜ#fH0c ZҲ`1Ϭe(7IQY5#pRjV c]_=PU'|vsѿ|$>{:>Ro(;}A: :>y< 3xֻFy{{wϟm@1+FLm*RC@xE|^XR6A!̀ZbTJD~r?"/- ^+GsĤB=u#I^ KF^Sl?Ih 1R'?3MZhƙk] e\]znK$Ƶ'_S R\(se0Ԣy'QhۆsޕW XH}YVџ15&ʮ^תN f/M;pww0R(8Zu MlDiO>y7_{ o ^C{; c>Ooc$m2Z-{>&:sՆ7^{ 88Ƿ!~Ƽ-7_{ O >~~oݿg|va;^׺ֵuk]Z׺ֵu-,!qULY1!a'f ^Sj)?a@YW'gаH88H 1hl}G- t)!YwL \.@tP V>!kI@IrH`Uy!!`ޥuRTR#6ssDz1%p+col> jEjCc2۔8rB]*1' VrKiӐ?8D^r Z:"PR`T $U{74c$G:i W"/cCF Xhh>4;3P"j3ڎ,UT^̼e>&A\vɢJ#HM>b٠ T(S8AKjsN( U0EيA.g<~B/| )U}P=X4k WN7"޵fpCG*aOE~]x6}2X 3ȥv ,MnK8 10ZXZ LiA/R"FJ!Ab*.T fc(3SKup"/Je1em'=x2`%]&ϲb/n xSI ~>j9lv3bctR=ȎT+}C@ewiK!KY*$ APW$ ~{:\u4YǛHE^+R@mgum;M!x1/ΦA$(3b(:P_t 3!=vjhF*fTQ[5/} 4/.w&mQjCqO5EpKbkhq>" 9!򒉌}ao}^Lxу?M5sI4g)& P d>g|fAZn*NV1 iJgw fQP`^oq~FEYbDǵum}=xNf@v:0Y1Q2Siv$P1$i #;KO/ CC~7>O˯O>G5gw5^{%{p~? 2>xm|o>?>g_}[~CWx U>1+gϟք,bTg_xhuà ~k2?}G<joOw\Z׺ֵuk]Z׺ֵhNmˉV9e3Iv;;!ʤd~ P:A*3%*j iSj¼9\}(aJj _,3A{zLxsQZ9l>C[.т^6&V9fs nF#RTM̲, `R#*I5fú5"P |ȥrfdmIeŐ2؝dyAZ M)RY5(65ȧ2HRaU}T.g}/DkUj$(P VgHXVRyR͆leN}s՘!&Py]g`ls`RR+מ3 HN <'o  40I"H8ݔ}ye.bPO~^we´&p0XKit {iqdbS´O,wA8ջ.vL:q,;g3ڛ ͤ ;Rе >`s>Y֠Y\ ^EJL*AӀ.`&զLdS{| [+ֵֺ_A{q(yf]k1 $ՔQم,}tw@k@xf-96\$9 h_k 'ZCP$H6`^/^L:Ή}rc3GB=.,;¸^ڶq 5&e%ʥGoqit0OM e]@ffOnT s'b"VcpLLԛ1G?Or1~ br!9B tHU͏ F$9mC">oN' a=&ww~D@ wlC-$ޙ niTъx1'J5:z@.Umƾ+~ν_a"ctXIl~9} 3~@O"]:*R+f@xDx;>q'Jݐ̰q+!P7bOۉ%ρVIRxi9JQhWZ mCu2z%J%93QF"c_їb+ __ 4ئr81Wޔɜӱm'sQJžQ7t½f7|1_ւө3| '0ʷƷ<ķ~+ǟT 7]EtQ'juZ0pwvO}{s{jVdk [$xD, 'fpw@,VX@,մM:ݐNX`X4d$ɲlɒeJ5w}n)LUY z@ w=w9M~}mϨt5)_\$k[xfqs+(~ !ĬxFpU0Yl>{)h_+08VR jn_*n0W*FW1Hcou5k?N-H) ׶LUNk[]+RN]II`4ňu8|СAoՌ{)n-R|6 -L! N U%oI@Ә`Z9U[=%T@LD#cgrV:y2W  ;;KqS{p']\fĨܽZUArc>'O`kc@:̱o ;9JS̆ ;7!llcccGG: [{a*HQpcx[~ gNbP[ؘo0oT9{cw׵uk]Z׺ֵuk]jTԱh sxɕ IDAT @3*)ZƊ-ՃB*E"%o*h5ÔY``lKSMMB`01v#HªZWJ#)J.JgA!%9#@ ܊Q%TC @!BR]%QacX dz$!q=>L 3iqs 4N9^"0cힻg jmW8Wͯ8!J+`EP25c)|oJꪾLle[I9YFfdFsJ"tڻ+m019f=;`֘t17g1c1: ؁4>Z)FNpp0+q4fvYǧPȞ춫5l1+י ""Y 1ON^%b@ SuDoF@&uteb9lkוkqK%B%n_w0ɤ&h?7߻ќ+ 6`-6NPUm>ܫ~6W0ѹ R/1vu}v{@=7W:-b;$i`Nxhy..&$u9@NSy! >zȪ$Z+̇ 1N4VjE\.>/!$.0!p)ŕ9r gN~3 pp]Ab?jk\'p 9ISd0K=U)jʗrc)KAb0%) MIOR2RJ I7& F$vTBD. \L%ATeС]6̹˘\Y H+9 HdnS0]0`!x^Av1&Xe~r ,./B9JAmSU2 wkӨfn%jS bzF`uE40/8HJꮎj[AhI!J2WM2Ǎ',>2]Ҩb =>LCaRݙ0Ԫ]u@OvVdWv\U蜓T]9B-g]}%UW2C[=Aw Ձ̹vKN0?sޟTq1;$g&#T]k`DWLug8!pt5spyW9tj8@OKzW;a~f.2ޙf2fhjmBvjU#LЎjnjT_ "@~*ҥͫVAjִ\PE@g6ߛj'-ԊZ ܹ株KV.J5,88J,bWju_1rDJA^ɝHsg=D'Cr`%C$8m;mUryfyCH*s I3(I?sv\f7[s][!&,[cvr3xV*Pg+޺ֵǭ..8ZԆBI1̀`H) X7@cSKpFUZ ef}fd u I#3Zjƙ'0p)~'a_m;~ح?u!o| nx+= n{ݿot~|:lއߍ//ۍ_RF|~7<gO{ߟ^`ރqM/pؽ}G]}_.<^w N7C\έjEx+^K.y_}ʸX׺ֵuk]Z+[ )_-4MF%I"y.Kl F*GÔ \6 i E1paD!JT0緊C UhkueJCNPf PCPl YA\ҚgEj-Z`(<1,S5,{e6+ʮt0d*++ATZVY]E'iޤ6I,֭u.U<7V`.b\Ѧ- (FCX Ԋ!EaRZkh~n4 1s 󟣪+ysn 1NtJ4a.-pW2F풀ţZ˨j@#X矏֩XW |S6+*&56Hpښ5mŌvƮ6+dQV`g|6VQ=C`[*ؽ{ c-DJ8;p O<90lyu8on}Al^~ 4N>6}p]܉k \5BTM8I܅ˎ^f;g W\T}k5c-@VU=ac7bZZ]+ g~ $ABCH y4Da6E 2E>;E4lg~Pw \}'8;OGр) BP\zbx>0𬛟z=N</;8x^\rrgNIc0#N>`]~aγr'x7b{T"H {Gpickg~?eބ7ȷNjcح9;~ř)w߆+nx.n|ɷ= )e݌C!NW= {o?.&x>LJ?3wY;gzN?t#gex2nx+z=Vo|cֿ|񳟄Wx[~K][Gp?|Oi]Z׺ֵuk]^8C+* 1'055)"V– ]!E&Esd !Wy]K62yBflƖa0C畅"DpcVew\ AH:HbTf"2AݶXRAI#BMɁިW!x\M@jCʘ["LVMWFh ?WH"!3,!&rsclEՕrA 9=3(P Ȯ Jo Xv'f`wB&*&!I66:i^06#f1pNCns tdmWjv<ә>ܮL#8ʆju;ӮDJQ4J*6/ʻ8v6~-U]iRWA2 (4+!Nc@@ ynrWB? C kPW+ ʓV*:5qNn6lˢy<61GFZ۔[{NkkEw+7ו]e#C`^Yϛ<&8ر(fy*Pv!sl3engdN]'un3yE]biWmirJeCv .p3P4 9++|hW+W᠜U\j}eb"8#Q*R2 sr5`PIsY2Y{kWhZ" Q%yR\>zZn8DLlxUc?knT7iZ} Xr9RJ)}ܻ?O c|]fsX- t)Y!+?S&9}n{ߺܠVVp;SdoN ͳ*D $>;;5fV s @aouRWTj7CC3{]H h{0 eFJPIO4?s5cNl0.1=.H0e'=wޕЋ1#>`_ZH:01wX2sA002($U8 K y0$ʧ<.s$Ij%N= HZ Fvrk R+i#CBByOQckH^׺(8gC:yoѳD(rY"R U 4*Lr3F6ái1'98z95nS80ၳn#+_5ᆯ6u7Lqa3z!s֐ {'"3 b8p6|_nRO0` nؽ: ,3Ug!8uvy(9#vٍ.ylgT6 S^JlGx׼lۛoy5>g%Vۧ>v_K.{Ϟtp]\//܅G•7<ߺz-{{߂܃˿ozE~żo{A^<XW^> рCW\Qw;tOL7  ^Wa銞Zbq Us,+\TN~V*A B Ckj.l7)]A%>ᰊOLbGHupUfg AfTfҐRS 05hkB"ڣb۪Ag0:@@HTh'^alu:g4`@u`llmP,"`DD9]m]C^zhn WQϕU- ť;.INPeZieϹVPʮz.I `L ,;G5 <+Me4Tb*tnNq'8k>i]NKW-a+ qK_Ggg纂[kWǺPU[8G*neZ nKL\atiuKVP1x(`5:8աzKZ]"=N]?s(@\2u*ܲϠk̅v U 26OSn%FjTW4fZJ#<\@N'N ZanS8КP3HCE7\:l-QNŽ1BBspHCX= ̂Mk@"5jZl'`+\)6].nu۰"RZ jm[Brjީ-siȥ"#HUKb@-_+D!"ŀ(! p ̔jѠ AܭQEÀ1S;I$Bč QQĀ!D1{tCBPN @j} vvP ߯ArןJc%gQMRx L*V5$6"HQh4^"jIJSib\H,IY~ZV!-[Cən! lV4V:cB'E>*"MwϚNU_5L+U+Y|~~hA0Hr9ֵu=uqP!Zs{nOSaC˜GPHZ \;+lEy ]zv8ˮy>w8KOڍ/݋w~88,CT1.kq0Gq5EdAw~1kgYM9pJ[qMZC {/ lmm!oF . [Ko>?x rX,G]PZݾ:1O%afؿg7csݟxGxC`}<8~ߝف#W?9/.0^t8zx3q;?Os<8=_T7jӸS^4_'?\q9s{?쵭޽yY[V6}G؋oǷmx,^x-~? @~-koǯױ{fo]Z׺ֵuk]Ee$`T'`66M4 n{ЬT@kBȹbV-Ѣ2CtGҭѶygPo)׳eXdalB4AA]V]:O06`ik֥U BEI- գ='!YF7l#sH$"5nQJEq);M~7'lR֮"ӚXY=_}z ޷Vf$wpʛ_\H`A`9n~O{\ץP#ZP[]|5t`N'chA@]*J[=h!F]F)UcBxu 3[cuxn-@N Q/NEWxTt6[h뀟[_+P8j@⎅7T6"BE!1F?oߏdu(w6hSo\TuV@j$$%0kA60HM5C{\5*.!Ң~P#Ǐv>׌l5G䖷 8uPJaG?X3 tpu}!'@R! FW^Z[;٤vCYմ"]A&n I$0:jq'@Twj8ENjm9J<%}9iesD Q hN Y`f9 L‹jm)Ah^3CH]JU:clF8' J-wOLuy y5gĘHS`X "Uԛ[( Bj2Åd5*kYt3H wyL#3uHks3UZyD/jH8oa=oqӞ}{w5CJƜc)6X+rK8VwR+;(|EDJ 'NAp+m=Aa؜Q+-|xV=u^|\vMse  ࢬϷ.8tuxo|kۧ"x3ۘm3ஏ{~_~G/}xSi'o~Wt [pk~x4/X8^y+qa:_o7A<Ogٸ{{q/0^xZuk]Z׺7+Zl3 PT h&س85l֙H߈k `ŘG yŐ[wh!g &P1H@2Af$Ǥ(VϠ޸`%k6 ljMWl\'$|TS4c1*JDrƨ 1ȵgp%J]@+䞥[KݭVH$uԠWTv*/yͅ 7,n!*J.ݱ=j*ug >8A`A"3WbI#Jڪ:Kpā5N5К8}.\cϲ' + mk%$6xlE1[F(STxM3+9ZՕUĀR@3Zݒ]C* @şN/gzR[CL{?@W[*jߝ6@̕VP+f#WDr;nK5z^qZk3UĪev n:МP!X~ a6~V@o(Og:Y#УI~-pF6L垙c)Nຘrwɣ gdup4DP6!Tqb gq€*üA;-tЄs<^&Ҋz6&Ьà6֙-l~a hH u#K'"N@$]@RM`|`t@P "' lUt09x_ u-| h#߫IR2R= 0%Hh`zm+ANB"RbtSjASt,jAP,m4$,13΢fA<<@!GC(%#V!P[lf0 nmɮz,f)vx]ٝn-و: (%SqNJ*3뢢ˌ$7!H+[S;HE9_CUtB ! ŀ|ZJ]'hsݸR*JjD:85:kV |vjtuk]]]*TE9!T!0H 3߰QY5FJrx򕇱wN>˟p=y{p),Ҁw~Snz&>q+gq_xkV)a% X.R2Tp#魙AfL_ j@d۠!`>f2X7P@ g"{:Tnz}Yx ._^ÜWko3.m(Ұ*+%ކw~G#_??Ӈ/h}~ͯхo/oMq҃.WZ'xS+ /1v1pMtwqUu5fI# meK@ƨm`s8l)_ƌ\WJ0kU+>UcV[['!CʞYm1kk"As*;xo3V=!kŮcs((7u)&M퀭9`M6ɩ ŵ9lWzg81av˵T-_a+kjbn*,w?ݜPa Uƴ"fl'شmpIV5t_7~}u{gus"83VdiUAdoBb!ITs0r Su{x i6pDI (& AA5V'zdTNБINsC wco1[y0HNȨHq ɣ+U9BW'͕sgCBm k!eCMts-pn:Lq3`tV]5Y#?rHRR WDP &\68K.f͐" f4!OU Q0|1 ۙ4c?R[h_- ) Cl,R%@cõVڳِke+6탸k[Obh8}ozw#ȓ-vow1UO{>O}5)G/x7jxGžK7-qhZF2gPe%[׺E ,%I,3VVE/A t-|7>.}" ?gNJ>u`gg{b9G7 0̞ O{3! :2(B0ڸf f( m):?P*E l R3K-eA4; !,vzaNP./vpϧ>r w{i '96]Izؽ __7w=C_ ?๯ɯ{6C_ʧ>_s{_ÿvgϫ==]h]} ?zcQ'"/~ǹ׸8qq[/|=Mlbӿ?x'^W>O綮uk]Z׺ֵDPƂ >b(5(yAɘݖ91'w` kjlaZCŬъP2;Q%B&.Tj2{VҚ9\e Z xdA΋6Vzw^e.)h?啞W!&@c@6CS8S@s] cas'niw~[ IDAT, ӭKeoF*Ƃ@7T45 ѨnIhE05iQ*2j3saM?V*YLqV8!nUgCAAW Jsq#NYxlk0) sЌ!YaVu]pWT*?v}#hi5`6o9` NX!c$ 4G leɪI/`inۺ,)kճ{}SOuj$ pˠ$b0 X75 )=}$p2ݮrV`hW*5qD@zF"Lϲ't_Rוr)F*C u;V'18 XU}5Z9_?ϩ$xx&eslP (FS={'+^Ww0jkWMvɾ1V'lIrSz_w-( ND`"q<1g-&nkk<30"bEP]W 1q /{օ޳`h= J!UZ(N%TaiMKF[O&I`)mZRAh4b5ZV<a3Z>k+3ٳad^߷9܇~ 9qU3@ A샨h-HKv"R"|Z9Xf='14Cl@IZÊAh7p3 ,bIuJ> 7jevth#o{L@sF و76p*KD#9D` -1Kx r!,w$Q# 8mE{cPԺ8΍"r()(pc$h>FI׸d:JY|1*9 ¥  l@.SD3 쓊k]Zz]9 c5jvcLYDӆZ>O 8̩Jwr|Ưu8=x hm}E*+0o?߁kܽ1_otZx+nqwop߄߄%'x{)uk]Z׺ֵ+T$eö 8 eɮJӂ,A5Ou )8`c@ ԩNR;j~DHyip3rT ZW<6 xi ڰ'hsf=[pk"lu/Mu@`SBdO<5rk3-u9-KF.QHւs 0,@{ͨ`.ޏqj(GF.,EDck])TIGQݷe#IpRZs8N9,zH}q/s "PM)zD=#7c{.SQSTfHz" n&kO˱;jQDԂn'ό4EaͶaNw{wW)ϛ2ruP,O%vӳa Ts]0ֵ1Zo<#gx3} Ra+{w jg쳏Zoc3;_Ox+xMo~w |ꧾ |/x:y3qxx駟#EW~&o|~KOoK?xgu|_;3w/?mנ?~ǫ__-&{~'^}3Za㍾ٿ]7'/oo^5~_3o?wʇLj7|H߃߃w""|^ >|Gx__W  _Z׺ֵukWQf4qƥfS".u#ۗYr)pP!!UIeݜk)8K_LEdDqYEq~^M#1Z mo'9#;Ff_ ZTM303;"L@X)2ǜ lJh9N$ Ii xm$#D! k '{9 PcM-d巎 = F"JT._9eY,zwedv)F:GK66 ?Kx, tPsjD.{q53U'`C&(^o_`fE)0z!n5msNuh)貸da.s* Yy0/R$N򩩸,KEe[aTH'hNa.8rs'C|JJ16H8YH)*h53y<0s,uH.BD5WmaZEde^Xhnѣ+൦je ePBu\*5 2y;ahYwey-U$,02b.s0"hDS I<瀜 lZ7FXqjo L7N]%rqL'5f^DtUrpe)Q*Yn, F9*s`^¥~+ Ix2~ NkmJXk_cbO\3%zu1[Oe/(v_W`n6><3t >kwQk&fUOZV;ZG۶a+ />}nN';wwÇqw>?n𖷽 y{?O};Im+x _ڲX![(l+۶kX 0 %0 {bv9e eT.\9?8XMW}߿#?S>k>'_~#ʆW}Q ?[_ctwoھ~^)Wٻo:|ן/1=~ksts~ӃGx}_Gr7:턿|#U#/޿w>'d]xsxރo7`GGKKB|!_SN ~ߌoc|t k]Z׺ֵukq0`Ӷ`!j.jΥ]p$w`8K(dV% T`&% 튇@yȑ̝Ob3.Y8. _`'w j sEAǑ=Ld0첨E*sWZr3 x*Y="<.M (ic 1$MgcJ'$dk5l+}uϜpKn*<.5RJ) qiU`3sʼnEH2t~DN[fsVg9!Sr#J97}Y|CKRVK rR董2p1Dۛ:ߧh3k!Zw8EXymLXH);T RԑR1ͤV.C]'Xɩ\]{@ВT hP&x3]iLk9LK"\v$T }QwB1V f裋Ġ3L\L]urX8ҟNKև(zoIMxsC$f: J91uy2y9 Ӷao q[ޥ߳tbfy0N Hr ᾬ+ =.0-1ε/hp[wln1ZKA.)D0U2/ض }Np=|r/FSȹcBnsDwusٖ֝>9tL0n(S;,2' \B/ZLw*1 gAQ&H;`|.cǖ.`@Pb.,#9%<={QҸvّ؜%nOF].t'\Zؖ=z|(-@e)8ՊfR;N[=^DZﲓ ޸9EfC?E >|[~ T~ Jxw6p{{Bo V{1psss=Z%mq7tk\|q?>xh5Y:ZyX ^k-XGz0b6.vF+b΁y ׺ֵr8~/~/ų|ͫ~N_/zZ>x+_wSSoa_Z׺ֵukS>I|}h7 ,euQNh{GU&O.KQl聊1Qj$ tR XKyh6_˼I%O aEhͻUVp*st & ^5>W9GNl2 F7)m^AO\O9Vv*"(1Y ɢDR 1) GoArw?kɝcKH1&UbcgpLw{#qHZ:w`A7Dk T ̙}[\lA g$EJ(Cb|!;#JBSr!8r"Ey^̨5Cn"v1ZpH@Z *A&ѐ G~yq]wӳ@o j..ֵчRޛIUsq Q ջC7VƵ˾]T7&NP>[n;T%J!mL}#a`ݓ{F901T(+hcb;N=> Mpxaۘ%cز'5}sj-m dϜA:O=Huπv[KzC BR "cHc)̖/uWZ׺]l￯KL;+R nnNϻ6Z3焗OCk;_d ,L$^y$>mG;3y3xrwu>k-@NlN7dE=8N8m=:a΁& V^b9z\H99Dc"fZ{k0ѵiYFֱBÔ[b-%f䵮u2ށw x+ފ׾|~?x/ދSk]Z׺ֵuk|P 5xJ%lf` XwnƬAE˂X/T$Y100&6$)_:_Zi% Z#{` f+VTdP@l", jgjYH%-FHX<]8Tl.30Sq<:Z J}*͑ 0>QlAaD-,ju+h}`Df'|& t%E {M1mx(b)z8?v8.p\Ng<*}4G\s3j%A9ERsp^Gu1C]IY ׺ֵ>vR*hΘ1:F9,FYU.u>FDeiV|x|wO[}?53.iz*M{Nk+ wqG nΡlN$4od3v>xnNz^.f!K J7Znۧ+ou32| ..߈?D9_/@C x'i4|6>  !%4rz ux1IT($)_(3 ^S}n~Nj=T Zr,uHInbD&ZoR*N l8B/;u{oY:7c0ҙA7tٱUFc`c8'cO' p[k Ӽۆv5 7H{F;kH:xL-A`)1[|F ƍk@cl0>?Z3%3 p,` ͔)97EIwVd n1l0LF﨧 ޛ%0:Uqk]c[ >mn| 1PZ׺ֵuk]Zo\NQ'mMR@$Aș;lKlE I? 8AW 2s[CtU9$6|{U[ }745Zi&1} Nb@lFЮ#dQOY.'HlË1kPlAJrI4H,5. VձseXu-̼\V0p_9KG`[tֻ9&J= ւ:g2V Z눠򴋒l2[ɦpڑ{DoaPvۺKMeBrOWo89P*DߖK]sɌ?DoHĺ7^އȂKM.xk\,ϸPH/L\r5V1ϧwj<5o@#2~?1n8+ W$*I(ɔǽd{} DVahr. 8(aRW "ޏinzS v!CkCiC{5 fRJ[sN[(TF]ZKlbѷ3uΉD8Ƌ9ч-#\٦]0F")TؾL^5UV5Ճ :#!7#{iC6$k#R@ė7A,bS@4R!kVp>7KLSRmouu8eu=S#h{/eVBjz:*s-p=C=UI2XkC$\8 @yƮ>*Z;Np_-귏өbv}da>yGqdUk;- c{Kj`΁12mycב.LzXm ЦDIj̳)k!I 2=\7 y䎐kl$}" )s&#A}g{N^uLuk[o(b$x.dZbPrʶr0`dKbC[a0\9ŗ_C vd >Ede_ZJ\al qY9G%ls@zö. >~}]G_/럟>^}8k]Z׺ֵuk+T-(E d$H;OEs)=@ wGiL_J66 Qqb)(<yg6a`yX"Q+ېmnjTOg*,U!G?@1&RM-h}abR%'0H؈iE3qJ2Ga"|3vmn&ۤ^@ z'/ u`rINT b82o &TcF_lM tQFgcx:67Y_2H%#5?iRڥ!uZ ђ$",ze$3"Nf-{}KoX]xI+"2Ι@?Il y$ |!pt-`囮X10!~ Oa]+nEtDtZqO"y;y2Ucll(H(8,(}iR"SͽvW.l.O`o3uzT[e/{{`ڞ׃T|d10QdZ HB}b+,MN97ṚON'sÍ@qHhˍ~N77!AsױyOcV0zHX3Pv1s; Mɉp:rʎ_ :Y)$O. MF͹Ĺ k^FP ԽqmGc*v"3-EG$rֵQ6DxY60Z;yFj\< @FNm?T+™x!Zk8ߟb˩Vaxr;;n>YU!JJG*z3`![?lB ^89Ĭ_xϾYܛ3ka4=[.(_uk]Z׺ֵuk^n2T3 fܮ|۪SG.s4F+jF alLۀ;r <'% ӑIuK3[. UH AFIKf![Y^T<hP(ɦlNΜ{52>P sz@q4#o!C9 62/dDjR@/-iV0:lt %<6$"M6lLX0֝GBxc EX281`kiƤŨ# n9tk$0@cn$Cd"S MD|B/\PTN <;V^ P.%p&S s@[)b9<%X.[sH O*̒^ w}+.94' [.<뱾5 ,x`\֪T4/ia.E֡aBO}Sb8To Er69G7sL+6CQ{& 8?Y-bN;4T"Y$md 8εyGJԕc/+f9eSmEZ}͵~`2{@$ d9}_d ɐZW|)xe[l%s]9^ڲ^8 ;PV^uS\)`cKPl f"l!r-j-&{ Q eEU9H^\~nUY+YĈp*ݱUR(vQ_5xBs{ /pemKp4tc6ޘ3[qu(M/Lg Hٶ J3F[Ya$LfR}c qS+75- 5|=1Ұy 8j1qs}[b Z|VaeM!RDG)ևej}aa=197+K&ID;ۆSu+h|D)$ostI :_"7:RDp;zp"RP)k$ y#"lR6b ]Ie[ZCG 0R"ZP ׺ֵ> E,=̉CM7ZRTmz|Q#p1S0'w%#q{s7l78 Ulӡ9i}O$i tT0C(B"CO{bkl*xg}cN|s{}? {lO#`]Z׺ֵuk]Z׺ֵزJ#=0`E-NYט^9Ej2T?fÔŨH'mLV#'r8jZ=j:Sc)HC*Di(dLK_pi [Ç3Vsjp+ y:z2|Y Kũ!X zI}4 -P<)ee-JH #}r$B@2L} f.DE!XBw 0!k۹,Q>90fKٔ`:浔Chid)0@lZ=Z;oR< %,.hrSم0]Jq& ™nr]63*3u qĄ!œM(2#G eh̾:㙓6\Sds/gmR|8rMGrКTO ߙ>X=/SvuSJ$I9u \̎|ݴe|ҥ <,C%zf)"(њa,ןu}yx,c]i&fR1ѱ,dq[}ԮVx(Rl>;"5~CԊMQ^ЕAv G*JD&XlRƠ1_r=cE+뎲ԋ8(ls9Rȶ u+} "߻," $9 hꕖ}tJK%5, V7ԣ _@ERI;Т1icN #0d*z˪S=A1eѾגʶ\؏uD0 }/BG4AH==GӖt W6f$# =_CC}@48}׺zd)"rec390Cucu%+AE'<5uw+Lp.hWoDY@ʲ4McVޭhjye?u-vp":,f\ REH2-?ZqXQ-⌡tPJp槳"\JhLX˹ @$x- 0)jlOΠYT9Gn$ x4*c0 2P>kȦ73L86wlVC-렬J0;D$k\׽'"no~t} d" wt3Sĩ٨ōU`͎G'mp zNJlaQ`a[[-tp};>+,̣Ԁ88Z[mU:Fx߷bLf*B[R*]׺ֵ>;r $˯g1AnonUZ`xbU=xPj_ <0Kķ|<**^|Em.TvcyL+XgxՋ}-lsƆ|ϗJ`NC9.Gd2ahb?G7_Xw7<~na-~i&,`Z׺ֵuk]Z׺ֵVZÜ/N1`F+Xϡܮݍ0QvsXFny}"J;6+55|)F H3R]n$rcNgVhs8iR{b:^&p/p\ʈ@ 0H1q6leT"1ɬ[Aʗ\QK%lZAfp.M!'Ieq?T/tUN@@ z zL¨m]QaU L5;8I 8pʲ}—gz.G^r^FLd -:A*:f*OWs TN~8MZ{w'5؏~? A :/+՜`Rp>/.;ԋF0sfR!:Zk ١SάϞ}@rDذ58G @9|Z{"\p6:ȕ H_eW\_y\T  `5]uU)4Εpun.TT&_.fQ}Cͫ"T DD)e!GG6`'/e7΋%u-NPI #~,M Q,π"rc -+sלG{ߑqN9 Zd7+ڋx ސ#p̰7k)"@1:DʻMR[N uQ.{%W4&?{?$@nZCAoarxD@Qk˜t|XdI VtDNڷ8֥4_;o\H5Z֐^3!A}pn@qH1c(psS;I4DV;S-;֭<dN>W̉R}'$NTz;{ hXJe:sٴR ^ x*r &[Y$Cb #6kyS9kS,ܑʕ{ Z/?~}۶aNfF{/P~ ֵuk]Z׺ֵukPMk Qj˶jfI HBE@iyQvTc17'9 M3>qsr4X a0ϖ ~ %C}64a=Ik׎lV&h\Fo ww:՟ R%K{kI }kꬢ 4*!z3(bh40(/Όxl|߶ BيAFd{헭&UikTЌ t)R` 'x]\J˒5b1F?b2,B%] UWH=ӏ@`:_ʺ҂;n~ʪΉ^u{qsz'DN^=Ew4:AlOi`lxd,AB9h|& R_`uI]`x8O,@Ț{'/`-5S@q*5wӚ:O.u"S]^\Ost-՚3$EvDZ`x~37B~ƿ? Ojw9&@‘^T.y>[HŦjTBNd+31SqC5죏S²eqU@ ȗ-E3l/|c f6!U ؍ue˖we@zc~ |L*# daWd)31mP 0S#CTښiCMxMle t^ZYSVdqqej^URoZ5ͨ2R)D>Tz+8BvP~9Cf%P<1DWR.BU *KafhF#R/"6@uѹN晧zxNj=I (Z{6XٿZu?@9֗,rs|zf>N)h!"ZSPѱ\Ҽ.4SjC1|9Ԛ?zR3Ie K7FN)h+?Rx`F[gucky m؇/Bp :3vYܓ[gn>֧84fk=[vԕv*/vsj5%+B1h"+rPAZ{P$ ]"14ڻq-K71s˓5&~ Mx"6A  xzROO7p$s:zOahZǺ%]`}f?v: Ӿ7{:,1b$ pm6rbk[@'x833Q+=^> ; ~ l0r)Vs|_zXq q_8­jf؋I29Dω0Tqfē)o ;FI.' $86fS]%dt1;8x-~pm3ѺdmZo+8")*>Mb G@8eUI^⇙) 䏹U5(؜=7(TcT麆pVtڃ3@.1e=ڷt\vሒqkRfsrm>cpFW`hܝ}u$;8c0I`sW6,טû& CvԌ8愷-ض&n.id{Ze&ٻlN - $b0k:W.#1$9ܕ D]",{Gs*΃K( M!V6L8?e7n qɊad1Ͱ5l=WV3QĀsݍ 7xD\/L]pW,*L0X440=$'&gcbEA 9ߏIë3ϘSq⯺(+ |-^>rDk/; onr|nLJc27c`7mi2(s6z߁c {Rٶ~ 1Z,LYC7*@oɆ6c?R=]/CUW]uUW]uUW]uU,[.)%ɺ+uՒeufWh9f'SCa7L:D ٣ՇrE d@v]" _%Kt_6Z+8_~0:KpRYd`%$}D*Trh$%vUr4XP=Q 2g(/p!~o3iɜ4sn-EhSsMdVv>><_k*lۆ9fa P_v J\-tNtT-2'ך*M 5GEuTNFNEm!Ὗн;Q-,~+]๻,Sn\/}T?;4OBZoI5}kZND}g"H4S0Bw.Лr)OQcR-~$vHҋ{^!!˙S8fmcNmL>o)L`t̡4UA|*kodm=yxߠp_ӆY} _od-`p10tHض ǤB |y c8d+Q/*]-4Yp|c{{~aw7Wxi3z߼E<o~@aA6]żꪫꪫꪫ_{%6&f Z: hޱ3ˣ.t/T 3h\Ief;5唺MH`;s S<5RC5z80jw6f wsǐ ȉ&8ǀaNEǶ Bs9H @R&ŜɌEWO] j> !swx.+iPŹ+S~{o)u m) j(Yr⡸黬^Jb.LKm3sX%Of\1EpB@_ 8]^H";5hq;OqmoN_UA &$/uo5gi-Trla PJʋόS>\\-dqi |[$z fJj`"t5NDzBǛB75 &*;rG͉Y@&е~3B98 p-8{Se8KsfK5AgxG)tk QlK$:4E_`ɾU{.0QKirq}Z3{dY媓DҴk|̓P*'XgDmI@k}X` Γ O$rk=òZ"`S*+lpTb،9j TbvyLf/{#!@9 kᙉ# 1]uL迷q>ֺJ q2<A.uK ecokO*=E60D M@1/rO%f/H"ǘ)/|ns1zuhJs# 6أD"UIQR|V=mҌ DZ-L›77Do[wlϭ21Dʓ04HC_s$hc˕L*`J˸MZ IDATh瘧㧙:1&Z*Q9A" z5ms@+csvjjp^e<5F51H55Hr(Gbbdb cG*@k{ daAONd]YEs?um U& ݣ)r9 LD2oSƁhj9 nkd e*=Ѳv)bAVRV$8Kǁ@rz@u#bH(u- HXR+CVӣ)w ToA`8刦fae JTf& xZ 7dSJC*qeig<˦P3Kn.4a"8I yYC=I=UA3y wBq6s fpLNr@)GRjENA[SsCI8tN`6Ap_ i|,2*4d3ϔhԌ/UB |d[NI|̢,bjfM!g<,A|qO$jZ*7n 31>QOsEFs'*b͙FE "|-bCG-+ss$_0_ 5aN#as;Z#)ħݦsόA_*VZ9Keʘ^ݴ,jPbIK l elz8al$YT<$7Q`aN1r]))m-KHY?QaTu@yTqS5tG) 51r9*dVDa:KvlR oL(4,yO5%"he3VWnr@r֕*5dNc¶Ytj c 0GiL 3M4*(~Dtͤ&=ϴ&jD Y/ä́@M{aRk;F?"J hꪫ~~Ǐǁ>{;# 3&م<}x "5x=VQD#H[dEFw| 2~/%+)Rdfa?hMB3[?YW掜-R\hƾg?~؞Omn7||_>ǭ%zxn[+3?Cٯ~M 31R|:~UW]uUW]uUW]on>IQ{G3_ ,[N*Ji5$`HgT`.hxPf9lv7sퟨz'M}um75O5ɜT`4/t wsNugqiȼ9ȪyҰu;3*-cT`T# @%|L6Ѵ۵S!ӢzIPŭ@Pg+3wʀ\ / ̉V}kq@'0 oXyMZb}%*ܩBHex+7iipd,m"ElgC*/6n!!35yR 2}̚WYL>ւ@9 R'_8ErTuSQh|,洮UNik>ǜPjfRx +%kA"ŴmڜPTK9pZU\vmѰS@&30]맜Esj*5{6 YjO{,QX8F9țƝIʱ#4U){Y/@CJI$H?;Ԝ eʋwn"Aj3yUPƙFc8k|0݋Tv}NW>Yiga 皫9ӑ Tdcm;qw;MH-9?{R杊t+:\i}?@%(2c(s=6S/x93Hb,pOڣPSj`:5w} !b-=n-Φ7OI/ m<%ceӕ@:$\ꇪş ߽7oض]LcEjm:`1^2,5+59pcL~cV'X㡛Ɖ[8TGC=iݷnV.+rb?&>O[> (>w_oo_[_ST_Ghcz+# zѯ*3ફꪫꪫ?~cHrBCV(3 w֠M}^zxjzOlc֖`KiZxs°OyF4C-:Em|6g6WmaL6ީwTdD2`i<'8lEKԟD_~'f֕rk77a}? K]gѻ)s iA\εnho&}j/KX@%.tghTH<ZǬy']@?`NUߩsA@̴6'ۆ1̜hkHbBsZSNFN tW(r,t@J!a@k"Hj1}"*h7̀;Zc8FlH󔓅o'`T&`r 9W Go#zMG1S&}PjR}4# 9_CJ%0q-b"|Ec=S9Z)(#ckx#*J]CNqp}Lx4<5w/wܶ //J,vs7ӭ)֠G!~N8Z:47NrWlA2#Jnim]W]CB? &m1ˎ骫ꪫꪫf#N'bF{B{S֦aERPͱU?0(؏ޖY6֩aު̛,{SʊB*6{st ۶iS&zͥjF ނ#dwse&IefNa2YY>i#eOIEʠ*DoԠl,RF#ь*)]{c0y o *Dy B]y%[gS3d y)W,0.BXb93QY8rVC*JH*ܩz139i--85xeyx%2'zk<3u R5PRqiOR*<34Hp~$6#2dk@X/4Ӱ x6˾AI҃/zٍk/Ez:a5}H"z^}[k)SAp8d tafa&3Z:ƐEi\@WTdP&L;8e[3?xֺT9nhlw$R+SkyYž6N&R$ƻ; Skq b-AE׼Sq0[]Y\RO@? б̊fa"<^ &Xsxy+:RQ}͹.+).NY/+ԐʤmsNX#X_Xʊ: ѵ@8$x+pb0zaxF)?V S&f&}ʞޏZꓗ@啕K&VU>̰:hÏ9ER3Z|z {m˥cD4F$f%?akދ1E(4h:OTcSRΉnuM5{CߚX0܇e7w1rY\٤̔r7Zj@dA,OHMŌ4si|VI""npZ%V#VzY+sp&Z嶂%Z`&h YJA ȩF60*p-'*x B u'ߔ(0,ٛ11'H P2ًܐSclk% *-;恬q/'5#a"-Y`W= L ?QLsTin!u@1p7˪]n^ "Z r,37 @#Tj67cb\6Ǥ*m[CZ2K#'10o<c$Iנ0'e,J8?!4QZ@9Y(f6JܪqاGݲ0'`Ql~W=T Bj@c!eVR 076(kԀiǨ̿#q*R EE7)T:Ht8ԝKEEab&T xQTqR>e˾/bPƬ98ȉ}Nf،y*[0_y( 蹙{ٚ͜!qcI‡g#3aie*=,)\s ˛ KSؤr@6KiBcU +3 ̥B$UR[gpx>OswM_gg/bf T"/6c ?ů}w yiꪫꪫꪫV&Y-`IUrjNkhhjH:DvջW&OZ+yUǫѼ8G8Dڂ* o]l%uS34]j&AX3)Ap6r& ,Hn"1mqI K|ؐ=&ղtd70jeNX|ASDN)͔Iy 80\\eZYF^36SKMK,+ LYgZ4]B*e^wXR&9ms ~*3e*pn0=[Ʈ5fj xkfRD|%𖙣PeIaNg~hbe vg$,glO"Tf2?,~ XuRߺnE(^,؛U'xJ0>eQ#K;~fHWVs罨+:hLSg#"gcޚN?Ur9a&E'UйRb\>U~O̢]j1_& "qkU,3ۙGqίÀK? CЊ}SA  6><D p S'etv#,ⴡ5sM]s&I˜stUZj\ƒw%9Z{*UMySϔ&@vǯL[N%)ع?ߙDmKR z>q%$b{zʦ?UcL*,9"h<>YRo&"Ʋ3qZ7w4r0pYq[8jP1F&[̬^\'yi gs5xc sC7R|g 貜OSe "˃t9DII*Gp{@sC댳9]ZO!%:* '9lDuzq ?DVƼ#Df[gƽ :9ftȈ0t)b-:j 2@rnQ"^g)&茐$ݏ)`?iC0p|^aZ *qzYx~qKϷ'23S ;ſklO,1'Z˚6;ǁc&3Gɀq OO=_|[|x~ _}g3lǎOϑ;7/O'[? H M~ T0ǟo?s_>f;*UW]uUW]uUW]uU߿x5+s2&{<ʊv8Bˍ}HF;N5p&A$,U@rڈh3jco%+BRu@~iOZ-&\2(F!hA@( &[7,дB{IYR gc=)q50MB!ZMTw.YIi`;䍹lZd .V(X#;x& jkPe`mHE)TKM#)-L"^6:IK7V˧"pf HsK(_`\f</h;=Tw3IpfR |j.qO%֐c(NEy!'dfz|Ub90']KAR2yzIX0fj PYahѩZ51_8(kiS3{dGi=rC,tyK`Q;nȚTA ܅~vkV͵z I1 ?g,)fP6sA"*-e;x/׺ |%uM`qypIq]R@!A d]@"/Xȵc1` (RD"rgkMߍ+J;.;畩[rd {DPe\YhY̺sγNpgho {(1'*jLd7 >mWqAW.M *ȇT~sk εT4 q >t-LX1+l1#(`{fI<̵ H"LJ¡`6-M 83wwL?|G|<^*9gwwZ󻍍/~2GN`GS@qF7`p3V͚Bg{f)v@RI;"ۖN`x@jBR]rޏZax-] * kuKq%ɲCao81ҧrwB xh: hѥUBV`꧀,&QV$c2Mu#MvsGvuHXTQK$#9zTG.DsZs $7I W#N`Ths}nvoPO5FzʄNg\쯴fУR$ k`*m/g:E@ި'IbaTvYxZZ"kwgVńTɺQ)܍R:I3<ڑtk`n4E>b52vۃm>L`h&H>X7Cц>AM $Ц@Jֱh}@`uC+2H$:S8@nFV~^>SE]"IT٣ f TzRj<^~]$Ik2M8NC; ?&vbUx@vI^2[~$ଳ7}8ƾEし ^==/ ^^^vrY6~cвв9z3كJ up|<4y5kbw/Wi51շ >?=^͗k;K%ڻo?|2\ec>?}w\}#>=a_p ~;_ >SL;%5~:묳:묳:묳bceݚ@2i{#HHИkELDo d0Vȟ{Q1Vd a9upP#_b(@E3Fpt *9#0CUZe*U- ʨ}2 $\9 }UP~wjz"ad($U&ML b+E\kfKR5ՀQP^ CPU5x-KlNe9'an2?m40"%N ]j("099,(KbLRe"RJ$[;R%N/AO"Pn0o,MqYjx3Qax>ΆD 92_uQ98C&YU+`]@bHZ$c*sRC,1X zո*zsME~^ABZP{Ҝv~N*ʆ(x -\]:xfmx_9uGRlMXo\e;gA2jH(ơÑj:*hip4F oRPyeV]@_{} .+bSLFmWqJ~!fwcש{S探1m׽H2QVkm:!:;=Ykns}T *4SsHGOnx((Yd ĺ,$ ֺ5{b֐ `3sEΈ{Ö!xӦL`S!YR,qERxr wT췃 gj 8 *5PFiܬ@xm}[9'(:IT1`>HN30ck;6E}bvM:W 2y~'۶s/) M}h>*R`x@90}tR|0ڑy=3#i Ãy |tfT7|#` ˬ2vXRIBB26\fƞ*$+nͨ1rC'"Z}XcӮ>~q6y:L}#zYaO vö<~?c} `jdޘabXŲ|p/#kH1<EtYWׯ)Cu1<][|o!gV@ʡ-RH2~dr=gRJs,C*'V>4=y |.T^^TV vL䀊HKtR2'$!@bwGST5|+LPAY+œJIǵ3 `hrq0ukmЕLh(0c!u,iK@3 Bk\^p`fu*!Z`CIJ1)c \fʬrḬ=llE:a"gZEѪ#z8U*guo<^V|D[~I IDAT۶F;D>0(v+X^; w~XJ(+@ ee]׎/X&o՘/;R/e:sNFWOOXw;_L6vße]mSgH9KZw?3guYguYguY$JUMf) в~t86?CSlX6"3WZV!̤h"hC]ZRH"vQ\ aD NeK.W;ܥQVF+5BKJ!lfCu($Uӭ343 Ԟ Ag ҁRi)8$"gЊД'ހe_YY3#79,A_[ʭ1,jT6_YHNTC9D'HpAax -ז㞎uTᑘlh9 (+!&Дe%A,U܁1DA JTEٝCjT2`4kdN7$[]Uû^pU Rl::d+%>B&B@ FYLn</ ֆ .Mm۶W~ussN@KƧY\ 3hn!yvZ6AfU;P.<̩R㡾iq^^O+ A<-EdP]/e]/^n J,TK((Z#DI$WZQHo6Maj=V~b-Rm1-_i(` UIw pT5<ּ̳H,c !j4؝eKsQ`yЊ^)NI8= cH-J$ṮCe$Q}7 9'ˣH,VzeFTF8d>ԵrzY2MpW})ᖨB%D3oT́e"`ЅX+"r1"yτƝYdG"&<9@\ߴDF,M!̦Mf87rwX[J5M g!A; 8`'KmY`ڑFP0Mr60>{,K*Кu~k@fiVV7ʽ7 t3sddllV0Lo=uYƻp/VT G w6B%ŗ?;W>_}?묳:묳:묳:UF@:sܡ2(Uغ <#RE}{2[߁9rYV\$9Lx!nc~IL<*y4`ϕ dhݚHAtrP/ PqG1 BMh°{M`aUӨFVm2LгM픖̿zxS5d 95ŘIbfN Z%(,M,p իRVEPM`f CIH1apU),L~Q`9B[$ A:_[Ȳ[_:Y9eMG k&ĺ%:1-@e.' Ҁ^u}zmX5V*yM(z uVT'~ӊK- ifX,Tg>+Oe,gW||6h"ƾ !9mfJ&T?ҧV AǨSf\!sBT C>I`h`oB "J9ai=~ wowXc-"A'@R/WtZ[/r]{fwEy<YinY_dY B R)=g^~#A"g.{)O kS1 ߈ulR%h%wlgT.gb1Ø]FekoPAL1FIDRN "\$)aY Ջ 0 Ϭ֙UO˃pUB+ /׌|ʈH5b1`uPǽTΖ}l0yZ_"A@;$ա2{l$^b/{ Ihbz)Tf3[1-U \#Kn2ۘ;H(W^C|NKdEBy wl bQ7>?Y:jhMk{ḀW} l036wd(s(](Z;op@Z(P*FIƹW#M4bn;P9|n_v*-IꙦ"n ς@qV?rhޜuYFd(}~Ǿ1kdY)>~x.|Bۖ}~\ ^{`෾ml}X}Rh3[ ;z_X?P1/ڠMdk@e] ^AKV#^m 6{:O-޼y}8 .+ptoD)~緿׍guYguYguYgKfJq1kT}%hk&P!׭s)= ,Q @(V M}Mg  ^ʥIzVȵӸ^PXjnV9vV'ҨNeRjRК 4נ$GSEY{0BEw"|؞JeP>m 2,7iKiU*he\tR,NA ?_Te=EgubF]Nkђr~X󺓈0Aw3. .wffRLP٠#KǽSSxH@TfMZMJ%[Mf[z^@D)Ã6&H"}GW&Cʾ[h%$H:x) `n;hWQVג$q:zY|C #ɤ< jԾ;6ӦYʅuLѢ5Q`78#gvu@Ik\zz+o`5*M@dJTbT1t{E0ُJ,;kWjM.u-"Ϫʅ5E\w2%}!p}۰e[+Tv@*+M),sR勉- SAX`"2s5GHlc^ r!! tpɱcYWܷSgJ&Dy%B,A;R{ }8}8!-mÔYUErrph Yjz%B#{2^  `V ϛc3HpMҕZ#$" uYFh:;mۆkTǎ1,+`+iB\/#`L&(2&v\۶c]?c '{ m#x[ 0͂;>C~]:C YSe[NmკKL|g~H=Xwmc?|;/, zڇgoћG? !B :묳:묳:묳~V 0J)(bЖtɲl*I\R+yXO a* &cP*8д?;v3<9^ U N ~T&ZV~@B"9TʓUEQj+0&N Tn L&5xD< 2ŒG6jDžCY-'8Xo5+"hg-B`]ԽRYy [fdϮO/[L^6O*nx~uZRR)j pG) ޑA<(yUF 8"nt~<)]@Y*#**@XzH@zCV =Apbvyj߫) r͎>`LQr -i^=BU^yq5Ņ2W .!a=rx>0rZֲ3g)u3*picL8Ԓ\ڝ@U^*$gt&{dbR${ ^g);e:Ri `O]*(kGVuQ}+3 >k)2 L1z=*-ۣ>2vQ3G̘o:}#ZGk~!>fT.LM"tj.zb& !'{vl ,;3%J  ѵ:FwM) :yF<_!#^J{M օdmHui v*t030 tI$}ɾ c0@T =e8R;R{=p"6 Co;\cJ@IC( ::!1&ӡCrV63o^hCH mǥ6u%i-v*:묳:묳:묳IJDY7l[Y%m̼C0S0ᄡTL/NV<.KAZѪl%.im*IuQtiBG`R$as8HP99ܟF.3Jf q1˺I1hGkB0} \yÒ.=P:f)MJUQt$ U`kTiTj 6 <jǡ,pf)6jb̘6,(Up(83BŹ:lTC>0#iawZD%hW.+K -yMި,V8K-ZJ+Ip.V>u| VK,fSє=a} t&u5**Ǵ̈́@TkSȇ; b*J+A;ud#0 pgFfa`)BTȘ6bnG}ʮ6-{0s7~c3 02wSONJIc)Q*S3մO(^*v *2R Q"=r)Ȧ>I5|QD,D3'@f &$mՌj1ػ:.R}ÃyRsXY|GByo 6kԤO>twP3̙Rk(ki` ָ 3/`2@;1TC>(%bTZ}GxWe샭HN!vK=d#x[=y=C6/:깈QxDzp"<FH3| fd0>PÃHg'ؚ1 ؜Ie.K}Vv,jb&J3ǝn+ R.ӖDi^$D3E@ DIupxi`Y:a׻2p,tpZZ-*rC IDATeix][G6NYg~\i|^,zdObYfԃeEfbv@Ku]ѝrruDPyvĶ ^iw;ܙY~FO1&,k٤siOKr-PU~ {GPkhDA/?{!G~gOwo^ vYguYguYgu֯7@/ ODM3+W"b$8X!H#L(bD@(&RʸDʮs}Z3@d@+sI6PT@ pd>FF@wGk)3բ_+ "@:^T҉cѺQi}j|mb:`,HX@(/ .;$&SJEiV|@ y-pW/>%1Kk ֬,ae&>G:]H'n[196Akm_x{iֻ/[̤rR䈀Ä+ԏ3KjTz.RE&-zޅ8-PDr5K8p{ye3 -V\j~\cUpDF8uF>6jS:|{o3ܚ>X.{e~:묳:묳:묳:Z̃ LRe&@:%^ @&N+-eY a߽"*킊+r،ҵF| iTaO3`" %83}} ,Ѷup\  0*T;AAur8@Q9:Us \tr/ [C0ק>uLAe ,ԕP$ aez{κsO.UQ*b) / mL'5f看"~NNk^Gpr$Tr=>Q0j>|Z`4QдRNĘĀ'67>%d&崕 ZS}_ \W(`)eNũs))-PM뤍+_1尩 HY]=h{, ^LUGrrǶ5fLĈBYjjtQ`RyhƿWS-B`B%wQ Ky\bڗ:\PMb 렷 , vU1QZ (gda ,q5[*YAØ P,p媉`lXMpm7ŵlEI!`JFL2p.vH 0s d5-(Ϗ -1E*>zU~|o^V |X5K y\\̱*O D)kaPzFW{ʻIIRS*ۍ|M#J [[W|uLޝZW/~<\4;y;}IYLS~XK=c}Gfh[k3>"1Э#<:)q_O̊V,=Ei [ G9@@ Mt`!W#1; ÅdWᓳTbm<ELwG&#R$Ym篃!ჹcqFl;m1|wC>/cs V=h =Oy3~*}J6hT =w s m qtP٣ [v{;@ybC_ܶjFu?~a\k437_v5XNKkwS>rXUu]"ஙa3M-+ߓB7(ˑ۝ký^u h`^? ?Eo /?~?C|~v:묳:묳:묳z뤵s j-˺i}K^"֩*z+_m3*)QÔSjR(T$\ dv~Om ~Qʖ&GV,nS`q8"L^>eAHaigaxpX-KV) {TNI)+9UE)Q4tf[OH H@$J)RIsg&`8ȥv=%(`$jԠjuǜTAbi;ԄY hW=,J"ŝ!D hk5B x[:VY6ɲ͜*̀^*֥1#[S砺@MUh^vXS{9@s`GejHǪu[3]EmB1sյB;Vևc87׎/^.OrQ.޿8~@_K5cu$HhB; YxC`O&/] ɞ3{ē@Kҝ.ƒ$$H.P@Qjv*(|r~ާ@kL˹aQt Y.JWMBM| $!=h5+A"s <  /FhLDe.T`2;,͒VD9Z ߙ;VjsmcfHx\#O-@[p皩|bŌӴ΄{@ZLm !K'y" j^OT.$AdNp-` d\B7 EяGV }hJJ^ /7 |QcvuDЂط7"P"$*w߱jR=%EI醹m5öxPIpF,їi> ^mPmvR uDѕ/d$RH,"LRS%u-8>fK = ̀ >QUeY0yJaޤ}m'I8Iu!Q, - ˂Q!QL CQA@rH~<"v,ˊ*wOXM#1b .|FRSSq͠l0hEi"XAguor b"zXq,m 1hm. V\_#x*;q^UXjӅۆ베/_p\W~ D[:`Fx_ b.=^H)NO_+3#⸻>|'[zl40/NUVfF[w+io@0@OL2/3㰆z'XV??`c__ꋟuYguYguYguToZhDV~t CzTf*ȩV `c23piY#0:yFBQ 9LԨ =-QI2óif81HWٔlfkZ NAVde DZ|A8"g*Y65n lc3 @MI'Y #Ҫ'## i C'̅^E#nLbBr'EpLEݒ@Za7`Q9*: ͦ8AEE/UlEU}50Wx1hXUH2tM͜f B*055L rB I?iQCi(Ũ0=*xW>\1zlUqɰUn<3k&3 i=Vr'Х p_}Kie*I$I7;KpMrmZ~9q!SktE@KS<DR:1ϽLPT'D0ES=Bdϼ p\WSu|Xvsyk PmY>GH`@= +wHb\! |ݒTJ2v_gj{?~E6Hƒ;zL$$pW sH5»g_%z>M ]հNrCxdp̼fr,E:C1:lDn\lBeoos*hht8𜗣t"đ*³Xʹ 1'iaB3*}p PA*Tp2ǐ )2[B5hy^󾙷v Tު FgR.#@-zkTz'V*^q-RF]Vڒq0SQ!k,A*&1PyUR#,׼o(*h57|<gzBIuuh*PZRh^"-z*}mGx΢m) VO̓ZA Z(0c-TC TW[3=#UY4ףwTꡮ?묳XG{jhu)<nzo(b9~(۶/(cR+.Ju \/W ;|p6nx}y@fUn e]p> w}O^t>l+~wuYguYguYgX"ERh![df!|JR4P6M1VK*`>ޔuS2v GU{Z=i8JIh{(((}V7~b.bD6* SqIk:h!ظH}!Z3tGJ@P&Eu[ vƵI-mlp4GN*m `> .ν0UޒNc83oH!?]"~bh9gԢ`+@. \)+tP6RqΥ|F1_ЍD 12xp|t((*I{&ȄE)FZkIw*$"QC덯[T;"RG( ,$ ZyZ{<}a (,%, t,wԕuf0;jcqQ օwyG z?$ 1L$0r_WHtg(A I/J-Zܞf/w%IIБe8b ~h=] d73,Fh0-$)& F,A xpT{aJxw3syn<7 cO}|V+hƲ.h{۶bo w}EY'&`=/LZ+?l?m[p\wcf^^^,6{gW|qVb_~9?;C#3Fxf!?@jZweeBd,1xyyE(K=zo? o/Z _^^??묳:묳:묳:k@*Zbl|y$:UlTFHMU3s6S9Up=MUiGE! !0%㣴aw_,&(%@̚c 6Ui -(aFJfN5b&|mLa\g[<جV*q˦Y =@lڐfQ,;F;"zD;i z;Q-^V p*oTUٞH} ôжrt B&`?, p&j\eR='T lNT.QMmTT1WZْ*\"-3/i+K|o<MuǪXj *0q,FbMWETT5B6!TAr8Ƶ7H1|nivi .&od0ĤͰj$FrpnIT[5sUv?lUB+>:8 iѫ?Ck1TrDP-fm;3oHtТ<-=At&Tc IDAT`m4f^A ҵr;QQy@Q̠EKzSU㺭xwX)&G`˂pG&'(ϲHT8wޯhgi ~4NgkQ!& VQ*AfRr$1L oH(y[[˴u,y_;`ƻKQӗw>{Bޙ"P0{qo( <ۋ k)]>$鬳Y@׊,X̩uhZUE˺KPXyk5#霠)",-sBBb>AFhOL`aAfw,KuAMUSu)B%bnz$)&H9^G(['#PP(JxU<>>X%1PG"yEIPkr W+`Ygu~@%ǎ"uaj=ؖ򢓖"{Gɍ~=WH'ݷ燕nRŻ ^o7H`. Pı lSh $bx!xBj=ʆ ~`Ѵa75%)$'u%`5hOM 8}@YVh=?v:HxրKsP |T:@Ks5%3@gSBFړP \_#8~b 3Uv8l҃ʳM'UPlU]xRk>zOJ#0)VNQVCEP&!1|Pesh!`HIyj*+!+R }<7 AQH{r:.LŠ\ Z5䘯jV0-Sq EXD#v mi`GUVTdZg$X~?zI 790ّAsX d.o)ze&g$AI;4:Td6{kS B` _h©֊%|P=5P'9=U8H^1@iK5\"PQ EP]+"tI({*hE O vKXs{I&岠ZEkMfC5e#x1HZ#T`i]=rb/^xO\WḮ+^E1< e-7= OF2RDCᠺ7\ {ATSIR[ u~<}!*Xx$J)}~DzV˂Z nfD7=޿O޿,o~חW|3|x|5QA{x zŶ"eC+{]A9%hy‹y11ǎuY"O k˳*mJDPk>h%Qt-?|?s?묳:묳:묳:뿪FI r_N5IEOŨj; 8lEđ7A+65u5`SoiBh(3p%}!MT Uk#5CK`~HiLlRa6U!";@vq1#|?AB %t=HT~V6g.&@$n ;$HO l:s|.0z՜#Rzn)@UYj11TLjJON )sW\@gJ'Zާ8!G~6_`a W{CA);E5Dj( vQRJd(VUoNElQ* ׽c6:ѵt!; Ad?[n'4!#SOjZd/|J5<nb }r4:[@'}h\S{O q%^nTZJ$H*HAtHRP-qbE)^n;uӓ}˖nRq*.E!C{ùZ9zM@5U>5wl-@˲SAQi -V>Jp>s>,borޣvmuY`jt0I aU zo5,UAEGc@&BHTkIC$!><1Õ総;"c`%Eioxsp=U!F0|xf$s$ $@.Py6"!F*j-yߊ.XD䝈v1}J:?f$xiA˶vG)TTT,3@cZ>_O=>3//?~c# 7şꗀ* ` ?]oG+*BKO>԰l+;er wX,cd`x6Hu[!XQ_{C-L>ADU oT+~xo\|YguYguYgu[@fE)UeXcP=OPia? Ҟ2N%mV@Е#T0iG *3hCF QSѡꐢJKI0uא.AbwZ2ˆ3,Ws{,finK([.7i+XKZYEZv:1#֛b̗ alNEf;r|{##SiBmڵ$`TВ4rQC2,*L#($QQ,_|8 vԈJN:S 3ǡlo&Y@Q o[!\qvGN,G{M3$0y`̖P= IBh,FjX .uGY JVc|@)獕O:(p)pj@PqLEARH?[{`it+@҇ M iq,O߬~6½*Ĕuhqw0f=9m-DA\^FED(vKHҏ ցC4>HGI6yyk0 8*g+a1k ǎ֜$3,+-ef84f82b<=<{jP <"*:Z?iF~wOWSU:/r|>|ߐ}YguYguYgu^:L@F-iuhD930R]B^-TN2&OM DJ64'2UT*j2pUoovCH:1 9Cb@0 > EfPsyMJƬTHzچUEA nAXa.TuFiF@R@З H5,ՔF_j6nֲ`Tw*٨("zfЛ|}$@ ֠l)'3帚n ?Mb1qU= T'+TH3@֩DTlt^ ->m݉36%ץ`TR(ZPDќNmL,_UXB#fo2k9*+DֆTlkW5M^lLRj0#PHV'j(7GA"VaL[|Ēg&-W(W y}1QZZy|$m`z>:}<rK9__UV5@Rԩ]Q J PbpUyi>fz]\{\rc j0acǴ*5U3'>~=㹷y$yᘃMiPe@hj_ j;k-Xeyn ̥ a~!KźV7Mclh^ T H \AEGՂIvqH28Ic̊U,JLPL'>aV/g+ok1Tc$]G@$)Ƚ"`3jZOG n՝wKrd=shfGK P:z:|Ni”ڕ@A@{"]VDUV jo:?f$ݻ',ǣe +^_oJ1wWlOnϯ ^ŗ`ƺT˿@=hx}cZ Ⱥu%"\//<+`0ӧo.;vgöm@FTNQ] o @xUuSk03w'~fVpڱ OiS|?SyguYguYguYgU(hW[ pV R BETrV#0f筙6H0Вֿ?:|`o ʻ dHf o#3 4QB ЭTA@s4AcQaCM 9B t$Hȼm(WY`Vc' UlǠBKET5DY@[);+Ne,UkWa>ZF*}xSEjp+-~yx)|$Ht<[$ :< 8a-xTbT,Rtx\HNt֘kQ`j0TITT{P޺=(#ENDM0\ڬpl%#+UT% 7w$_砢tOR*F8Z2 Պ?<;c a=@*jh9K( 3';Ui[2oX xn' {* &YS)i;3_/@ BY?bNI } TlJC 흟 bXb$LXu')FT'#4#` bL*jP4w)Y 皙@ָ3#]pbМb IDATULv<<A UO`۾uf xgVn F,RQ5ձ*$'-FŮ U6y>3b*FkǸ@|!2$`Nʻ@`8< c9s2}SR22u4/w c۶aiL*岭蝮bT-wW@bFrӼ# -J0hc ;D ylY+=gu~=k+p<=]O CKhϟ F?~?eZ{;n.ۊ۶z}²0g-b 3e[ ~ATq^J}ohjLٛ3}Kv`PU o|*i;~xy㷿Z;˲bo~Ke) ʺ?PɟuYguYguYg_5sh$˴0 `U)j)Vxk̆AV 8'РI$&8âgR"qFӲ:-]5miF:^4=%Ob3vΓTjT]%=5PSePаF'ؖV|(,E2XW h"qlL*}tr|E=SHjd`Z<* bJ>`kŇ9@U*ӟւKU\R$Ȟh :AEf&X-BSC}EZ0T灃D!.5. ͱs>rwN[P* Q8%! 8<3*;mZ*b;I  Tݹ%qu7- !$&BEh: O2 3M@/=ծ%A\9TTE>[#8 HG=jthP[kJ\HHjbqlfF0?XD@9cZ`w?!x,%m;\tߡj(IdHu{pC A@pX83pyӎ[G@w08 x4`tЙ~u9gJ.3 ׍cjBXm}ӖyXyT _OrPM`z&A|Q@s?.\aB fm)$TW9@-yV{{ AӎۨXKe\Bж~!Bu*p2&)F:}&$"(nPqo pomtD;d恪QhQ-afG6GL|DZg $79ZoıXT.TI'b3щYJ >AnS1xy@Zی:`ĄB\N7>a`1@5_05i?h`dlsjGY'9`5E'"t,k.K=-:]?i~xyyw'0zٟv{|Khze[q}h;4emW/_~5~~x>ۏ_|_Kg\lۆ;n7,Jw5-˲aV|z~ߔi?.c {㇒J35<}xe`;l>5WV|3O:묳:묳:묳CyCf*H n6l XM2KbUob0{{^oWM.#U=rT"'1@}\ǚm~;!T㰎6QMT>x#L$ɞ"5ȱFҦ6t!\"$0|I*PB IB޷ _E3G\MTť*mܧ3׷hO w` Ǐm pNp눒Sh=R]{(aKbJ@>`.xbӲ?d>A Sr+ b1m9f2"ILJŮ<ԂeHAk)#CqY*jմ 8Z'>%{g, LwXjI0jRA8{( {@Z?qVԵ0>^o;{s|/TC)ܥBrV mpVj, ͙+GD1K$8Č K |{26FHbH(Uy{p#F@'x}[$i4I9fN6! o.&XH wEȽPjHWEBP<}\*Jc) h#X֊Ty~ x jayaAO?+ޱ=mj1޿1 JQ>~9@DZG{xt}((KOxz%Ge[p?{Ƕփ ,dd;Zó0?Pkef;!@aAx[/|-zpa+m^H5pnk7uû놥Ve~w-#=s!$?(+<-YguYguYguYg0 \Ӵf̜ 4e0zU'ZtQM%`+c,6e S k^CbDTkcҐf%z%mi9ATR*y"iH`` %xMt`UF/RХ*Tt: hf{q45{ =Dꃖ  Tץ"I68sȆDAg栚z/Mf)T 0# +;=m;UJpx'Uh{x 6[w,RRq] R ԢJZ+ׅTN|`;pH L2Jj7TN2UtQO\‘ļE->4iӛ}.hOJ8=3g3'T抎,F\ApBX8j-cp/Ԁk`][=- r*z(:ZXAjjhxÜjH҆8@‹%22M{ڋĹϚd&I&bZm?310A !0&(PAŵsLpnHp0U_s?M$wFayq#1pEnX.wJhRw I>R5:4`XJs;av& J\ARK8k%B]{߱^zܣK`+ם@*lɞm$PE@R}@ *'@my8 Pk9oPFUeSE*B# ^q*BI +iC -fIgKD9hN#}# 0/F+9<&Rwd T+w-Ge/1r_y1 ~)Fyc@EQj):HrWѣR*LҚ<L1Lgz$I,ODU="/S3=Ӝ%_\ꪼED"Ѓzq ՝5U5 RA`߾BvXgT-rOp%Y"K:4+BO?{VW'y[(]}؇'FtB9uKp a<͆ijhS=}׆ \,hhϛN^nS[gG=Q]*Z\ҹogZc,w|}y4o__qߩi\tPkzβ,lw|>|ͬ1뺰}ۣ+X"cF;v;52Z\_ζ/30%ԷRg>fyn%~}u ˈ/oJv?:fEN͛d߹GRƶEGߣG=QzԣG=o^#nS QoӺdhAfRKק=PWZecMen+¾jZ+|ZbN8{8+P9wc5q(xuM2 r@)=!囹Fr pO EE4i6v("2Cǘi̯L3wUSIP{x@`8qݦ7~K\aa/d Z<2)PBynTjKMA[؅;0Ej4b!TƁ6^C9RiT9A 8"©V.5`<*K ZFqIcJ=rCD<-T<,m05L҄osu,hPMQhlkk51E2!6G7<#fTjr3U^r|x2w,m৕8N( :U[XK/MRhА$;oE>kT1aNd',m0|ZjUrMX:$SU2Y*M8.@_JnZb.iɌO sTNε;~@2սyu6gk- dLLs Gul[u[[c=dgЌTGMm5~ؠe.V ;% 5T"`=DAa| "ZA4ek "fFʷ-z0rMC.c=h b6] cY;yOmF+K*pjePZ4BU>뎕L}㼬,5UCHdzC*,!xPr_P ޏ"Bm&聰mCgJFJX;>8ƣ[ׯ_>az9ϟ@[or◟?qZVeaY}\8V~{᧟7|X)ݢ|:ӷvzr>p<=]vEI|>%]lK|9~Υ1ↄ ^_Zxx-rCX[X$aUʟW~w(|ENK ':ZXׅyy}OG=QzԣG=QzK4[fVj*P.$Hp`Z%CZJ`* ,,eމ`RedɬQQMFLj(ĕab*Ч bPB#4-c'e49e1ĎG>bmt7FVR H9 Jƨq%ƫH%0m5!~Lvi=I kU[dOrd-UXOH^Ǹ9h$C,@S1v(۰8wʘjd*Ge U[ԄE566_%T|fkxPj|(qP_ N)2\Ij3g.`=T3 (7ªx†ujfqOᮞCj|z.5]%j3 Z<\dZXP7+Ms;U\ KSzx=rhFCjcZ3qR1yMr͠Rh)ټ,*ѰBFNr"se;ȼ{΍% Gj7&i@V'(E9#aJaH2KRA͏( t }pݍ;yK1J dfl=qK b7Mly ّUܧZqå$qw~}) &{"dN]ՕoWTٻSfEdF TjNyAm۳'z6]Dp5‡Kۇ,-Ը9ka0Ĩhu=QM85&-@] սqu뽧{:|X*T;,R8l^*c{9q͇s3TUx6S)s >*G:VҦƚHD"TpH1X8!g#G`xl#ܓq-n*}CH^EDhRf)Jٴ@{){ $V- Yđ³w(a%b}o*``Hil׍DTF[WiA9d3m۳mؕ|MG=Qu$n}]||oO3d;ڢ*>|zqhFWe=am|{#GXvr{g>~x wryחW^^<=U-F?sQsN||%mV;g۷zM6˲rᬗSfn죇3β.{ٚMRq:\+ݶ xԣG=QzԣGam7$,qR9ቔ@%ڏݰ"%;+<5FEK=eVqG7* 79'D#Uu 5gduȖUMqfX"q(HD6&yk fce6ǡ֭qj#<4lU}tR @oZ̲L{Zsʡf8$5?,YZzZ!a0ϋ ;ȑYMM"1i#ƭh1<aO%ݧַ%砑yVokVRYPqޝKĘ*TC}\kXxZ9a)9jݰ}^>2>lh$řWqFP֊UhI28犆uZ̜Xެ}N71o\6#6| IDATC;9 }B?~J"fgX{P%TiE{̹E^1z4|VIel\sOFؽh?k3 P"evjƔ*{62$-Տq\N.o9㩵QHp΋nu>DbF#!>I7r=_=Ad!8sY<.V4qJxX>&ĝ6djX{cy.ħel6x Tf:3m*aYJZs^$֪},´/c|#< 7n&\9l#6\n4kH{G56,Ka4-Sy,4<O+UI16:Ìp=졋 -z]Q5K91Da,>26bqe/;RBQh8rvjC56d|Fs=#F3{Bㄐ ^)-NAթCنSj1Ik,U{l·>,}\S)Cys{6֧.Uoݹh Rť`?q(%cNtx5,-Ծ9%xf Q(۝2^r=3F2XDJAKj٠Zi#F,,hiI*4,6A"2p"2[:aQhYERk*wXB Ʌ*9>JV6ș9{K7!K Ͳ[Ņm$Ӌ"#RsQ1 &ʌ1XRfMb NS]T: pryMj* y`/P q= PTP>(UpZKR+" Z&*%fSQ$T @ni#ZDU*ºTrnOJ])%Oȫjј7"-*;x(ç͎k8lKq4;|ZACͩ"`h's4sٸ! SE,#ZfӃ$W~ˢns8*ùS,UeIu;]a4r{8Y?L`hd#Jnێ#]n,xI~yaW<2iFՅ۸#>T;󻦝v8?RY h +6]q,ΌwWѽ;NNJNl쉱?*h.5z"jZ-c1i glۭ@[O%2k`qf=Ӂ:Ra"׽N1{gh4V{߳90 9tuJP$VG??}rbm >}_3~G^`8?DiK6cr}ouo;߾~?ݻgF0˅/Η/(mߥq^B[.AX+)Üor~:ր}t9 z;*ħ_>-;j # heۣvߩ%}9v2cOCǎƁH<I,]tϜZ CEc зH}XګBElfo,sgQ5D@3{.3i*lzv \}k-op4T\xɚݧJ/T LBqIId+gV3j)djGXTi)9C*4T,|*r’ k%cIVȳ>PIGTSQO8#m ><`̯=ԩ& MD!C'ocBussP P=u965YxPW*,M.]c=k=,i@P-f<f>lwg )-Usj;5pץvp'\&6:R5CL׭n5lXd>ZFX?BPE#a]NiZj(&Vw?wsIvkFExkcj6Dl* -P F9#ujOP:\8L9Y=> ՚VhTш4mKRI3"g`"tlT TU٩1S9AsZ ćsy@(;ZӅ#0hU4֚j̭iM^aF-%2}0c=#p6Ka`HYp,ԷƴfY!qRK_ѼtkdqP#fc 63^AyiqسGsQJa=]s%L޽{̸o5z}e7Hlpmi}Ʋ&v<3{XRβ,\.ׯXZEi1z|`ZH9=faT.VmuQzԣG=Qzԣ=JKǿUA*QXTtfwcmE"J]U֥҂r [Bnι-mwgƾ4 EUz(^wKl8vn,Yʶ˩,E͍ۮl.LiEU`H.mBjN]Zط4 %>sk=TL_M{!I堄tk-쬉1!E=<)Zi,5"J'v;aΣ7ur zʠ7Ke{P-[fjy }OSa?2)ͼ^R͆{׃\뚅:EhFghJDP4κ4TюWJ=14S:ص*ZDP59WS㓙GC,0t6@Tp.:T{@aC5,TOa?2f>183[~t]95㼆9eQNũ`>c<kqHnc+A9TҪqswZ-%$0eMЄp$T]$ߣ!pEt=;w*r4& ő+#%!P<3Y4w մ~S]Nd+F_6/\B;-Kg{S+Jt1@y"\䤗 U)G5>)i53=;-/yX0Ϲ‘SPJ6? N-'>!:T>vL!jڷ=]J6Z:h9Wֲq^C^Uw tNY#2bT=>-3;ߠ~ "7V#ۍt_Ίro\WCՂ 3g=|5: or:)5,8Ͽuw<{fK>:?ATiB+w'^` yy}۶GFZ ϗR i+/^7^o7p(||8=:\YZvZ/;6.l}zBylzԣG=QzԣQcw#VS5b@S?/םe\PZZh*4!TI5Z[*3/qGCx7Tvq}˻awU:ҧ&MYpEr Ѫr۸y-Tm,Nں t[ R]CͰTvDPLf~dIS)LYT! Iڲ2߂-gZ) ,B@i$sqB!m*3َACR+JQU#?P#zef8q!il.[8-Y(D 7[f̊pssVUah4ܡRC(&-WR8r/ߩ]FΩnv=P'So,fږ9M6A1==Tw)HupGuIki Yٜ[Ek{Pƚ SdzDC̵T:oĜ7ޝVCXv %im$ϦNC9UCu=TWb 5TP]_IUTN-9v6\o3<Մ[2&6TBT- h633Ge2G<ߢ AD~]Ӗ} *Yt,bC=Jـ:= ڜ*^Ut[0Ry (ɐUNN!@l0-q!tlkLKK9a⻗Fy"f@IgF>?Y B4Z /sS%XDB jٜ 2PzIfj#4"|QCy:wpo iYt0A#S;2-(8Ni P)*1շ)6d Źapzc=F~Jad>N)&9\u^wg#Aja:HjKiF CYj5c*J€󩲸0PCA6xThLiu.G#p8JJah+ZQ-0e&sPOK SZDxd };;RC6dcǼeh: wDl< U~lan ==Q֯Z>}~?xtxw9|9 /PJl{\7ZۢSw\3}iB~wީfRו˲WÏ?ᇏ+Ҕs~O.g+(6F vj-l;˺7aHQZ\gu=:O㰴ڷ=:r$~|N ӅmxvtyfY*__^_ԣG=QzԣGezca;H uNӒywBΩVj S v_Pgqhdz$eD~oX–p5=$$@hA3ǁvnH7%O KCJwBڍvUN4T:>wXwW`ёxDN[XwLpUPСDC`|dTG^L%V@Nu #D=!g>,3SA-IcPgci6rEf4wJf]K TyZ(M8M/"*xYjo am*+єPPY`}oRov;_shE(NeP{iWBxI}c1Bfbbjz`l[PשV6REYR3p1ȔȭU:?q/u:hZ톪8i@I"kŽ Ux!aZF!,=rF1" GBh;`i^4c(EC6y5TK ahP6f"|85.)2h%Tn5}"W$D{{̙֙Je!`l-a'/Mp MS.coJBT§ʖw` hhZ+)^,+3k=Asdu!AsP`yq"S0ضkIur87j^ugFJS*}Pptwvb/ ƥZ &RG [ ON cٱNTJB{0S'O܃J>xj*lcU-OI1l<{8Sȁ oư]Vqe͠6 ǰl&mcQ9GJdzYOt>؆=Q< NM^ׅm1}h@Sn6uJ*KShVBk:Y-(V*c(]Uc#*{G1&ں`9xdZ*5j+l{71yJk3dB-}<5&Vc]Dc3κnѮ2s3縇B>Z3fCgfNvB4W EmnV(L3ީf#A4) DZ͛(n#@}TϽQzoW w?P7 {yyyamN+Oo/7gj-+ޱ18O1Xׅo//h)G?)s\wۍR}mYv }ad6x~wWR)Rݝu]Үg~¶ma {|ʗ/V@{Z96>(}߹%6J_G=QzԣG=QZ20 kj:a1BT)E򐻖5,Ch5FNõ*l͢k' @)"FbQp[#o52zr*@(s˽p[Ky#H@{o IDATe⬵ƽ{6@=?Si!G TƁGJc$<)ӢB9 =4=r5gR{dFS5h¢B@2m%ˡN *٩C,KB7Kc B%,PpAJ81'z0U$@TShJa j-2CV͡|S>8 " VU>O@+y=5UڞᒪWt^7zf`KQb%*L`5(HؚϜ)8aOP4AFD`OE3ƈWP0G3I}ރM5:rr% THթ&tL H5o ROS&QFZ6s fޚy'lwT *{*S-ഄp†uͨ(&o d8KߒʾR:Z85TT" s)#QߜpK;j䕓c>! ?2\uqoe9{3:s): SMy;hQ'P}"r@^tDәG 5s6?\0>UJqMޔbՒ#-r3{5oVSu{hkgk{׹UDkka9NB7h$Dg֒٪ 6 8zrz0Xglgٵ."&F Kʹ5նYC-rMx!|'ߘ!N&l`7gx4H47x7#иkbb{i-b5jsζpv)*9v%KwT,p)P{gFZSaZrD-*aM{o죧 Fl7v X<:(}]7 e5*rDm51Oj9(}WC%c*T&&T%wl{D):pKԕzr:W>}B/%]' tûw{7|_~u]ܯB)_n/'jV52/603˺p9o;w܍o/|z]RFາzb3l7vqªao;松wP)Q V-2dJ %Rm `;3ZfnG=QzԣG=QԌEV\AjvePDh¶9wv3<2|/kPBR i>I(2FZIj [+aYV"T%C % ҖN~oU4m;2<Ռ\H¥ Z9gjun9)4 x)2翉l~)>oZZ ]#a\˴\Ϧ0cgQ|C=Kш!ioB/ْt 1V8CL8ֆRqw}/\s}Kh(JSu;Vh" 6huaAw!3R~4Rk@Ԫ*PuJ4é8gMƠ- !=R}羽༄Y})Jt* *u JJ;nƲTlWwh|SJxԣկ>|`Η/_ؿ촥^ă>N 3/_qGTvw* t_a=9VF\WJ-3V^^^s\bSvKmmطPow0. ׯ_VZk,۝]XN+E+/+U+OXO'܍gƧ/1)0)Z+[wl#0FtԌVPR*۝SdȬG=QzԣG=RhaC)[OK5ZfÍ{XFZKdڭ²Ma9s]}jUh*D;k]ǺMhIdEZg*٥ɡ@S'QoNKCyc\Ҕ~s#sN/Vh{)iVjeS}Zm1DlS6h8P[FG$%zjyЯSQM8 0%z'8(ݩ Z6VarV%>vjYX4D+ɚZ&3cVrN(41"el~? +;"q-)ؑkIC K@<ܞ2;L|{]gX ٥{ub>ƌw pL؛;?ØΛZp RJy HI|ۆYd[njoP_’`w*Z1FbGbdmqijXS Ct66v̦ Y,EÿvB"Z¡a=^;ԏxR|\\tpR{ILP;:H*'p*r@FU\lI'UMZ!AaAF m?*,03K6XdCUc>B&$IXM2-gSX x&MgZC1-fJaEh܇sW3JQ)B%l*,K}_Αk*}ւoz曍 yE7y}Fm;@?ʼ&c=Dc:9-w-a-e6FkIɹ !`*yvm}z*E0C 8?" M, s%kum0l*;&űfwc{>:`}PJoJ̤;V,U(֒ \Cg[tgSs=hلNysN뒱q_ Xl j[lU/YnlP%0?\iњ2Fx] CXK3XkA,m">u1̨F6{#Ǟ̰$vwضs}2)Z8 ^<]>{h~EQƲ6 r PLfgQmG=QzԣG{T+<ijP.9|^Aъn fJ@ } q %N}V*?QkldnFJ4 ', 7=pZ[t<YGE*2@Zú*4 3[4,%,[Q\mwpCP}Zזc8mSxX4i#CX0Mb|8lP Ɉe'T@&j{K$=2Tui/t.N@ $ lvWS H/A@H%:ȬcREQ15wqsDP1Q&z ڙlb/uLvVǙ D9pܘ(U&ijiPܘf\s̯5י#{j&*8Q0gx)L ۳:V0jv*5ylu^i{p!-v`0mI{ϧJXˑlJBm>irZIyp掚u]0!&SA9ͳC]\n*m*Va$Ķ5VlFAjahΡNu(c' gVis߮CPk-9⭀5V^ VO1IbZ'jz<;0u~Nv*E`a̦rxA˃Jyڧ +pveQkc֮.nqN(^l } y?Jh<*\O%x#(17i?AgnP6#Q1=! s}jǁ!^XV:;6B %wBS>FV,FQT4?*֑f}T}5hGJ{jTʤT,w\Ci5(96H/E^<'2 G&vt4\ZdHl+9[2H瀁>xw˜?5G XcM / !vڀ۶!o~ϳ"(΃돚jh\԰a1apZ/ӮٴgXwLc OgTP}he֐}z}]u^ /Xoӟ ^_n;R/z>˟Qxt;c, nǡ3,][\n//~߸H=Z+~upw,qxwԠei;} |W۲gDz6|z,kKC;ZKۺ` G?nۆ cl+z;t`p±?+nkcGUW]uUW]uUW]uU,hat)VDގtt7>,XB6 _,ORZɢ1y ?_sIըJ _hvA# dܼxa hkZ Y2 (hl:N֞ ~O%8E@&e:<%ՉXP קzPhYJїEU Ӡi@՜C!ش/ס@9V7.<2羒W0Ӓ9kpN;+jӪ֋;|L DBy\8ͩoOsƩ= >ei(n>h 3 N%8awYOy\|ZNs^; -h pt- گ9zEkT {wl(ӪOl([6ML5T߳|ߧ'g*tPɖ N&@l2ϩTN354aU _]-k yshƅt Y5׏PÕ̑|c/MYn slF(ġyO8 aUL8"@9 (5 YEcjx^ )য়~Bkr;9~ _ra~YebV;+Z4P+ 7ҏc߱Ѓ/m`+_<> +bq{Ƿ߾- G^^nq]/PuCoBp3> ^x7\uUW]uUW]uUW]k]! 1>'TG /rtA3nܵPf ?m]mZ*x=F﬏oqǺԂkD BN5DA=zL؀6*k%5)J^#zm^qm}Lk^~]h/THؤ4sفFGew¯0cè6 8z;lH 7),2 G5P\f\ѨUTA@^YBgWRjOkOk%T./xL@Se)d $hTSQ1J9ՁU8ðB:-+ZL̈́eʈu541|BvC՜jip`%0 gꪼ +\YM?45mB/sT.y1.`sLFiɫW83 3"&uT~*0; thӴȶBZ7)z '6s|RNkӧk8!mgv!QTB0Y\Ǵti-h CS`2# l{M(x m#L|]Wӑ@rox:63+xa& 녗 +s#f }|P3bⰜϛgH>'Ñα4U& 傷'@R S}dO$io8:#d C&Q̢z6|UaE4*&W֥1|FΓ G%禫y(xm˅M,K#oR+"nx{ǯo;|"_q+z ; mi;-z|}ǶЏ9 ^_ۊoop}x[l7 go_o4BpݱytZpOYЖ ibU/m8 l ۲UW]uUW]uUW]u,BEMIs;kv殦uj2<ܜTG ,gh-z NR_pf IDAT:73u c YqfH96TSS#Obڸ7S#Erџ^ [8VQʹI;|t |ہMCАRIjTTv)eܨ7*Ba v g%y6cz&3I(104j4lMm`?CȨDU@Tm;4`݌!`^Iq P7nɱhtb$T$7g(mR4'\v'8-A835Tup! lD"Qek7!‹= \L@wd>F?ϐ8/*LyRv. JpcAJ4b6sSSˡ 7,axݸN:nlmFp(bx1NXb6twI獠Cef!0 n\X<4m1(@5KILx1$x7B1:}sc thBJW2.4S9k ܛ@i _f&5}Ȱ5d?8s@vahF^9AEHgè<-s*kf?-lssZW3[ǯmxf4 8Pwf+LRxJЁkx 5k8#TOEw \46γdMz6^y_c)+9h+>Us]*75!|vV igA<̆YCi]znƠ }h9-?DmVB,+~P쌭8Iwl|ܱm+_ R'?۶RkebC G6N;yWCζHzkVsд^ Xu2)lre)ڲ*l[[Ł:`T82G=VD8@TF!=,> 4ɲ@JYNx> ۙΌoj:aQ ;BՑ|/d2ѐT0?ˆ8,rբl̺ꪫ]; plx۷ohA+Im^0kxh}G[V;mC^^?!߿}Op<_܁;2/Z~O?O?a{} XvCeWЖmq| ?8up:l 2 `W6PXq՚}Nʮ&q[WGꪫꪫꪫ>THAŚɮ՝P`P94C%q,`k̙mX~ttk87 ?.;A?Dd<7[]TZizv`iHs̴YM*~V7#j$`3eGqI*JKqѩ)lu7ܶrÿrGɸRD OHd8U2&\nwP8-LS¶0Њ9%kke$ F:AoH%{*3Q@W%@ lbۦ.)P@-]b];OP]`>D/vM8?5^@79 VF_Mq)n GS08`ks\qH exnU(d(5Nc\sҩ R־_T+a6AL2!!"lq D&4AःXm$Kz*ĬC Z* * 0 A2`Z2y6O \KJk/l4$658,X(pڎJϛނ*ie6g,i?d9oZg!g۹6h=Ӊb7q!؝0xs.5iSj殎 o5kf#DxNLYzkg^;LYˣPc09&M< sM2iy2hpR]YgPUI.+:l:V)yp6K5"}Ӷ| :zWƟ]LW=;~6GL$we O߱8Z[`i4YX1[e<*)0Zc>$p6@;mețaMhYK۸U=wDsܶ PVEQ P1m`4:r.k贊Z MyٰDqe3h`lK}`U.G&iBρ^o|ńss lZ`cZ0KcMfc5|zj@b$FN;<ec=1Fr{i1N:%xNmAKoT)Ϧ%;UW]GUwO?QN~3~tþ,~`];h6~lck]τ=v`H?x~z .K9߿ {TXn w^np3/·}߱pm /i~zxy^hY8xek^D, Xqe_uUW]uUW]uUWf 07ca 1:[<6d hqsqc%lw+ k(8clgG?sa洤>7ĝ`'MU~e}DӬݕ;Cb=eTha55A;*\U?-8FTFIIRr~$X9or"Xb}Ej{0j1jʮSP͘ ||ř'LJQiZmp̽*)E-)sv'B0€[+ XiMB3r;n4o-R!LNTV \q~ݴE6C Xz`I&ĝ+<-6~ 伿31{BŬlZN6m5+ &Tպ1ΫʞQd6`lQTC8 BO%:jlho-Q[!6h N`:D5,p  8WY\g %I12<LkaT8 k4p/fwz 0o+f~vw9dsH1`26L@7v+b:3-sLqt1B[P2.n* , Pw n4> T>gM_q%KX} jp7XF*f 3g|@%`jHRE~jǏ O:B*t}k1pCٳ)s7ȹټӚ%68FjF_|/+8LJ!;BqDwNnP, ^0L7i;](aG=" ̀s])g[;ZkIPc$W > huftpw5ZT-cX6֛fe0x/~39P-pdE iF&^GC5;#\<_Y [{*/^|5Jt=}c@I1ptHaZ'5CTr{Ƿoj>074C7ek3Yk}B8M:粛am K;נ¾}:B?߿Yann~5,?A헯xy}sǿ?Ͱķް?X—?}_~0|?//o~ï}7<*J~lKza 8_H%wt` e⥭MOKꪫꪫꪫQ JmޞHxK`K9ż@eZb9!ϔd:ٍ,0FDzXa}t%<`〔Z(X,?ڍf*Ox\@ x\-sݪ`MlH{p9; P`j zYRQ}+^e>#T-3h(ﷁ@]jxXw6{1NMB6v̟AQlhάQXgJ*pQe#RS ks4'<``\K<󆣅\+9=}#ݬUF;vZϯ F~p_vقhHU|긭Qry5C@'p99:q{ub]5 d K%# rԀ62j FvZr_0J,c..ȈhHٶ>cVIQc`!0 ԼaE`$Z|T߽􁉅bl"d'L< 둅̆0}dp89}*kGV|?ִ7fIg0c ,jJkj`Sm[uEPhKɅfʰ.e8lt\uUl.l0 v“o/'܏O_`t <_~ ^/vYb l/XņǺ08>˯*|Gǁϟ_;O@%ooxw.x9?'ǁ?b > woі·89ZO8zEht|}߱/F9?vY:kꪫꪫꪫ>i٪)c֎= Yf'葝hGxxXy?|m[ `ffѳ^-)5f"n8V(^`5Hnا!m4aJ ` H:5C R@2nq x?l?6,[J2sf~72h'qLPM VB&Tyh,3nXi&S-G8p̀~ר4-m)ש+^B3 PXSp%am)k'F#$IgDjږ(qlpaFvO{V|ȭ j>6K$*?:lS6a@Q'o nWҶ|R"Q7B̔bR d)(HXa@\&rHN0Rdfs~$զ3ֲsGfܾpF5f5!uglXvBTSdfVL4c@2XPs<$М<#7ks6Ha3;yh P*SMm5e3Ke}>{ G96CJiБ0*'g./96՞wJkLo|zbZkRv5?Gudt2+4 >s|rQ -) 2 6e͕%i P)J`Qj08m9nP& Qf@{$;&`S-*8׊6!9";8ڙa+ cԌLcM-f,h}XKUC@Z 3Czd |d L>eva1-ssfP~]f&3a|Gƶ5!3X N/|fH54<{r̭˂nKއ;FrUAwcym[aXcA- 낵9@OU^ku}r:s{;hF5r&e:7qVt h U1xti͑]W]_WO˂oow<w|? xEwfGh FnfZ`R ~@C ~v]`?:^^_wׁ1=ж Mڻj|5//xw.eC/~MЁ< VڵTa|ή1vX[qho^;ۂ _uUW]uUW]uUW] `(Td *rn%]3m>f:m^R D.OïW6nLReM%dЏk@FepBցC.p6 Y]+禤6+Yi+iUX!B4GGnI3 ^n7}6஠s@ex.,bK, mpWQ!T M M׶Re•l`-f"y3GL{hiB`=d9@Hd7 x?!>֚Kaj`0nAԂϝߘJTs2igik-Ƽ܋£# l|0*XABPwdq9 [F`y& /H *ho?IŘpL%-OojjYrTb4<,TNcI6xruNW'ܫJԑp$>Ix5ofGn^X-PX28WhK.)d 45׉9JnPg֤q pFiikA6'8m.?!lc罰gFPJ2`v%\?mq*aM yQGj- d3|)1 &T NRAk>Kzгϸ^U9&Uy- w6:}}^ഭ^ I$峺#bZq{p݁;`af%jVKLL.#; V`ۼ7G;;Т!9l9"RdW'j#|3\Yb98Ž9m>#h *ЋPc <0LLM s|!&,A+r3dض0SjUh-pºxTn/NgaAtz sesc]cᶠE^ G?faRc5¬a?>G f@VXv7d{/'ӫwqUW]uUW]uUW]uUhhITq@`^7%fx#SfgTƙTr6̻kzU ~{8Xוĭ!KX &r & ɞCl*DR;73 :MSdJdT@M`@6WO Auъ?4Gj+B W/D֠rr3YLzSq: CJe̙h̿4ҧ2K8e*ĂOn!11jO[3l-Ό"aVHXN>aM!mkN^hH, X% kf: 50QcT4 B))i :00dZ IDATNff6&8:a\J[`WevntfaӍVYʹJ0XOlN[e!O+h3[ؘ`Gk]:Ol"?3,FE6t JPc%wZ@LЇv`ҞQGNl 0G[6OlAh,6sͥw#1!G{@y˄o洖(tA6vΣiJ%pE盛}ئY&jR%#jQ X&'v۴2}L07N@:s٤Tx6%͜p*S{6GeS?) &.3Q! MYs.P9SC*ClENk8JAcXS}6m̵% 7yćcIJĩZJ[4f7IJ"bpfԸf hqM׹lPe'df~|+%u haX@[T9^sbpdIiƶԌ̵&QekX(%|mXټat307ey0tekBnZӻZq {J<n//֜{< GC5q7ww# v1bGb$7]2*}upc]L.! l&u>КrX3 m>9֦w-MT 5>֋MgcUW]GW]uUW]uUW]uUW]EִR_M 7m*PLO3Gd)<7۹[RqӼ2[ᴕ=1jh#ݐ9>z }` l[;Յ`:p& ^ܼEO B›Ái:7џj`fnes A[sZ.zx FU1zk^vRf2::)= '3وN{h @8Uu<|„`$ڮ8@rƧ=4s#~J/d9u*3a5 $=QKt™p)IѩyҧczD(,iY/v0R [!G@h"ǩM]4*tg*-myQD$,ڄ"m3sJ@M**xPqRDٜ igY F3"0yҚߑ. Pځ0ǑGd\85ͤ5K94?fa ?~ow( eT'Zccq z2[a 2ګV糟.UWu૮ꪫꪫꪫPd()lE#g.$,gQ4*52*$拂rhȹQmh1QoY3Vm]%PVkc= P+lrFqt8|psU˭5xŤ5'gqYSLů8,fGa`#㴍 B&0Qc/s>jxήqU mCsX oAMu'#kn<炲 jPGJl0pe0s4 sӱ8j8uM`̰\yьҘk?,P24Bag^kA1Z"\HS9uT$ciYQ]1q- bYui ΞSnd=ߋHPFjް٩@EվBIg"x /3r0S6(Tm} /RnI3 e8m%DvݴĴ}~1~~cͻ{g-1a8{]ĮIcƵz6Ўo 3m'75t~v~?>!\¿M+sKΩy9حPDG8FRx4%]MTySAƵ][Áph΁1d5{:kI% TWJA_eah <|ܰX`i@t ¨tdP&C=р@KK1,H,~x'  U} fP~wc`]7V-#[xG^(g!Yn{P͉g_ģs~ mD5YUWu૮ꪫꪫꪫN((Rp F}LYBQCrC3;'$Ry Xqx~A'}x"1kSis_s%,Qa=ްyn<apo@-4,U{n:)57j+Њm v# e9~tTUfɖHBQ%+yh5 B) I#(5e.?S;* hd >5tR' ]ɵq@Ѳ{YS( H]۵0`}'|27ReObaT͝6Θk1 <&Zw mJ[Ĵ̋LD*RY&|ڱN&h]T-#TxPT'1׀*T4X¾23-OVvi ט9w*Z OHiB{`y!* C53?Uj xI 5"\Yz\W v޷(S\3|);DوNku44M%p!M>r2٦a˖mI{D}niM3Zf`<# 7<@BH{Zya<jtJ{ϰ32L_SlrlU; ħ' j@Y@vE;_\$(Khݖ(*֩omZܻVr|9$dZi#HafqkmM7P8`:QnʜgP]-!œz\HoYCN#c"b!Qp q҂aT>m4a >@tG%iZ(cNbsVV9U0=$i 7]WP!8H|^o LzޭMRI,6uQLcjMTHgχ@mCǁ맭뮻뮻뮻.0PrTw؀:rm% CmI sjf@9ST@0msIy&R*T)!DW,6 ӌ.e3Y@&E&ybNaJ1`ekIEa]85,z9ʙy< Y| O[.[lF'2sD&?y6+JAWZ^IIp&33sÁ3 ix346 0_-ttnH|E[ ][J*eC[R?^n4T@U i!eky-$%@ rN[]]M`BRJa{"`$xb̬v}H˒M z:_'x̷MB6tC?L)9 Be.[!Y 3 =0> |p ڦlY;Al 6dgiTpu_ 1U09hWlR+X6Pgn4 K`VPcI0$XA ȤO=(T9 .5c0_t*b|E5{Iu\8F!x+t&F@XˮI X"[RMgmjH-m>D`AǺ!V|NڹK9 T$ suA 5)~. ;t.l`c6s_ki.5[*XZ;cj]A?h8ݰOZ631EʤҒTxs" ç@xt0#:!L4 M'RnF`5o/~`g(<ķlL O /ĐA+io|P>I\~1 |Xg V怛(+'& :#9)ss` s</^QӾ32k=/c"蕞/7|y؎ø뮻~뮻뮻뮻i3">5Ӎ 2}qQ6iʀ )U'lj 1 SqJ ]+:_"2ur͜MЕۖ;63#Ͱ*mVJ Tx(j ض>~h%5]qpK.f@)hU||  o,2qb8 6 =5%k{j!~aͰTa+;g%m U0sÀ)i RoOZ:O0 e%2UZ`~%) /"B2*{< $UmuL*dѢFYKƙ ƥEI](UT˖R*Q(#ORW".^jѤ0-ʅ3S>+j3fs8F@djF從m, 춤TTM,05bi%Um-NEwvc~ ̒4s5 7M<ù F :NH.ݭ^$ + &^pmuK-DXaZ $/T~5Xg{.@Ե ןjzR+_V۠:w @%3qAnT7*}&8}ENHt}S}?\_ Ш&(yi罛 6X^ 1FfnmMr`w IDATwZkк<V}k"h^-3(wtT݃XZEYE:9;1Pp 7d,&I[&L@ޥ&3Ģ!Ap&YAI06jJ*Fo0X 6w(hvzj.9a Y*eݮe3z*Y׹g@ $f>׸<8vvu/; L5gx(cUb kD74_'8 Ae賣[%ᆑ@E82}$hxtG0sS훐٧gia2'^`H~`x''l u3PV25(% ̒ZfL6ŧ)y.f̥l֗)*YO6\'sh(h-ZK>iy'`!W&"_Ǽ+8F""16di8ϥFjzC(YD#5u--]ڮ+%`Ws|׆\d pM*뼂y~qx$h'uUI|[,Ͷ@A2}ں|+`)RmI#s8tq3hф2vRdH2.٤\ ݕ۫kȅߚmοR T@ʀEo"#>"?_.n|-e h.YxhPچ){evP(@%Žı-Q hi' V\IZMJA-໪a0Jo  /z|xvPX뙐Rm&\ Us |~ ږIq3ب7nT dq$:Z@2uZ VT 4źrqK@W@:ڸ75NtT|Ja9r[L51б(m5qը jR^KkTk/?M+81|l2e/kcPX* Y$X@-AeZK[?_VH*c{IMm3Аڐi(ؤԴå"+UYF5;|y SOǪܱBF)kucvd #HUWf2`4y\$P'U;y?zH.y VIs[3{g^sdCe 2}}$0pm!/}KpNk~@Q͋W%E!Sʳw]b-r ^U D0RacnۣJm1w8-},'$o{3/[Vo2V`VB;9h>HS]{lϡ Qi@c*4Kڵ cNlIB*:}431Uy!o3!g%"Bfcb)7=2 -V:fĒ%vEZ|9D`M2L`{'O67gYxK uRSAe30lIћwݷ})KYL@vDHkg 7`8D:O}eyLG&;2/: ,В>x@{wet ~""𜆉Br<ў 908_8cs8 AujHPe=Haa<tbf:|r뮻~Q?@;d#z(/:d fFT gJ+h?Yǁ 0wNe)kd˚-TlP(C&ـ|uql4XAֵb|%j܍s3S cNZ WpƐt,{R;nwDO eeqV [y 4S@Cl;2P+ m;N$4'Eb6>Q)2ﺈ&TIIgĘݼϽ2M/}-0C#@ |kØ+֪4wr \&tJZ)q=@hRi3e&aw­ll-0թ O}ʊn`1lv %`7.+eN=ܜ0KU p[-s'%02 2L:H󳂪o_r:3:f%q<ƀޫ/{;$)y/em pGez/U1[ո}>ǝV9M%-މ.\Vʢ893;y9 T%V'9i/MS u\2Ӈ.\Ck$ .tw0"p +uK},i)RvkM%o+}ƊCH!Xă!TA1dNVpsPJ 7=C :FaVSV(|e\a$*vgJ=:[\{B+1Cy |y;0x(zy0\gPw"HTqELyo(a1~Q(bDA/fY07Ls`8F|7BRTblPBdFkLzY|]V%ڌ]/l(0wVd1w~M0GG2d `ueu`~ 26RZp(#,Mfe@+p!N}\x0kiT֭ێ,SfFC,TR5wov*<愭 f\l~  k811`$Wa jRUFۋCc#)CdBUnC _Os){TʲJYb:~YށRlϱpJއu.~moPbEgjRr]k+ h!ePy ;F_nùF%m_k`2|&VJKBt; Im۝"j,tzm7CH%^K:!\Xwu(l ݝ{wȢUyǶmUKɽY{ﰾ"jY-Zu|u@Ei I>(mOּj 0ʒ]=|qkZǗZv\Mo\~It3sRCF&H|||"sի4ОLZ`B{FldG¹Nng 1,Q`g^+SUZ0\kV%/3MaNÍji뮻~Q|  ۦqxb>ڪP \UqG1cY-Eؽ*QI%TU +h]I{$RvA@!8 AGۻڀ{ۭE:` vN<0JٟjzP7[fs}ly:Y$N00-J!qՕ:Q;ZY;NK< ڕs !p$*Y[sXS)UXQf[ThL_DE^ĔcJB =,yCJb" خ] 6hD ZV6JboہJ&s1U N7>sz>p~)2;;"ݟwR(:AP LJ[+EV ٢Ǩ"  5 A ^quOIۆ>uMY8F YgĴ S f^4<}b~tܮ`C4كNtVv Vyz,ڞ&HH<"{.Ϻ7E L N~_ XE` /tC@f`E"x1=2i#oyu$J*c%õIurֿ0 z3"y]nNd]E,OتuS+΄T2AU' \ X[ ƃvU;* ;x逩 ^Ӓ+A NY[rcjAp9T.t.:.<iq.pbu|S sH-n.pr:1to/+= !Z[䏟?&G|Ν,g`8J~rz«.8-1klUsḺpIKl?cntXNӂ^_D+G($g}1Xpf"LI期),!e7q]?u 90:Ёaqr 2WR03l?Ofˊ:>1bl⌥znTтu-.v!m1ޖF};ҍ/KCbm*0q'lu]wu]wu]wWiP`4Kʾ;d>c"AhPV"}Q)iykVR$[ rU^ %*!P_YY Yh+(0 jUte[Rdžme7Jm5 Ys.xku)UI -Q K#I?ܝ8*0fܰ=f7(ym= j gsz"MPRi@'I *%3j%l* mgfN<Zr^8eI5CcjG ڶҹ^rei9 ɍ1eMs`f;+MVW̦ c̉&)QEfW>azX4X E"ØcL-@L?x xL2 sH9q_>'뮻뮻o0mw=FU<>A l8pYBQf"huiVl9 q ,Sл1`>n:D<^TAsX ب|u`X?d0clF~}+vHcۙ̕!0jX,Ksvrc9'pЪ$H6?`2T.ڠ:^f>*Hu |8JBm>6OM@{(#QU߾B7աyEYan\J`t@lγm2jz-y&cEU-&.6եbIhx[LPMg%+x߈d̺VFܨLQ򉗪kՂDTtTEiB2Q-HOk^8XVGh]rS0ڱA"ftu&c}Mp0X P^7I NաkI>`E$H+{z;ٻ3(&1Ū]1,4 ∀Ωͳ1\5Vrp+v-/khoŠ[]xW&.YRJk1lӶɬ Ibi𬕧1G d7< _/mĆhb} as]wmZ Em x_8'3 o`Le[$_HL c3|&`PO0<"1e@):ٖ\U/=Qy?^/6|Нi+ iCsV:VjS!9阣~n$7C뮻뮻?rx}o_߾x_ / BP3TI*342@( #,j V*+&%b58n%BF ։Z$qXD@W3 l iB6lV_%]>P9&.yMx˒Sj깔 HKfـ!?Jʗtfw1)R+ nC(2rHiW KYh6˨X/!ugkЙSJs[Mkn Fn&Q--jW`JZO]#e!ۄ p;0LQoMkbuOHpQݟ"Q~>opk-`As0w|\$4 \'ⰀO9/((s!bD=o2 *;c ?٤!fjS*󞫑 x{BX<㘘clj? gLdT{^ĆV綽:0xǵ5FsmMžSDoXİڀ&pSQZfc)eyZdT+iZe.PΫ3~"?ކHkHb< bQx{?'8gN10l ֹc3*)̀\<+M CsxВU) j '+yI'(oI9^I p1h z}hQaW?g!(caݹ*znJ ^k|^p}`!z|ac2FZ]w1l@! Ȟ0,Led}(6b'+=G0`/`Fv ȔL"66<C,d3QD8 moh`cannn[c`897λ뮻뮻뮿='˿ <]|q~89]ꤽ9ƄYa ߾p<Jw{vM=fb-52YW"ƥ:a5^R.oB ~\ 1q=x{s aqJQcR,rJLiKZ+bRT 8-8uv )mSVQc`|Q厪@ [h!Wm[ap6@r6 ٱZ FY;)r Tb) 87FկwT.M&y@=.W!AH%$b@VW%l)=S!UdM& @fXf'7QO@6~2Vn x/F&V{Ա Z,eXbXʙCgJ-Cn{]~[^29~ n6@n n¾ڀ ,ڟ]d\I }iLAw4qpZm 9?+R]8v^gFiV" H'Y9%Z.[ZV5} cAJy&X;@p k%jwo[{f}dۋ7(rw9ƽ/0 ~CSVbIUCk%&h=r+S>`)o 7$%[R.so#%=.~R> g7'j8lN,*? ǶnPFQg=wK,POO(< _y0/5xh oٮ/{)qPQ,$ƅD,e,tf@ʐ8 >d/{v=p/x^xˢaE,kZcNn!DZǿ o:g33Ss7.bASwuOW? FZaîC6 e~:5L/Zm1#"]zq0Yy:ƃ?Ћ  g0l){rLl18P5:E6˖;3LS 9ܘ$5 癰3yOsg}©[r<뮻뮻뮻__1/ o'x2[D6αr8:~'/~W~so :a60@I#5O*lAE&Kz HVp5;ǁ"*')qC@dc9 '9^ 9,/Vny[c-i\N\ȕj"HVnv]9yU6f "[3*V]VtY-:" AiXfH /k%bVmU1K= iRz*0M gލ>)mAf7[ m"׷P"hi劽* Knt>\920d'li?] EQ/Ǒ?m+*}ʜ/0E]F pnE]Y#2ƲII:p`sl|Pk!lERCԇ" ۆ k㴅q6lmm"q5J&s A7JPhJ[ng2#9`o HFa\'gP!հϬdYB.hoʶ7)N%|`)zPp/6)uq!l&ƃ9t*Qmn\㙴׍ B-Sc[s|bEGT:vhV({4oF Х‡#`ڧyO4ϓCI'C6̯bvQ/m& qu>xWTXI*3k{AY FCD֎^후3-I&쟱uY vx>"c@s̘]6]0lص'} # $Y@m:>c5 x$p\!kklp'fQ|T r\wN @ EN|(o'ӻe?Ɯ8Wq>9?짾*80v=Z0 H11oZpڍe/0Cy:)2ngET%|M|y:" *JVXV 9vPȅ9tO "lX + wu]wu]wu]wwKXf^'U{֋21x},Ƿo?g??__bo?/c[TCM3~i<oT#1h)'۪@ajʼnŵEaόA R2r&^Tr+iCR$p ʕ?_tR\M eƾ0QBF 헢Fp-,[C8Y7u#jIhnTy?95isH0'í/gJSi ԮV^ˡԻ\oAv Mf. \`zN=S/MSϿZY`Sz98%G~Hd-/f+70$&烶*Xk8&pŏW5 ;й/H!?zni^Aǹ\x*FR H@$ {x"53NT|u E"X&Te0yPb0=̐k uӧk8f<N!-8vqgN1&3<߾ N>iu;s{ GsfbfGv9c˕5퍇LӡFq6꾔M0|NcTB[q*Ll":8r~; ޕZQ2R_Vy٠Apd&7Z-=9Z*62-s^c|1. WA}iyf\;[.h}"@\)P^8Z\ 3ؑ|{WY_2 PPj+Ka6oFvKH۳`a}I Z T/K1u)[` IDAT{7X*to/g'/mUcG nBpsuGnoqu/<{ m-8(OxNn9`} )noo2k07R&xk͵,e@phu+FP0a ZUzɍl 1WVж/FI jE- V$hSXkae)pqA}ڸ!;{~WhzP)j66) wHcȌE9Tt.hp!IŠ7jPKsx%Xԕ ,8maȪd{& \d1qAྊ9sk@̼SwTɹ*h(j E-8B^߂6yNRtƴ9.B?7 n}ВV5[)TjmTydibCWTk ޞ; FT\gv0.`޴̂PC`ܶB T:ٔ28b*nڼӲ[P\*L>OWO:V  8 tqГv 1h!6;m0AR?AgQ UZ0):cINν.Mũ{*m\Ujn :T ]8!9g RQ:jTiwbHyg.- 7B<Ƣ&So)TƎ|rib<;֤\gsP9dLkhz`]6}@?te Yr:ES[N#<DshkT65 CNCܦ7)~tC4UC_3Ŋ`qL0Nめ*R`6ZR*j]p+=r嬗!\g|zPMCVټ9oãX- V6ԤDx@dW]_.X/0`Y ul#QÕ@ aC'R-<1u/E1׾2, {|3__BkjcYv# .=/K)gե5?,_~銗7ܔYp> _GepX'>q;ρc3kkk]W_~WQGg8*o?g`/:|/^hOpPW?[|g7/߇CS@HlHfCh:z[ǦVLf^CRK0u1EX ˂^R Ӷxbѹ!)h,d9.ͤuMTQ G3?|!f*XbLJin6}QɦSnd&`#uZ:2{ڼv YiJcS"`»*Z/}*jH-J!GGMڀ6%gz?8nn@@R`WHI(T0H ϟzGiXČѩ5 ZB<1bcb4DѪ'`cAU*!gaIĚTԫB.ejٴyρ]+!,f >vZh5!=zG*eGii$T3܇.yT}KS0N$I9mM Ajp+/7[< Ojr+fh$՚-BWTܦ.ܕ1Kp,'BᵲChTa~SE|%ȱJ'śgm*oW0ecReIJT@nȽxwgE;Tۓ1ǜ4O^ԘſSiv̝ssPM!¡jMc1C-}1МF3o=6+>$abI|c5c|v^\{_ó jerk0wwX=3f P-h;b/U+DzP}>Ƈ<_V<~MЩz|x戛>WRpw܀7op>pu}^{%N'G?G9a LW0nABKV$)Ff WBߡ!EcUЃ*iBٝhژ&skg)B8 Y:'R}AwC!^>aeo CQ/ϗ6 U!]ԭZ2mqϹAb6T>KTqH΂`j¤!jϦERAEhF0ϐ0nZSY7a(:CH@l~9u!Mm M-+1ŴF7pF-&. CZAT-i"Fڄ$OʳA%lqF:X}(/|P2|MQ=ihsRخ]F8u3;6byBvQBucq TO:!PlOikm 녹G88lYpEcC:!"7,de,X'I[抺wjl:4*m/1fš0]D'dTvhnmZFU`)Nܵ1VB<{tMx nrV1*ld6$)'`k=Wb|^C{ϱ5͆$=(Ro'u)SPd>9vًvxaf .aNseojj( =؈Sr2*[O{ ӊk6(Ng6R+ t\ƌE؆3Wx`^j8b:w)ϩZU\+Ci=4skLa l*q7ȿt՞ƀ/,(^š&7- R̃N Y ֩.px-66:)DVб8Ptk krx8|a)"m_ *fn hK.XRff@eGaL{gf#KfB|pcx~7x8Rںzp}< @1\G3>P4-ٕxxqዂ#-?檢jx<8^,hˊyez,Lwߘkkktx9W>G[ .= ޹C\p#޾}ׯqs{ WD ˀE;8^~=~/_9~W `}g䦸XLCmO,Y-H`:\Ro Ź;J"Ɩ1І#΍0*-֙aL)wYek%LhR8a`p ^%tf^@Wřw7f~n2 &;3 T&L cCy_Q &E=dc*DCcmzօJy(cBTTR6e&Kdf׀v9r?3A8S ~`ѽ "zN|jt]!U]026(AɽFzcRU@ҘzՇ'5n@q@4 *1PEU([?{=&$OTB* 9F{Ѝi#$4nFLpcjX#E,8<797p8ptsl^zѕ[ap\0Û !ǤF|uʩ "Rcޔ+:B5E`!`yӉ͕m;0d-uF֜3^O`ZO1Fnjq ttP ysFЩsp T]O -ׂlv.j`1f!-e cLB?rb̧Ro@ggF u yk)(k3}w1'jέ˶غ3o0^Dt]D>[PWC!9?.R(ٚ]FM"\/rico &pd8\]?{"x1>N(7m .8w.jh{Ppizq/i w{|w@q\_,[Q``t܎ejG\-x ^ o 7˗1^lYǫe0xxzC}Naeu>xLskkkQ^p:?"Pz'nn5+}+Ï~W~(_`im*(rmT΍e &# b.Pߋ)kэٻ A6H >÷Bp4fM54ZB*dۦksyXٵ9DL `783aFK#2&7-cCM.FH 5U/8hP2٧0~/mLTe$UݝhSVLtif.z#e< @pۋk}`P QC@)" m7E]oniZʞLs{Z-CmS't tkZr-rZ>A(A Ad^n*HMI&&ABr97yl(8&8tO :#y ;ms*S162mbLTz%0Fx-_#R Ojx* bl{Y4/Auq!=/ éA]87+<@xJe&GW7\::8hU]XqRHA/~{DhmP{w ^\yӅܯO Uzpw3(FGo6m8=p}}T7,6x<88N3zix8q:]U:_h cpR^{^{^{oj=Т8,_ wa0|7t;>w/_'x9w7>D޹9WXE6Ȳl% rѐ*oܤ)U)MnC%0Z*Z0|~F'-R {wbn7KE"t`@x. -b,3-J 'df!7wk`Fĉ9!n#Nu!OK(v)4pй8U)8Vڈ6I8Bj*BmC5hDmgl[MaiYͧ@2.]nRѥs/cݮ%x?AEZa@  F;9%h-m4 `$Jְh惩 AdH 6atDM@ Lϥ)#`FRՎi)0Am4tH+iW6a*s)1/1@HK|v'VHZ@c)5^SOxkM`c7H <H̗-4E ̴E6bG{%0~gOo(^h?!ٴ:"fsUklԊ6,{Pf,>R-ǵ֏T\jleh|ܧmq|J_[&7 "Z\ 1x6sݠ 99 |W&64t~t0e4!'< !>h|ysiz)sM zk8&ܞ; W Ϋb|x|Dp}|aT=|Ms}>nq.( 7C_/k p|)v};84, ;U+)c]Wx;ZkX f$Tڜ)ֆ3_"/P1ǿ^{^{^{ex9N "6ׇ?erXpwo};8hw<ܿ/ \]ߠ, ?P //}j9aeA[[DYlmtUD_ kC[Y#iC,t경ѵ:(M**mtis`)#m*#Y[Meg[0)Mq@Heqnro6 BP֮/qD*vgk>hAoR;hs=u*tA!!CJRi< ,m3]5hPlA9L+>AjGyuiLM P@ z܊ 5t@dˍv<' fϐ#f0g)GPKM d)5eӄQLeihɈ@z+NS9tnBy Mm ObY@GL;%r= j]spfvu>/%aaNش>c}1?ku`(rQ0X6a=3nG`S{fFА{ܮ;r;d wό`ݺ5v-ɷ^T'dNy*!h5zC4L뎚o04.v[Md ҘB7ټ=;!>5&(нËnj ptQ!3̲PCE;0Fcrg ,L6<ưDEFz; Tn@5gs # 8gQKsb>.dkgiq;q?AZѢ0~*QR _yuś78*/jnޮ>^+n7W b4o;q<,9hި[sF00ZԚ LU-[g; |]_="Y]>P }0fCZuJkkk~uiܠxx9CaYx>`8\quݟ8*~G?3D4ްu |\yVQ/0 rF/^ሟQ.s8GEXa) ` ]k! (̇ˍJR!9\uSpe%Ihq8ֱ@)fLkxhKX tj>sI]P&U=>u lFMɉL{Q OtNyt.{i`1@`f%<T)[=c@iɍb«HN3^vRPw>7G]'6a~!bl*`O2>%A@f05[RDK44r$@&@\T|~H b? sMvn ,>OoTf.4a 0Al*pc=Z4DtSijt)*#L+!8"3x DT[.7-cD @STZ"&0,2^e~_&X>AKYMAŮ!NPT5-Q`t.^G͉r@aCzᇍu{WY_  <o?DY?QK_af|.ޢqq+s[N zo+mt/+=G {c|~Eec|(e.fxaRPˢE/B}PyrkkkvsTtiˏG8| '_Gд uѩ<. z︜ ŧw_\|ワWNJvEx6 G `#st* ~Jtwꔍ8vdڀt#7r-pYR0m;[w[n+f!E2_fB \Yt>U $ {(^}G6'la-q'[|{?vuGW0X-z0tfϙAέ# `J؛[APsEt\d`p_Kښ2|!,N)!9)*nu끣U}tR P5vd7¶g aTIBy_fFou C+/1#s}&t#2η!6!]fwS,3C9Zн f#ENR^vdO1Eabm {u}PSt_f͊aey`U)=4mG(æo1?k5 m55-T =CWѐ.|32@gO&$L{iDMjfJM1|W+jlG y& NJ༰­k)^5f0)K]A,l`0z 9^_]})ٯ?g}_bw7~׸~#|_x)3|9üxb/c zdzgXt]Ei+w\ *i# Rpu8֦OίX_4KqԚo%7;hc]Η Kjm B-#BE9 >p>wPw Խskkk_%޼yqg8㳏re7>{u9bm- @wT M6g,+}{c׿_gx~w#zoq} T ]ߥTQH 7i@Ƹ"~1s;ܯm;Uh!otXY0|F0x| XוMҝ2,K)Эp*FoTFk3TJpJ̈́_ 8T'|bJW~kzwR(GPy<-#2WI0 if,)2qE+p$T,]ƸTɴB-K6}M%e^rr#)QB$tg\;[܇S6[bZv PTుAr7bR)́F# lR̼Ԋ ⭲Q p&\Bcf~FK2]f!ok9׼zM#)Ÿlخ7XXYj% ^ܥ8Nix릍2LjœqL`:EZKG ɼvCn6糁-yB0ot]rl=]t0mPH]c$  sc'ΧNǽ2z Z8]1BwsER jvW=IbY@El샯(\Oke"kFNR E~%AtTO) ͔׬&6\d8]Q]@Mҹ3*h0 5Ĝ`ro6^{}u? v?~o3|F4D?wwo?޾}ЗB~ɨ'!-z.'BZ=V7q91`8l^ΣIV/< -lR vtfP ǡhZry ʿ?X{۠}FpAz- GNJK:%(Cpg^{^{^{^r.Zsp8rt>c9puuW*@"! ..nopx[/~G^Wc/zNx X#ST$&8"LqmJ4,nHE$ElmVZ¶iYIpf2P,:aIC1TFA%kP-fRs<蔪B E6 y`[ĮSD  G f/wnܨh3]BrM~H6cIcqC ʀM X@d QԼ}HͩvtlE$L ( vh_'I!wjZ"a(67}S8S<ɍM@ B0:1#[i6)\Gntgr?A q)CI,,6(UϬZ | 3TuUDZښУ?ɟ8 |8: #P#@6kЖ]IR ": 6 &[llv?)ިnS=1:mc`  ;Oߚ {V3D4PBcHJN,uǎyshS8ݡlI"Pd[6r1AO7sDjY&tT czAB݃v ]hG`Z6T1?ӾX@?&<%| sX avyK'{ Q^18n99Θ ]ϽuR`xĄ0S0ӉӞ7S$Fl8tL#P`Uy#=P#*7g Go CEkM6:\bw60sc@c puKj9$80-(d)= ;ˌh5pi 3d:[W.kq>MYWNTSlS` рzOxL~fRb)9i$ӆy_k=վZ(vr C^^_m})?O,`m+^1̤>??)*.~^pM%'EIWFjV,=pmŁsCa ~@uocA ǡ,8b>*b Og?2 ?X1DžU|q `#:.nh+j]*G0G_~[~kkk'uuuW|k\݋wqYW| '/ ST71LC1zZ+Qj z︹swp}}zuCq+j=x4\ѰԎvقi3F,Tw>^D"B0kQ?՗LZ*wp035) 5hA m֤@.ڈ,'\4@Q]ߕ0SJTllxcJ&}X7BTBK~ahVAMA[w2D=FL1qfFтEp}),#szey _!)lj dAއ_n 'a It:$MUbQK?OER-2< mS6tMR f8Lݨa\MmC.hMȪNK[N'>s}BHzc(PKU3ƖU R< mWQxi[RR| 82ZSST0䬾[† ژ3ז=cri9%/Ħ5p64ey]j`T] ]j!E!D>A$ -t!t%FLp^GMXZg$so @M^`aJ'Sj>3fӁ.KTfh} l1p| ssxmb\3`y:Ф & wɿt݊sf"'CԂy~S79t;KE 5q Lg.r k#4E_ .kF2לZsmz)%yi}`#El X6PEمc&ڍZ0,T|g#muoR\cFXsl ͓nt{991#شfl|^6MA}}j8Ԋw{Es L|)HE+oXKc)fZ :ƌ1SlT^_m})_K<ÇX׎w׿?K _|cXQ^RaX Y%q@}0[࡭|)Vu\p CSa/nZP`8 kk( hI`X;6c/VeY\hO5X(=`ccjt4T N5pjr<`9^{^{^{%Ƭz? F$s`*m9hy9#tz/8=>x5avy@^pw{Gǀc]W|xWAԅ0/~# f*E 7{cƦse 1!`r#yMe'KweD ,pY,;sTʧ2f* *k(9#.X~Fsf,[mQ>x8"qFn ~Ԙg BUMtluFnR٩j=$DL'0A0G2nCYE6=xjnҚ, @JX*Tzv5W#03OSN c6*Z~ʘ2bCDm"0@ "4O#BkF*D.v*T'.0 F7,h_<67[i$a#D Xij X܏XZ BoBEZ\!>%Ѳ74-TAsEX 2sUPQ=マ IDATa9I"fO Φ]ZvϟdTr^"lMlK(3=t{$jɹ}hoR"M5)JٔS1 ʇaNQ7/&BpVP8}qFۜ0@# 콣,u. >8ytw ] r7x* !?42 ItOpckϷ`T}T bw ڈP\gAѐu͹f`n6 Ԡ>T=OJX9e 1\~5쵢*_7Os#u Y@ψUGUQs*RL`RzRVl*К9SZe^_u})|}<;OW o^޼yD_Woo^y bn4щ"Gxrq$z<U:OCo\/]h'_<#|R;\ǟE ` :p(fz)WKg0s\U.4 _ έxc|>`h~kkkfm?{ut}]`Kz~55ahkph3 yZ opBp<eM-3۽3 DGl$R(,I,:(MBG=gOʳMQlVX%JU!J(6 (D{ۜ`} Lb |y߽{[it GWn]fu~F)Ʉc- pXK&]1d}$6(1=O uc,çRV.ߝgqTtzWث%u g5K nU$'lU6B>!VvkK6p'_u^-gU>ŵQ_s. kxs \yPjV&Ino*d '[W7W`y݂9 f>bFqk`٬LΖ/khvd1_ M ; QԚ|)%h+[J(x0MxSvۺn @(D_w ~-GmkϙbiDJj%}3mDAzUƚb6YmT6xcX۱)ޠRm`XjVnsݼyy׫pb Zkj>MCo__DC Fo,<@} o ~8>p|D*Olm/dc>]C Iq4"/~WyzӏޤQceuo{NOOzu/|~tb]7#لw/.4$ G_fqk+X0GD)֤AtkeffA3I;լI@a#j!n Ki6 nO1[fXz JUTm`"宷U-w97+EغaxYa6-ہrUb-wU S!4JYzP4괼_[UUr59zj ({i?(RZ bs.H"kZg Sx_'jCXŵ+/[ᐫL@瀓 ok& M1ZVT%1DkސYf9jEc y+ soj *.pw_p?'S̵[}jz*.7bK,{9"_`j,RA 0x}NP+61| \:kΟ-zN^ŠRGS#>!ڼR Iv+jPm%\\p/~b;g5@šݶۧb\y-ҏ W|mΚ.fDmb`S1} BTsCi%ˆ'0bFOl,E1HQ؞{ќ6gi ! Ͻ,ƛz^S#@wP{WJ \?ݺCbϕ&h']\9JMߥ"_/".nn0PC}z$7okfܘbI_ c\[Wvhr"{H`5'yNf״Md>lOƠNa]Jf2m9>7qXs)ׅRY,֌Fu#`H vc&-<:^QrUa2[Ѿ<6V]dPf#+;&' V4뎒 \#ִekr|D}?;[R],jjjST6Lږrjӓcf3T!Iy9)~f( E[&e(9o@ O.L 8 |SF*@La &'4L]Gtp%Q\"4 D,הj)R*nY-3]U0R|C}e?j84eBq`=W\eg-V`&.,fa*xMfu*vȡ϶DGs@AšW~2?LBUh|+/&֬76nV8c-n c,U~D%u ~?jo7` vOŸj܊6Nzͪw lU0NU<;mGLlԤzB?l';ވ(kacZ Q MD&٪n6`Vps xgxPmmJk Ujl `EUwXN,-E)T!PǬn[._ϡR$[M-- 엔|ܫۅ l[>Y ϛk||/!hɦutAVz: H6Ŷ٢ʹ Km1a"ZBP] +l9/Նa奪jRT6Դ_RpPvm]zw ;վ% *Eɘ| JO+yq}9ʦfld086=3qqpW3k@o`F7Ε'Ef=5tج8;f=yVjϙXXzfQ>_Q<\ (S]}Bkx#LsHmTZ"J6 P|ݮ*ޔخ:/H:sh@JF̵IJ]AoU4`kFW)1PC}bpQlQ+|>~Ft,O [[3N@^[Vb|kKNLF#?="@,dan_PQ[&M`XҥBʅ٤iR2{kg}V֘'Oyr2 3&\۟sN<=^RBBd{3\8g<3ZeEAyusf r' Dƭ]jI_`.ã3Bdt:6!)QC 5PC 5PC 5PC](U8;=d;[f[s&B6%K(YѴ {<7n08>;K"h ]N+[{ x6-N@V[ Jj9w!w4bp]i*w-BlۀG[ۀ6c@ŭ7۴u]u:uƝCO+ n͟j U3Ts6ujt{υjcZ"Bƭ1|9{nz So9wMٯI-kK=oS=y1-)U9DI }ǓBگ)![#,Zuc^ƕѡd]MǍaQS Rz _-#38lسܜoP)x5gu(2!6Mo5eo :sioC[ԝ@Ơ% ]ʴMUޡ3kAok=7 7 $Q1Z-eP#Msȭ Џ(4{ Fmu ?:'~-ToNHi)UnAyD}E G?9թf?T6ϭꓩ?c^7O;44L&3}ek?!_Cһi`h4fu1e4o2j"Q$u2:5DuNG ?a>%,um鸥KP5/|y[n]|J?~v>|9\Kٳ?2!-tzxx,{`q~ƺKL'&#ΔbQOO9[ۙ§otlBȍ+~0PC 5PC 5PC 5/N1i4Ʋ4# QЎƬ⌳NMOz֜3\(Rb{ov4ћo\ttJə/1n-hԲX9xz½>#nݸAVXٿ|O=Oϸm㖶d -_W,}ChDLjW b%%&D1Fo)>7`։ H7`S WEH˴ǨUA+]A)S"ƖuJFͩMs%O&F l6M`]!4A&TQMxUv5#Q b@. UDq8+U% M+4Sϓt5Uf\ NJ ~m+[zU۫N뵭B*jꚹO{{}qMoU8HTɥ6T}jMCՍĮhvA{~vſ_qz@fM$8. z}Z,#}6WW۰ּnweSx[37EبŸ`6c*H$Z.x#j*}SOsT5s6PITWw>' -.PՄp6(]Y5 |zbj_hԡß(W+& 4B *}vĴf )$kP3Aq劃i:YBqH}ľaD0wje@Oܮd΀Yຒݗ爭foU^*W b|R`6cM.b6I=qq5w~՚sa ,C$R͋B~T+6ު8$ԒH>ٚ|6M|QJ1n0prƶ5@J@S<ؒz&~KuYbVjЏ(ZŔY3?CU6`RFSbHCzg|MVњFf9lfk`9%%ۜ=l֯A֦~M~>فyUqwꓬD~hOǷ;7>:3n?׸q}sB!JQó7x"dڲ^'K[s4N9k\ڟ6wl6e:n9],݊gk:፟Q_y2n#5._e6ix7x|dkͫ/a:nGu6[[<9YpK[o_z<{wxsW'ӎSL5g~:+WڿM@* Zb۶^ѬBi W(1Tɸ3" IDATSl(Uض-2F]S  XԬ-MЫ *sa/W@Qs[j69l*6 zХ5 jO[adqfjm c7o%R)ydCUͪs UEs ?mnpu n̂ል5ZZ8lmxe7܂j5msGG~RjxRpAA?o^%B6ZDIQj!f~(d|*zR*{@_*|lqP^wݕ(0\ަ|j=, ME{#Ց M̱3p"8P){y֕9) ~yR\yX3u.йl8obD5% o`]a|AɌAس>T3T )gJqg5[ JP>vvϛ&e5b۝JDvXX*>l~ƅͺ}LWsd.6=<~__hY \E(bD%R(V[$\8OEK!4A=&R" 78P|41B.5Yo2qvMp1>(mlZxvb:4fܻwT|j>`uɿWKokDdA}u )\V>W'1\ !FB 5wP jrcŞբ6w2PC}hz* s\W !<*o3W*}6r.>ukinٿz;Ƿc|_}w?Ν|t-w4m˵kO!Aai.RвLJOؚ5#R6ll6+j55L=>ʥ}^~yf6͋GOv˯~Ǐs͇\zWp{$>ٌj{\,]QRW#DSlQlL@ZD4%-f+YY&@O%53jIJؚ*Eick@͊6猄tpWL$os MӚ-tD Cf d[6ٽbJප׬`9.5O_\֞rUJNچ|b`B"utU\a- iL]'ZPg2+8zvqŞT9kyA=܆$A4BU?]Amj]).7ثZ355H{ ^ɫP`"reeWK s#̆iEN,۬MuWhc]m8Cu) q_tj],KY:WSNmP枺"^[P  J}cz'*~/}VϮ$]9kigUjv,C7CWzS~jٔ .~~6Ϫ8zcP J 8lƽj!7騩QE_'uԫ°u%7LD}O/ ~:<99˨dW힑p3-RB9hnTpX!i]-6kMBΖߟ:-X9[r!H7vW(Ŷ*~UC1c篃8LobtW "fGlVW5)`(R1r ː(leRm$ѬEq\mjWEPdz)ճ^z' >ZATMfhkPuQ wa# 5wfkMFrV.txըȍb뿊Z4?U!9ySA\`|\:Bk[ b7LR^f|>+]C IŒy/?mv~?w'.\J R"Eu\wn,Oxt"Gݑ_e/?W/r \W^?xUHk˼OR/B{wN?}?}ܺÇʕ=>lR ?`gkun޸lq6y=9;=oٛ<ܳ*%pR\:g:ko?˭[nxGҶ-G|t]pr|̕+WcF)K\lF<=>i[SRD2UG “]G'\eQ'|{1՚_^K7_/p~xw~\`2q}ٻz&* I-綪.HobE"Ɩېnt֝O\Ӗ@2,qa4TeA\6zQszOvj3kqe&84OG۴!JSɱ"jnbU&jrxQ$!*T=ӑjl7Kδ1Ģم}bJ,69|pGµ`مjy&Wny[8_!`yA6!:xpg}L\55C^S-r sd6.Jv |q!PRAgQ]ov(53kfkFSj ƪ[lګi\lؘ@ ](u^Ƒ(0lpCux5H6*J?Ctyチw-Ŝ{xh1Ƣ`J vɳkcG[W*d >Ƴ3gI v}Dys_Q υ:\M?/ZxVZF_E(A~}L銯W_gR 0q]{9 J iouyv(eg#m/dL>BTJRNMl_BIF 6-i򦞵{FG>Pd^'S u,WYpBnꓩU'HC~AO9z^.O;3^/*'O udtLSݼ6i+\n'<*#DA os}/}["K}otS9e2^-Y qrvBbl8xb~!o>? c ?x>Lc>zJ?OH9W^;ϐg's|r¥W89y|03LiX.FYcQW5!:h|)kĞ[ 碾RE%9ZQojl:ن)or7QzթmFS!:rnE] TB&s`Î+I%1jrNr*+bh)fSАTΈ%KL7ҫ}6v5GQIlZAcc+z;ÿ٢&Y^>.#IOU`+U +`TNv}Bt0`ϡZ>1܃ԫm DWm,[TkM5 suo)Ž"nt ,-W^ Iz| M\}{u_./ h\]BZVrAퟣZH*fJ6 ()o tэVdӸQF 48*|}(b56{꾜 Mewt4!uxdsd١n.*FW2uGc=; i}Ulg)fy@J0.n^V}D7Z7 'ULf.hnW35obM(ao,6y5O^CS?m[J. k~v1_J>6ϫ}yQm.8.س,B$Z8lָjN"B+[F? ds 1;%;4R~şoun1ݚz^{HJkF; A Z~5{jQi,Hm /h4Kڜ/I;TjcQ׏b 2`mV 0.<jO>Ѻ,G/wࠜů|۷o6ާMhhV BTRJD"|b6lSܿC[k|?e}%{vxݿ+\ڛ5(8_2_"+L#uGoW]|D«crˎ'g<}`llqe^yy>>GlHh8=>ggMi3[lg{='l< l_?u~׾>o.>u[[_jjj hĸtE^st|}=gn]gr׸={&o}hF SJ U_1@b۸D%klL j֬9TM.\uDuM*!٧+˞µ#EXhF<|ak0enqX 뜑j߬fNu#>dK$fG]6k['^JS`ur= 1ڹfq݇ x -ϝ}O'2ƪ}ݫ0ll+SAbje:sSUswMqc]ɬ6lT[';lG:`I+,S7L@VRj%\mfThR/[[K+R۵Rܢf* R-*Єƕ5\@=ǀ:;w tl,#~<6o|:VJ1u/{UZP?{ꁕZ'Lan} n9xn `IqKXY77S=ۼ*ˋcRKj[GKfSJ%G.vss۬1Pr\ڑaFNt.3j ]NDĕֹCu{d{CJ ec3EOUwJ s۩_.hMC)YӋg-:ܦߎk L -t iZM֘ap#S{ny\ǎ87 m|Li-xx}!2}]y4ޒ־6o(gpkt vv+/_q6(%!n^̪kNa`s*eL#"{Ct聰Y?AuP_{ͺԔMtwrM.J(B'J h֕Rh$:U榰L:-Q ]EMXw"hlX5])$$ gm) fcĚ%_6jd5~k웼?a{WD[fsR_c<'sb;-kv/K9VKKƣH5izw}~ywʋed4cdFK_klnsk揹qY^\q3>|ޕ+4MS>x}]nܼʝ~[n _+8:<򕫼3,NOXЭ?PC 5PC 5PC 5b,@ '''_mYHi|w!8?[U[rMe IDAT:ݢ4hQ|Ϋ_*?ȏ޸ť+__!#pppȟ_reOy1EuOzM 4MCuP ׯ]7dZod:7&p?'_}'J /<ܼh6=Ͽo8x|@8\qxD5|ʣ91D<ѣG|׾LJ<=:ǯի{ y ˗ygo"ihfY=OXWne!2oQ\ |I<=`KXGc(ИS|Cw,Cį@4Zj.[5If)Pm]Y$Lvښzث( Fo^]BhLy6R{}$`Lu9gh",*SSH; oدٮj})JLHB2DW9PNmQE\OLv mym39Њ1 jQv; >gW[ 0!~T٘ ;F,U;U }H'kM m!*dSc !Z1( & PҒsߨ&^-ХY.nmAݟ"V\%PR늺vϋǪn+x<^.a\J?vh"}Á?V{}!h60(TA UbJN!Ent(#j[[KT@%~NV@ͭM=&S@-R kɮt++@ZX_T _e,=L4kp`9nv][,qZf50Ymk*Jx=~[\5;)V4[ug.5ZX>[?ob4l? X+7T[Cm, :6;ED{k\t3wkv 6w@,)T-:ƈ=#$$bBMuuZԏ/*y>e#cl4 MUUZR35Ʀ! gWu׈V= ]sll6W4Mw`rD<9L= BgK!s2~m%+)Ԇ闝\rXP?;RA Sr!K %=B$%!BN"ª(M l|E݃!# ݚ<7פ\hhvعKG#$5ս-u5gMT{,Y5 6$,suj>X6_ڥ"3sDIkf{|!ل@ӎ88[RHR"'gl9sݷ&76__ ᗿoM&Ͽw4WW vX.mKərL&}Ǐ\"|K_mZtO#f1w{k7ٞۙBFyǜ^ m۲8[p}|NO ̷{wvpCkoCVη[]CWuuun 8HE)(v/~C@oBpXl% A"  {;s凵r_bhTgs}C\Sw7Uq١R6 ;hP66ϲn?% _ 24X@jq(E;XU vR*)X¤͹ϲL>w@J& k`P408eKϫށThƆϪܠLJ}-pN30'4αVo_f+;ͮڹ83b 3k^-V(].mLjS 5Rsn5;H߷ ,mVm\LTD2^ۖש 1~phcM)D?.2w9h'T{JϸZ\2L .WS窌S2q6TTUW5*S#vs48̹!ڗZڻEsuymh֖ܚYُ v_>Z@g$x#%FZ37%uS.-`(()&>^Y컫Рќ BtBeX*a+wSWWb2ReDߕ}?e<^"{? _י=o-b`cmɄ{hUKck|+ <-/ܺy<9nj/] gyWx){;:*='~2;Z8?`rxM`/ǟȱU>|&/эG<|o??㿨E-jQZԢE-jQ-p pQz3RL5&O> Ocy2Tit6e:B#X]ezt{Éu69:V&3Zcq7-ejUɄѡ^1B0UBa2w]!lA-w4e vUmse? Ք/ڸI!Z3Z V)XL(yM#0wȴEkYGԏb*A Ki:XVST XbnS.E7YU2o:=upG Hn-)Btk4PY[ly6kcqu+sֈ@pVsp\bA:2"*bǦ!7*j2`i*>TͶղ)TnREFj3 MFcn=R@BXgQ:2QbLu5RcC5&!ބ`nxUf=Df)1- 1ZԢ>\wXSx@0+#s)޿>^}!;,KsNm'_+KyBuR*WYZ^f2ZY^]e:9#~&ׯ^[glllRv鄷z_浫.`go㧶\c6q|M<~B7Zb%wkT`:}#g~ʛ}/ ];>{+Wx1NmN=_7-?iE-jQZԢE-jQWgGP2lJ"!mt6#u'R"Γm=}+.qyfRk&RD ZDO=rn޸w#fZ-2ONl< W^‹ВuvvYwX[eqvwY]Yamcþ<~yvDXJ̚%jmJ*bNs[:@)uPbJ[]SjSiۯTaTl !MJiJ )& RkX|3YB"aP=5(bʑʑD4(:%a6_Q4:)!dMlMh #@: aDME:߲/-rq[Yq #6*2NARZ cj:YÚm+A"544oj\$vNv`3Km˪R}{/vSKCFj&1 RIC{Jv5}06[rfscz?\29\ V{ޔj DJe=z~0Kly|lgdOQ)vsIeKe X8oUVo C\< |RgEsm#ZR3$E}Hq7ͦޔ`klBvH@CkFu-- onGin\o @!ԹhQmY"f1o$S,"VT7g @-5O ކx|R.*T[Os(ީT鵒 (zt޾"G{U$޴E-ӭ_6@ U.F ҂Y'%x3Qgn̹ou4VN䃟)==hVկ})}̬H̦.?W>9n߾C.tƋ87__q1^<2R+q|%?gwɣ 1u8yK/y$Ϟq]]f3k1{=/|c<>^8ͅ.113?aiy5NlnGO١I'OnVԑq)Cٔ ٹbP`+~g*F縚[8j5}<5plYBGu`[d0uV,;j+j1 hWIZ=%ө9US9R7&WRr<ST40՚:=ϳGiYe!5l l JDH T ADDF"WWmkpJr}\[mLg2B-۹ XA~Wm@!w4VMU(juz~N#lM;oNME SB2Cvf^k s;ygW/ 4;|s!b{#ZD{YâĄw r: $0T||MoP @ >b+HFu<$ɝ X6;bBb#ns`*`ƏWhP4WW;sKn&vU= x:k휭Ѥz橺%E5>BDs'04|]f}K{}M.]x /| VVw>y#L&Gol2Pe&Ϟ> w !#Μ{ ^M9w"?o_*W?Fў}HJc:կ ߺ͎XY^Yĩ >[;7/u,jQZԢE-jQZԢwe||ܹE*hLя~\B?pktE>29hlóm3>slsO:xMV6 7y+:{w8{ /_feu*&cÏ@Uy9#8y'O-9gܼsn1u_hiO_"J<viMpb1Q)^@,6jƩI=Mes T5E/jJGiŵ-DM%10-)\*0"4yS(Huyk{ϴ|TFms`` 65!M& XKPg9 aLc`|w!v}WBuf1хXdNs')pP]# IDAT< :?icc 3s̪X00^RԜBLIu{]q \4r aT!L QS O0fShJ.U9g]Ϫj湞 碖Z WuPB hBllhw[Uf睳ƶX7,N'ؽ,`͚T24hg)E=RkfU 4,ؘ62!kS nqC1E^ePޙ-g6U+1jL$m>\2JtX[Q ?YӌiSh-:@E+'0Xܚ@V< ̬mC\0?X\%$9\DXs q!8@oَm"s@ z <`ߵCsK(7 UVkti+ʛDć5̕_ś41 YV@j6n,#VXσsj ƞ%go:f猁qg/QW:kaҌ퇷UF>6vn>{̛lb)4>R7+z#!Y㜯;v:<+zqյcW@5۱TP縣_ԞwU7x[7XDшZ2UcH@RŎ"Z醀E-jQVB߂Fo/պTr9_ K=W"~@LP\Y3d2eccݽ=&B3k}K-mX"Zɥ՚zC!{2Zً͛iP4~v7̨TgWsմqru;vb{}Oqܶ=ϙ֐ٳUd\X  `7wIۘ35Ri7 8L86_#y !45jVޠ r徹CgO?>wwouj۰UW,&p˜ )X >R uic-t8PfuW?f@_ H_MܒL{Fc`J$4y֊)(@HYy 9k $8HsaPTC†\::`?B2š{AIπ;AM]'(.|hb%8B!!f PVC `6 H =!+%6yPs+ M+4 pUV4ܬ~_V<4U3UijUU A-]vc9G zSFRԬ7W<*LƝ6djy.ku;y }X!؀յC8S@SCyU5[(ng_:6Oj Wq@waP(;\4ւf';%(HmAuCE+Bm5-/Հ+&W}6grCLo6 V :1M*:`sDkPrgpw9s 1R\j!y`}Ng`V֠Yw6ωuؙ24l j5WwkDBjJh}:XMs7(e:xe.%nݾQO_l5ig4S߻Iۦh%3@/pٔ+XZm÷zgLhɤis2߰lR춃`IenN.]dpY{ a@HDnT:2 Iu4(ֶKRf3rγ'4āmZjsR' Ks-!@Tm6`$ }9nA-V5@)^moRy=)bIHCjS7=ZT:W#ToP:nsS c6ءlCE[ClPzfn:ViQr6e ]_L$ּaA޸J!%j1C1:T&h B-\lgkR͒XDM$BLQ7ZomZf}招S|NX.B܅^kЌLlĖkιu$FwЭ?1&T HW(َT YY12SCŔE""F vqSڪڈ.0\G۞ dDoLmZH)Qs,JkDH]]B{*]l[k0ÀF q5k@<5Gyq X/Zԧ]nxLu /ߢ uŘӬ/o3@Fq7"RNPYOL\b"vʉS[q66/ 뛛ZXwZg8v̲ ĠKCXPy1oG,uDxp&w|qt:Gﱲ<3Ο?ϭxx1>KxxVd2hܹ 8:862kgW_gyuGﱱE-jQZԢE-jQZP\ckko~[Zx+\ye2|1N…zn߹۷X]Xuit]dogOãOx‹v5Ν;OMQ9<c ,y Oܠ*>oΟ=.o)p}v(Օe.]!;;<ɕ鴧Ќٻ͞]^z-(j!'9lTC$a:4(MZ9`fMBCơFUWSۓنh0`-s;%QjYRƔl\5rrtíbӥGJ18⛩T!DۜUA&+ 6%sSQq 2(vۦs&R!mf{vYSG0.c3m3?"p@CwJ:YUuHti; ۈބ`"J f`QLQAkk Lc4N&Yl咉"J fQU0bsRK,elE"`hk,.H 8O4'B BY TqeA.))AE4AiB jDpf۠eu^PQ^ itve$jdT M[>C*67p)%T* U~CRՈHV2V0Ew5X O"o"]&ܬ> Ht|qp%k[kZGBծiUJqv]C0+]tT5 []a-oFȵ SԔn,%xmh[]k -\|]cHɡ]bݚnĭAdn FqAfa]Q?FD@\kSȩVF} YAzWaZJ4WHE G[?gYZ0]JQbb.*\@o; 0Z6n5#W/ _ ϤZ,>ke7}źJ)ּĈZ.;W:Wݧ$ %WBJE-Ӭ_L˒z[1kUc@꫒"NX__aGRQ O?[׿`ue/7۟c:;ܾq#N9Qά,-ӟw8v_Loko^|!qw>f:oɟ[|VVVVxl.]d9>qM&A-oMV7Vĭ7~;q%v1{ܼE ?JaEm-S4FW* Fv=Jf=nT ]%ke_M$DW*~[M5#.Tϱ5kf_I 9s8|\{zx65x!)Ekm 3 8,:gS7J &/ߝ$,g=]z) /Z4lV{a-eZĻІl ɋ@qpdJR=Ȧkkhl9˗|91)F_uCNy~rG--sMJ-To IDAT~?>|v(;|]v;?woo}{o/}#v~c|_fW޾ͅX^[gs+kܻ{}}t?=n޸IrKpsk<{3{H,vP+)ޱ)Sa]qIu\m*.$+JJc+LgDaA-Pb|MRNȐ pSSҠ5"lm1F.IZ&a5Cq 8n͢)JS\걎Ny쟃+k([ @)mCTs҅8iJ#q02\y)$0FXmV` Mb3mn`b~la8Tcwmj ?X *6:&TՁ)7AAT!RjOcڦnU,CJleF5O5Fl<ֹ}3{fm.ޠ" Df5J Sp\N{>_Slj1B*XYYUtM]G3}g3X[#u#ht6e*>k?~J ̓Ǐxl:p}C^~eΝ7oB֖Kwc^,8yЍH݈ULEql;ϬTV{;;\X ? | g+4 Uԁ 65s<Qm07rMX͎:4R]! $S$"jZa8#ij;Ղ`jUW:<#zAmвưj(|8jW~Kr*niװ?DTyT xwUՖIkf [AܖВiMVgmMf+P0{s!E Vt%6ռ* S>P?wZlbPBg(:Ϲn 2rO fVEsoe;?wnݭnYKrfJ /٬ia&{YT䦘iQ\b:EMoU~q8;K;ZvU㶆Z{R$aW;D!ϲGngbK Z2 *E,X1'RK! E)iv*}.bkejY"JR`V6Aj?'{Gths? R5 !ZS;To>IQK,6pkʰtسЗ 6'[vWIAؽ}Qw ykw^,jQ^:Ji]TfQjaȄ y5x 3^pkKШYiReBcKEke:Y]]hB q^8cƣ%bJCM=pKܼ1KW3rƣe觌#^ [6ܼs{xQ"BĢ0~)\@eCM W[TUw8Yx|:'ӮgQ3P݁dmLD,w1gbm#Js(E/Γbhur͹JSjk٢98S=wWPk.Y&Mt6>s!&m=;PD3`#(9%4WA])x-*]IBjLɴ)'8&rJTSmQ+¸@!@o;ٝЏ'B W(g"1w;MǗ*>t~3Ue #/tkGNt^NQ:}'t䗎@AGM{ZEhZ#OytaJ,dqD`K7|DfN&9쐶:M$m`Bzeg9ēvrH (@n7%z^I2)ͲwQRݾ4JQlw~YNw#z`]Q҂Ftn^:JР_u#v1Jc 1fT<4IM[ZxU(E=,)?V+ݶ܄6&r^Z24#6Rfuu`PDUUb汃ǹt|p,-/iǎq=_c)[{W75Rmm糟zޭe/g˶XZ^ay43<?6l$pqΜZdc{yO}ÙBUp!>s _Ȏ;q1Z#055?Oio_su?*N tk?ۿywY:N7oge~|]vz^WgU?SR6p~>?૟~׼wjn#kp16<7co}3m.h{]fNzǿe=ͼ |~gO{w^M#{7V~o 9s w|p/9w->GMk-ظ2`5>G,8|AdRԤ&5IMjR9y}L0534Mm>eB`a~a-lٶ¦-8YYޅ >pAV7ci6m٪OĤ*M[3 ꧒cڙEy%T9uz˟r59v8Sl޸꘍3C #2=5E mˮK/ 85O8W%zU\{S۸,Bi笙|dZ)!蛣60mfBJLTS f_B]û īYfJIP<*W `j1mYQ%龓ܹHN_UT*|+qr]^((Yzܛ ~IB롘.H}@uR1 KQqB,ƶ1Spzw rH^m"͚M){e(pU)]#EٿW9B(-dGێ qUoM\*|?'$752Ķkk6h)(Ж6("E#9\P4+()4 E ȞK$Q̡;0Sk u$k.匨Pt8gΥk+0 _]:?1pUPz{M8vr{Kbyѥжcp=Ep=S]cTQs0(("=X<ߗ{Z(kynUQsITf:YֳJ{Y+Ү M@H)x`|I%Dt̉XĎMAq)@YXK]$Sͪ UhSrĞ\@AqНa1uzʌ BrƽBKò iRr1N]tmͽEt4 ٬ƿP824ш f GUkImۘ{`Q;k4oB+&CZ[۽]"f˔͇1bɄ$5PFZ'☻CN*N!\Ty,Feu8gi!8s:Z+_DP[AsUҩu. (fU GCFx6gIODK93::\=6>E:) Us`ĕB6Jڤ&5oe]-Ns2C1Ve/a#sbzS:7mfj8ͦxwTVWV1`K𼗼 >l@O]Ew?{'wM,;GT3'L͒bW;<؎f9yտ?p%|b%Ws۫ҩ||^]?f3|Sxʳ_ʷȯҌy}{&Y`z_j/h{`?~?slg~w}_')९ENx~osw/ᯰr8=?ҵ=y|OwooP>g~#n{y翛 :7]6M}~RԤ&5IMjRߩ} $S2=7ǡ#xi.rsc[7/kNfffhۖcǏsrqv6lΝ9y8%%f-Uʲ6Y^^(v^z%93oSȞ.lټ-۶q!.ڲ.Օ%dwy.R!pQFgؼi/ޓwn'TGgߡ\~3H(7=u)V9ժ~l\Mkj\c{q_;@Z xU&tTLL*lM.7)Npm$9Ji}gSxթ| ]lԭb*B0IUK e~LD|sne1W6\WJʔB kjIJAq).|EŪRpTޙ:8Pb$H.BM;UƦ-7ᝣrB>W"9H.,*^+lЬ:dY–Y "_yI@bvҝz:sZW{îWvĩZ{'ן"SjgךZ(৘j~=Y޵O#E|b̽%5r,W}tʹCvQ0 uֲOS zTRA8S| ^&`].)`$UԲ\] %lurdjiβw,@a] =`Sӏqa={9fT*nkx*]ݟt*\ZTҊJ&Asə^) :ʓl-b5'ӭzgWrk?Ygc㴰=tת*LG)x*:n3&5-RoG;rmNA m&D-/icP4U ir'a?lsxoe6i?uo䔜Yq^Xט &[-wº`ZbrHAhЦLL\RLQ`\ǖ'y]ӋxZ NvR)D_ زq#9~T`mSQrOQBYmwdߵIb  KWu%(|\%MEӌ:,$SxZ^ A>*~wD5̀vzr6l}8;RrJCCgz:S&%OIM[],ޢ?%ÓR;n~>! IDATDNx8\Xr-~䬶Y5crhL]WTigViĶMsx_q~xln{ыiroKx%>ˮރ{ -D\}s6y+s3guM;nrGY>ygr2v fn$ffzq,-c+my ^⛾xGbC3ZݗpÎ+o_~8+ gמWO<_S}|'SG[E~`ڛsdWq4\;gN>uus^N|w=]||Ǐ:}w=?[_} ʓ^{kX:u~os[=zS;gߗO?p'Oww3___Nv\qyxfe7v>ćՇ4IMjRԤ&5)'Yau<™Um-MS8yl/ݻv07fDL-c~f b}lܸp *?Fmdzzf<?z];w0ڧ^`j>m뙝enj])W\J 3Sd`jjedsS9x)z@ȣظq#3)Yۆ:oΦ͛9r=ni-aˎ!{8b 2`jQ>Bͦ,SEh+rL)!^),͋%g{4Q8j7 &x@oMT_UjYAgk:MtuUضt%#Ys1 )}A F%A<1&6z=3LlM@Vtb)N).%6c,nO=b \)*="&H W.6YRr ׁ)*RbD&ةBC0T,ҁ6BflL627 hj<=wUlzo)I##lc_8k|9M֍{gc@bL.+Y)P;'FAR@k6h:{1D4XsU/Q A햽s ÇJ5K(uo )S<+@#؏!Γb`NũEwnB$ v9wN:T+QA/*9K&N T\T_ =De]WȦ"M˾}$޶ہdR1۫ň7!'p0IK졶]%1bdJI:d 0Sf5M /(aVGJ-ysбLf"pw/*CrO ֔Zn=}A]2r!:']qS2!#u}ֽΈ Tg59gJVWڬ^̒\) dQPYJ6Cڡ@Wjr?Ulx7!<:nv@eUNpr3IMjRԤ&5oe=6pѥ{8|8۷oe]\ ;jX9Dj{݌F+l\X`ĶeqisZ<}4\mٻqWشe ˧Np~oĖ͛𜛟`J8qf[ڤY!N/-07ErLTmY^^b0bvzN";wnQDXY]cb,|E*Y5#++[n=?ɜ6W #^Aы):m yObɚDJxwbKAr"-Y8h{ݖ#H㶥8ͮOΉ)B(Xm*T]JR"DAssʌV)x2߽ILj//sbVZk1A.T^ (2;ThXthSV8AALVUeg~HG|yK)EvM)!IM>kVt\đ\9}ƹxBʴ)H$ uI^ h20;;-l̚5>BTu=\nZj#${fѩT7N"-'p{Gw?L"{]JER.[Ud!|cCIMjRHu:jG-L;4n[YLhmI xo.e $6A[@$][X'{љ$+=W~5_=Jȿ~[^CmΆm9ud˧1`nv7=]C>M/ԑ}ny;.M;.;lj? ?{>=,;M_8^~CUsM/)~?޷|Oz-gQ;qycwӟe??47=УW{Rw^ S?u߷5 wp |_ɕa>x ,J^_W]t|Ԥ&5IMjR^b;L6-d ٚk)B)e8A;ZfvPю8r.6fMJ"pP*[5G0[$ dj:콁',)xAO 1G4VlOQ&x #x*]U&2 %Qq샾.uTNNH#-r&UPy突'ǂMgY 5UUZ5Ա@mH\w਼mȹ=K g5_aF#N: KO.Taq);HaPUT4mO*r鮞PU^Ϗ9e:!!(''ͪ,%[:BUqeڶ:%=e8pN4< AohmLY%'Jqb^u!e\n*(/mwú9e*hST;xs58tV)mU-뎓evs:*)덺>]ZJG'մXz̕Yw;;79%yTUmÉ7{D2h5Uē6zM]8c7tQE,8G UUMlv&;xefU:/ >`lҮwĤ> ^N1w0^%YxL)m{WTh8oJxQ49U"+!zu fGJ1ٜc1bO>S`=}@DQ :kk t'0jw,"kUTU{B ocv*< X_7+Ǣ#nvҹ(n/fYxΊru Ѭů;ԡR5JbIʕ*ͩTVNtw)S;GQcZ&& :0 Xa]!ۄnNVA(nj]\>Կ圩*Dٶ-GfÀ?zzzz҉Ev_t;sDʇ6f7_ŗ^vfصr}mʙefgU 9c9O->f3WpN|f`z30Yg粽 j0ӞJ>7S9Rls?sOM;ms׻to7=ossSG9}|ӿ8/m07|_ 1GP v~zglq/8AW >g&IMjRԤ&5榁`j LS;',pU|- ssSjN?wGtfq2]u1S\vcj#lUiZq&"*9av켘j0S4Km-[9yzFJI}1!vlԩSG#2pWSdmm}{.u ?(|hSƇQU7qsz iZ@ɞA^mf( bJ29&RLEڔh:e:0,`i 2UajY5KApj.[bO\fl2RAЃ4zd{H @* hW |t@*s)9{v6;klTT@`~Ͷ4g6lܜ)R%I4!J䗨O2U\Ԧ٩DA_f b6"l=;0kJ)PY9SwN' Jӄ[ϲvZg`:U cUU=\7$ A1?7쬍ͬRqcP4az8E*dy'4RJ!ⶶ{LLMёkOfZL5P! 'kHc닉P =B#jMo,:0LH]zc;L{|ǦX'w7voa+'2m=E d#jC1S(^OJ(\ܡmZ£9Y¹u:dA.jӉ)f7-F#-墖{gR:bCkg3LZ0&]o' 91(YI03OjR # E҇,!5RFbH[$T@̣bZy =)&퇂3+Ol>JlݸLwֽ~.~5=)_zfuϺݼwЎFٸy<ĭ/~ SCQh*{cY;cdW_/Q wfXW sS5%QPA،8pq mǾ *"iC4~}nT`䡽^}C~xK/},/]?19s{wp_ĻO{+F/d缽 =NJ|{޹C0m^ O1y}}#{N-.ƯO>L4ȟqs[;~y_.T>:Q*޿<"I?D'5IMjRԤ&MJ.9kvjs9+Ws)lqS8Q6.15=M;v 魬9sf}AWָ97s߽qե;hFzSghږ۶Rg?Yr!peƖ9zy]3l߹ `C‹y8y8SSq8/–]v]'Xj| ug@f45Li)" +DjmSRrL\ok<8m"f$[nM\#[s;S#jN:Ub whq6i; SvVή;+T(}:<[ž:?Bŀ;(n]aWLMN^RԾ1p=o-j.$2)6XVY[ZnW#;/:{L`Gz_)jXmT]Td6gŔ JfZLE1W)fktq IDAT5lY3)X*qA3N_+Vel6K`Zl# !xB u 唉m߳EgI֬pNT 7 V^)[E֎ZP?gU?WD4g2pT JO{F։cʽvufWƤ,as}~llTЉIVնƆ327n,baǷ׋$u7'̚,os&fU*+;+ێ{UR ڜc;UP"JBV`#c|ZC i+@]{cF$|er\:U=J((iAU$olF&jxo6BU2munLWU},%xUss狡&n;8孿*9FS|+`*O{ũ)pO,q>)R;͟u;bti[3B76\EUyUQ@X T, f,ЖT$^Jm5\(b6oCC֎1[@B9ѫ8W(: x܂aMekQ:M+%Ѣ]fݞfVdnG%LUBi[:;M)g8Ԗ<5RA-!9C4^0<3×*ԥd6 9%ژpUd HeJ4R4غ;̩C$uJTby{S*@Lh"sJRR^YGGmKTēԤeua+T~2T(Q:(/%}`,jk1'* oJ Ob#ve0Pڨ%ƌH"!p8}=w/~>OFb,235Ů׿hpv<ƕ%v]v LHPĄx]}ft >NΑ]w2g{pNX})*75n'1y6ѣLn?C|<:_ fyom`z˞v+K'9=l=tk9} SλD㾏،~y5Z^d!xxޗsCoU>73gn|kHc_>zkÙy?B)/~}G<Ͻ-/\k5 /z^3y&pz#Rԫۆ)xNbJ/Mf}j<U{,ԁ 5N} :|}v[JbЮc s0grT4;2ƖyTCfg VAM!jl@W!+oImwXTbFXסPV[Y^sV[T,*ŃHgA;0t޼{2Uh%`Jͬ׍kwFfP+ԠƻD)"<9YeԱl  XY3+> Y:H֪UfaOU lb6e=u\}佩ً{3:*`joMYgs[~tvΝbН2U]*F.͹MlA|O%+Id3,lK1%Y;b:9SjwpB.bDUjm}Rhc9.]b% h@ ׹ڕLIIHb"jipd0P*@V+jY:' h. 1ㄎx A)#7;6&yhF)H׌F>ukGJ77 ]h|,DNEAtri @L)ӎHfǘ1Zv無窍IiKd}W;UΓBیHmˠ,rgjTp ڨ{j Qڨe3MHv fsH0_=^˝o{[DtQ]71BbNZ97Zb_U6mgﻏg<\4;KӶFAh<MOMEF>L8amT{"L)ݬ[> Z٫z21l[S f>Ξ˯ dUPvƁ>~-s^TNG;Zej3|C\W\~ x׵>ux/WOcf;6q罽Kz _gkxsZ\wu gö]?}8f7n\׌VhF+n-a\И䩷ՇTcCwsɵ7Lj>^ZQkRԤ&5IMjRw/1<ĤVk#xrѣx8yˋ'X:T☥@ileq"$TX\bi,̳}N>ȱcǸ/rI.u1GVGczfQPjSN> o`j8d'Nx꧰_M۲eFk8~ٺura6o݊Ĩ3)\kWٓ#bV nP9~ݯgIݭj - 18B(U&Ɖ`$B8$$!T8 1 *L(H-lْՖRZ=~x={Xc}VKX@п{w}DLvK sSby6Z*6s?cvm3d-eef; 0|r߭o0aƭv6uY 3?3eU]4 cgm칲…̀Y=۳femݝh %%> =7x.93xoڈ.:K 60lQUb2٭;x$ݤ+mL\\)Bˍ׸UױykBj֔&no:g(RrKSiH1J"{V+MQCurkFP|`[j L &D!(Rs| 6lJ) 1۪=ӢG*h#:9K,'SB;Hl@pKF$N: huǛ:I`ʅ:M,#" j9D`@ΥήA c"C %!%)h}{m$=DrEZeZĺ)ҊEgRi\IqRAlmx`g=Cx e6;rXv|3dG:&aU *3Q'Fj{8/T҉b22F[{CmX+ g) v]DP g6fc.b OB[Wx~~O;oK/s}on?Ko=:|o|2qY|[׿p~r-6~&!pysErguz?ZDq{ ,Z~c9gn]jWծvbJc"f f3qv|sLcC7^a X׼u.{`iӆ@<<j z_Co7yӟ!ŋ8??^KtOF~<>.rqUx56Ļn^~y=%i`q_Œ^~xM}WiH tȐ3$Q̚A$aMH_-3֭[ni*F榵\M]n ]Ij̀ T: e)4J TцRf[vY"hsey6Fzv^ L%WSiEX, z-#03eADɵ )1 i="!8j`Gpծd 6-46hkZѦ:_Qi1G6/do4UtAMb̾7%JɜOWb&l2+xOJ0ʴgѶ3F=+Z i+ Ԓ1(+ŔnnEMjkQj씃}ƔsL,K ˅P)%RA{+ѕq1B"z3yRoVuWPZTEJZPĈ6S<%#D׮lEeX6VЮfA. Af#\;V+I*{=QՕ8Bi'ST禌4tuu/;(GSqK.6 D#58v:gj4c0`sb['nE\I=UGDgr?!\`! T 8dc ,R(h\-ܬ6?7|n/B́R1_S\\bZL7 {Clm9@ay喼:;ArZLL^]j1VDŽ)mU!ZX`?Z:1m&wi0 gc-XҪGjs41k=#s6Bq@4ӴAbMPe1?KCF'C 6w7kT%Ɣ,cXBǑ1ϪVl<ޒ@k@k%.H#$ ZX'J%^Tah\@J["`*  B4W`09_i#aςJ U#)2[-c4=`T!qU%7%Jˁ1^}툣=lIzծ,lG4܎Zo7!D-r4vEKӞ#eL9k˿ENftpjV;X̅%ϑmnk/E݊C 2O}Sīwg7Og?&\fOR;86Up`aHڜe,/ u))a ـbk>!q԰݀e}._{7ja\7[f} lm_ۿ:ɕ΃t|iu:эMi^~Y_w}=ro|/ʒ}ו˽+os!{5CώY/co_v 1_`}zC_ko=?g}]'~wor|K/o_u|wQ~7? K>o;^EoVyw{W 5Wn޼ z }>Z?SUx?oGGK[7oRjGc4qE>Ӽvu.Pb HgzSvKh .c)=wKV999 v:r|'y~ ,r[f=IlLWz3iFc-V9)fQZoxw0DFφ1KcSI Zh)+f[ҺզY>?fmHHD4ѪThSjy#Jlپje}b CtpdI$S x4Q+Z5%*V:йJyb)%O7 BAzK sem~_r55r5;۔\%,w7.vWU&a#Aa[x[ϣ6Eu@Ԑ34 ^lC9cev_ہ.86l B`_S\b"Tˎ\XA AX֬vW59o,#C,{sm:`%n[{_[ ڬ8^sp\j"Dˡdv1wpη4_#)@wp-Av5ٍ ]%Z0>G0D=cҮaSgapW0CbD9:T lH<͟=l>61;NiBԣVWq27 ĈCWOhL+č`fLDVH J+Z* ;81ҟ͞_-.F~K$S0t䠾 z=F y*^o&;'m;?pLgmC$bBԥTT-25[s%D$値D6z@hF@d˧֨h8A -C`3eJm=6 {MaF2r0qrb3 v)ƽ%A]kUo ^# ,';SJ|sfZmalahV m=,실?7ԝ(.IUZAkf IDAT-Ob٠\l6QZ{.s]˲rMmbęn1E7ȵ{ʕڙq)5D 0=&4ܳ{i-VflՖw?N_|;7qϴ>%~:wmOn_˯?ȷa< _ۉiG~jOoZШ?7Z O}oxkO/7oRϾ ?[Oܧ>?MԛV }o߱?/w7?GvS<ŏ)w]jWծvի8(\,l&$ GGQܫ"/^GގVlu7ny#="3C0P/w>ƽ׮Qka~)6SG]9:> Wo88:>a9}.6V z>'[q_׬Vkh.k#ѣ9Q??kFe0%vn1!bH%*iH5BJ Ĕ,ѻծv%?%u_6g+̛yb F%a] r\uv1n0>sBj8P M Հ(?{n}QV:m~I%mC lNOpxb0n0( øM-1@+iXPjcS,_­ծv+U ]\"?En07s?/Ww]jWծ[?%?sn:O=BJZ 0Qe^s|l@O}x!ia\r-Vgl8;;祗sx< _|e{㞫ws||b^zE88<''O?$GG}.XVw} \tpw۷yԧ?K=nuJ8[9:.Xo6`3;uJS@0wqp_ e& տMQ\kV &fMC1\iz,6{irY&ףSBwQ:*\asCm;mFRq\ۥݕ]|3 n_tMz(Aw7|- !@]_XMj6`z5{xW&{7yAB HJd@ )31&HV~:%Ȯg`ʙ q9c >%JMy0yb1&]bTkƑz&g1Ǯvz+}}K j0V˾bc-PS$:cu`jRBJɬ5j >sHj͐\8URZ A, B7rf*lra8zi{h-)j!\݆a .U4.۔[ ͳ]f y;#?w;S| |xgx v]jWծ~uk^X,hژD-s$ZyWx }"ygxr݌k{Ax u{-^|xrrzTqdXX{9>џ\8<`ʙͭ5ix4>nܼŋ/_70p||wuk׮GLVrrɫ/ī7^=_u/).u*5N#{LgJPMԒM=<)E2]}DWa5噶 S6mE{2sE3#SjjVHfQH碁Z*5OxK o m C$ ?Fz~jG,8xrbC'"Hz&CBJ۩eGܼuj 7X%zܑkx:679޶ \]MÅ]q@80V⚝fj~[3(@Rs@'kg snSȪ@M*KYks`c.Wm5AotruP zSߕN0ЧFJ¬47RKv|ՊZ ZZp*,T 1DjmLJy hkh@qEaYS~!F(Qk%٪Fg}|0s.p1IDՉvAV:96Ā4EduVlj[ka9nUW DԲa ދm3 K~]JۙkZ5"y%2]#1H#d[Ď_c8)Z݅DmNYfUNAȥߧ\dB5狾.F*6[&&A1wmՔ]jǢ5؃+]*9nWk罈mu"DU%ujde+ĂaJC[nu$ľ>8Ѳy[ɔ1ښQ 4ayUKJ5`ckcfX(%RZb1 >.Kit4$W'(rc`9سE9 ,󸡾Ƥ!b(djiRHYbb4${$ښgWDO"EbH!F6S6%"@&7bj9{)x-4q=0%iWW4̥,jBDT0;r0wTufVb&3Êg%, yԭx dl3,BJэKɥ0MlZ-1Fnr+=V+{K4W+1jY\bO$o!o"{/^O~cxiXO==TJUJl67x*/^K>ϧ~ݺ=čW_7^0m?&g_Cg? 4}m1,JZJ&h]wq\_Ү\fLbd2/_@LkNNϸLsdJ#%g$)LЊcj(A-GB#\C a\iڊLSnL\'oĴȥz\LT T(!zv[S#J-ɛBHlsOMa I 0rfY+miր-݂y@ݦf;Z*b"Fk8f1j6v7Vźu=Uj0*Iȥ]S&zSSEJ0n #ȥA2cu4k~\7}i7X5Ŗ)D4bkȋs2S0nUiZ(9gZSyb,)D0P|JnjY}*)F[fdؽJGbn:hVyd.fU;bQ]jb76%|^mnAt;V3} |uެ ̫#ճOMiPw(A4yrDkغ Zc֕`jUgI+ՁBCzYv͏&@ޠuюmV45 nE4U5X#xc.v$2[Kf}mL4T#VrLLv ͉:0|ԮjCcϷyFlf󻷷`^OZY&&FP_}5jEk#q&ԌCg19u0Y_zX&f/aV)?FќT TtmF> @0*gR@,*fw0`K(3{3$$1!:fɥ \ g(Ӛ۷9;; \t{wqvzFZg_^$OkZp}U^Nؔ /`\{qrr ㏿=ƁsV"與|?bILH^qٰr%V2/#wGM}NnHɕdjX-XapZs,1 f(j ZybL) ovZl6LS#Zl&SPѮ4 i;۔F^d TEr1hꭽ10Uk rΘR$sͨ#1n"VL)w-^Zhm"g5Uy!NkSo*:b|YM~x')1EWǡ&_V)xс8m^ƀ(X#}>MF E1Xn bhHWfh@puԫgCCB\Ursmj)ZRz3]e3HX5eL)e퀻f 05Q"j -)8x hpNXC4lW+:;jT.Ky`XFhFIi}5׾ ׫+`u ePf?6`Q剺__Cl+C 9X`@:(no#Uj@i1owJ@ym͕w(km/Y cT_ 1|Q66jm5?cjIwiC¥m?[Ddn['_Bw 㿳<؀H5 HS#Pv}i:i٘B4@5 ea@L0 HJwrՒy _v+W[ϛyG]jWծvڥƑSɜ6F$Ι {=Od:瞻Op~[* IDAT~Jdb=4g)roq j`6ܾueq4D*Z2W.2,s}{*GǼzOxCs%a-rzzKO?Kd苗.sE/66$;?gʙv/Шwp|~㉣7^vZ!4rv&'+6S҅4 !˄dy`*ŬI[ωZ*9oɛ!X!DSuYM^mAOmhfJgV,!Fs MeֽY LY3Ma\;'WM Vs5ɈQ}giWvP:v]xj R?C|Ov;1ٳ|u!F 'ns`kWΔP`Z!Ge9OG(-S`ds|YM\pwbٔ!CiN1ř̛ljg%ZUT߸C6y" agaupnLŘqzF4rpiXq4  +{\bPGWA(7E Ew]jWծv]%KJm\qZĔ7ЃX\|ý=nyO7rr?1SS^y*svzj6SiuqcnݺŃ]W%^yG{2Nog]wcX.)Ǒ&?8_b:w]DnHL5#* iBHɚA+|jM8&3tuӚY<\u֜j((֎bSFh'TKdC='2xF&uQW9ؕэR-7rp2aKnhƀ+P)tlgk)2 indwpֶp*-$XbHASUNy)li\?0@]Af)H@z*j{qtVy]l[+nMʬy̪XՁvWݭeF=W"94 *s6)u{jWA\ !=kՀM.ƳisյzO[Қ`+ xg)0RVOsS LĐ,/ׯGoRP6c\x~Kn7jcވ #Eսc6Li`ozP Y<"d**p-PB2B h32EW˧&nQ37!6EZgUlu R%Nα୵V}uB̾[v fp%_Oz6rtM7Ah"Nt>6ZϿv3҂x_πޮiVa^-߹gaߛ-S?@i@lW,<`,m97!J4xWl-ל¼Ιz9Hf"ǒ=G78PH4US늀a-j!sC BsV&#!~:k ؒ+ll*~t=Ys9ms6Bmi3%D DKۖ`av[}=<1jI1!)F hs҉[ډXiYVRZ]k)MX5Rju"e<]jW_zk լS,`L;јArT-)_1NRVCfYw֧yv@)FX'ˁq 797Q3S(Fc?9Y{Ń%'j2CLÀ !"\r\+S51˖ƤY%(H#mUծv]jWծv^b`K6mʝ2]W?/<bࡇ7}G8pTy6Jun<5O<>2WO|_xy8gƃ\CƁ>4=R}/]}x?C{Rxm`7R98|χx _ƍh>\M^뉩6*ˇ |ɥp{֧Nv3bpmiD'z/F5a_㥗s/2͕::>>R)\IɾOjMat䖋*b uU陃"&xlW` 40 ;ϴM޹5 ՚Yڕ\DtCn *nu ZMniLvpX]fh52뙾i @sbF~ R21p}d96E)f~C-i͕̚궱6mLMX3Lڄ ]iƬ_f6q`/xtuU`=nAPNlTtֳkegsN;=14gǦ8q€ĺ*&Ph!) LI˫NZMܶ@r9^.RSdVYlrfL_ch)2YޣRF#90s䙽".FLFm@9: )9:sd!ڐ`= .6otux~bY6[nF[b!)) Vゃ=C.R`3}'uw??y:biNe Q f뛪mu;F pd)vܥ [|vt=ml. ͟"bF"Mnkn) 63`ԈYs.Ȑ#U|QɟuUr.֫\5O ʖH3N#5VSFbp%=s2Ϯvm%oJR`R1< N7澨hf)_`GS9˥hAf % h3&OJi޼@@fӦ2 aʍq4SU}X{T-SOm"qr>1&c c<#4R HIu/b6=Um:Fc5]1/bp͹Dծv]jWծvzkh*qTl2}y/ُ%u9%nU&٤f53 O4 6<$K)쥺k̻E?[ ?r ]Uy3no?΍S=,7ܺ{s~˿xyΫk+~Y.|7|,gs'<_9v={\\gLvk'w3-{f{?8'ᷜ.W,{ANX͖fC'HLGJ3#}㤲u蓏\\nsjȊCsKF7e)j91+Y Ĭ"=5CCӌj@{Z̍T}L fڬ ňc8xS0c`DS[RY̚i,TY86Ydc6R> jՒ_SS֢8ӞɅ*ڽΚĦ4CLـKk+1(6) oJ&5 Uo5A`o -cxVjS~'uSԥE\䈑je"J6hY HZtzG fuEmlPY juz"WeNb?HnxR5VWM9=eU#3`" q z?kNN46{&Aƴ/J5hBG肎Tm7ư=+edFFS60Ba7:O5_J $s[xIzo"PYюɦ)ڳJ:aT[xmRԣ4 `"pHMcֵ4<: - T{nu* F)SJ*1ee}W؉cLR=jԌT>o1x}dG6j۬R}`|E.]{]꺠j'(I kʸR]ǐ2Yz Tfܪ pVSsś]wNcTUwF*!5z=γZcR|| G^Jv47Pt>`릑uY'aO.JTSD!31*-^&)%s==7lDfͮJi:q yT2O2b"՞uTTCcFs9eFg>9`quh%j\v;u]˸6|~BbJF3Xl\2TQ|c뷚%D4aL2$ep4zppJ5ٲQjUDع"!dc6j~DK@t100\ e_zOq, yjU^b׀ zX]-g<}Uvv1!#e:E# uᢪ?~euT:ֱucX:ֱQ!xmJgΑSb_??!^=򒋋W<~s )|y;,b__~5ϟ7/vd 18J.7&[,߲Zkx5UgO !B9_q5_ /?#nd_R:%>l;r\qׯn_/Y:'ٕens>{Τ )7ԃ-lM p1 ƨS  xn5WQ) Ivhh:4gU^k sg՞Az{VjU])j)e(o)X \)⑔皚N쮵8UEAv=wDL_!M{X+Tkc1tL~M0i1)^歁o.ϵ?8mKRܴK6fHfLbC 5r9[7M%PRgggCĞ&Pt|1_Ɍ9x.>j2YZXg4}g00 I\"WM,#* TVc$赫Z1b_U J!)wavށ JYgJRGIfxZ>)87|LJH-|OhN~H$\@S΄0T,vjXSks;Dܥ̡堰muAάkӸjvߥd_!JbӫZ@2\(`H{.myRՂwT*]qd|6SW98H$(R΄.*͝wqPUw28+!LN8B f5Q͘3}b?$U.&{P% IDAT~G)pޱeFuزy}V|~nhyt#PS)֖NTq:nֱ)k]_!xaLW#O5:wj]cO1EGb&< ⬇݇u^]`E4{JIDT)6~QR삒}^ᢹ{?zFds Huφ* oՀjFjƶNzpȐ2bniPSŀbxHE O~GƝ:4ҕsBujZt)\@ l";1l ˆ'<(US%ŌR hk\)~)ri(ó)Ketp=ֱ.ؘ9a*U҈3֬Hh8"T;Ԣ@D.|zjQз[>2Q[g8(C@4 #N7sJe/_bl!gpj%x?mdTIx'o O<O hLBMEKD9sVc^,a3>{gcX:ֱucX:wEԒ93:r?ryuE/w/yc~jjNkaVx5?E>`E|/W,+.^s]rjM:ǭ'{3La[̱Z_b&,ݷy NoǔwՊ>|1݆傓%W,+W/_|鏸mҐH+՟?bq&O{?/j+];e>{$2bj|ZSfIY٤U@LETvZg5Zj-( 7p+XN/7~@T&'2Xf}KܚnڋL:Ծd8.J{Qm4g0QhSN4asjWǔm*-VWPd 0;~֑8ZJQ5]ΌUYٵ:i,輴g_ m+LUUn1`S2H5g4[oߩY{'jb(2_Q Y&dnnnܮS\SFeo&T塁G:r6scCcҟfl4m}ŌCc T1*`P<l 9Cs-:&]s˜8U)a/fcQO]y27Z+xr5gdd.%bYz7^׵cm $ #gjzixk1r# Bz[x~fw1'D Oj4x9cZ,-)V>8/"wji\!Ra_B.x~wfKW3 y7*)|ٳlw䈐Đ 8iLyI.^hv¬ Ӻ"bix^_TBv/t}Y'jn.Fik?DSnw#9 '3^uߙr&zJ) wbX_ZM0*թXug.s/jJ }'Fw-A0fd?Zfo8EF8iBFҫ& ɲwlޫ7e":ˇ.:ա@mjsT* 4S,qmBu=qN~cLԂBv'=OcFUԜz=rUVu)ͥ M\32ηR !cȉcXZB1 {Cf*v,ETj9M jo1RjD,jU]I)kRB%s{˖FcԂ)y*}0R}%=oq{-RQr& 06*s,;RCUcX:ֱucX:o)A`ÐL! <}S|Gy]?"{s,\x=z'ύ[w>W Ϟ=)/_3#Z==ܺsi[X-WZnx,rO\s-zɃ{:9rtϸw@s eͮw{~}.n⊜/-?ݏͷޡ/IUZ xU'\쫋K,rjGʪ2PX䈥@L)T!Z4u<@Hi_L* jKM LLDOmyeJHTaH@^4p;&Iuԭw.{;Ɯw^{CThe-7`#t0{(18z.]/^:saȵ0 tU;ɒ/[Ӹ5[sի.muŜkMPcLNDJ>F ֜aL D5YCik1a 22kS[XұG2 wq 9eMplojsCH[-ST60႑ ОU Vtfmᜑ6PbsPDoksLJbJj|Ћ k>nE.uT2K&jbѓsfجwܸqbֳl?R{5F) WWw4cFIѫ]{T %uJTq:`V"Yp!;gti 6WߨrՓK5}ܫb eLڼ$BA)!{)9Qsc&B RccusUs!d̘z`3 RN]pj5.R/}jÐt޷}:CQEE7%kM%qZs"7B1n]ߏ84 RY;潧aɹ<b,锘wA8)̻>:g"\h6o`FwhB3bj_^Aչ \g< kH3mgHJ6j l?VgMDdMj_ŘzÀQcm6NrJB(6WSҬjCڜ.N:7{S㲞K hP:ֱ~W)me~q+~U=.x{ 9,YR2GP Q` o/.2a04R/8\56h,^gƔ:VP+">)B.iرXy٭aOƈj.g:* -cX:ֱucX uݱbz}ɬ1'Ö?, ~~?_LZ+YɊO>|DX,W7θ4;Kn7[v {b U خa\}}/o<য়~u?{ʐFssܸ~-fCΰbGٜ[ׯ3/ 06|; W|ٓoy-nzfP( Ĺׯ/vڼ!Mq0V#%%W{)E-c>bWsjF+h6`$*I٪" -o*_ \iYifXWUj $ o [kݴFnb.@^A-+j׭ZU|}Y{oUrL֥(20l*MPH鐕 ;>ff?٭c'S+ޫԙXWN1F,MqVʅaُ;1 tݜa\ 6et8)Pԃڝ5M\LuVJƵʤеOU)Chb5NNDnƘ4U|@.-SN3@ʕT @8WVo pnO1kצ V␣A["pţو*-$w捦-M' >#W7 | h9K)d;I-g Pl<Į18%+d?mnw#)W-gx'ȵESjZ >|4@6cKQ^Se L#ʤ15@!:ԜeZj͇uy^4YbڸU0U4RbqQUhZsL9׮X!C*Uȋ5Qc s֌! @LwB!sqawJYw{$c90zqeKϠsڜ 7)3$`\Ks,*hB_rƱc@%\/zU|wAUߥkSz:rUݚdճY4GxsaG5Ww^TJ|4a۷x{8~:~n7< NqϹ~g7n/IV'K...(yj~^ME~/s^<{)x >dl//y뭷 > >C_7oSJNNXv\lv|7,g7oRk&bɸKSf'7T'LB絩\Y;mk/ڜ^oP0k]nvgڴ,%? QUVY)Mj1Fk{uJ6K2K}1sGhc"^j[ߔ*P,43JDGm3Y8:Gpֻ_Q PTOosS- lVFRQsh@Ҁ#1+:tkQ"Z~c50Y{;b J4Kl~N@K oL j-ƪeQsI8ǹȘA|Mp CPu}*:jLPn1I"ި7P{li*SSlcfF<, Bg105+t1\` (k)F-7d^Ϧs.ǻ:jGU u'QjRjUU+,Z;ׂwq)KRprNRB-`n*r,(؊Zyk>ohwJw<.v.bt+%xS-b-OYdh'L)߽>+qjI]*&i:FMmi4Yv!zU1q ΀x>W<{a(j `i^-#5'Uw}uD](E}씼0F,- Ns`T)Ok}AkzE$lw:qOJ#:%0x<Ja!uA3;նn@&C Ǒ!%|'>2:r>J;Zٜv7@XDmuW(92R|r׫FyDSjl'ֹ"YG?0;U>C*\iċ}PU;AsENUmˌI]2҈pRDr\"Xss|Drұ/JdIIñΕ2U+6Ө*o[Uݾ\N*3Xo7S"bP@6ā _{wb3qd(A_rV!^5Q]:5[f E)#mIJfav/y)U7ZӬs8Gp\79scX:ֱucXZ.nv9~{q1Z}S~s^|_p ͆wos=WW^?2 {-Ϸ[./>os[zլC\%D7[07t^3Ĝqb69_0ŌbNE!h7n'zu ^={v;mtūĢlw;v>cs4ןc>oCf'g`5_/MY1 ͖ϨhqwĠ)k(kP}(^`ZV'=ѵuK2kam Ve:[Swo+V9SVc3+[>Ƙ`7́!CĈ*fkt{7cgz.:}a\P,#V|\XATrV_S0[?[ L\+wB]+*. b̔]d@.RD/xuB3._t]$=kUz,LL{l,R m' t{<`U>T|fkf2fSЋ^3wx 2dV˞;o""l{[bǘ39'S"|ҍ9U938cJhy``Swmg 6PseG\Ug͉{oGosgOqZW}rqqv5Ч!`lH䢮*Z' VS&6@U+E,T]Jf9W0hYoR]o'nn?ͯQZFA u7[Kf9qk׮q~~0&hJĂȘc. eVqg}m>xt,Z, A4L^ܶTI c,uuѹUŨvj9<7op׌qƟ:@,lǁ~BΪ.9)%N3yE.ad3Yڌ(9鸮YX{ j#fJIz_ !JRe{:idiZKke:})Ea8YXgjwQ 8NXlκ7$A-Fཉ\BzƔ(S|{}0,f9xh_䢙*;U8,/a؁okUlxJUW!*F$2fc'zNv;]%ĒFج3xpN1h.x .(*N &f ?-sZ0bH'xG#""At!5N60X7Ɏ^yZ=|cw[ަ0`L!]Y-@LR8T 5cY=oY&QDnS1JҼ"|q]c9哆 㽷8d`>ubǂ%nv29%^r{>??? x5@ƈv3?ɏ?n>cX:ֱucX:OV'Ԕk OKr;|wxjG3vscv<}S4_p=}./^R<~xϗ=U3 IDAT{P 1g4'VO=#9>({_nX1 wOrqq /uW=_}wÇ\[-Hy{q`WK,Tr* yN-Wݖ.:rxXz:- )UDf Zߡ5%Ǥ$qײ2M]] gM5+ҦQR&4+fmZ2ˮ k+؟:Tt Yvh5MBS$_,jʾO1 M%܀/'z*L )UBFo|5@GS4A^ AK9X}|"k Sk0B WbjL v.4.:.7kLڇvLFd{U8IT-f4S>brLNT]MqVQ'P@{^̺* HNw67Ϯ1ϐ9j!=S>bs4 eP|UrYܷ:;iBĀ100潫  !j]x5w, D!0:kbޱڰ)TElhc=Ap2 {NOܺu\ \:65{3n"%ଏp2*Zuu(Y qrfH@{3"$tD.jo is䝂c8.cD̺abTK_WlD~}eyew W fZo1{PƁh62V"nꔋ:1Z9((qϰ&f,R(U킽Wәr }wŗ]$ѩCeG#+n9Y-99]) \ʤ$1x\;S cy4KEZA.0y"yL{e6,$uNpK!/jJA|P`|Tu;rM(X.F,(M#)P&ZZLWp^P+!DflqյvJvv*Xo [&IUFTXC\q5CaSVh=Vj [nX{jjjèc9e0\o”+$"Mm[ez;,e n3&8hybɤqۼ?ß;93v,.ĆݎhαucX:ֱucҘ@T+.ڮq.1Ⴧ1aّ57NW\~T>V^x3_}9?g<_r}ׯ/mw@jS"t+r3 ~#t=1<{rAG ] ٜ׹s6gO݂y߳P ,b$ v_ {}<| ?a\_n3R\3ysXXڱ\ׯ-i,e{GqE (}KɭtnF}@-˔eTIfU)N{}&3F.S6[C9̊2xlVXc,\iإ[/' (z0.WfX欠*+!o.8:*gM( u6Cc|V.UYc֖vpj}uN 47rу,U\[\lH&OuVsKV!42 `jP#4V60Y6*2 <ͅg׸&4;a>ڍ~~n;]?~#WY7 .4sy&Kv*U0ękV0s jNe-;7]_quDZ瓏?|CJ_qu~η׿B6R AG=} x9; ѫ+K;VΑblR=0:!xǑq3 I-XQ>_+=cwϟ?k˗ݸ0(k~nëW/(՗_O>EחPW+=~s iL=-{tG6+=;~mJ)\Wj1έ}>͚.F՚'4ή ֛5KnW<[R#b|qzzµ̖'/x}fz_rŞaCpT) 0cJ>uŌV7f3 ˺':iV4TҀ j//l 1Mʼn6F)A"_GNV1L_15sB$kIqLMWPE4Dw&2MT%*++M'Ldz/]SUQb3_u^{5"}SW5m#WkI PK_{B-(Eɟsk-U佛a`t΀jzesHa1,| 3<rDG8dɹS fk1uީ;=UsSj%@vOܨ8S҉T=W+,jx,g0w݆Yd؏l[jl6WY`uP'ȝ{àrwyRY ~ bz xYqyr , w-e")\{wo2 wWx>&g/Qy5ݽ7pqcu =/v*\r^%o}lG{d ws3kZZM u2 M]1hK$YGJؘ_t$ZI5~9cQ2P?\Põ!VQ#Ty{]$Lk EW 2cF|KQ܄읈_4b\8xhED Uc 7R)'up7R iXRFi7fucn쌵4EU-iucwěױsQgc>mdq\@GY0#₾7MmjSԦ6MmM,pkl'rQ 5a\JYKBdhq4D0t+qx^ W26¹v# ה~U9kwQ*亮D{%@%b$IDב}m,u C+Wn+3eޓ$B0D" 'ĺOWcV?#4]45HUpkZ<ĭŴS4 (|iI1,KV敨"@@sQ(Z]cAL_ir>"v ~xgu-^x/tEɼXUD$Wo(97o+ݻ|>gwgL&MES6(^]5Z6Q4 ΅lr)dE`~^3B*>Day"gw TUd2 =θ,iZǫ篸{6{7 B!"3 BdZL**mfklA*Mۡ*hLkwD+.hR k ua(%E&d0mK0`a2Q.%v,3G/s92\)ᐟܾ{|1 ÷>AƬ S3qQ<\af V2 aƟhրeYUHYb6_"\X@[Ҵa=;?#?mR0i[r 3%޲=q,A✳ NϘXci!5yrČV ^zM1 9::FܺsকE۷m~;?s.P:&W,{7vx%e|)1 %i]Alq!5-Jxb`>a#tVWo eUl piIfئ:ݡ08<'IrRm\4Ib{R&]2>.[$83 9DWTjPv#R Z0@f_#Ju&@ؔ.ګ@"_VED):v\>. eb]+w^c.XaDp[ Ś-ƆѥE лQN\ֹ!im "FzD[IFq? /$:fYS7M|ڀI@ZC62:}#>y?\% UB 0-4Mn(wo,֯ BJk=kr]7%'gؿ~Kfwo=f+vvL'lo󔫋ˀ"wdIB;eA3ϙr5fG%h1 ;h{{o!vDJ޼=JZ Ǽ:<1|+9ؿ:9<%ggn.}<,GKz|ij$'Ο \?Wq缠&KCd\i-M530tx' t:9'}sBץJkBp[!MSsyd8-2j DiL۲3%u,hZ1љ8;=c&JIfm[$di=ʲ;AQz9JKwht:! `nlbQf i0-*AIA咭-Zt |-o .NO0mC0Ĵ-fYVd$ Y|dtRP%j2\,έ]8d~9aggT N%(ՐBmE]mA&%|úT ;ܖ2q~2q.9(T2A+c @$7pĵ_':: 1s[ .q$Ekmk4 DEG0,A mC^L$) qQFJEcLpD'K38o2Ak8l1.XVmmBӶ-^QJk,-%B)NN4B+,d aXnZP`!#^HZ4Qmx^'c{pe\˪&KS"aYt\ܗ%wLJ~g;wy훷5 :Ny׈)|AojSԦ6MmjSԦ~;ɔ%u4uMS֐-cZM$t;Iy;|g}_C^xtռK"ڰ%?Ƅ歷z{x<9 [#i)ij.&ʊ]~ɳH27eë3N/.b8+拊z4?/gw6fA4zi]q&:4$Y > 9.N'H!Isn1Ƒnɜ鼦3il8Z11xF$k3:UhY`gQ[e 20`@@/K%I4bC5CT^"m Fyքr9E,tDIIL{靏((VF1ٯ^@kW窱/@(DG|ol沔Ua0>VJL]\+\IJ^?*9D8nZsϞ9[[#^G {RISR!.̻"O4^; kp(^'X05BARe^g9;wP7e~J4u\!3I@KJ;w~!{;[dI5b[xKc1;۔M;y>uW!Z A]T̥c.Fa3m]ceg=5a668S`: BI5$AЉb^iXt;"GIP-4ʠ2_VG8'I!B 4;R%ԭjFy173s˒2 5J%AO*NA(T JpҴ!a6/v34(yD݇}& n56%BVLS5(iId#1М_\! nu.ˆwEu!~Ac-at# TUD6ZKY\ii%%^7ݛԦ?_K^ t ڶ Sd.L C]Dk!s5)~R9$֡{ %LxR> D+2>2C uI8)C!omu!=Yh:n:=.ώ?o6:p`E:x/kkud6MmjSԦ6Mm7^tiCIgӶ-*8%tF4y!ňaHŏ~|bQ|6|3)⊺i 42%ٌ{<8͛g z!ӫ u<9eUt&lsyy/@*.r5-US^=\_2Np/>k~tsoﲽxG'Hb:!aiM5ZnVـ5&ddwkdz#[_OGh- 1Rwtu-uzslRŗOblƮwϊw$IxuS7-RjN3_|#nq-n9[[c$.uDiEh}!:I"IRLk89/<[t^wHJ{>?>'nAִ$y:t$MHҔ1uV7!E߾CIQͺ[d fH 4EX˜ZF ,,H+>/.#R|O~}o^dr[ǨNK%Mpzj」y{)V{ 0sG\a+6:?Ū'G7A,[@}k*>h2w$9}V,ˆC/ (N4E5*"M3P7eq~9jDclUUM xn9\pr~7鏆\M\^\p5[`lXuBʓ!,ƴxے3ή(KAߣ(kJEk2miHupmHR)m<\^M11"dytdXupMiWXk&ƺ$ž' !u:j=,#ijo}[MmjSRim_̧BO$edh{ŵ T4J '2=+V?.G_ͦY=kRx,3֓N\#ԓ[-R,L2 4KGٛܺ*aUv4qj'.lqX_٦6MmjSԦ6Mo7Z궥YhDPZ3C(R*Z,0_|ɧ,#]vl|O<;k_cwg+Kt@=_|Gr==G*= Ϟ#SܿGSP9r"{(-v; Yƃ3fY@tA*%`g4w65Z[H)iɓasvu^h 'Ŝ .UiJش$ըD #𞺪Y5ql;h[lX @ǶӳsO):!q0!dwwK: y@뀝LD+I']Q$1&rYQP!֓T {L;FԲsBs8+F)OxL &)g%Rֈ/_|_{/ߒ)GoOy>cZ..!y{,O^../(~9DnAS7kx]Bd u Ŕg0.ii!x1,;;c"IrFGG!Y^\b%N4.㺭0QM^ DQ7e/Uؗ:YD@A}$ffs+XX ^xjΪwk&kA?,u@U짇rA{8T^6m "g{go{nq. WNU_pGxeprzl*ښjSE2tw;tn K%ZngdYsn8/ )^Ӷ,%Mc>Mf4bƫG]M( Myur7gLK %Bk,^@srz @7d'a4U]Q{,Ac TIzyF'h-1u Db g[d21[UilOb@ wkWOXm@HIk׻ <"Zn5 ZJl[/6go;Z`g,:$I|sBQpL |w^s N]'BP{"ZʯTUDieUb%A"ƛךp&6R-XM[Юw08??B -Zk'p|t2 /ܻsd6#-o^+O/Cڇ$ytO2#&'(TiZQ-+)oޞ,5ݎ(R 4WX[;xLb!Q%so9Ak$F iKe I6ZD@)*aAHi lcRߕ9+f]pRB82q*bSth&3[V\{7o17"Ƅ 0(Ċc^exNL\Jx{8./B9Bi&%R%^GiQtr5\\^]./h'M5`@ۣBmұrYctB>HX54|C$MM'Yf̚T)FvC{_sl`R{!{{;j:$e6/ϗ,˙Nl$Ҵ60x"Z@b(\ fgg_q|rp44M8 `~$}]r~1 3mKU7[̦3\,J%uUdY%q_\aZu/9:9{ܾs$'gYd2ƞpyyEQH>FEcC ^>1;l68oA@1iɓ$dZgZ $m'I :sKPa%%ٔc^<$2s<&S"\o9׫.;۱)i' .qBHHDt6qlS%ٜ`EGwnP ` er@*!E}5DIfs]t b<#m*1h@8ݹ6yYq|rmr!0c c{{dp`拈WBiNb1gb^hb'тp!VdՌ`^⚲jKUtb6-u˯u/ܹվNC"aMR0c-^lqR>yMkD[Ě^`M2wd-عOߧ+ :ky)U] JR5q$d"KeKU:-vww t<2د!ghbvvw#4[Hh,LfswFhbD0sN/?1|A,H(:5Lf%U]aMBkꦥ\ផQ*+yсEKcY.Q^/K./Q" (!qҡ2xc lDYUmHNf,+ Uc)<NNΰe1Ơl BRgEIiJӴk YV{('G˗_|I9]6_~=s8cɔ"KiZwh""`BN@vb<W!$ViU{mX;tÁ/"bc9rRkAZpZo68/Ӭ Vn#Owlvwd%nq ymV !;Gȸ_WMmjSԦ6MmjSTY5}NNɋJ+$aXF禂4 0itv3%FXd#(S^}+Wt{<g[*a,~`0wdyxgrի$8{ ]_Pps^zŋ޼ E@9M9;y 5g3U{G<|˯>~8snpg^њ;(ˊ_qzz@G .HsWtbZǟgO$FIh8w~#e/٬jI]5dy՜,IuBV^l^Vd4 3:eᰇpAO5ly}De-~`2]n  mJaږNq ??I$%ӊmpZ)3 IDAT>'3Fih< r9<|÷?O>??#%3 -o^b1ss@ l+XE|byFwء뱽'Ou:R U,g;,ˆtbDg J+:Jp=LѨO':]RX Zg]AGY~;ʺ宺 Tui _|)TinR.*&,IMY@ X[ qIgYfl CZgjAMNCOӹqɢeH$Ta9_r]e7G~wbw99m [eü% V\Z&N9P_R/?C:7}Mm߱~-82at~! _3쥈X<7t[0(Tͥ*;FgљZ= xA a\ȝ ya*i'MMb:ZvCz1'84Jp=/N?{$*k Ɗ8k (>#6dSԦ6MmjSԦ63N"/QmqM@KBbЬ B+d\dwo0ϸgQ6Te.UY55ʧ0֒f)"ܽw4nr}@岥jBp TlI۴XuS:EW` [\tq P!1 Adi*"h61%L/EֈclM@+u:LSEJyA$$+qCkY0I"s8qV=;`>)~#...a7fޱX@K)JKɟ =M`qrzt_c4\4+?|n.>}-:oߣ-{;{$Y_}ڵ"uUfUun4 AB4Iゟ{~ȡq03@7-JWԡõ}p @,-##"=<={N25؎Et-#,`>9s6טϦMàQۯ$1'%7m@,>69qO3EuI?XR ժaDdI|̍[?z>46FjzuBf}}C^Ek<~)ɳ}\EIQVgv6ɘs\7,K^jјn63U![}y&eX*& 9ab6Qzy S㊥Aī4/Z:k!IxdY!%*ILFe+,04]x"h0tTS8#NS dc}n eb9 "A8$ )x2e2mP(J;·tM*"uQ隥94v;4kkU+@Zc1[|~+]_U-83L*QE[梻D IV唒,N@c݋U=Q9\N湞?rWQEA^NWe#C:\\dd]58Iʢs}HCkAzxeضFA 젊w6{w. s\OID(rW0ҕt+]JWҕt+m0l٘"ϩV*JA Sym ^ײ}!E4,_ˆ#)vOfDqȫ#vvwː! FA$>94ˉ"1d`n=.ɋgc 4+M}G(W]( @s┥9rUapzF\r~r Ny&''C4M'$g8]4<X.՚GV~~|yc!%PlkXJ C0$98.ׯ_CIjJ6.V<ϸyԦ|Fm(!J:ᣧ4np8Ri`6Gg}&m>*wqvx>?4Oٽ:8ڥ^,幍3Z*”dgreDŲglnʃGϨkܹ}'g,gu*I~RWpm$Y* l+++nmqzrʍ7Up<#>|p4 $~g4s ~[]#B6Vt J!7ۄa~loy}_}${?&F,W8:>AYcY&yJ4:\ӥiFǠۛ Tk%瑊2m&DIDR*%͕>=ͳNet_MqPij#X/EE.-"Os02M:[tZ-O<@wV5TӲdL'He<󉢈k{[ #+] '#ܚNۦű ЧVm$o&9G' Lh֫؆A6,X)˱vz|0:ee=N2$%#FJ֥[#ST#ts=ۋOQ jp˗}kWt6Cl2+&yOll'\s)\}^_Bl$~hٮBW'(ePG/{UʇBsFRxzx'Wj*FױJI;Q!/ReUP jyqnhXeIU0 ScgBKS(o['(gbIaSJrU`Z䊊⬭r}w=Zi,ܼ.i!?>'S YP$9ʰXeQUI\ .Je UK$IrI0ʐ\V'R0-LaX04MːlmoS~w:{v9 ˴N>@&f~XtZ-S嘺Wֵms.}rZiBgUPlz++ibr>qu$fpr&j {u6RU6׉\]ٮt2 "I4nò l0"tI0}Y,/t%Y\Ci5uGB/r)_ݓ.~ȅFq){~ $z"ƶFKw6Nf ' j~a|vvM}l!p ScG7CnCR0b Rt "/PvKFkG; tNdH6^BL>4 aRvAD ?%M3ldH(@ B@(T纲)bbRD mkiFض6YFjϳVl Q`Y6QUYB)U( idHi]"tgLgK6FΝ[?4um,C/fC2sL2KbZ6a`9T]C ,rl-mq81Gǜ _tXK}`0YG,ڵ*Vd421 8VDQe C&e2yキh[ c? ?Ǐ!kqrnK*?0c`JWqx˷֪A4 ܊:rs0!TiPWxnLGcz_Cb}յXw060`xcs $zdYN>Lg$iBuI8:!IS򵪋I4iw?gܻww߹;oߦVu; =rU$S:hX4 EauqyNE)4^HYB/s /+ e}cV.Y[acnݹE];}ܪ^c0Yr%M\#cX[ <ۣPB1W,>=U$IN s\ˢDaapttl:{{<nͻ_w_0L^|Iק4{>]-,fr\ӠZsq.׷6ܠiS>x iY^ړijln#^XMh7<%.Kߧ! #~{u:&c30`r>[fҕWa_ r/U@I4MؖhnffkQN"R=AׁZ(6iE&V_3XߩKcrbH_.'/y0n`CKduyAѢc9[a=:B 4^&uZ+]JWҕt+]JWNv~?~_{CRq]H0۲/r8! SJse>Te*u]TU*<{# ܼ} P޻q=:FE K4L,S2 99=!rLf9_0̘,L3f9yqm_:v? 9Ot_eG)Re`:1$SiN+z\ۦj`8Isōks:'cFI`o};mI{xz[xdߔR޴-,,V09>9G 5W%8 Ibi 4%IS(&R0g I2L)A'$IJeB0XR8C),%N$IB"(&Jr$GNZ406R(t0$N,Cj ">^PP`&NJz&VZ | !<,ir,Ogz nɘx&S ahabUu,z+M!nnա>'VwZegaejO"M08:<Ѩ:6KZ! ,'+qhW48s|| AfA{+8A%vv\uFu IDAT|9by>J)lҦT*.maY)!YGB Oy%'oa،Z&0J,qIrj۷' G'd"Iu}p>*C'7rcNφm6ש<Kf|MO/ ITWz2PBH.J倢s4%b65Ϧn[" aH NΩkl0 zd9#nܼNeQY__EkS(H29i Kln)8(}CNOI*4]i0tJߔLY3语1/H0NLd9d<[b&e8-xS*[* ãԷ9Եb:Ǭ:MǨެ3_QD7aH*&O iVpz2nBYum^ [;ۜ B]>YJ8(h6!T=njūF%w[iJ^gߥѨRVj.ϯ[i(egNs&!>ABѴK>2gIѸFeYbd6c;\EU6%RHL]34Y ,K#-C8M!A !3rJZM2 4ez+ C @i\tSHe,.E+e iA2ALQ&4I9:>eccͭul, y+x7X,f&x6{+T9)l dEF̗GAs nK&} 0?_9~~<#bV]쎂O?(EQVq?ΛoAZc>Nc 'Ar Cfq&)Jd4[U޺w QVPYJ`$b6[3^{ m1Ι/ Άdqt+_kܬ!Qkkt./$LoQȋEK=ಟK Xf2&E]1沸/eOŧ̥|CtxP.D^!/<" SY>`6G'ɕU0$K3<%KSTAf98L)4D| LCQaK*H\AfJ)(a>_0Y,DV, S:mn6Ӝ0g XS&L\ =}.!Yygs\#"Vz}NNN真X^g1R9O=c2.F1Xvyq aa.i^0ŜU)=ӏy 'ԛ-?xl| ?Z;`0fmc ͛lUz;+rllnrr|p8&Ku:, X_]0A OZ”8 ڠXbJ$QNBQzo*{; |',d6)j֩VXh2c6_rr2ZX믠ҧnbZFii7JO8p! S#ѳL& irիC&#b,+ӁҐXr>##OW$wnuY.| 6XIg~W MIDŽq yWHQ 8Mr{.ǃ!ь4IUᇟsv6h#a2t* ,dۻk}m%E-|&/\W)rRXe[؎Keٚ,2pU,}Nb382Ô/<ux#eX$Ia" j4NHa)J# #* \x^u??@a0ExcیG#l0$aJ )X,(&#(q]݅f, ^{k{did࣏ %ј 0l0!37ȳ۲0Lqx 4juZф"95VNL)|g1!qל" ϡrnjB*,g4YXDdYNНI,XRHTH8i7ܺ:7l֑dr*ePDiH¶6Őc:eP2 = XIZEaaYTTE8+E Fy.yF/@ui^8 \P-MӤV^&`{g*FL&?`|~NQ#{-V: 9ȩU 7vZӨUX]Xu0mJk /?l&B|;p4"cv7} €j?IR~Y;-͟?Cj,Oq?$9!K2 !REZ-_[+~@bs;|{<0LP9E{3ӌS~) iBX,i5[Xdcmk+]J=rBj#"7*zC$@m9#(MU}¸0h('`WCrXHyɯ>oP-45FB%41|$.d.B(tZXGQ&ЦX/3Vַ4}eS(uyRFeY_z+]JWҕt+]J:oloQTHݵYP M=,LlֽvҐ$yFJ! nR^1LBA,STk5ܽ`pΟɟs$cg{rN/IL#^(X289;#sǃ!l{U )x za0Lui2_ q#m0QE\trլ!m}h9ysco$I)>N8A^5,dӬzܾǝ[h6luhh `L2#->#0>-?i_R.bYl14צY#M`J)+lmj̷~W˲I0Isy:[4 tatzP&t$Ô(γ۶tPE`DB(U@*(0IaZAsl[[8#|*˥_6 x4 DZV+LC^0MuHV{/˘ڐ4mBp> 89v!%gy9ْ0ǜ yupᄏ?GPo4Wߣ@)z^K2MJJJݦh|g<~kkk4-\1R>ʌkvscg. ?bpvƵ-}϶x1ia~U֝<8F \/(Ds9=9EO]ڝ6ՊO?C+Wئi噖aض+L7Q';N^N~}瑩 UysEa2MXZ+Nϸys`<2g^l9ae9BH:~ SYِ񟲽d2DZ]T=<ϥR#ɣ9<8f4 q8=0Mu 0&lnn1g#RQTxP~DoKZ@gϟt \(Bq\L#˥O!|8b6_h6H(TiaHŸnj9Vz/yӳ1*?J( &"NR d X a$)G|?y9Q[\)&Axcֽt-T4M1^x:xXpyH {ocG!M,SxyVLaJ&0g4u?1kYidui.}Q`[GX[Ő(LX, _Y 0Q&x?Gs*ʗ1 YP&KSY Q%畕 ݧy.fn 8?2-: Vwo|2j_}[=kkڃiDQ/]^kq6Ϟ<'xx7Xs||0/y;[|7in!GG\v?OV & ,CE1q ~?Fʷu~[ߠ?ߒ욥e}RZ !|wh6z ^Y[` WWҕ[1?ˋOlٖiID9TS+'Ei" $RL菕_S؉B/ o4K )Klg^4e9q,Eڔ.X ."]mjϦ,&rj4z\Պ"K$+t+]JWҕt+ eY$IώЈ#oy!L !1 "cLh1y޻~ X[]Yx:TyKXvy{{׈S, ˶I3b,1R Q 0tbgef- K,rF1wn@lhJmc[:u;.UAB4o 8RI! ,I\BAS洺$>H$ӵOacY&f>g1K?d\29?!S,APUul)]ENG<$ s}N0Nx{{4UL`J$/lfK?8 jգѬ3O˩0 uOɭ;YF|G~׮]r9?z:(i8ɐx:Z.n~O1 K*?Ufw'11eP<p7Ȓpz͍ Z[[d0$>Zu|R!s* q) ^|8;>?ÐWgϞst|FBiR!QR;w%vwjd22s8b&}|6Gӳnu]\JC 咵~^w)u mkczFu\vcY$Yd:/882_O# [ׯszzL Tg8q\~`ah6/<' k=*t^0_FXnW#אi$I7LI^D1y!ɑ,>AZ0n ӶCFe-aʏ>`D!0-7|v ci0,/CVz=VWđ6-`c}?,@j#N;NZԪ5Ҁ 𡀊mj6fg S$qlz+]jzx8akѐdʽ7n4K,!(vj7j7v٤R0Ng8H۱-B)%YX͍>w_j@aks,M_ ?W9;;gpvp4$gCTs|~/j6ɲ 4Itx8W|'|gT+xB~JiH_ ]6fŅ[~ޅ{̺{3{̛kо!%ne83ZU%"oGGx 7xwyFݝmVU,CS0 RTeuOLR~Xm)Nb@R[>Maf~F3v$NN6kOljjjok!ZHi (i٤iJI<Es.~qlˠS m U1Bj1 Gb˖<~Q˨3ݏBR*]`h8b(V>O645YB0K.2DS CW0 ;!,-07[,/-Q1%p} vwMLs\2IWe_ݥ7 R\*\FVgϞ/]"G,f7)V G}ݽGLng7Hӄ :ޏϹTef3ܿ8뗹MsNDQsg[h zM| IDAT5s6g &JysshE7deeA-<|;gϝh٧?=>|)9gskϷi1 TS.Q^^d?)D)vF'?G,/ΓOh{}'ҍs Tu8%I%w(`eeNoHTbueT%FcL?{ IS^2MgĤp8a>m Gcx"JZl`9㣕2A ސrD8"?1 ϧ32{.qxY_[*C12?Wlf+_QQy 9<ףVq\E)MC8>S /sK,㹦*s,,imu,VÃCR\|/?g45_8)\!sj߸ƙ3xG<`!ocogK/;̙ 0`41Ҷyil5}rb͈F(6A2)HCb1+b&&(KEi333em.^$5oFfEv4 5ܔA:kԱO}yf]*> 0H|^_|:MXZZ_Lxl׾]&uֈ2KZ <@ڶAtff8),)il)W(FqQ1Cla#Ɠm&& P$Qxwm(!] F$E|."B$jb4D!qSʵ/-r<Ϟ8B Q+#ѵ:m|!7^} ?灊x$ X]Z㏿~mlA Kh0`n~iY\{R2 }_"b9ʕ2 s-nhjx(|ٗTßrLϯ!;|XM)mnHdq 4-ja[&I՘lE| f58 €^ƒr%eC:>M\?O?$'f)BUp,\iSr;"N>BQE30XQ0ϔS,u>sst;=\"S&aJ$ٟ'+UB5Er9($c\2)\*ܙu?|x!M>momv8a00fQ.<<ڡXSg8<:buL1g,-.1hm )VY#I1a;)a4)Fi1*0+9JkfDD Ja8ւ4LE+%c8 T+N4͹kmG_(M-\pb4 &Q*w( ؖu-6hZܺeDB:I ;Gl6.QB0?Wcg4ސvw~C4j c KU.]$_= Eju) #Em(e}m6 sK. q)st> RgueS*<|FR%X-sz}LŠEJF}8q|BRe!$X6$sY(;'zjN=ǿG\^!)έlYgyeO>k׮pry IVui,.eZT@bY440)A͟AlKiRټ*=RM+-;# vcVWGA>׮]gܹu_}_Qs9\#Խ#_MB?|Ɵ]a3Hv`^g7q,9>)_|%\diy #:!)z^$ 0 p }:!Fy^~%硲jx^M[ﰸ;|FHJ4Qx%ꚞ`P׊Ώ~BaA.籶jUkI 9v-w{>TS `˶Y6qbf%-/l$ql6xTZAl AT_ݕ_ lҶǶq/%3UaN!8[w ܌jap/t?>f(]pP'JʘSM5TSM5TSM5TS/MQᬔ۶}8I3sKc #Ŷ" Rc[&, y2ϵQach%6.\W_ϤUJZCR,30{G 6OQC)|sW'(Vi4 CEul  o#lڝB,!h[709qb0☷z۶QZs]fj5TћH }\ʕW_6&+4 a1MLX\J` $5NF=RѤý[,Fq"ObP*I_g(ӔP`˱M'sAB2MbET#(l$$\gfNwh:r>'L"8ų%\ϤTD.R0eieIK0$"s2Q2lHR، *efǹ3UK4n}a$\(N{fOh$ˤ ~G(~198jrC6BZrr1G2xe{{<{h5TfX_?[1tz=._L㨉NR,)qlN ˗L礇kWiIo\f2 zE\vb?ypmc3oM\J"HIE5… lmnRW_}.ABȰ?勬Z? 6?~B:./]Jec}Oa\.&z0 ?]k{+36 ,ʥ2Q@po uR2q|Q#P&c$B)mTFǖAdJ ¢; 鏛H yߥQ!AJxz'JR؂Nb#q x-a~nq[Z0Di̺ĵRbVda΅Ez!)(mcS)݀8`qarwOvP&_G5 L[;e9j3Epp9ƓQ Rq3;7K>[;PD4&\<Ʃe|g35(\hBp (Jk̞(dfS$5ߧZ)q)VW y~;?`^%QUޥfUN)#PJ0;_Ҋ0 sqHˑ)aZ=(f< ѐoyw) m;$*E -0 L&ȯjxDf=5dKjHm[ )q̾1?U]Mkb AC &";f Yha &ll?/ʒ/:="JhDa"MJmaYY+,1-8$$0)} (p<7XN-੦jjj )~GaI Bǒ=XɊ2Q. 8Ik:\JHfff9;`H\<{J1a0X\S.8q .v ILNEjp*,_A!^i ÄqaJHmgט'&\:{+NŜO[Z'{Tk*MGQMmv\@:hR,Y?{\HIZkژ,*Oz4}+;[Rfq5IbfiID$Q>_$I%[O ZG3YJDXí]ӯ3jY"E* Ϥ Kk9*tG#F0QA1ta.Ƅ43I=y-tfI`fKAi-*Z+-t0h6ۄQB8 6yyJsH5&Y6:NB>|FXlg'3xBmL(!.^ɓʕgsI|;C.q˶b ﱲcIx/]>?wv. (n ~ޣTKs4 Ο;k[,,/>fxDQx<"-KC%fH4!lZ6뫯cKTEVW0[%& 8EiDH˒%F`3 *)r9مR@" |@Xa--S`[H$2OW ׮u>ZNٝ=zu>U\?G," i %P:AǂMPfDZ<&ݟ|J7@XXZ"Goݤ7MEQLE 9јFC> W( H[88'o,-3Iqlױ38lmTٳgw!jn3[1)}Ix?_\psMRa<ddЎ1Zd,Na+3^ho#%4m*N0Od%PiB,VYSBZ",mYaҚ7|ݝ=j'3\..Q&n27__PDnJJEh2N6|%MbEZ&ic6WY}2W׮|b۟,--lk|.&!yoS,QQoIT)UBduPDI`;+1^G'qB0Hfq~K:<}˻Zm6no ._@ץXqkŤchŧ_8:;gjf:O1dD+$ɶEAVcSWdn"2́ڲ$B[J$xkx cu<_E} 27 '5 ^8=,tߘ2iYqVN: g }m6(aDB u,wxjj?߶M答'g(¶LMHƤJa6q!"U q(}`m)Lm!߽K9Lc&qi*3U?|A--AQQEZ*Ry(pnGa02 cT`Lr1lsY[[Zu9@ǤJ|{lllzN.Lvv1㱸,c177ǥ+symIsnMY ϖR' @ARl1L>Q@o(H%JI6 &D$9" l& Lrβ) 2'H5&c(E AG$$"cf`%gJc6{{DF)t*og?]W}ĵjaO}F$y7GTD{&5Zm,4-j:~.G # Sm,Ͼ= &&)Q1"McehIRE:eY}ñ*hmxLܻtVAčG C.]M뒪lRI[;L&BivvC,|'7؎9؎\*(I#X`.JQ1ČDi\gU IRTgDᄽ6Lfg@$RpB3b8?N'l>ߧ+v VÔq$e )U6<== Ǵ:Cz1.ϞjY[]3Zsmh۷97MVAlqRP%r뺦Ѳ\]X^]fc}CV66G#,V$*%Tj*7 67Phf5$m13{Lm'[6-8d2T*Q'5c*[ + Hm@"ql@J?_p<٤junMqr޽,/F;8svw`۶Y}8!p\oOzF+Ҕ( X#Mcrׯ>B˖KEi/%ܸq>&Zir9W#zuJ:Mݓֆb)JN)TS!- a^cqP¢isryE?_iwzFo8GMXgϞҚZup̿sf7j~uG?Ųlu<:~5iDfheED֍{lbVx`1C֙+GaaK}^ _xf1V_:\_m6-x+Isr<"5ؖel̔hbd_EjjjQ[GX  -)ONcgYA:\P  48M\ZҲZmBgt/ϲvj(NjT|u5V<}M8|tc|AWhrC4[]"RAhp, FW/ca">S>S0WQ,i;Iu*a0mJ>7NHKhB>R/+BgY_;[ʀZf`Iˤ-0li#%26T.t44ItleHmDQL M' -F0 JtҵR1B%2XF,2Wk9ϯq%3ef%j2Q#E>Z.R.}9OB~M>3S."cyOY#(BJW/e<3x (,ǢjGRڵ9&]~燿ؼ*Ah<4aem'ck7.Hۡ8dחPш Oq:K5~#)|m3(]Gm0T. **I{/ w{Te$fF)E$Ԁ&"ǡ3 J$@"i{ӔXicR' J8ijߕNX#OkhC,D m3* OvIdD\FB'Δqr34W79<|=Μ=_|A^% #>JR( ٲ\HZI}u\l`g_ R?(lm#^y?D z P)u 8R0Sa2jv2RL1J(q>4}ʥ23*IH.uJ86Ol4ܦP*qUVl=}F2(Jll[sU&!VtQ@,fqBRJaYpB7'`0?޻p0OOh9ՙ2֗xHR. DA],Oߥj?}Ξ?˿_orK?TS wbvbc|lUJa t[˶ bI'e30r08f@f$m.F} YT8u=8= N~}ot${Js\=` JՉ)l>TSAhK EM< F /TSM5TSM5TSM573"MizКD+$F%*7%' g4$ITL&A%b,±m(&b,+t]FҖ G#fff}!=&(8E1:Y͠fS(MΜ=*Bݽ0 @wV ff8C<MD)tBt\ץR.!!XR: mڝN(U6 ˼ʫ8~t5@35.'/2:[:B ~RR&qlƄb[$Ih-)ll[$iJR3,tIeٸ`0?ƚ45w,V rf`򮃥֛KWHA϶CuD =ѐJxp>Cz%޺M ,[",(Qɘ4 Q*ŵmf*%<z:~{w]ޛ7Ϫ, P0@ HMMg3=(B3iP$ WBU]zO&Ziխ^**3MwoB0 16X[܅s;>͍m0"B>uϐ\f`haLYok^уǬn8vB1F <2ŋgϟqnݼ&Y_YdkmOh&'')wx>3CWwڐehcPknp89Sʑ`yq 8q8Q33;Ǎ9y=4M*t;I3baa$h2qcf1_._l\g?޽;L<ٳ ڟ1u6<:qW^{[! cGޣ̍>gc}S''ean&zN+r F{R ̩ z:TY,J5zz{fai3h3qlWJΝbxh]ǨalX_I0Hk JbE^lo]Za cQ1q|u يH&l5d$ʼn}PQqNbMt|#P4(><6wd,OG8$ftl! G^E!6|i 84AƓP_LJYX1::P..sz(8㣃%yHШ!i"X6(C8&\ݝmV!" t*!=j*k[.NuE 0_- iľ;:әIɉHM-31WƉ imSF t$[W`$J*SsWCD*Ac0 Vo]hV \kx991FOlom0g86:ı1z׫)ϕgRJw6X\ZBP*=`ssMΞ9M'͙KeZMⅳd3l8N?6:G>gxxS'8ܼy67Oͭ[iF1cl.Ͱe EmVT+U?F#Dk#]QZ12:,}p풥e%NL_~NTcE I|ѧ)D.\<ʝ;ժ5XAurgDQ>`ei5;;<~EץTGOs_wmCߩM7h d|ZD%1(B)㺠Zk}QIQNV$Q!CIx%_Iq g"1 k7;9 s=lE76W 0B 4!5tSZ4>>ZCE.IaTr6vO9EwV[mV[mV[mI B<H#S5UbSEA*Ng37adaN%:GTuv+ dY(Ə0=5@_ |1{\|ѱ*S3X o,-p=%c2)}Fcc^}_D!_?e`˗./*\z v6 jgy.]|0X* oD1g&OK(})iNiFŝ۷pc#4È3L=ac}2<:yvٟ)uFd|5 ,͐XibAep?$ Ejr9 FkFX^Y#_`v~B!Ky8$-@101:FQ"0]:ɏɱQJ.^׸m&NNpbOMpş2}twa||  FnQதh3'`tt ϟq]zF6E6pw\{%~8/rb|'D k<{:Ã}2<;;lod˼t${OfWku<$ HW YPԪ5@Cް]QDĸց%l+{twf1h&I,aSߥXЊB 4&Ӎ# 8jX.J v0>8N$(mp+#̵QG FQ P8B"Kbl7N:_sZ-$"VmE-<.]QJ\Rx^23@jzzE3j tGXMZ"J(F+kr+ecuy8t|OFk% ">ͧ-R?6BoIbx?`?ǎ_9Yb{kd|: y\!m4|יz>GOtalPʰ2sKb'-Ν;MwwO< ʝyLV~z{FՋ$C_@M3 AHy3<|V3ugN/"Jizٯ?60Da'Z:]Nw~3ǖnW ^J.p}hxF -|z?S`ҤRI05 .q=zf{}b0 ZdyyRG_JY9VϵX%Z (rÓO)<\CkAjj5H"ܼ xG]gcc>~޷q|E`+|NO_/;3Bd3Hc#OZVIJggd y) h38n4p=8p|'!qQID4\AV#j6( \xlh4D5~&|ϱvI/qpY%(q M8`w Ȱ>} ÍjBOW'r7 +ܹs5Gyݷӏo.c:ad#Ndnz=rQ+Ͽ@J;4-n߸cQω]dh]H{%Glmӊ4FH r[k.p#^zѦbGv<}9]I Bm*ED188Ľ{Og8lponv *",/.2y=f~qǝ/CFouvvq}ɹ Jۺkssh]ywS4aWCSapܒH÷"Vmү[ge솸 "}<mV[mV[mV[mc5eGR_zqLGxҷĩI)%Q \O"MDtJns):fIILVÓ>6:s$l?ҬQf(Jds9v*DQDZ*VdF$Qj OQ*u("Jij:2l5J0ILZQڨ|zA6 \0) IB;:(䳸"N`us-.ݯ.F ?y{IcG`Ž al^#t0k.y#Z.Hc 1MסaskN~wo?cukD{.dr [=Ɠy$Q$OY%Hf "zؤ#s8W^7dj&=]̤ = ##Q8#-u9q}<{$ ÏwsEN.M'?C66o~KqiPǍ?1#\{ ~ql] [k<}<{twV+|s|#|>ǜ:}'O1'1N.?/8vl? !~ilh2 J s>gXZ^\pE|Ǖh4淿gOףY 1|.^~*<u][HVwEl! I ґ(#[a Z1Q K$L1 _dieRu2NJi:: vta a뱸#%W.Z8E8E9 re2J;)VZJɈzzc|0 N]׆l LOixlGl7AUZ'ecp\I-%HW MJ)h\i4*RRqI.9N>pq4j1b](bhpl6K$L>Iq˓GOexlՍUN҅(elJ\JTiFuu38Ru#h֚_Y_ui(풆N Q{w1;2rW'o:kQc8Y^`o~>CL?RGO9gN qλoh WH@ƨ9\fyq'aLGg )Ϟ>VWVydMD Zu~2{;{<~:KE=e*'f=>+Y[<lllp y)Ag||h6^HV + ҦՕEQ.)qx˝PY FiCA[ݶ8h’\uZq ""$YZ-2 QtasQ{LM߻V[eh1$IB&-MCyx( daW azL_h} MZeKޥVsXJ hc1O"Rb,>ꫛCCȮ6&{)^èaj8e3BX~PNqֆ_){uLKe6&>BY'sg[mV[mV[mV[rGلN(!G]zu$Hbe\aL.8$ jp"HA8 c ql8} <懿!d֚o!D Atl  1r R@`gBX$ ;'Ԛ58Q4{5d_[cye[*drTk#6\{2CChX޶HMX[Yъ#lmR9S"LQl[niWhNcp¾1A@त0m&c y͈~ȃ3IhztN۵dࢌv$+@2NJxZk_>)u8~R9%mwɺQ_ 0<2j/]H;6𳟿.ϓhӇO(bdiqu,ɣP*wp%Hdtt͍-{*'looq9;2zK|ۏ[z/>m4k^fdt8Q$I rpUzRe.dž)uBP(vrzr>6wy?`eycG(dIy!p$V>~ 7xd DowVuyoVtvᇟPe] |G;?IR200_˗.TrЯU|4" *8f6>{ɓ [o|yQьB>SVWW9~BQrn! (V#5&=$nmLwwBdKKxOXdp8O8yP FHb$Fz8KY`KKS(0$"2ﳽV h  KɲlSdH9W_8בԪuj9Wp1V6Y^YLE:SSs2>|Mz6w&F)wﳶ}mn.z-#VήXN;yMNONh6HfEP4?ēǎ!]A#ldocΝ?ӧSl\pA}MFGz/?fpp'ޫ` TBd|vAiNHh+gY_`#Iy7(tyϞj8w4C,T5r&J! #s'GoOx6VG'Y! B+M4$@]#Tb0iGwB[2|(Ⰲ2v~NB=lY* z4 IR̳;TܽsO"#$Q0=5*K*v\\OEr! aptm0;cDa:-? .oUdc}N0;7Go簳v|/' ?YYYGQ3/0:>>oF<}*IbX[F0Jk\Ǔ5r #j0OkS<}FjQ,Nԉ1ssH /#/ ҰWVXS*<~%T,"f g'ݥ^iCRůz6+^{7^a~~?Ia81='X]\8RUxW^BX a4*jMc676Y]Y(|m Ϝg?} BFN66-8OwZZC |/ɓwj `mYݸ8DJIFd&wmI,q$Z fcױfV 1BH$#AbE_H$6}51p6ۗ4+@Cv8<`ڛA9^#LU:8>RĚ#t#R[#]xsdpLq_m8V[mV[mV[m+W .B)3eP֠ &cإ]JH"c J٤pgg 8HW $aH'%ᤏ2H\{y?޽D5FTq=I+:*FS*wl6=8[M\J@Z~68|Jʃ'SD SՑ! CkֆVbܹ+IJ;v@ ZZ5|qr"=2_#ev|'OQ(p;E?zfKgY[&WȱBZR9w6:.,dY_]O88egg^qOSG<|ή.Nm~-+i O`3,{{ThZd *x-Oſ=Fp5 *G 󌎌߻ӳGG 2ۋRF=-8$I<|>QH!eDŀaw"O9"YSԑzFOWW:hd<~]?f1vwX^^lb0x/*u9yrU@M$JKgm8I0Z.V{?.7n~A fDeo>ιg|=؁z"rPm*eƞGlgV5h D!4$Qhdž5wٌG>d|t(+dBOɦHrb)<j6ё{:Jkj*=er,#C}|o-߸E jE`q~;;q 6G%ɲL!eqam.^:E8Sov>Q+ $|$u^'qIWlo1I2;y tL}2;ϣ{_͕k8wvޮ2==];LMMӨ?$fj9\(j-Ax8TfKa H\T8>>$J~#MZ'0G!qC>VL5q[lh.P;kvh4Ft1B @Ồ>.1!}F=K ߳\.=;ܹ %4-VL&%1ϧgY^ZoDi7 vY*IĀd6u}M]eVl)>;;{tuuqjzY4{ 3ûy] Ο!c7.=OnFAWZf,uҪr;; حvkŭ{>@ sK\U IDATv>]VW'7(jN3>6;]Xaf[\IT: uzkkܿE=J>avzD% )q]yNNs)===8ҡj0qrRfə30qlo=N9MJA.cdt{wS024pOp͓_Kccs9~_o<;IDfcٜ+j^'jI}HGUf{sBGl6KqL.ذ⩍m{ytbCZ{>}}}q9nߺԳ*4!@%1}}= OYoc=IL""ƛ'7xe_A6pGiC>ђXkNL[G7dgva#]"eF .i_Wseٝ=2DYbE_dtsLK Ug)Bhb$hB3ӎ,IJL}aOz]Y.9g_Tዎ@t&n$bfӌQo6a`Y K' $V׮2MV7?ȼ\eks_|ӊؘ1M08x`:'NAi6jܾq=g󬮮O'Jz!Z9iwڸRiIRt{m>*ppp' $RXu>,/sC+Hs?^aff\.at2C^ڤSq?IJL>0/.͟A6qDr=vwcfvu|#JYe}c@^-8w,}ży.{ƨi "7OD W~a@̔.IkW.S~svG)޻M@ژ 7쬮p3S MmǑ.޶{Q.$q]d"A!%|V084@.&362̃ض/>C&essms<[{QTb45ҩ8RhX:'9,̎17;G+LOOpM" '4sB>8<$!5mGiwe̟J0L Nwj( )d N'Ohe'/?i>z=x >Sz]]ÐL.˽{$3i&g+43h" f}F0|ͭ]\'rPayhm\ 9adlшO@%bzٙI^,~˯CE$In!PPmj ]ǦѨ5ȥ⌎ 15=LLOR=x8h8,-?xt{RjT"A*f4qBd"sЭ~k}n^bwgfN:&L5>$f VϘbvnQo\!L26:ѵ{\l&iJE2"#clom(4p]Sb&-*"@$!PCTtZ=ˇ4:DXݣX*AP,x׿ccm g384 SxO?}D*E4-lJQNtg 4it6*Ґ"?y$R Bp]y!Y_ߤN~DxuZ8='o׮P(AncX1u58$ ^KJy!ccNS&JcXW lð;#p-r4/Zyw_ab!ɘ!|aT̰xvAM22t24slIǍ=8<lhIkcb$dB[o!^{;{Tk5J%t|6h"-]*W68iko$y$ !i>S xKynݸM&)wNM^>3.Σi&}ϟ.K$}Ξeph C%<%L [mw5P*fJ)ZIiZlmmS,iI%`mm \|++kԪ 蜫&Zrԉmr٧w8K<&+029;hv7-]`B1KP*A Oqk7vy LLL׿IZ; gǴ-~QffrdY+T-# >ĻB4 J&&F.|>A7b_?IL*Rb&Ҝxt:}~W_}~if:OpZ Rh]G~exTgQ62#E(o.GLP|oW!TY}Xp !Mp"_R-կg|}0x"nI}_!#c)txٿ~KW_}W_}W_}K}?Ȭ|OY5CCG}hQר&5VXE")7nOֆQ EPҳ;,/-:=RBۣu*³OqyR=em}l6&[[V,87tff2ӡ3:<īx^MgI?x>Å NcIff0 n/gwo1+v/k aG{ Pd&JB\ʣ$USU/&u@I8QiRmSNp4nNLAFFӤ153Ea}m P4)rLL133E:s3$Jm\~{sX>dzzRVb}s| *xژuz+5ݼ͗oq\?gaxnЎeQdH`jr_x]j:3 ,&3HLbk؝.g,02:E\W^v]p|l6C,GlV{3RᤤQjh5;w'Hg.35=a\zMKHiZds>7Y<{VhRqlg4,t}xϰx #Cz]Օu CJ>VW8ybm>sj*RjQ S_nu{q}Viz:m3;=ŭwV|>v_^躆!++ D>c\yӲ\0,D(Ad|bm<Ʋ,zkN2U5}Br,dqOi@kUo9aZ휔؎M"dhxY&&?I9Ο[;;Tuq}AՏtɉ1x"##C ηgHg҄ZD\PFU煄F:;r~o319JAa B՛+FQN/b x9^퉦I|y>fx54)ܦVbw\zJBihӊXˆqTWHR5 BHȦSp{>RKLHiCSq SCd*IIX ~Tw`Z&%n/qc"kkIŊq<ôxpl> u6 vL&s ~H;dy]w;|y6ˏѪ^#L0>6B"fE4m~?ųghZ aL48<^a277i?SR,RhT)ߨ髵>@j8f!f }G C4ktK]and` GHc,/300@REj:fT&j5mt iR,Y1#HGG}ՂQ9L&=n~,U* Vw8(h'PNޕPIѤR9ntY ʼ|>$gϝ&czd,"Q IDATݥapl ?"6LQvv/) |绯3:>B@yZ]=7$RT1x72 CuFh? iw:h>糾A:fj0;{{ܻLR!J% B?<| Dˣ"cujHJZ088P,Yub&K{B`whH>؝&3SN!A7L$V󑚦9A$XZat2E%vA룛:C AQ%#d2Iia>{|Nz<ץVS90;;kF,fh(2A4[<!1>fY$ ՟kxχ~Cܽ~67n-Qm4 %̞)Z;]P!3{ꫯ=40 eq !FZAT"//AԍkJ ) Cݠ ѤQiBꄾ¸H)yj^- { @D}ʌ4RF0Y-CMᤂ!w&%b1-lGkCCivUM)a08{f]iklsˌx+fay>^hc\hJÑ;&QT a!"@BhuhzD Cl@U2|~UF!фNO&oÈ1:6&v)!<0+W)Gxx~׿'.p]K2 QV)r81g|>'N16:/rx,d3ib;;4uYZZi+u##ll>Bi*ik *8TO=id3)w*Τ8NB6Iyw}aH$,3S23R4Ͽ,=vltz: w=L>QC ccmQ>|,/#ĬD3;7Rs 8\b3G79sn7y/~i[sy</Щ6ڔˇ|o.?g; f{Z'<4Mr)H$bɹNϳaB:.GHb1GO=q9~I% wWo259&z_|wxL6! @B5Rl|**Zױ\b1G!aIڭm'Orvqx"A~Nmch*Rk4dTb1 P_~N&f}mG8NXYpD<ƫx _Å'135[u3NP,dUU:IPč覩#2 <MPT@z.,]03- C5M=u ]xw<` n8=!nMp\/"Bv<qua"^4B>c#|0\v!Μb{{@wRe83^fp`x"I:dw{D,nfgs+/<zk;0 02=?$"Ad[ۡO>#{"nlzVA:l uI*j!!v uLt2eJ|wH8[m2#iz= c4m>x!T8Ne%0cq=X"Gz4>[TU c: M3un~:B՚ܸyVaZJX x>|/zDc&\qϿ~(}N7Y[!`nZ!ܾD`4WV.c45 0,iu<]f|<~/}rehFӥ٨n[wⅳ yēI <6769ڥYo L:m۸vI&O#u>2J 8^4 Z. 5-"XxRF}JQeWZ˨5S) yLavv8p]g/aZ&ާnsfav.gl4Ȥu<|HP ڭ6JX[]^148E_A%,C7HӤ벲O?:{S2ӳ3fMRx)\sFLPT@\yi~ISu!%ry `phL6k7˫ d8<< <4]|?dooųghk\z^8z?@#H8sW00:9,-ǟ9dccǫYFGf8G}|ߣY蚩`# 1Ӡp}e|j;J~v"^ Aܽi4-FX_W=033ɛ?LfEt3T+>z4tL6FGGb>[K^\`a!f3ZG#^APKm28jf'k 3@A?9p&' O LCg{{*NzOsyN9g.qpXCJ@ 3nШY~!i20XP9 >42D1G&Sx;h3 jö׈4cec10bh 5ҵ{*J ;+As=▅i^Q5vl ;dB<cU鱹M>2s'8ybn'aH3>1ܪZ}ŋg;9diӴo(=K 뫛lmo?zfG+h$1b&z͍< - ~y)P Q}0ٙ F 8د&0eȉ EϜ"̔ut<')TK]7}6n@ޡh6[t= <{4[tDjf#cr|L*AZl0423O?I0,U^ ı] bj` ZA,P*R5V[Y~w91-XR<29flirϯsP15;/z}MgP(ݯ I{n#s4u7^dksS Y'FIXtR/*~vs{c~ !%~ 0,cvngΐdh4t:6j[d z2b|!Gq`M_&wIcM:.i23=I&bttFM:Ƶ]6h<"eR8?`Td~$٧רUkM勌Ovd|l>v~XܦjhԹp.׉f(HM2x[7`ogO,`bfagfec}|..G2`|rj x.;$I?^hy?6t h1%EA.&QVM(\eK-2y8ZHxM 4z!4y2d.ꠒDg-˗(bwsq_% *$Rh IǑ~Z"BB )UzB,RINn!sSI67h7{xiY<?T8Vːό!5&Rضt- ˡII!n%@J~Wo!atL͏cY:{e*C#\x)> }.?4cc,/?Ķ#LΤ9szD"{17;ŋW4Mz?wHX˫&X<±B&'[nQVyWf|lw3 R4Mfft:T*UtS')JG>SȄ6F|D"\@݁FVl60u/Hkn14{'l7 lD(£M1<4 J}n)dl|q9x7nO2<\?Tkl&I2a{1S2M&gfY~D"ən޸Nyou?յ ~R, ˲N*!Zk##LO|oY8 S!5ؽ.a1::irY[Qrzqvx٧˿9o7|ƊeU{Jhs-ww;FD7 ȼ$vuS]ip9ԩlAP(Sq= CGILMozx˫qxxsIguV%"awq"  t ^JI?X* y>i mr 7&ql68?dT`f~H4F7$j[o #ed.1L ~(pB"t^zC0E-f"c.xgo3s?,~H$~.}R,MzK4ju>`Ѱ3Dcc1l{ W_fwgD2I8IG|AÓdEz;;ܼ}l.%rQSS<}&ί fC.?c>>eb^el={BhԪLSGyӌ#nݾIP tqX,oEa2Ǎ[WH'S 9wnFrr\a{{yAK2 -OHĢ4ϧ7ɉ[ñr4!#"i~%\|FG]R0]*r4 V$*~1/7r % 7'oīF6#4L]G:. B4< B)^_ 1Έf]בi_{C_PkB- ɇ"a,ò>Wkv˰AEGk_ "O(κ}ei~MEpR;s|{7zc1c1c17z*ajI4hq<0UW^ih=irKg1ɞ hbdhH TE 9.шƙXi\t\6h#R(IgH<$1 I4E(WN\%HRTmC($DfQЄ2 ܽ ~ Y>1npmKT* lnSsX3?;͗"\)tid*AG F0"bY$q $T6BpkOm{X<ƽ{I$  HshrRB(__ NJ&VNYl IDATg~3|PoԈDcJ0??o|F`0Gp.fX4—_{hWϡiX,՛A*fO?}E"xp8bm}s޷IۧpSh5n=ŅK稔8Kdjr7)P.Uֺ`&^G"wU+;j:^сť":#믡i{{fJRi*'UV/cj {dL&aYe &˱d>_}\.~)}  h^pdY/Ttl7a[4 ףhr[{6 K ui+aF('YYYbvH,bqu._:ZrtxKz!'Uhԛdd"Fx^m&'&=GԪMtأ!]i78Oz8c۬,qUe \&χh:qC(&1&9't}_"4H&4 S瀔 a\r Dcq4)u28l˲D膉 4FàW)R8.M066>5*&29ԛ<|·A4 FDoJMYX#L'x196e`: ŋ\|B.K"c~~bid&IMp<"T/2 Çkz}RlÃcvvy1 LI+D-vH$$S L RNX<`CCu <})>3n\l*˫L|d"I,3AA:\>B~k^xy&,RD$a&~qS,/ |x<`谳:R MTD=Qd*I*& }P"O+XY^?Y]]emm="8JAbGc2,d 17߷o&ݪ* R9]}?4ɪ7L Bk 22CEETi T(,YN0Y*5iǮ P ά$&=|>lp="aZIp嬅^Ss{4Z~??;1c1c1cm o- tViX4FĴ#TMu>d*C$~ͺWFu$b1Lc254]'ar& 㺊t&JSF6[1;=BfiSlmoz>BtmM.?Ct/̓8>Ϡ?`8E֤03]bfD:7фNijalnnbZ&1UH!@7M py{.z 2#+ZSiYcPn.BQ%>-NE~$5L]8VI$:S 70 w}z؎C䬮JSL:I >})%SSS|[7p]L6MIJp=ɣ't6hfdy.^>)ym}y>s|>d:6Vb "tql:.gwbj;T+DbQ_F },,fq "(hDMʛ?g{{Y8낄bt&V7nOyM0 4>233Ãt{Cfg{r0MOdaO%HGݦjIg'O#4MbL&y #j4hw*;;?S~21Y#d RfJDt&M"$Tv ME!kAt>T*E<=jo{tM{S:5<]2k1(E:ĊDp=]7l|?2#Xn  ]D0u )F#&%hZ8*n8H^|pq~f0T>=<\!˅xCڍnx>Lt*MF7 򵯰K>aiaZJcvn^a xɋ/@4׮]XĈDD jW]g?}vIxO<a׮]XG_]Fho^ ^bqjJ$ql(iddvvh,Mꪚ$PBO?x嵗9ݧDq$R)^ē-j'<4BHR.^>"J4(MuzH)1s;=wwr [8C2^<qZLLM(?;c1o?@ bYX"O. |MJ#+kF3+g2P JͫH]4T*I-V$>t"GN{uCR@ s+7kg,geB|0&tK}FYӾ83Y$c1c1c1!{? ʰɵmc+BhxFJtY7c?@h]F"%LJ,,49K҄]'31IVn lRVsjy.ZNà7$2U?@r98S $ 7x !"'z:4T:THg8Ž{XY]_{c T](uC {: $ZˋNOb:БbhH FX&&H")oa{>;L'{|p0BGk5V/+(L0,VVm0t7o21U`dۤJkk ݯPo4ujvp=]^{eyܿ矻C6FTk:QFWWRוt6<W\f~ai^~Yn߾.~H4A4' IeqNC\bd;T* (1tzRU{6w>}|ץiw6AXt:}<_H=7yUHKj6k;s@ekضKşK֞쳾qfIC (+x>HMtW^y9_G3UHH4=l'p}LMZq2Ov990o/<N_}*N9.lxwt"D,z.]&7 TnyF6"a:tfQ[[$i v }:/oj6 u Ҩ7h4 y%BȨsf@;usuܻf,,sO8l7''2$mW }_O9!@d# Ύ͈ ޮC: ||סA^w12fœ'[4-W[.ԈbD,L6DQb[{8H''0 ="H{>.I899QbNk7Ãcv $c#N,f07=qw@8Š'= CdY:k/LHUHK|'|6=;oXؘ NC6Kb1b(^'n3OS58*z3SPb!bss0->14%%SH4W <33Sb4,M3 2I4PHI"@GTSyO:T"A |X_$Hp딦U:@"gd; hQֱ,W/Jϰ"j*Q+K/ZI<SD`:0,v0 jJP҅\q^1.^$jZ "P79>9w\1AoQ'?<29huFJE20hkI-U@i A2gq@=u*7YtHސ=H.t 2Cq%uY0p1 SB5"xS3cPV=|;1c1c1cm owTj}#5p ]Fhs ½WBp4 0xgS>-^}RɄ<<$&MMWnyr:R'qxxBբa6eXXFAѤjӳ}FfysL z]vm]aj/:dܘqBSԩ[ H M(7{gi[ӿ2$?7 Uuo;{&JFd3O>G"WR9*q}\OHUeuE N/py]΅(L_.HwiZ4lȡW\X9#ctB>M.d4gl&&xB>ih#WID#y;]{ʗ^}~wfqq{ܼuD:PyJisa6áC$9~{{,/Fr9R: OS:HWxB+|/cF{ ܸq]י,E{*.%Ąv˞J)M3g<]sa].PDS DfR>>!259I4t$%淾N,Ǘ* Nr*D K[a8Gk4[-2 i03;+_zl>ǹsկ4Bɩ)/s* sW`Y&W\D7fp@abR"9sPv+J׸|$|s~uI'_&ʤ5X$J%m*Af$cl&I"hcҩQ@jJAB%Lx"ΕF#4j-@eZH/`g dr9&&фaױIVJ[ĢQ(SC#Mh膉&4Gz*d8 ߇j m:yMulO"t)%&5_ja\uyضM&89Aqj%]tiJdqW^yg4bX@>Zw<œ-Z&?daD$=dccyZƿss8 *uy.$b1uD cɹs\Tl5$I<ϥ^M4 &ׯL' }|4ALNMrpxKiwSR:Mˤk?Ԫ5VWaF,4 $kpttLs9~V/sR\tQ- aZ`YQ\C7ufK=ޤV!i CLN8t2NԲ FC4ݤL!Piwzt{\q"aL٦hB$Qd2)DXm{zh7n^8.Wɏߤﳳw@VW1kDH! <ס^9A{ۻ.;=f,aWYYYdDu q!gq7L=88)?<43K, Q5ST*u?1fq**RCc1ook&j&A,T| 4}UiD#Q\{?8#`UwHUq.J6q/dQ%`:k?@A EhlY|r>9UmK ] {_f($l 4 B ̾T$_$c1c1c1 RJ, Ӥ$Rg5 ?|X<ȶ/׈Z/aag^-2>%nJ%*t]0  DuqR0uAx^&s+|[>xgz*4ml $ԘL_&Q71L K C|!eq 8?r x0yϐ8:Qkz1+8.SE3DL GPNR IDATsaȶUb(V.L:1^2G#>C.^8dFw7m9:>fayR9JѮih4"Iizɉ JSSLq})x㫴M=ZX & Oz! \|'s*>,B@4뤒I4$)SsBӰGC"ճ^"[Ue3GPx_|x,z 4!QFJqHd2>7o4t!/g8)t lAʀ WZ,)S^ag7G0G$S1'os2dfKE>Z#H M}fL .p]q-h4{<:vi66wD#RI+$q|<$)2$jR*N'תϧ7~W?s>>2YZ^G(+j4Є%%„:V]F)H? ͢ࢡqԩk`E,J3EN*u&wmH93 @p'0#A#f J̰b\xj))fTL4*5:׵#︬e*N2Y|B I3D}ϖxc&jw~⛿ & hTLKJj؎Z`8B7i@: 8F=!/]`8rxk_=wG^@"÷=?#b򥗹|huy5\SǬBʆx@ JjD>aElm?jS><+_}o|+NƌXM*.F$I4a4gSE*Q/4Fσ F"'!аtAS7.kI 1 X"I|5F&zRWM E \.c& 3Hq$055ŗ~>dDt<>|X"F4I:ĢQrklmnW_a"W~eB`B>Dbh,eh# 3_cowJGO(y~ß@pe,K&(bjj2m{gc TW_{9?܋w'ct]lϧih6E"4n RH"γC"p{_C$xl9?| 2`{k_xt]S s{0#t lKԪuffJD":B8JfEXu)OZ9"8))?wiV?dr=8VZL|t>]Ts,@DY3U*133G٢jE֚{@x쳷eHEy2 J9t]"Biw:q#s+XN`h;Dz*cX^^4tZ6Ox$q<2>ZgV/^ňDpOp= t6h8dognͥϠi=x /ܹ}>}"{y6/'?)3E|2u+Qv]81+qLzI&[ = d@ΗJ>E?S|cai%J"s%!tfTJE>}ʹyW~D䄙YR$#8tJހƞK d@BTbfv˗/1/,w᭿9Ov\rrQ3 )1#1N*M#4p^<]4c3E6K$SI4jD< zy8`hA+W|st13Ue^:O4wXIJt+|0#x.?&7~v,TRX,1\`XZ^lӨwBp* D*i\L&J` Ǐ7x՗iygyG}7I&D+o?\>˥Ϩj\~瞿nTj5FC"G?yG+G0\ЉX&s%fgxK/r*7DM:6Th,Bh yI\ SOHuBakpvrB@y\>Po6IҼp}wy5gpx,0 z>xL*/~iPgh9/`xrE~/:|W,4vFd^K|;>88xc п[.O}pA+4@@գi tCMZpk1K b \mӼdLCGuX ]6 lB)j35g=g]D.]+Aػ94!?s g(27TvIyZ@F6_6 ?/kǿ;1c1c1c#_֙״,*qܳA<?70t4) (a:|E`.v,LB.LlVF,SEVPF*4]BJ99IqyuKsiL.G F4T+Ot=F" Tq2  BT: 8p\!,M39 9^4B`6ǸJLMk XT躊p u]'|8(EC tqu*RBKF|j!{Hi~smȈH]eyoڱ rh].v5O,iFL5fMv J]νpnds6_ 2"n\{yu P0َ0(}F.4 ]8W_S*ux]C߿8a[-~XX\d|lt&K^chx?[#!P)I0uT*EwwEzzT>Te^z9 J#$L244H6jJY]?Cü꫔JE**| 7n&I?0es94MC:ʵk ܽ']sN>xaQyFi4ZDSbbr18T"JaQLxdyƿWu'b_ӕ[(׶i*ҳϐҧT*}yzH3T5~0;;E*Z$ TBPO?7ۣ^oI+ǶmۇQaa$cr Mx#Me{h LSN(yffu=4;T*zlnl#W5&zŅ%*abE2i{3 ,{;{lllS5 C}+Z&lFhi%M׋]ЄlQ9ǵ+)nfR#mMD"$ ]@?^! #ԑ&I"4\>aߎ 햋8KZ%2)L:E`Y]Yt*ɵX^]oz2*Vj/oP(i5[ַ_Dj :0d2A2HX_ Um($LgGxmd _z|߿EOOW^{T&aʅ,d-_sg`ee5nݾe  B,v AuuHSDa<۷-pKno1w| r#.1!!^2>BQD!4"%]?@Qo6F z{IS,,Aah9M)} L[btd7^ um:m=t]2ȿB`wA8&}4!dzzbZi5[wcjt|&C"~ny|y\vt&z2gΝLdR)Rv۲='drz!#)._9O&ѓ/9iRi|CmHLOObY:g~С_& #.\<2J՛..Lw"#;ENx. Ȥʄ+vOdiy{QGM !מLow7=H0L s=6V AD$[;lWh:FmZiPc3H?49/" ~h Paߨ~G?\麚4K]1$ ֚ja躆@Ch&In\۲T&CݜZ٫qZ vƯӴm6Bu]g5M 0'u9":L>zV1@D(K=AwA=:::::ϱ~_7r} @7ta2B"% k.4("D9u] dj7$ 5,cת"?[mz 2DPRtLԢ{bYvM\ac%Rdr9VWVYZX(bdxQ,KDzM$DB! bWId ַA@ޤZoD_Oh4H`Ys=f'&i-GAABa% $RcaL۶04@ةQD솋3|Jp-B9h/"q#{t(,Ք4 'Q(xl?Ev(WʔJ],ȗ_(S K+̝孷~X_$ nڡqG)R~O?͉ǘa`p۶ȤS6juZm)W3?N2iJ%>v3gO3>1F)aht$AM;r.^@ZTO3;;̔ePDHl;;ww!!=wrln|!($H-TuV_5Am"TƳp"!;LLJ& P\v]:S,Ǐi;3,/:sg2I  @J"୿;<ʕL]2`o!tvP:K+k$SI! {@J2)R+4tjn253mla\>r6[MTmMt!\KTfvf->m;dkc"Ded;[pKˬn!m!46i9!aX6dB*TAB Qo8hn8yEC>caqfaD$CPQ{csKv6%IQ7k*<]ۧRf1t{359V+<[X$LЕϱz\zZ Nyw\_t]e{ƦL:Q0`kkVVW9#p]  ԛmNn3LW< gWW!0Sus)^|4W.d{kyڎǓ KǗ\z;bye ɭ[wIgR\r;wk}W1B[Aa#cnJ!VH$$6I a E&)QϯwPfrz^|!ʕ].]> B I$L堏s]!dtb'HRxG gcmmf˧ZoȈG`d/:S)2d`O>-Z$XYZst,,,259A6nÙsi;-" |ya.CvTHò BÝ;x"O4B! Qd RQClo3̄Mjtd*d2 F's!A Cd|Pp J=݄2s]F 8s]Y^^cldT:GQWMSXf (9!kgƹPHzE+ДlN/j#P7X&uaj2Q%F.uQ^B_M%b( IDAT; 6:: HŪ{bLU s#CՉ;>:::::+;at|Li#a1ɶ" (+Pn`bgi,(M7%ٶMc]Rpl:eRl8_Fvi52DtlR(܉Sr]ÉcSD$ɐ/p-KҶi4v; JRTY]bmmJ_JLCgmm ۴p|AN4Z 676YY$Ncjwgxp?1#rM;M(,D a\LtUT(T ;X5ZWMjZ M%-bWCjb'cg]PִC7@`Yf!]ع(sf8~b1&&lnnN*潟`?ns]^wyA$h74- " #ΰePkLMj֙2ilnlrefB!`qavɽu:+<~z $7oܢ͵篑ɥΑJhƥSzg=D@J|qwxp1\K/04<ϟ܅s2q2^ѕ%T׮чNg|]@NXj=JݾD8ɝ." !|O- щ+>zU4"4 SELqu qlv>|'[BdkֿH%\x7o>Dn\g} [;,<[ 8.T|62/G²LFU|XxGG:AFarE .vV͵箰LLۇ=P #6;qܙ@7L|wɍ7YY^ |*2k?yB l4u'h纤YV6UT+u@.tm A`kw B匮VH]WԩLLQh900'7AWidRI>&b%L?.C앑a\?p1t dϾ˛oBGJB4ujŞN>o|zG1.^<*ҏTfs$A`zj!(*e~0x"מJCJNsNE|Ud|Wb !i.[~@@ccdʥ駿%U|.:*fhxVٳ]ue넄e-=:ielPh9jw)#I'T5627RE-ji1f033Fw1hШUyK|$}Dbk{۶h[sy67p]X["259]^~؄A:bkcmLi6R2wj E6(b7&ՅH?yF2dvvҙɄ`?C28\OXS> 6qY_@½{g1tEklmnd~AqTay"R_~ O~LOSV5nܸE294J}FG)ye!# :ϬvMWW |'dҼ| 2,w>M*͸0Igp]2t 3 ,ʕ Rw>ϞGDi `3S2"׋&Nl:wXZQS鿎j:t1)$ $Dy!a._9:[؅0H*Sv_zKEgqB$RvUMWX8G% GaLw61dP9q8H#;pN -$1' Y$8ꨎꨎꨎꨎꨎc Ο!@m+Wk!#Tab@}rLёzq]m~wX_[gk{5 ˰hZxUdxdb!p՚FwOa(iHaH 8{ [@rJ)BL% 2&T5J%^~E&&I$҉X5m0^Qn Z]Dsul&!x&rH È>#$SI:/&J|.zy{/}LOq K_e'#Nc籶zwFJGVhJ@7 ɓyvw[7o N}wbd2#ELӠ*t ~YZVJɷ Fvr2{{q)?%;{zi:- ?1`?2`jrT9YOd8yr 193IwO7h4_i;Tk5։" +$b$lM&*o<( RDVbfOA뱿WREB@2`ii0_HHG:%JuݫJ V+M*2Rgkkkܸqmmݽ}Zt㳓N22˿7NooZ?0x+| d I$3m2=>fW^N?}NW^oD:&$D>B|#F*sLJ&}uxxk;ۻ<aEsqu`ccLMOg)  *Zcn<}F:Js026$<|=)c=d[bogϑsYܾKy 7,.-bYKK  35=otfE(."zz/WBKz>j 7U#n:ʀFv<( I ,19*k++9mN?:V[F(#_|qzQ _FA;<~gP{9)@˲Uk|xadYF'ǘ=y7Pܔ]i[ܹ{{&jF *`&M0tp6`Z&]0hrDA@©c j+ajx.Z2#p]t4**"X\^cumFa&I+,f߇i薁`J"!~zi#G8+W*geUhq] Z3踅\Wi:A CY֍[>֭;T*UNa8ٮ,fl6wufɓ__jD&<) 2W`orL>cu} ;i395ɽD@[ZE)/kt<7o4MΝ?ǹg9`oLVqDa_: 81iYHϧM< 8~FR\&%.P-ѕ{<]X+zH%,/wH^@p_~_JPC V=Cr2mAJ:0nn9* ?Q|/cH$hu(`hqN=Bʴ6JA.C|;$LL?6I&Y^XA4|29=,/7_|{ C2Hɷ$@LXB\}@C؍.Խ5"\!ĮeHe J^R) M]c[m'd@_O]K?zy:uL&EX`,>9r\D3Fp=~191p=fb&333ܻw5^yk iTk,<[o?=G[ q=|OFHkŬТѕIm]Y cC9Ň394Hw!Cywu ͙ӳ\rl&3z9qb]S?;T*Jş~W/342ﰰ@%7tjbgeS6nakk! +nny6{Jnm5&n*- %SI4M'a'tW]Bp}*{{R*{/>`|bsbuu^~I}fvwTƸ %Fh.4Hfk* VǏbnq3a?ԋaw <\6HZ5 C7o&adIǘdk{fEQNzz(aDREeΝ9C: jM>K*W^?b~TGuTOA- 04]">1vB#$ TV(V+؈Їx?BLC܊f*( ,?qAgc[G^]aWeHƈGB||UOuwE@!?9DRbKhF.e}mb~0 r 0) iJ}<54aQʱ4ݥ" K$SIN/o333M`d2˜8>C@/ kUZmӦRՕ%ki[<{2a&Q0bx1W !߇H A$|#NSvYk-:mQW!'*=$ache ZN^v)ך4mY[d{oviѱj)ggcA+XI\᝟Ak_?el|V6C%oR,vT]̃OYx?ᏸp Q(u.2ݟCBpt@h:$0=F/fzƥI .,4u{@G9O}ϧRӕ˨-MG>:PTǰ >|‰sZm ]Z+O)w-2<:L>߅jq]([?Ys: ?DzMF'Ƹx"ϖdfT+uUH` eL׮T3:Kowea)V,Mvq+0bey|m.xk/E!!N`dx瞿纇nT:D34&CԱ (ퟳ ߢEբT:#ÔJ iRcÔz:>2;;E.!i[Xe؆jS( I6T[7nbY6Λ9ַh]>Ǥ3izK%nܸIDYv6wBJm[-B7L}k8yen\r LOn9Ddfzkm^zEBr=ҙ48UHolX1nn!++F>A|wRo4AI$,,mlKcjrwa:Vjk  )<7oLZ' $9}|sh5z Τ-X^Zeiq'O/mvww7^Gh:Ԫ50 xED4_~yR?2_bf ME@!oCQC5t] !4%iy{hnܸBN'$s[hA?&H$mR$m#ݽ}l5%Ǚ)!-v:='F+8epnUiDm !c n'l |d2yI"#c*k0wn= bmm+~f{g'177MbceIwω9FKvw7_K6"gI2w|A>hZhٓjՕ5._0V9q8*c@"`Y&CC:: 9RID6 %rpP[y7x3߷}'gF9sR7;[ۤR N&iXN:45.&I' Ŭ+ff'?=4 R$aw>xue>gt \vMVVְmFnj86SS\z@L?17| w'9L&8{4Opfb1Ϲsg_~~iL޼:fg1/+(@!Q"ٗK•"AY$# LԚNV`=BIJ,zte$ ÐAΜjOx⅋kU0dd(R r, >]]]X[I+ͳ͵7YY]?ܹu_xa<ҥ+H3>>9J$kW)WѬUwߠ=EoymRֹ}g J)"rlp,Iڵp\۬RJmpI]J9c#$Tj5:TJf(pmm[Di4[", ]*uǶC5o+Lp!Տ^ED><r)K臯P7=AOW]@̓Oo VVV8a/_绯Glr<~?142Oܿ?c9O&}/Sgy''5P*+{QLPlA_,uSk8NPqR*h֛\r={&#%/JlW'O)vwq]x-;JW$#̐S)hsa"qmV__WQҢhp3KV%c9r~nݼ2?C kNEخYor-'9x 33ZN;O?ξC<ɾ]>W/^`cm~?qq{\pLbfY^x>DxA@$4ە*KKK<(8"xՕI}4 Kb[Q:hO`cu_ 'CvZxOmHbf^NOriDZPD%IekLGQHD&He2^o~[?`l|? q37šÇs..\3Lc'G8~(oc~ǎCGpov?fI.3kGw391ν;wxǸW<ܷpSL-l6ʼﳹ,JY8& "-t6,-PJE>߾˥K_4 fec O R "d20=KKLLQ,vcӨ7v X|)FG9| Ξg{su\̭7 <!$q^'QGg1 X!`enrܞp4eVY/F16&}y3,錈c% #q"A= 2%h Gnߐ=‚J!Jb4fW"3yd&dZV ͔̃0/cZ7 1Ec-CH4Nl$%wQGuQGuQG75:)²lJcvn1dfC[ȤkV`m%qS)c*( !-CJ *my=@Gaܧm6(-){[Tk5,"QcF&t.i[ R?h4=Fכ* cKAwlj< |abmzۉ]ZWIvS2A! foJ&Zl0pDQL%Q7\.>F;wr@:;?_}uݘMae9;XܸqFСC/~ɽ^ 僴lM?p,}{yt{r$]0QXXt1ܾu_`t(r{UyH~U;( bc\6M|Rw?c'jQk4ĶlǶ-& IH9AN')^c5b6Q$o ~t%Cq>-w7($Kww7Ahqј-d66^ZI;9R* B*{2s?𙚚'' $8؎1m:o-CH;BR6JAv-lKCϾ`}}CqQi6NGjɹY[]u6B@L" C\[G>31>ݻY]Y\?O/={#~X["V'RcH!zO> خ!^|%*fFNAK/0:2 QGObI}-lG6ma[fiUJH[y/j'}24k q~ؠXbf۵R3Hqaf8ò]67PjAܸqKWG…/N.>S'Od`)/_&ukpd).]B MWD*cc'u9%zJ)eIfq]V bMEdi4me,..c7np9t$hz ,;j]B>'Op=n޺K&F MVMjB`iy-JE\% rȑG{r4 @AV*2ݺRsZǚ( blۡL6;|(dntyn%cB:ƈ˦ Z%ɼvJ'?[{ig_@X 3olmX*@kوLq |N ;L׌dϬK)qS.,>l!VV9|'x̬C8rݻ'x1< vڅf3#]y/df(²lK*j(s:5lW8Y]\}S.388@6% OܺsG MeY8MZ'NjmB==i}S9}\-*h7%VWخldaã#lnnc)Z4MapU=—7aiq0`7?t_U*+<|ġ~MqM},>\Rg_| Ҭom$ s]39sLK_XLNkؖV-r؎/w_ftljFQ#lujm/pA{M|Q,H*綕eА\ݵv 7``hWRDFZ/\,;m;ܺyU5{ƨU+<|0'BJE-m IDATԅ2Yq8`;|1޽SO= AcYklmWIH2Z5=EXZo ʥ+Mn߼O~ESUA׷愀|>)!ɤQZ-]RVͱQu!ffP?tvqGX_^s~ FegdK(#J* Un.'>0?zoQG}3':ꨣ:ꨣ:ꨣ:UQ?<qФiiA&&cGR.QIj ! d8 Ct, j#MZ5M|? ͐dPIZ71x͖٤ulv\XġIFQJWR/ }H.ãC_x@RMi4خКHCBWXNIlmllyk VWWloP?s%BW)n-BDsU9Ow㶱m82kd_ <g-Iڠ#LXi(Ԅmt&'QM8CwOJ*Q92,bIQVkYB 9<,/pu<\~˱Yh `}}7~As A_O7㰾Al8_}HB+$ HPNB2>ixG "qH#gR)41)as}KbIj7 om .<@:o DA?}D9m#mV$b5-2_ծ2`❡ 1|Xwɰ4hgnJDI7XF& NCV66q=d3Y* cb.l*0翺ıG!N&}B_}? /J4a8pl\6ԪM*Kԧ>/<q#ΰЮQЂ+WܗٻwCGQkx hi GFhSzZ &p~wx58$l(<3s Ž{37,\%Vtu35sgs.0>>ƻ|@X'O|7AG!L8gڷi5x-%\P'2Vq^OM{ss'\a Z 9xKknݼw*3G糜 ʥ>o%!O? ϨUjDQTeI^|9 F*sOϰ]O͟b);J&]3Et+Y)lG(X$uI)ӄA@a8\/S)(Òl C|.45&'91kzGaD~C6S't7oܦS-xݏyٻoIm)boZQoxtr  qs=_~ו^H\K (!u_X²mR))MKHP¢ܕgkmכLN!NC8nJւB!O*R ?M>ReY^Z7#IӬ;lF M:BIRSV. qmIW1&QPo{%bb\F U,[Qms>{8xXk5:đ#I)fx>7_Q&˂(fR4&&}zGu]pGuQGuQGuQGo~ִZz"eX#!i;]EQfq,KJMRIlCR"Q¶mȍBcl%J6b ^: DamdY BϣYk8JMv:Ukc8QeIlqlP;Et`{wnEaQ:lVEQ5vLc҄ M/ W(vjQy#llmF鱾UatxS) kUR;}g^|PL_J ƯNЮQІDZ'm,9Rtq v\3IJ) ,a{ vPAYlǡZ1epJAvq #Ü|e@p54왤^qISXIIkx8-&z~ 3hCW ^umlJ.i7 ǵRG -BGX8y\,qLwϞ!cΟ;G<' y82i7ET,<K!$5ahЙV; ِG`h8̶s\1hl2b_APV|ߤ,{'-#:NzKmAhD2].0tL6TD)EضE&bJ¶}E~7Y!-676ͯJل^DyD~@WeIj&Aȑ\|)7S<͝[wnoS5!1}}kg~~>8yAh  0z۲gIJ<^/ϲخQ9|5:D*KT5j:Z +[ vPC|EVsݷvY_vmRy)R\-F+@TDޡ-(Q2)%! Jݱ-&Zzӣy*jFkqp8=zV=#-I^XP좻LTJqB{PT/IgrtHeҒwn U=v2Ctq̽{|JRl&K;÷9矟'}30 Z!\tzNX`;fX[[edt0Ԥ9 MR33;&)~r5FMTǐ|/Mgy5BU*  0Y`Huȱcػ//Xe&WdX\Z!|._xOyajK9>$VV8rV;eI&&G?~A[ͩON#l׿]R[VWyݏl}(22ƲB\fq>[kk,/G.1u\/ju>HxO~[[E\6@obR I҄a@E@clݎF.RlzlmWOy'طo%!8Ao~Q/JR`?3wGy'H6R l[SdiJORJX&*utK<,,pcjULM/q\FI,23,"@) !ͽ^*"4*W/_?eei0LyAP$}"So01齓<|vǏ!nV*&&;K޾v1bG122mL&Em9\p+훷f\eưY!ﳱA6cpl#Pݮ_}wGuς:ꨣ:ꨣ:ꨣM _pum|Ip:Ix)fA 8$ur 7P Jq64v A l&.QmIi<`J]!plˤ#S}d[lq9v^R1qՅ%MR&lkB|.yzFzF*#Ah6L"ζAQ*w:+kܺ;CԳϓfZ l5=l5IgY#cWM8gaL2-:Iz) 2W^`!,XN+ r}xa) b(ˡO,084)P(R٪PWYY]grzm;f;EP?׮]7e? "baޞjoa8nc,[``pL6& b\%B9ǣ8 B|/@`ۡPȓN*sfg3;;uҙ,jV#q 扵P,Ѩ7p?bޤ,EMLI&6ML! k%_  )IVғ5\%혷srδcm2!dIX 8P qBlxOcñR~KVte}u-!plJʩO>J-&?d}sJTq<+kܾu\vյu6ٮTGX1|n7{loou@h BHv(wwRfpCYRtLEGJٙYnݸE.ccsz#:dry.^ʡX~L2=`/+~b!OiZs{۲Hg:9LF.._A峱k[d9Z^IS5C0FGL* +J* R}֣|xriΝȞIN>4CINUR'Ç݅:Ǽ{QX|q AD1^FY_EPLͤ'5+kkuc)fŹ/ry\›o秿҅ܺ}q n~u <)N<^1g~3# #ӏNkWȱ 1S,:R)tQJ%"#NvDǂ 8֚Ϩnoq~8&`<$׮^O?w`!tw "_=F K*FGGU<(KKVkyZ-2,QQ̽Yʜ8q>{ ߸k |N8A.eiy|!C*x&=-@IVSgkfUVW6xhHK[wfԋ0Y^Zdce^W׈.J 04)\6ƤXR)j+K;~L:DžWpu ,k(\0X_etd y^b&fme?i_Ϧ>C`){wf9^x9&v^fhZ8W)s"==45^y&'Il3/Xܷcۄa;o@efJ -mIҎCeIy 9p`?1o ?yak낐f-QYΜ>*^uSq!fgM.&"eks d2.O>8[\z)j(e:dž)  2?ÇZ&BӧZk˶mdyiNʥ,/.FtwlVADNCO_{&ƙ޼:fuQGuQGuQG7NҢh$6R ɤRGR4HRsn6I2ؠ8"jPm['!͆VI&5%%Bh&l(LG !L7`ێ$kB$JIr]fnd]#yT*5\%"\7E>#("}V]#f3zmt d Cޠop|.G$IkKzh!QkllVV[(%)vP(IEqKY8hHqciTѣvN2X$F]462Iq?u_dRBkcP 7"edR.B&Hf}(btt^Ќ3wR2Gdiy-PǼ!677HR#x ZDqLWwH.QRlfTҘ^lz RM:"ˑN9|ѧ\t%>b{NT+u?f}m p?۷Ig2MVfY?OV#/`?I2W) ʝ`;)3D?F'8IrdL,c:IS`4dZhbatRa@phF!c'0"@|#h4\~`yq>=9Z͖Id{zNu{)rd2Y4ʶs:Ƀt>===Sr (Je"v븮$%LB!O&"N:r\G!A6O=4/|z '=*Bq4|b[IvOzWVT*6qlfgms5*: ?f k-,FHhkSk+W6MlFG1DlͷNgzr7iVrl$0dn>+BIA^_n,uBgee{?>Żo%=2lmUК `a]%}){ڵWo066O旼,ШבhVvUޛ!_U$rڪUF)*Q0<<֚(bC h-RWor~J.0v7סlS|:#ɚ-i|>3'Y] W7Z|q,ϞgeeZo[[đYh4jܼqR0~ad $}*| .w1kj.v1f̰Y rƍ[tw9| g>?' $ }tF!333樂9{x꩓aw++TU*:*_~qٙY\!51F\" |4_Ƌ=}RBl6CRfP#JR~sX]]Ǐ#h244ǙezAhAP\±mʎ1xO.R $'+Y{1Xywn=z{\v2~E>\3_0k ,bjj7  "MXJs]Z?`^`ctRN4UlqCrYJl`l|G)sߒ8Kyޚ_lу|uKKdlKfc{Z^}>\B29N|[0?7K/?w ^ ljM޹Ca|.V 0Xj1V2B <\>]e==Edžyq.^vNv\7c9z[*\?zoQG}3u :ꨣ:ꨣ:ꨣE'{looSPR8~#RH<#JVcMkfИi 0Iclѣ~8N):& B1ԂztWӛg;.A LzqMzmZ$Y86~b;6c<B!-ӫU.S*p3T+[lnnTku8nt!ZtnkWq3BѨsWMT(ALBP(=(Ki%rKR R@x=Ӿ}wyo6NV  !%=MTM{;}08C<&kEpq\'1-ww IDAT증`'+CW060+ߙJg0 R0=LL6BķRaX,$qR>G4v{wޟNB<#L#1 Sr|0M)t S_yKKXdPjK +Rz{Ƣa6z u}DP,Qihzѱ~""dɽϯە ,&B ʕ*LX"kL2=? #4Cb1oݰm "|rmN"J)upASl:s6X LHʼn[aD p=owMNNby,} ,27@.E|A'LPiJP.os)b8w? xL*:W4}5n}~o -Z۷>ʁ XDc-BDa1M6 ??uD+בRa.yg3_Vil}KVV)JcN:FoO'D @"!BK:Mk6C{k=dY]/Y(Rը7]3qMM'| $O)[|{2w\k D#Q.^ĥ׍aYGzhooC('S3U속{u7oEسworǏ1_[|iq6XZZ@HI_?R %7Ћm;77yh/+u67 t`7jtu0 {%No8 77)oU!݇0R MaV5;devvOҞ S>"_-χirYOG{k1|%%0 ebRޮbejjx pB266B"+;M`&MS(1 IV#JA<chhlEY{kwv 뱱0:׵HFRC>aHvIu=گ>}YLdquJU'4AJ Pfzz m8MlohTHfR,03Hl'9񹞏4%ˠpXf@ $F00$xq즀&kWjPuAk@? 45q=PxI{uҩD<8x $ FD"FC㜥aHu<}ƷN2{Jg؎܏4$+k($G1[b&{&(>l !bz{67x?;JHpѱa "˛W ˔JۤRij2XHbei|GRL#Cjp]D,2$4@D@@=z("hctlU(R Otq뛜:}O>T*5 i!}pcum'Ӽ`~~jBWO/B3gN! βZYPjmGQ( C#-"LΤI%TG bVLRR 0SAJ(ooS;ܸr͍<3ӳs N<̭wqs*xueR1:6BTf~iU6VX\\%NHF-Rql: n]h 3I'3hchjyFicc#ϧ>#͵_ ̲N?v3bڊ 7u:mGo* 33s(baf@-i|;oԃ T\93s=L|?Z%F"6VC!8>,Z'ѽU,s;933q$ϝ0uzŢ<_ P.J K[H)ؿJdYs;~@:?Ώ.ׯ&a}ulwﷹxbX&zKEQܽw7 ?Whok|^|%\}3"Ӓu]RJI>z^ξ9rzUOz3O<~2#{sbk.Ν:<L7j~`qqVK\@ȀohjVf`l`X`r8}]8~)!E v(k LFU'x=/ |jNl6þ  i`DL,eQԸv{M2+F,YXg8\uv$BNB<w]چiE{<{Fiiiǜ8}\aqa )+_y\&9(!^y. P>zD" !/,ss LM!u244'LLL8w?g,rKcDAT2(6I()Ec8/JoJ5ZjԳD[TUzz"+ܼqT2E!_i4DM_"JS.Wy45*BJJ)솇pp\jJ@e):r(  %N1_v "z$p,e09SҎrR++>F|&JJAA98Kx,J4p}384@Z%ךczvt*A&b|tc֚G,,,S*"Bt"18yt>f橖=8ƳgdmeӚKhm¹3LLbY=#(mmT! LdzaRq._70U,,,JgX]]^0wxz<=]|W^ghdCG&m;w9x`/R{:9s4 %rc͹m??1-z&"')nOwcyy!G)twB$bkx#C$)i[K3},STD"Q V 2a~vt&G]<)ĵk7m)RI&sqn^O>V)$Wۿ1yp{z}{DLMp=<_vu> ){F;1b=T+| maff6b(/0dE4j:n؝ t>r--eYVU~21BXn,?ghmq iooò"ܿˌ?s'`s}|o Lh4N:H!0LVz2b Dk $J3;3ˍ83(ePhTK0I $vǶC<%QioˑL%plݥF C?~8b֩;aE,rZVZ:}6!0-g{n(jA&ɤJ{n]zpO'bY%~wmHN:anݺC$giy !"JS*m:l_WI$Sܾ}gϰAZS\|)%Zd:Iyk ˒ r 'ўNFܺzdz$QwUevvJld2{b(IZc7n$x~{SM5חF@7 চjw4jjꟾ?}Y՗^0M <'XM!@# k54M.D1И @Jt/?RdJF8C:H#c˦ٻgzAO9vZyC:? |YgΥt|}@?; ! 4vpJ(421  4 ݗ4*<yÿ}yN=IiH|a>L Rb7byyz zzY\\]|Jm- Fu@@nJ&qR*,'O$6]ELb|830@a6>J8K*ƽ{ 4Ұp=U;Y_]8v\TK00"HJg Dgr9:hkkaR&hda~5(҃7xAUW0:gND1dU |>tv3:>JG*M\ml.o X a~f'173ϑ,,,PU=ZZؿ!=u+/#ɶd?+V,l7oƒS7cGؿ9K&ҥk~uZhim}::1fmmY Oq+$SqZ[Zq}خKԲ9˙fO=2ox4uE30׾U j(HJښٓ 1҅ E=D<}}R8kJ"Ĩ~mϧdhqm:s۵ѣ)$=}4lRD*$Mb7l,Ӣ^ze*2]lllJ{{+L?6 8: k P֩5\t-=y&ʔR$X(iulAσ@CF#8 01g'G c5_ŁJ C5Vp H$vJرkBIYLSA tz)1yЈbX/9|?"~|x%J[خIv܀VFZfb8AP t:w}67 .r2kklJ >ZZM2,>" 2:6)Le$Hp bjfs/cbψNTHAIJtpl* ۦZQթt>^јSuh4lXK'N;_ A8x `'2L &|Eo%a`Ra#\4 ?/w ->}9H3)L3:uM[[o!#2M5:\ IDATS.WY[]NJXS biQְՁa@Q%Hg܇4x.uI__ J "M^T*ťO/h4hmov.q`|z|>O"gthJN8H<&{>>zi$҉R imk}\#pA[[,/,a@,l&C ӟKisjB \$` J&qM߽An{\[5/\`hx'P1$9D?= )~67MOgΰijObܾKT H?ϋ/H{g5!L⭷o::Z/!n޼ͭT+}^ʑÓض2tX"x~6GEN8…/ʤxI^!K ˬP(F-FF=_#áG0>>,-h4S'?>~Kרʜ{)Lw}-N=AGg~pRsR tA=eZU>|C2;;2zLjeZ,ͳB^'LiY0??KgggN)R4_yy>t{wr\ߥrNöL׳ ãRimi88qqiҴhiɐ˦8xpmmwJWWgx\S._ yݿ1y&ha&X||ҩTH[,.cessx"y^+י~4MMKK^Jcc&fh D"XIV%tpz!T*^O"VJFBPU ,P}>?x\KCynw(oUQJΤq=C }:yq@M ޥc's#|]ffw 'R,ȵBU%'J*rLD&r$Y7Ds]v iJ[<|N*+ʕWٷx"FkGÃV~ $S ҙ$GLFh4J*gxF&߻csƅ_pTSM}5 চjjM5TSM5O_Mtׁ2&j 91҉la6'1lS˰<<ץSܼy)|?p=L:G^G)㺬(x )%rq1 ~4jQ1>>F<cdxeWcll7?K C,,#9()XE4ӧOEKN}Z =_@ mGcQLPJ'm<Di[iHY`XDR>ؼ0a;pIԸRF*J2ۺXzg|| ˊ ~ ױ!p^$OO335SSܽsGВ˒mR"KPV,ngsEK,%# 0~O@d% 흝MLۇzlmn##t~cy4?}9׿V$xLG 6wh*L>*)_Xɓ^~z{~zZ'g+z,l]g}nE,$FމqN:J,>bnv_opQ&gL=&JF) 9uwU*[[[,pa>xL%#L=F䃞.ͯS*Ϙ'c#=tvu¡ɵhii%r-VJ0:6 /T"JY_?"{s ?òm/V$ekC8i]r}Hx?U$II')$bJB#~#"v=dHI' ^0ӻO"=aWh춦jjjUߕJwe%R#D8|,iA TaI궃Kt}"a)Q_x(BIJc#r+O'ƲRJw:xuqleZ92^T@ |W78|l;XGIO/шqߩ!8bϩttvɭpfm Gxs)֪ {d2je:ڻ{&O*H|MÈ;ہ,qibz6}oAh~I|E| CO H !YITr}%.|b>Q*dj5ya7xv*"DH/YPkԩl#c?uSTu!bY2#Ã/zx/"T*U`}}]ş%]{ϟP&'O KfJcW¤=e| u|=2qA~׾2Mvv{M% ҆2Ţ#&Bv@R |O$vK"T()4@? T 1j\KSi 㲲BowD ?&<ږRUfhG pc# #\MiCH沸"`aab@<%ϣ JiZTeרi+Y h4F,bzvDɯhkmaR%jމWAH5d*Ptvuc X]Y'?)TOp\u]|ϣ^b>lnxS7ػ,[b[Lb{B=H`7M zc`["SL](Zlݠޡ~$qnݼ01Cu CbqaRo8 N}lk+2gCymJqK X,/^^ ˽t@' }`C(V:Lӊ D#lq@˟~z{DdRRi@`+jUPx끇]L}8?$qΜ9+_}64PT5G? ]t2>>_h㴵su_;nd pQ^+||9_\y| iuj5L.ko6sKC&JrsJtNQ\EHx٧v*V4<+kts)M@.Һި3=3T4u\? x^HЕB˗0o}|z7YQTM p]A #BKk+BL $I?~O&jLju FaVW kT koȀh¢T,w8CCǃxp!C5-nݼN^ԙ3rO:45j ,,.k߇:)|*/TŅUj:衿.^櫬.WG/\ۤ\C,fCXLI$bɧXQ*US4gLQRF* /SOfe Tj5rm N#Iik`m} ^'>\GRP `scfW(T(aL^D&~' BHCO B!)eQ􃂒RY!}(|P(^$+0BCV!D)KOJ;@I! XR?*) @Oi߫P͛DހT6ГÒ@Li"b@!m aB Ϲ/ASM5U3TSM5TSLЗM:,QTTj5,$: hcSԨT[u1LX@}V K: %X&JI\|ʣ` 44نFIҸٍؘ1?؉;;Zi4IIlbޠ LB(og^w~8'5_43b(_DEѨʬ̛s}^tJG"U U#Mw.YIb졩L193#mP9}$B(Th5m K%j .fCv)&&yO)egBqd~ 4Z nݽgzo8{\q2#lob| 4qҊLi5i:y<Ϡ$4h*3Êݎ\PVdFFtG58k0V@A H}HN`b`h#Պ)֤QZNf a,U2Tf(6]8XXXܾqfx>I+XyBw{{Ip=|>0@ny᥋=~b(s!f Ry401>х#|)5j?wxG \׳#kyo<-$ݫu vzIuZkǷGԚ6L1Ynxܹ~~r=qNgbrsp-9 *$dݪPȒb"p>㰸x?sϞfskJvE%9Q(CdIBĦ2Z\? A4-s&&fJV3Y__{v&Tf͙gΒfzIDm-?h!=r+!4qF+>FFL~F67V(!Azˀ?wzFAQ#$iF!2r/2^ IJy~㟰3o?>o޷f9?P!q%aҦسGJ|Cc5sS3S0 Gz$ib΃Bk;1;/o6hͣx1;{GヌR$WbυR.y9)ip 66!2N:ۿxנg=e|??De/qO&j5y:q222̕ˋΠTff4MF9y(fhڔ45,n5C]]hϫt5ňnҜ9{S'248HX! a@8HfgW?Jd'o?;t,Sܼ~jBann?}>]O,;loW99+11} ^3Q:Txy_?/xxWq}ʈ-}ڝ2=GZ!l:*otb@XG~qoy>*MXzV$##(zfUG1,1==.{q]$X~^y%vwN'P*:W_`vv|կَ$IRc`he<V8~C^rJ㸼\r)MS:4f`pO161n[X8~ !Ҩ');5>~Cv6HHF])|q2&q.i1W:L1P{H4KƫM:_FNJb8K BX6b0؈cijRk$A~6ɚ*EP`rzxܽs6.5,QoHaסpw(;4MuYXX ki;]e77j1}}}xOf&]m[,̼ZTxhLrm:s4]lL+#doר2eifmY T2QB8"1va5aL=?!#ڝI&ۭlb*$5IF T.Q'&|x S3T+5ִ-67Ϳurbv\) 3tCx LMMq|Sggapͭf Q,YXgxx?MZcn,ry8~lM&#:W IDAT_+KL}^^giR&mά $Ic:fӡ.js^?r-*;5:Q$BΈӶ{;R\V2qsn<.6-A{j <~ ׸p3h|t7mLsIFGxa.d`g8~%h\3&)yjsg7>{/R(c&t#_3GJiמ haJ`&H ޹?~)zV3'ODZc_T3 ÀfAw~#>c* LNMA%ktb\! slog^Aw- R8Iq7nq)jNM0027ٷo [wxVv˜y4*33ӜD툫WY~C_xoޛWݻ_y7uܛ7o_U_:vBIQt&YhQ>v )%Ф1EKh4hmraАf)Fv]f$iy\Zò Ӱ4Wa6%L+RHy @AFI>^~jHZR5&l9xIݿOtL$Sqvwq`UV1>:ʑsiBӄ-Fwܺf+BJvn#RԫUKjRj)A.O L¼B IT@+&)Cu]<7vIAw(٫ZdiRVM\ffv &&ZU.-S NS[\|? qJRv'jSƘM \Ee1~ޔ$iJ'&yܺuc Gp]G]rW ϑfHۄ,e{FP(bm}FI%8\#RDeŭZ G)KFRS%@  LX$`L2u"C|.^x}c|e>&Zz[wr,^[w8)FGxOp<$N)\͉Sm?% \*L̒/xiy@l5A$Txf6'L]==|oלfeڞ~g?K(B%UV&?&%{ H\u,b63ZQ'w}./ev I5&8KR6h6h$S 2q=66u]v~ .2T7~w5r9ѱ| Y86&;N.c ݊F+(J$Iʍ7(!7|=FGGr{4M AiVVָr?eIb__}4˨i]ǟSV o[|_>k,=|Hu$G/Q(cz.Q:3$KжA)Mޝܾ~Gg8$IF>ܽ{4cgw3F\HT`|>cJk2bhC_Tf%K 72šÇy啗X\\=Μ= gTFGqBҊbn6C}p\s rcD<2!y BNP.h?;C#~.؉tH;޽C$=FGuM*m"+ E # 2_z_,ڈR2g+IfRRv:!bcv]86kIpw@q_JVVxM("_,155j007>аAtkeia&9I;ٕ)z+tH Fwi"טX{h!l63L"~oez}(XjZP4Zc5{1,`ƍ꘽ۡlxde$|'ȅ#GqaR 6v'yO.]jsF :qL; Nh#+Jɲd/d`D>ȴ=,[p-9w;LN{٭%|%ݹO;?^7iVE>'HI !h:m͹sgGFg[23;wPT&/meӧX_]E)|ՕTkqLfw&1c#!ZHĢ5I144Os<+ЉbT:Mcv~A}flnordaj_?woDmtЬh7ۤQjḌ%3wh} 2 淾AZ%,EC%٭/:j6xP*PB2:2J4mW }RnB!qmB+x嫌 q9pV'+|*txLC20>_CGa x kIwEO#vZmT1<4[gk<ߧT*" ܸ~Ӝt:mNz JȜʠ{$IxsX]:;LNLp}$Cc3]^3=5eǹn׻*h1t}b)Oױk͞$3 EQ't+DŽ5h# gwA |o?tZB#<crU*NSoMϳP:%3ڭ!X,R,X8 Ý;HV RF qAH!h4(SZGadd7nsiw(QĩF]q](^Qt+kcn\]G ޷9xNulz%/C:L?p?G['q=aD=D!K#[#2P8;.\<ˏ~S[/_S|q,.`jj{Zi QIʃ8 rA}!%R ]>1+?W*_zE٠/8 wdy鉡Bx.3(@ט`;A:&)BǏr=A-ǎ}|gewyK0ac8"Bʚv4Yf>ˏIҌ| $7oܦj30X}N?oWYX8He8ZM8ag{0 X*q"}=s4MpB*\Hd\zv&i>.>o}+=wWoPqr~P.uޡU҉: sWws?,.Ta+k޼7ͯ6'Sp~tZuI$ }$eJo7A"LM x#C=yOrѬ10.XsjH`D,){Ec#It@CjQ5_Q]!+ [%ݿGЩ7ʤQ5!NJF|vM8K}гXw100H٦WF )K|5:r!sS.iM?C!_ lIA.\.j^ac׳hy2,!U1 J3"있F,믍!!SXDVپ u9q8gϞen Q12:VB{7heT``ҾR:H+ҊqPژN(7{4:! $Ix IDATy/Gf}c/%ﲺ>w0!&9s$p"ju"ڭB au]! ?S*9}'ɒM=_:NJ6oAu@SURp1]b;wS5BpQ QVh4tme&КvEˁHY[_ݗsl:ϜJR8iVZəgN? Nt3g2$jwXS|v\4(ΨTvCO;nGKK~4[~tXk (㘞d? c}<籵E!cxxĤm>JmOkqtIA@eŸrQ0$_sQN9i?!?7ަRs |5e9i)h#r2~(&}^}UN8u%r? 79u?%B5LT"aWELOOxun++4uls8V |ψPKEh4LLMsܻ}xr:QgRS,=zLTd||7Z븴;lwHF[7o!:y^2!( LNOf:/fyr>sҔUvv̙SEή!@o||)aGP@ݢZx&G֦2<67q]jjϷ04Of|8ZK4 u+s=~7|z Q_G>q`aaOH}x7%׮~_vߛo5' Hsw`Gl\:vuLںoLk\ϵ]\=֊WW8>ꮧ_[4;ƾ5(ˆH,̺"jYH|uU--c"\]Lg Pt$0y5 n/ K{ӛ7͟1zwI$H؄:AH>3I |ϳ*#h)Q*P³ifm9avN[R]Z(h{/H#pܩM,MTv=m> kJ!Ŏ4%KQL+K{q>\KJeӇZͦ]{8^jE'NZW*s)"G108D'jhp=b1G$G:G)AX_:n>nZ$20 [e;)a>YJԞ)]OLt/arFt2#$Ǵ[SeER.!%I"MRP(lmnnw@aذYfjKs]5{ Թ:81+wiݠVxIc\.-M\Bb9q]\P,( 2B$xSM|F2CK{pc̮c_S[rͳ<w!33H$CϊKF \g_Vyϋfidi=פ~3םjGlɏ_u>sjBJD6iP7Y8zu?/}zƁ~&f IS4HG=w畗_`zjm<]̙ܺy8>G ( wlygh`uurLX`eezATe_xw4 !M3+d4ku) ĝ$1,q܀7oG:qByONE;gUiFFxY~_䧦s@(}#8#_WxᕗhxҐeAh4Y㇏xw fLMMrQN<#xAHH4nWMt\x}380@`Js dg)P2m*U䂀FɿrsϞ!_,20G@Iw:t:-H  rfq8**@8tc+(Y]YCjk±™kMqPclljN)> /,Nggg[7od 7ne**HSR04Џ+%VG 0q, |<]%NbN=s8Ix#} ?F h:v}iJ@+I-FyxBpqm tkdg{S$Ks]LS$ԙSh4ZLk dumU217;4;tBޘDe #cif⭕"I3:Q|! 1!Z) Ѵ+;G gOP:r-u]i#P³]@g^5q \$Nx-i 8F\Nh ȴA9YA&8 Ot*+:qq IRΐyzӛ7Mozӛ7\. s=\!:1ZH&8`xG+I9% q 8xG-Ҋ:Tu0Gࡑ$qb-$_t A/g*D>1C8K0JZe ) 9S,ȄEԯu"N<@H!I"2Jɀ,U(Lˆ~ZCarv'<ܜO#MVOI#81v2ӏ=Bg YI.YLvwvVPf`>LfU Mh 7$gHEPB"R -GC3IoF7a]@UY}|s8'=Zpl2=2Ҽ{߽~ &aII apB eRۥa,s֦TkMq΅DkEOWY633whz1f~ :NiFP)~{W 3e, ">)V/ ٳu~Y\e67w(uAV'{odb!42_7M\`umf#D\؊I x(K[][e|>z{{78}<̧p!{EHGs_ a-ASTyw(S(ٞqDX#]ӊʘD&/UvƟ~f?O $uJ ^gT­[3FČbL$1R xZM@R*d8@G,,0o:5栝6A!2=ҋD-Z-<ǿW͵M>…ri]"FrG'ˤYo98NI2NN!'{58K<7p2k[L9_ K||?bkc?'N޿ORZ_ghxI^^ϙ2hy~_H|smÏ9xB226JVE+M\mGYG%Hӄ>Ghkx|1?{'|_"<ο;x8y*STU\ΙY\\۶==G+ ٗKd*%M3vj-ʥB7R;!QTYY\ūOYgnn5T9A flOdee/|s8KWy KK  -p#4-i"ChRz6Iߏ)I6S,ٿ?33ܸquy9q| G*alq:"=<%K3o0<6AϓvtIr <}_=3POw'ç $Is{1D#0gaLXbaa}34<̽8r(;3:` oMbn[y)N,dck8觐' DIB_*M(34{ 4U6~Lm6MQlCJ1'hvӿ jDy7fvShkjʊRฎI+#*s%IJ\l/ h!U<됩,2(&{t$}b6wɎcntH6yEo*oJ#t=LXD.4YLCf8U; b:L!_ _,XIJKjw/ mS+apZ[D5?^J{L8Ov[dJ5KZܑroW^q\䩦S,븞KF&UW0};:PJS٩133ë:+k>F\ #vjU°$K,/BZ:ISOPUךtut]SH9瘄=&90JB,//s}z 4M4jz IL.(KdYJ.WJ1U 8 줻pbeyjeL% 9ꍆӵ ݴZ-N> /D܁$&z*88yGx^.]gmmA>=Qkۓ2E[y߇ 1[=J%ʏ^%BN9Iq?y-06ֶM米S# 9R<5I |?`ffwF4|1u0YG!iP(MR8>&I Ø%Of}cOfk{ ubhx( IA::;¤EFfI\B1OT& H)xˑXkW9t N%_*s(db>zz{LxR5XYYcaa $677˜7o3;;OWO'y{Ӟ_oQLr &IHS̭ M@ q281]FA{Ӟi iO{ӞO[͘_W~/z8@@&0ն#1Nl5ı,I=|.{ݬ;;;Ԫ;4#6h, 2TX$̆Ck|Z#q4y`\߬ۅYfijR;ұ=Uل 5( BG6"#4K'x;IUǩ$2-EFϕxǩI_ZbbN-=ٮ6"2(wPfnnS?#$iٟϤJ솷IknOxu]e伀l1dr&!lQa!v{o-U9hDL~> \kMX0BcWDp%(\(`ϡSۜ}{B.Gqi6}ųB8ˈi&)Qt#9BZ|fd*๎g rq]=Ӄ lV gRfzvJo_?J)#bnյʝX +MX%"+ڟq [+lDg|4a_\DI"?ޏ7=C!{ÄZѓ9<\\ ДrUJ"= |/HI/8th?cޠ칈0}qjgAp=^{=z/}efg2!)p/_Yso.VH>Gߑ>y|SOg9tT+)]==fii3N}ӕl2JeUfFA'8|oI]L!loB>B_>sy9*SYL'"GGAHSgsm(J#]׾}$RPٮo[ ÈR(PYF䈢i\ }ٻ9IcekBsH27Fy?#|PMfC\ƵLLP*%I"V4uxȾQ GGG\gkk8,/$S-q ܺM>_D^jr÷g=V=t#ZP{¨F<<,p1K>Ǧ|{]柘/w+$J{!BhC+*UJ0g\`R^ϰB:.E7 ̢8Ɍc5;B '(;bC T)2Mp-FFkm=jp{Ӟ=3?/F F0@ \7>-laZJQ"YQ=a^Vqm/Ĥ` bmjP )XM ӥiOMu\Rv)Dh-Ʈ_w+RHfM#SK$6)+|+USO;0pqdrʈyE~ܹy2|$kIPlsh<,j?$#N}8uŇ\`s{'zL$ [kCWu{Yiz*HK}E\p _BekÔyuwef>"]\pM]aj q,Ҩ#4p]r*!NpǷ[Ӟz"(:>5ORhp\vH,jĮ;RZAXZZ¸L|N]v7 "샷 q\+Duv=`_IKbDJ;\L/0E'W r!  IW^Q$W((xG(|1hLB:VB lomҨchx(LM@Rr{FfK|~$IB! (B \n߫DvG$54RFiXn?5 `kwdMq\,q4F8U{ ?s]MgWo8O>0r VLyZ <,lP*c|bOp Mc6mr7Dsm CiM>ܻw4:0AGYVHrA\M\!|6)Lsxjg'sDq/=|D}jE>Hš9Q::ʌ}yZӄrdc].]aey G |2Z[c% J!h48al왦iFi,#R0f~aV/KX@)C:ؽ~ /75<_|OJt:K9W^R|yߢҔL)E딨4ug^έiVWVq]^G9p+\pzN#lO2w_J~,8D\dss\Ш7{lPpġCl68}qq5jmqTj j S&'K_LjxV,X[ڵLO)68{#@iSﱸNRr<ە*_CQPCiD*Q&Q'u]#*M$x9I'j+-u1[e{i횮_ cr7YjDaQD`_7IR(2|!Gw\]k.|14iQT)tvu1`[  r䃌Hm*s(\*<|ϣ\(Py-n\ؾ} eJ<| I;b|.05G"0~{mW))py|Y繒w\ߠX.Mv6~Wb7'Ed*Ioٽ$sl=Itٔ;fk7XiVĕW$S6IVEԊH^4 [lP.Uk98Iު#i>"ꫯs {iOdYI*磲Lk ԩQb~g5Rc  3e:ɵP*/}b ?p}n^ ;3I8p\ʕktwwqxutF2K PxKwW9(k⧯CܟK6ɗ < Կ y(=_)LS*/S?Gqx0g80yQ300@wnM|tjmP4ϞPlmlmoqc#ܺy8 9vl.MR5@3]4J8OLG 9v;bU~>^sn8swnMs{FNgg'J%Tku1=}r";*执gjnUFRg?bV$i>G&F!ۿms9#ot;, ߜFe)(B ) 澙d\p4i1ulprMܺK.#|q ׮bmu7^EJ>ȣ=‰084K׸{g%1R! @hnݺ:=݌P1~4M~$qܽyΞ>O|)::7;˝;dZiZHaC#f@Bp\Sf|btar&1==][.Z#®iTyVk4-|ߣxGJ(=6+hQ*BkyIB4<V͍ &O]&L{IfZ)vwenn)\$a|> nzzҔAFy&ϓ%67֙[Co`vlޞכpp=+4r9$i`E9ºT+Y8^ٴ08(m̸u* QJ[lY(;6dq]+$.i. @خg㢳 mm"<iy Z G;{2R/M_BvB!$=2"҈3T&p\J~E@oO{Ӟ=iO{ӞC3ˬ)7R8Nl:V]W8V$MȲ* 6\C ЙBH9¤42QE\Wd~T)cnR8S)#,hϮML:XkfU"Q `I48MiM2UX26_Hfxs4M csssh>lhT\_ǕYYY^fj0'Ow$W.]c4☏.\#CS*w*>>H%2+ ߤX{4rEjL+1}F 2ݟ&l8BAeJ)qiԪ,1{`'ч0=s8#%Z L>wgX_ߠgijTҭihܯDa|G?=-Uk*$vC{J I>kjM=HNG 58w}~0}~KƛԢ΀|HbqaVAItf k&~`z03mL8F‘ /7[TkuV  y**ۂ>O੣!xwp/SBcǸ|"_Q"I W>?;ttPX`k}Yo{iK/~Zdf.+F#%j pv(:=BFܺ1X4"ZE1}Ӟ8p{Ӟ=Nf̯B\8JLfR`|,Cq C#~9ƘE zäIr>B>_GJR+Ђ~] ʦBM^RD4aRdA|=' l_bh4tH uHm¤iW=$4H@ CJYcpppUL|<|MZqm=} tYVo"KGgVx !פDqd)+,!NR|TFBh5I4L5 =l=c>=m4 Epk+bmѻ_ @J f)4 G۔l brei[7nV ZjyiezMEb4QߏX\Z$b}Ϛ 2=MR[d`GFS'q%*MIӔ4IxV8 #Oh4Cia~&nufT9暎.N>IWOY+GgIOj{LUcui%JϙRsBL!6GFNba,F؊觫Źy\it2\ߤZo`hx9|E+VY/`mu?7dmm/{dYvg}{M*{ n0 IEjzTPO| bh(@40F{請˻Joul}2{ q[uVge{뷾ŕ7 R<#rFEN]X'9~?Ǖ˗YsCʦ4OeZ[2jJW.^FKe~<#U}r-e~~ש$!V&ZZTbOL jF}U9BJN:yOA61~kw/^˃<7M[al|?VĴVuHI(5l /[VWno<}W(["`/G;믷0gU~˜=};˼4 j:cPGlѨO9th?[7@rFkׯno8J 1;;;oyT+HēOEg:9K bހobMΗ­[V-Fo^p^y%\L||"ׯdmu ZFfll0 a;aGX\*."X[Gp v;[ǾDaDl16ؤMYY[ӣ315ٿgΥѹE!Btb wfmmFQYZ2=9 z￟>|e6Zĕ ~ocO6)jzreN;Bx"/к+~@J?hvUc|lAG•WUPCީ!Ԯi\UJT (QAKϢH EPr7XOBKxѨ0;Qt#s M6800^ekPv%ZARKs#̎QUR r Q(i$jE(P %G2eV;܂HݳE jA_+RRaJsJҬCD(7@@;-,%ښvF5]١l|U(GvmTU&rDi|(,^w ̶Nzיc:/]ܰRDs(RJbVo"/P??{qLÉA095$ښLudy(gΞRn}! n$~9h402Ѷ8'Vh[bu2H =[ϛi2]&{Y,OS Y?쀣063nǪPuiOJ]+!@asYo1e4[pg$_ɿ!r(ʕ\|q?ϕ7I'NVOl2?ʾi.C"-*Ikֱp~ӟ|oJpt;*qBz-"TeT+1`ˋˌ490yShM  cAP#RJ(D"q2eѤffvwlmjB1pUܺh{hmnRkY gjIhGCޠ`.,'cFFFgtl?^`' ޷y'low}ϓO?G17?˾}l<̓dƵk`,HF1 N9Kk{`*:׮bc}=8Zmβ"zs,ݻG DI&>٦d%VEyQA^~8ll11:ݻ{[5iQX\\Б#ܽīvQ^ͫ<}eײ"4Hּ,]՛\x,͸.|!>j 8" Ct,e}KKKlȲ!< cc#,寘:Z5Ν;Da;wy񅗸~:QT]{!g9yn߼^3Os1lm?:ko}HVlmwȲVJpoiNgNGaI^dnX>d FGqNZ; J͍ emm@3Օux=ZLNMb CLO055Eeqen^KX^ZN ͸rmAS(XZC G*%Ŀi[N(o~q z|_NPU(GfƫLըVcG!N@bnF[%x9GLW'%kc)90N [=ӺvR"\S%U fD%-$8:e+ѻeΒ;pзJ2'1h#/'ʿ0C 5PC 5PC 5PC (r%(Ao'|쒟ƿ5?,DAEt$NHE)EGdt&pqiA7c- Jf@ |/v(p6>y d6$W95%x7{'Yӝ( ?۝KYTA2; oHkqF`) oswx/}n߸[[lm8s_LƕW)#5*9&g##B)P}vDԪ5:qЧwUߧ lit:DE٫@e*\|ި/ӫJL~P`Y" }=‰sJi) *?gNeg)9 AGkpӋ13=g8rXASYCnc).Ui[Va!+ 6e: >K;ѥBʮY+| kfXKroe<@W(ŧDž(uM)+$ATHT"m~'?&?ϸwoU|_s<E?ϯݸ^`GL>Mh)5 @o.|k/~<#{=aA KHuS `:8u;=ܾ}|; XZy._)B,.|ʑ#ozkx+_4b*Iq/XAQ#>o_?b}s)N?FR_zjIqSO=8q(QSQGa$6 OX{sIʅ>fnns0 NѱqQ 6cc$XcQ{+Me4G }ǟ|c&nm",0>6JDID< w_QɓxwI* VC? b(*TyJS8Ca5|r B/Bh6GU+\vUf'yFE`-R̴p=?ڛ22ġ6r.Qr<33DQsƍo|SO195IEՕ3#M-VgBVih? |BVȾysO{櫯SOPV0FcPTGC(8a8 7q&U(tANV%cn\k/NM,\5N=Cg9jF,ͨVpؿ?u%,(@B_5)*t><25A܏W^zZ"lr!VVxwqp`~G 'E ^{Wq%dYk|*8JD(($sJ\mYx4Ijm5EFd&N왶Ab1J9ډ](;MiZ>,Np?S]deUl4 J~_Z\|}Fu,f*q XY^fggf&Dkw391Ivx#ǎ;o "dygX&v[m hY◽A:1 IDATWLӖ}x[ٿ,A)1@+ (o~/eDF( MeoiLڂ3Eyi`DQSX[$q֐ѱzZS6pM??[YV$ fu4ATDQ8 *pE%7_"vj%!5͑t)GM$TDq쯵ܧ~zAZ-رHLMNr 66}{A}1 9t(4:tPd9oߦ鳳Ç ԹS췞 |!ȋ,$ /^A9'+'THyC(xKoٲS?ۗ^{-N;K4ͽ]uJBGZ! DIB$wȱdi%FygB 0ktvOZkIϦncB9^j3??Nw[GZ!MS/c?O $T" @1Hs18u/| {45:k{˗2o@*In`q=whv˗RhLN$;jWr%1? SܾSXRsk8t =^z>ZE*q?KP 0ENFE\t߇T@,__")BO"ȲCGo}a_96IFyNEa@"LnJ8ҽUrHc#\x5;GXQ2@7^}/~| <2H5/~X?◿-tFN< Fͭ-0=;E(Ga%Ib9wzNǯ~".Y};imШVBp)>);N8YKXc`MPc}mc$I$yGxPB0lzJ~Be),2ni:IlѨ7!kmsNEIv6vJc+LNN'bBJI@ANɱcȋ4% j*J7nfh6u2##dYέ[wrd!BEE,!ֆޠGPi Mv;d&ךA>@I*`0;Q̱_ԇjTN𓳐kC51DdmMfbFY+g; [w:\9@XW0ZiԫX*7(UBA; Qb0` c ڱQ(A#E HIk8  zB@P fdQfF5hqhM#} ଥ3l}5GE:)WU[ g!*H)%~ljjjQR^ݮ1p@H5A5c,ƃvg6T0ڔ8ٲ?VR0{]kG&>EY e7bF㉃P<Ӂ*SQE_WJ)π)&f_{S Ƃ0w )D8P(lEQ,Y k!׌NX+쟝Yy6nIv'dHI-q`s!Eb s4&IQLSIR+k.7ehD ,Rck:= I'0( ( &g1JHڝyN{RYeVkIwg5&&²ѦVR&0>1N!"~j“{,ue߰,R-IfBd ½n(|½*;v$ 8+Ȳ#Tv˚ywʾ`Ig<-MP,B繢Rm('^XkzsH!J"\|(hml3=7ͩǹ{gvM@! ^! RqoG;ܾ~7|~|tW Ivt8u /] >87o-!Nj2V*PFY48ĸ|!Z[-QoWZ6yh6[ju&8p`ADZF$?1OlouzjU:;;ԚlmT+6;v=IZ -S8q`5pqH5s<؃&Fݧ%ˮh(X۽>#ۘRGW\'3O9Z׷3d?U}m]QdyіigyƀԚM${,q9q%AݛYY^k4Gj$0 zx;Q$8,vQ1&"$aRGG{廅 դW5,7ǟO?X()w_ol$Rr!Μ:AC9E*zQho?t F6_ꗙ?>/Ut}j\yAKhCqaVWY]\avf lmo0D%Y_F0x'ۇ֚0Qݡ К?`iqI4Eh4eDI'@Y>t\4r>EGFHĉ ?S>qq{]{1ccdY=>3LLMpg|˯uѲ@HE%)-|‹ĕ 3f0MfFأ|[ = +lmph9'C❻DaZg (8qY**R fftT+1XH-՗^!_!gΝ%"Ǻz&a"$E`io~;yƁ9.|!G!~oFw{ɉ1*"vVwhS0559{4F(& pJa9295vk Kl|Lk(3`m:Ǐ9qv^XW鰲4 ZI\룱gYd{hs 6!|CJo@8 ;W&(LQ0 0d(BE9 N `.{geໂqۏ3{Xx(|=B8$iq$A2h!JDEyYXZ)[8~A?JaiT+(͔VgaH$iJk,Õƹ־)B ["%@kg,,>0+hpV{cF(Ue9W"1j]PhopqBZm0]ROo~CgԪK(ҜeN9^oH)A(5ՄoRb:ȷ \tuz~e:tQV9 oy)^ Sv!Vת5D$|?^ E߱j 1^jjϞ{{ǎv ETӄ"*0ϪB*( |׍Eg,[fff</pUg۷]zW »SO=A$7eey?DVV9pYi'(wڌ2:C#~wo2ڨioiqfq+7 vcv{vg_{ѱIvǾi41XgB!W^RI>x PLA(BO*_k#?}ca80_9yI~(gN{^{n׿{\|grTlˎiKLGAȟ|<}|+{}%j=3X,,qB{7DLMN~GwY XpJIL4#ܼ6"rcW9B(B(cuEXKΜ>˭۷)&0hSR8I"1[7n`~\|dkE{ce KZ 9G:*1qްSR`$ׯ^捛1AŤYA^h,.27?ɳiwh[4u?ͫ7yw)2͕703359I"EѬUC),˩T+8w :'Ngmuk׮nTHLL!KT+5vvZ8y;wYXXE`9>-j iu( -YYFQ&$M*a>9Gt^Mq|[ꥫ oC 5Th_c"xHJ2H5AN4!㠡ʉI sXFbK+*Xta(BrJ C*%RlRP B|Y)ᣝ冮GJ]Zm K)ڢU eфAzoKu>XeI{ ys96*@.|QLx>U[nHL{ #(ߣnT!Jzs&JD>1>16J½5ڬmfk6w׸q}U>1Nw/'1Af$QThI/eݎ{B.n|50s'nbӈ ?{@U"bJ?Oli(zOLJ{v!@=LAFo;GQfV|'DA@io`0?HݷZ6(ZfXHQDVeue{KK12O=$oCq>řWe=TܾuC`uuFk# ͯ[w8wy>H@A&WƽR?{E9NɓY`fvNlNy9zGbeuǎr1{ >VBIVT∫oLqA+R;4 gw8x3o]|.S֢]-VO%Ξ2fYbKw9 ݕJ >qRdn\"6 a-}:ךZ66?Pcaddru*I(JrA;i훷YvV%Y&뫫Dʵ&#t Ra9ܢv:u$+;gPa$מT{ѱ >Aż< ~/i\UR0{ }JIƳ?y3gOs N?G9}4gϞS:})ګl2yEskU9і0xX_#tQF3s{st1\w.Q&,-QpW_z+.!% O*It7ǵY[Y&K:}VMT>1>>>v ty;y!H .~z,1֧I*>efg/g" )l&҉=N29Lj4+`#W$&'IQ81yΠ"t( $*(](Xg,Fkh`"%Z9(:A g qc& Y fssC`a0iFmr# Te9@)Keڒ~,)EIɜAjpT!zjjoCBu-,Y[0|A Ǥ*lc,BX0.b!5 ]ho׼I>*K*鄝zNZ8>M9t(o>Li܋N8 ow\SK IDAT>/,%TcxSdYٳgm}Jޟ}{GaɉX ů-Oysá6/~ʋ/cbjA "v|:v( AH|٥˒ 8:OӺDJ7 Ԛ ²)s}ڥpBvkTR._ƮŧO7_SH%ӂk+ ",0g7_G.ykÇ.f/`nng8^o}xatd]JQV˜o~QQH٤hϽ-."i20Pͺ+\ś ,0<2ag-Fzzycƍ[mیB'ݿzͅF᭷ߠ'dyǎK)˒,1kRq+L$!4]¯h ƛ9O+%sđ1uسoo1#z"/]g&33Br÷9\lnly9}4c|;%cc=Ǎwh6h[է5EJ9r=o/w}jFEVMiE!@Y@$q!-q8OY"jkE8x?ia!0T᝷v(,gpdfIL7RV"wn{g G)p$pI%lk/t]y]Hz$уӌ2Lw0r}FǏ9@wAH66._bnnG0:HtTUGN4yQn67Zȇ?13S ":6Hŭ7yͷ(@IΨd :[[ !Bf.zԪƆ.[[[.Co/ssܽwTp lmo3>68ct]۳vnC"/Tm8h*JM(Ξgk: Z[DqM( .R{0_9 X780zkYVv >u%Y[Yls1,q:.VR6Q%&s6שRjB.]W^=þ}v39^7ߥhllc_!=h$R9KqX_裠F(0ʞ!bEG )$nJD!yS׉+YtT*[$ӚJ^ueaŽ?䐊.힟K:_8{4wRk'oaRF#UՖ$lhf9&'&)#>tH3@vFol1?)C]Y(f>Y׃63`+pR>jg&Pw#|J8|uDžuۯhM^xYxϾsyn[ܶWJ.J8˺q۸K<ȣG&Tj4hk"r669~B4pk%%F'ħ\,KFB!{"k{kZ5c z],.>cdl%ZIkjU5F 7c\uti'9Rw|67žW_7>6Zaʒo};8dkcBVۧ sA>޻#$ "CJ|j+ß5k_>wHN\/u}:M|`o)?]*I,ǎxC'_!B*,hM(J!$v& ZW6PY&1GU$qLT8p 6qL>0IJpԋ׃Uk9͛7 KgN;]Kf[gR>&wMp!>D£y67]EB$*㓓 &&uyEIȳ(Iسw^ SR%QgE]罁4y&Bܺ=qv;(],+k,:~=.FuN %v=Q KRύR ) )̫ +<re:Gp֚={GnubttɩI À"/hZEXK% (%$Hab$Ga B ^D%h@v:Xch68u)I388HRqnnm#B)oA\rM,cbbKwGCCyFkJ캒[#dh9wS>{gy3s~w*X^Zb}u[<"ۍ\epj-Y%iviw!MIR9kiA$Akt7Y Z YCI7eoߛ7o -H\?P)ʭg+1b@Ir<͵4JHXU)Rޥ8BWx.p]s{4Kf9Y^P&$sLwp V0 G{I9,VaA͡KI1tRU<;$Bk88 PwV[K z+P%=<%QDJ*rؙٙ_wfgvfgvfg~gG嘟W򗾂m E c [cRIp8Y(ٺx!BNDx4.cOy'&C6ݳ1?i>1#{ .VkUݼ%80&/ro֖^wOBB7͐""K8 mx02ZC_`nnkbvCǨ0byt5O'y̳5a 퓣,| AxQV'$[㠟&}Q+z"pZG޽ $|mT6ްN7X 5yQ' R?Wǹ!y''ͭM*3;=ã4Zm[-}7+ܹw /Pv oy  {ufBKY_]ڕklm5x2<4̥ XZ^! bnF$q)jʭm叽͍M>0\rMʢ`phCG16>sg!B$dc}T* AۛF00616uoN0~Կ.9") A iJQ36xwBy!dmEq_BjPUyπP3=;~7g{>_}U%%.|t͍M,/v=7xTxѝi;nsóI(N('卣ZK3ydia_MpA^ʝ[y7n˂(V*ho}s.1;7ß?}@fj.n6-$/Crm?bk{(Pmff昝giDqPT R ^ RQDQE^ ,4Q^MզZQ*(uAQT5$as.K*  ٠ZO55EYhGFz;^~e|M؞i*UȲ }繱oѸjwv Tk5FFw'#LD1=7 C$͛dݎ;g!h4 3<<-,2=3F-5Z-VWy W>nk8TT]s6+:r,C(`xlw"ܾukJ$) Z,gvn %kR7X N# :i9ssgoO(4$NZZNJln j cݻzٙI1oCv362@קr%fZPBh"0R0><Sc đDݳ##D3zeIac}4X[嫌L…O(3޽vԘnmnRy)K*Մ(ilo%J / F ZҬOBtit߭wfgv_ d.Z i=28Uq}Ay\yiH߰"a A^ZFRE.K*C!vA PVY8ЉQH\~PO`Y^`|1kl%yS3c-eQg9ie^.q\(;^i)ÿs (@I0 !PbA[@x澵fvfgvrvٙٙٙ_9C6'(% .K5 (r. FԾHx$R'h ]B3iK:$]R?^P®WT ;\mQ^86eZwV*Q.{XĽ| E^7=aIY[%a @> xaUtҜ3/bld<͈Ðz}"d]SIc1N{f!]mODI]'"D/%~Vabt%NXw)oZoo>Ddq!Ea|VFŤiDB,nFE>E8OTc]H*ghlo2S[.ivQ{xTFML 0n;!8GsbPե nv׫Xkt<|Jšlm7?K:& +[L횠V{=. T@k͛Tk욚f3T+ @:<c!v:X,V+$C ܾyIv/|gLMb=LLnwrҭg0wg^}ưE^{wkz>pT뗼G ! (n7ZkliA@?u Ȋ@Iob޹ٍezjZ-A urPkw%;qje&'F/:ǎu^į%?o icSػwN:$=aHKkEt5š#,,p}5_'"6Bn`kk H,//^K0KnnmkfٹZW^=ß`C,ƛa\-;xp!KK"fKElnlPIjq.9!#C"A8U:Rm*:j ^ϼ<]|J^,5/=*vc'Z\x3 yQ5T9,$I4xVn$1aTBCf7oA(ԪML2X@t"Yuz_0dH)Y]]LMO$NP -ժj qBX y% wmiƇ?!YΔ$R<^X `2fϑgFSvzc5'OБC,/ CClo7h4~G9԰8 ϖQ9Vnǎ091k ssLQ0Z38XZA adуA CJ*aH%qYbyyҔ Plm6u>ѣG<^|ʃ{yͷmu( SO1<<½{((O~;y beeӧOFeqK/DԎ0 S1@=o#_oV)$]ɿd޽g0rʞ`\]6wnRSOH;$~۵G(Ty@isew9zn"sIյ5tm6)ܑtIiʳe<'C gx5.bTT* $ͺw?oWxB( ,8z8Ϝhlo I288HOkxSn߾$tC5nwXx+v ˆ8ݞ2j OR%&'bss(]DQw!N#``pCc 4<}14Zm sܟ,4^/S)*DQh߯J/̓$3(cZXYZv*nv={Q`]5~I533Syx!.Kmm4ۍ&ۛ ՄIqL$mҴ@JA^ddS eYp ڝ'/RI^hQk'ER20PcPPE!g^{]jdͻa/E^xknreZYQr=VVEDa3aey OjGOy>= vB!JYڥІn7m]H.rw/eIek1c :[*P` f%F>@MIh4iҲoa 33qw-iFs!;3;_]>]G ` (y0;}%~ԧ~-\["J+c8A1ANja0q"( :LȒ]t[-i}Ԓ6nZI3YA^Rd}L5pc4,0B ZB<`>.F)WaG@0D A'7u Ke4m@A@n%\(Phk#{ώ3;3;3;?;/o|wPB9̝1rK-`MOA ~KjJS$ǘkFkrP'y'jR:"TҸ9m ²UJ*J/pIѠ$q/-veq9+>B:-i=Zxrc+R:CL+/ R*)H8㶐ٷ=tNj!9t`O?bei_{4͸{>._&[EiTkDm}Zn(,PAHŔyQo( )(0 U>}#=J/zg{ 3J&]{e()KVvLMO3{hvccs) c48" @xuR㓵aq'g:vŧxS'!aqM.]‹/ahN'A3zu&5977@p%ьqgLEugZIzia]cQa%PB`v)zZU2|/*Jn@,4-F!a4~7@5attƓ'<}5GF]Ggz:}y9>'/ 'UV׸~XX^Z&+r.^LXc}GOQV%fi.\(wYA٢lon{gn.qJX39j5! zz^% #23;)x_H,r,ޡjAffxtZ8I8y4QT L\P Ck%/4x;5L/=kz8pIղoB XZ?O.;opu [OvnFf|իt;>wx)v<*K-T+,/l4Y|W3<4#y3$Ug6x7;gM(@K7fN@}3QHV]&rR ntJq-,,Kl)5di1N0f|ϲ{>>"AmNQt qpu@N#GadtZe zGγ0Gr!N8\wD]߿_}Zc"<`S݌'Kd20:0nNݡhɲw?a> |*:t'y)) yYEkҥ~Ry:H4-AHr.5n pF`9QSʧoa{!*tSF'xX@Q%y@-a$3s b}}n`<7ߤ djn7Q=%\K _j[ױ8|%rJYzK˺gwik>ߎ=WHZ ҭ ';AREܷE5KY?IT*>B7cn4tӔIj4>ruj=ۢ|C`a~`޳={f* f8dii'OP:1ٹNx| /Ib1e (Mu"̴.OtkϿ B gU70 "^ؿ# B=$ Bq~>SZ;9gn^Acswy -QdY8vJde}M:'NOǒIVyWh:4}.J{_綘u02t[R0]jFGի$qJ H℁Q8®NYm}zOJ%}"yWՌvϱ_]!??^v~2TDOo h]0k?1) ܉c( '&I{(Ox_ ^ϛo߿OD|r@96/PPSΛ-pyN(N]! ȳ'N ]d%%n3gԙӴZ]VWÀ$q #/ya1hSRיu]`%B_FMY9vOXYYs;GQDNB>R)ݔ' x"7^cd|qʲtA:yɥK<2?xAwXxK%L?Qlfymv066R{$ y^].Ȉ+Hڙ\gx/oxava5R353eQ2==RKKG^<Ξ}h6vZeI<_N@,/fm}Ǐ8FkZdѢZbas0;%/r~!Wjp]&ri8͋/{wvى;Q+% 2",Ռ%HyKjQ79$%"ϨICU2%5 *  09>0CC5T18vI[&jՄzԮQ*ID^jJ\ZTRTV* k V$AfV{('8]ls%’%nA+-RC+wDk$*-EtPs@ %PBu  ,5[0hw;3xgvfgvfgvWv_;(%xgJzAhѾUXBtNr6h=4!{@J߳?R/d} ƺ$EҔ5zESֽHg[aK(%#HKH D"+췕uTR:T{KmEQ208o,<]\ FF~=Zl*=PdddtI(, C(-EI2 õ" u(_8( t̾-@viD-&5.ʞN kB8TvYADm,EQF M6~rcǎ`!rVWVEvi?)1F35=#G8}4EslQk֡wk*<[|Yqqh:lՊKe{=cZvxtkWo066FDq. &γg"ֺmfOXsP'eYS=ap +?Ho|6RX 'O y]q3>:ƋǏ&"p$NFɈ'S'ͶCОn\l{iwy6VױD9Z{4?QH|nm%E^x3 AGQ'a9r(ccsGGCJO)zB^u<[wȊ_E=LVwz>'uz}  `(TNh~rbkߤh0>>F:m>Ν <5DW û׏P34|dŬAĭ[8%Q%\)̛$Nf:;{͂NKil{w9liϩIaH.S,79|A89-<ϧhgp."T.u#m<'ɢn39^u]Vz~G6Zu>x pi^ɫ4arlW;FlVŊ\>C6A!#: H1>%k)QaTgFF5QjT5?ioTP2xcBJ$bA"DZq&I[wn QJ%d)[4u tn)J35VEMsMqJi L1N\<yΰGǒ~#=>cf,$q%QS(X\\Dʼn$X;{|U\FUgϝ52 b0)ܻK)ajqX\Xdqqxe "vvEG ~4:I#OOMH!wSR;uӧO,AաXsXZ^fg߼c 60#q])c361? 3tml!mZKK Ex$$qdKĉC`hFlU*ͱ"Iuc qJM#J3ma'ЦֵLlq%Ib2%e^KSM30 -mV&u Jst ?z04B6k$qL؎ LLM0; GWXI8Kn( ]"uasgaekݧ^oK.S ͚+ǕWxu666h4LNM簄ϓgyK\v!l. Z+شPu7pَA L*pk1X$IYl4-J"BKCK_-,p5l|+x~~z ܾ8?OY}Jt9yVͭ=ww_&޷=7H} <Wcrj|.ڃ5r Ah3!Zq`cigAc},Kx:<3pD, =hlАfhjl259Iٳ//xg)OaJ|G2=4q +I8ein*g.IgCwww^%?GT|hLxbAlͭ~]m\_}0N2cJiMaZ>q|eq8ұQXcn^$B:$(<,l{*s3yV#旖xgL6wVG+ ^ !l{y/-U.B@T2ZL8%,67l]˔*<ԓ>:p>NEk̓5N9YUl4<}ˆFa<8{ϜIm $dynݼC{z#˔Jen fPxghA7m dױolf)כZ>#8J)OnF-,PTmre ۛTܻsӦjr?-KX(`br|U&y9SSlo ]8! f)W٣z:jG!XR|:%$XarGn%fEf&h4AHӣ^3yIL%:]zӕYߥXtL{/ːq q,gR!x)K>̂J-4!;R)g=Ʋ30th=(0Qpfm=8!-K|eY:񌮨I[m\NQ"Ҍ[⛅JHP&XH1<')qh3E$ƕ1(!-\"x&2&ˆ(-m b٥E%XX`i)l(>$g{ 6'qls[ba7͑$a>:M6JDQL-q#HX,-ڠ4Ea2"1q aNk5 K[ƹIkMHRX$ %#RJce~f}fU P.Q:!+[-lǦP8.j .),RԸ5tN !qB[Y_ݷߡk.X:r8ЊJeg}#Gu|u\̝`\u\z> Xk׹w\&|b AmcgHfjR\Ѭ7pwsowln2>>n &N!͚q$ʌO-!ͅ'.p9yG.r$Qom%rY y饯slnl"m?)38CzK9{5~ßG!3|㥯NE09a-8@w=(DQ刃F/jآjlz.ˋ?yvDŸMLETHsSH}]w+ͱV8yJ(2CWas)zl09"Q6Hkظq{јu)e92 *x|x p`uP$,..P( X 4{{Dn6&Q7Zr12Zu}<"#Ο5kQJ8gsZ->\|eog&*Жd\\*!ZGϟes};oaaSH~m|/20Oդ hl9vlomSgN-, D4Y|3k q gkk U D%QJ&jUR'=oHۦVh46b>Cnnq&c.~rqj5j!`s{5|a *ٌo256hvZMz2tӐe2s.2":Qie98!nB^?''c}}l9M Qu""z>uz.ykܼuWzݬCi(b\njNM&ٶI0̻W$m 4*M;+\JBu6 A+J]*Mi6MOh~!]bdk`|ė{ŨF5VQjTը~k$6 ҷ5JiĭD#Z&1e6lr\T4VXUTFb6bIg`V*ZY m$A)RaV`>WO:ilv L@ӗdK) QsRKLR')6\b OѕFxJ0pXDm0 )b׶i;CdXLq=m c{>o&A?djrŒë6i&hDiv_j5v|(^r9X! mhS<<<#O133C>gfr$Wȵ+?QoRT^;G8U|\|\ev~D%?+8r+uzG6S.峀6h$ȗHAȞ{`quܹCbfY]]|é'm2G?+ñ%ΟC IV|#/6Z?/ soIF؞K.ߏKE*ce4.NsY8:ަX*IuϞkN\PLJc:O ݷ&LRBc8q:Q Sg|4S(J$)ia~~5MF@&'LMM䲟5`NLllnS(ɏDW?X#˙u,bHh(BrmL&O\6"I4_N_!Sm~+Tg|VO]oyG[nqm3==M`癛LO|~^x}6{{S qyWf GOة<Ưy|pB\4] 9i"qb>g~~M>;[kUmY(ebڭ6+ǎI2V֍k-ѨX%mшo1_6woݦ^rI׷loUP'LNMdGu;&A{K8<y*c-Ea R|3qL6eͬR*9tC? YR ]=T_ͱG0M:{qk||:[k}ȲR쿦Z$133&!mN滵 F%1SL4] ʼn9n]~5:nڙ*X k;,hM'uyݨkv<naʭ;o׸w k[=%yqBr~W/ 7)XIwֹYcEx\//v}] -m7q~lQmh\̳87s^ى2kZ-:&6efx] GYdrBբlP*dw>ՠըF٪d2[Q;ئlGQV9vz]fd Z'TDizAz)22cBC&5w*In08In OAC\ {biR̳l$Vk;hAq<iP.:a-mHI$ <wQ*a|}~/8^agk~6VG#%o6GWy03= Nb1u+>*{;Dq,{}82| ۶9yr8 wo~?@Z.P,O h`}cG;3'gVsOqpP' c}=3gNrdyN<ƭ[w}b9viuT%.S>{#G€ofԩtccۼ_}ǵNKƗ~xs(KfJ;6 J;[ҙ4VXz87k!kcbÔT mӄa+Ou !*69˗>ݗpX҈阩7)W ,--p)>R]V#v:o./~.*c%²)z^F~?@oqdeyuefg_\`jf`I rgs7|9{zN+6^,,JWH=O∩i?7<]|)p6 Ia;7y^bL2f GRzP#<ܳTiԛC|-:sR=9<Ǎ HRJ^Pqm*c,-.rMzJϒļHǖtFQZ4@Bi '[W~dRHyD% {"Ǟlյ]v96WyGӧ__ZkrlJṆ'xKϑ Ҹ4}jU'9rsy`iq.{U~*( isze ^x>C%{g0Q.c A\`YN4'L9<^{ ^x=Ⰻlmrt {o"?kXܼ3V.∣+V=7oo{FdCbTgFF5QjT5?iow)OYXD1\Ѕ ґ)}Cz((qj,HE !BL񓤎4q̯5)oK)vѰ] "Jݟ`>Ö6*1bWk+0yB4KJ6Y꡸R׌pL]0ȔCq$u-kn>\5+ \yj)=4XV=pY&;ma, 3rBjUFRy6_Zb|r3.`uI~`a2u q(>~G EfsTV&;6"H7-5,e&*I"HӋdthF-m C N11ȏ4qcI8NЖ&dYڝ)1[ ql2O=%~󳟽FǼ׸u5&''9w n^xǟ.[DDm+`wgS$!V1h4{~ux78st̏@Jί a:7VԢStN90d,3,K`a$Q1BJr,ZVTƙ_ZD -ƘD%!'Hw@oisi8A,[=zon\ǵ Ʋ%S4-40@'SO?'|/c?6.^IclqIlG[̺%>)؛8zF 0XQ=<$˛~0|<<ϡ]/wjT[SQb$fvxq]\u\LsⱧC'N33\I'.&Ђ798_x & 2ϝ$r2lA$,./p_EZl&Ù'Qq!t©Szc%66DAN-:.Dž |HT<x EGX;&C6ϴm ԝFf.Lnb+m#Ա86(@ (S9(f8S@K1mAhf:4gl!x0$E0[Z: ZP2ba m⠶d\[hqtIS.M83O&]YA B8$R< 8h -XҌqshY}>IW'T.j`zf[ZK%esK2 e159MR ;+pY)@7w -PugIq4!f]ZDńQc<__ቧ.sN 3֓8);Ps gvxs #jl>ǩ3'i49r+¢nc%ܼM J%Ķ~#+(J\1 ~Hqe@X䙓%$f|l8WxG8Q?oX\4ZNbe2sJkbDi lcNf) \x LÄ(Qc麙簴`jza#:{@T*K (8!CN:ΥCEiSJ1Gr (i6jcJ30Xek4[ j8Cwslz*nK ,ffaǶB=CHZ8.;[TBeLBDV5md!iXɑGe |8hkHA'`O>%g¸þJ^BLgx˭뷸%ڍ:c<5Mڸ_n\ iv˗>$` u{A@PͷamsXk8·%B@RAkh5[IB+cS,UDBFXj  uY97< =0pN{R!Tp˓yկߴ>|ڡ혩 4SI}{'';uf&ɘ /,1:Ta`pO;p1bIZAh_<JI򹐙N,c ;ff,Ce`|F*n!BH6{&x{U^<|FfѨ12j$(*mÃE:*O*SQQ]>GTɓm߾σNpni'| 4as{yO#o4(կ~}կ~_>to $I]>+.t*)ٹ`ZeS5Z8wb 0v}Xug,5`!R=".{qbU@igS!rJ9H$}OglnBZRgS&[RӳTe͛+r'{fp;HzVʞ|ں( ~"guvUfYfN`)9{-6w8{"a.B;[`ݵ$ ؍ֱgM$TJ%6V8g4NIhgOv>zJYSg+2\CSn6Ёu!h 2S2c2C T.iݹ8I2ffy  "|)_ګLS@IL*3g 7n<{(/WgT50`H(rLNNP.e_*fggEg dX,:+fHS۰a!I2:{1+*Jz2XNV5}Y}FB@ݦRQo?"C*,.bgkjο"_(e#\q-|O'_9:q>j/^}>;:=GG #$[[hcN14I9>w#j{{>^e)|?T.355@B}*X{Yw.M^'y^xr=W177GXek 7ڱJtG\#qnfoOENSv/Ze,Un?ۭh pAr"~yfKԂgi5,, A`sF|rEy+|qɅPN9wS ԛN${`2"<_qT2>9IPp;+YYY%3z1qT%LLLPt vG },SJ2cOH׮^zOvU3g(KVhsk׈mv^̋2}N-azv Dadt2{ \~lĸjFRbdl Rx"I3m֤:5Ia(`~a(jv3Ç,s<0Whs1Fvk֪uvwرQ\aGUݾ1ɩ dmđ,/344HUk{xR3<õwk7iűfٶc26ĩJLOs6Ã1N20T`os<YIx!OQbqPԚ aюRScT 9^~sGVsM OK`k{ ~듬OMnQ;IcNqlr_d jysEHIΐJ2:6@9G^$n]a~KQjP%R +%m's#`w{A>ʡqiB1o8ե8"Ӏ0,0vQV IdHP] {qR)L[U, IJRX+W Jyt~f/ot{4hԎ(tZ-m"e> =KP.j߃I)r6C)+(l{#ga:%Ck+-D*gn!$љvȈ06Fۆw\Swa}]H/6]eX9;q5I!Jzk{LSoh4D#\$R5A McpJGq s'Olmib] LUJEg+cZ[)mK]{ѽQJuٲ=LtG`|oS((r̹^:{マSxKd )8u'XX\tt(\.]4'O>{<³|_ς?/088D;uGRvtD>ĝ,CÃ)ET܅ (G+V%qbUNLN?gVkpIhnߺv.l*9:ظO!Y&U# ,sN؁`J`Eϖ{ܾI0D IE 384@R!2|zmo;$ӓ%׾AIRr=aRvw@gIT[0#P 4NAz|ϲs_i3<4RV}V0?_0X{ɩ)*}5fkMRg^z^Ϝ;g_,g=C8}v G;MQ,p dCab0 %&''\ǂ0`wos3\8^oCv9<:p) bQ)xRGOM044B%KVWSAz _(p+;$a:e{cNID)(&28̱ ._LX$6׷K=( ;5IAJIVgRv7=^85mR=>Gĝ+$)K 'lc[[?yl& <0ǩ,@ IDATSgP~@5J۷&hήZi5t:L΢T*So4 "k=!##öAHzq-떂 M2kMl6;lIm107;Et I }+,T%޻5HTܾhwӔfk3X y^wĮD{!X)%~ =dF{$o$qB`n4pppH[ z!mGP <I&G?@)yPB,vOvt&=3 &n"ݔdfz^tifKW~D zY1ӌNLqyQMg863Gwzn6yy.^@\fT30P;wWկ~뷿Q)ʗϘ1A'IXCTƁ]v*\,{t@ X'H6gUYtI\3} V?v4]YrVWf-]jpٳg9cscU0˗_Xc[Zؠ,ԩVo;毿di+_| ;m,52X}G|50"N{\6 ;%Ib7>կPT(L32:Lܾ}w}ӽc.\%2&&'\81-@׮t r{Xsٺhktw-9˂~8۝.{;l}{MB5Eyw3BEܾ}]J*'BoFk >JTgKG y`Hf+e8FokO!f ($iwUuxJ"(sǙ;u4 'fymN^.#ܺ~͍u~wzM^| W^|ۄ)(RI20Bǔ*qJiU208doc,/?`wg]6)grrԁn6 3st~#C0^3p0ȌA)4#F[lg2ׄaSwnB*{aɅTtMڝ4 ,lX5dp=ZiY,uM[v9nGdY̝C4!P!mʲ!$G#FTr/i7LϠ<u!m*ITCܽy $i;s:3d[ifV$kTjh <%>sT*ܸ~RM8zpT{ܼ=w?Bx.©nS 㞩9V9 Cel =|#W({FGdUVB)e L,i)f'Tku6} \ nݎ}OJ;8v.ao_vwV݉BZJkJaNs~_\}"8J% @gE%i,fYw>'yrΖ>$T,x4y`rsgY8K8}j4K=NBP#tc,/chh#<6FR!x&4cTmG3'SXs͕e֟<&;xI(= B`O. PO3>55fN0=3MIZ=-Dwݹ+ϵոҳ]޹M9g㿛x) ;n{]+|_ cueq͝daqcF /=ݹ&n{]=:.ۻpʳ(?4R*]8T>$q"ahh~/I" y0G; <,lsϰϙsЉ;Af(l%@w BN.s 2lEZ^V֚o$֜:} ћJ ^j: O?!Ӛr ׮{:TZbRv|GUn߼YW(PΎxla9Xo) Q+W"re)aS~); 295I5i2<86{{O$Tu;1;;E zʽ{ɲ\. _<[o #I !oަREքakq4d &ffh5ܿw&3L$mкgɯpF@X'jQ 0:6n#2CRpoMweؘȎ[ߺaܿ{- p Cfrik \*0s|w#LGcUk8(ӄ,y<"|I D^T'@ K re.=~stxȓgo sC5H3 &׌~ Eu B)IFxҦ$}'m3fg&$IBYgZ# ʥ2rc#6ݶϔ l)NfEagw20X*f%FF Ã ޼'Mû =S*!;vʲ`-RկO><0>KGe{CIEČOq%kLѩ)AX+4YPZJ)zk, ҞPyVEU)z~ԴB 26OyZ:']7,fGפSIwK8=HvU? ;mWe_דOymxJu8uST{ӳ(?G7BFEoN4\dh&ZշuƯ׵ݶjn킭DaDirT/ <.dkym2ɵ-⩢88lO)trc_]WTK gnK(im62Z1I)9Fry NSwvW!~@f\e,a֩}v.D`4~iӯ~듫OWƦm|j? T)+Fb9Rg0 \HRH^rbqM:3շ'R*_{e2o+v9u4ﯰxo;,'~_~_W~?o~[Ehy)ibզ磻٠ )}995dSg߳ f)0 ZaUn]<}$q!@ [kM(! ",  xVY%:sk;SyjcTywBAҎ{`2{Z) nLmvx :n gof_VSd,, Ry)Y[[Ą˿5=h->on5dLLLϾSyƪ$z ʱBfGff٠ $ϓiZ96I/Pǫlln2wh@.}Z)I&zWϵ1¡uTg]p=Q.σ4M09=6ݓ@ӽWN'Z wϛMO~xW 6( dZ;uR)ɫ~*?+GyΏͿYBVj4x_R;8 N: ;mʃ;GjEw?|f~DzYݭVzŅ ,zo1fW.>`?biy=g>:q%q'ljh|˯֯ޢ^148@p1wҳl={wi7H!rKJg"=eah TB]4 J[ۄaHU*')sCvwܹcK!.>{YFTnsAZgq1e?R(׮Dy>#c#nfҕfgxFZØOY:DTbbf`|||l"d ^WG]c}v7llllw$I1Z$~h$CkxGi\N$Af,O*|w/KUg !6_ݛMa04 5&f<5ۑe䂀$MӄZEwN֚$ƐdF!`ok׮sm0$_(XE|mJ:- h:ljI"bph_)۵ES=;14KIӔ%6Jw5ڈ^7c$̜g0C6RQը7 yv),--Aޢjr% I_ |,--21q=IX`hd*z]gbb)R:ͺ0;w(_`br3βx [l<~B+C;fdBw>>_"WV߽F IڄOt[x.?{\ec{}rQɘW)>Wկ~$ IDAT뷿Q)o4fBv:BW"g[Uu[<"!ETeZ,^If()ȅQ/U`Uo_ǂ:\ƪ<LMhIzPvUY9%q3P왻`.zL^B I:U` | A=}zH;c ‚4*V!5(#tOA!{ΝHߣI5 2==˙sKi8U-pW|>D`t]V#@)N^؏ٽ= NI㮜 2ֆdR9xzc$ty&FWcxxčGR*g,qܱ BGYhb; ?x^~ ?y90O&=? MR~Y[]vA{ o,eCCdƐl~ hrAJt/;M>|-&gf̀pA#CTe^kt/~z8d}C<_7*G*h:1HAQ˧~!ѯ~V}ܯ~_o~SϾME{4T %=ZD'-hp𺟔{ê*fbDy8$Ȟ7˺Ys0"Y0N9$B(BK7J6ƱR dI쁬.ϩ˄y\cT] Th<#묮{ G)i_62VuA\zI䢰-e3):Qf@gR^w]e`y=)5^vmَM#xj,P?z.'thlTTU1=Ɲ9mb݅R?ص2{Srk}]{wquwSkt6])0>}x׾cK^ÍFbQ.II=|Q)y(bxdQP.{M&P58zsL︹}3‰Y14<ęg20 .ځr:dkP #vή\޸6!S9͹ XW.fk~zw{?bg{Cht!,tTr;&fc}sYX\䏿Gܼ~,MR{G~bֆ|!s]ѣ6֨4 Iq3Wx9~8c9߳kYFT$K IIM6={\.O>\._ sH +\vFB 5 ]5 R ˻pZ[VT$T,a 5=38Ij6_$Q>Td Rknq@%(}N0wb;kxLqkwxl 04<SQ.O!UkC. i6QX+{TkMn\FdYZsx<\!mv;mMNS,WYAn<4'F k|hh焳7UATe:QѪ7/ʵsy>+xmHv!^Z\< {(0#쳉RgekC.T.!U梀|.$Ǝ7)Qr޸ kq/n;yƏfc{>RFI',]&"sj{rxX%ISQ026F!v.U C Z[0XVMTkuN6$8M DݼGկO>\O8Jzn !@k*#ܹqY*  o?XMOORbdl rr1O.JBXbrz^zӜ^Z>x#>ر1KESN&3'OpM\O^;TGawã#1q3/Xc`f49OUux<;9 ",  D:I,ZeKCKOYg[lYiI$ 2]lwС~ArRx!鷻n\KԖw`f/{l޼Ç_bvF`s|gH &@ÀBiZIej1rJm,MVJ~_.T"+ځhuri/aA[]B5JX>dMָ,`JOa)E2Vn[8ϋjxUď:dƓ@lgζ]vj~+K-z}f<N?MZ!r(t۸sg/ q*Y8ET*%&QsoRH( AUkcy. 0k=[[,l1a h*" Ɗ6JzhrNvDB-]Y-{(osn592۶oub* c3\kMEIFcx'ٵ}+w};j7Ig_8LWW7gp1L^"i5su<9,Ir8ĕ29w,yÞ?˱Gi4I[-6#!شesD٫W9w4aR)رkglݱ+ܵ !R[kBѨ7Iy^!(#\t;0>>N,./c9v(B2V}_щ#) CI,ss n,6Nu0?]]Dq/0H)^E\F;gùg F6ltǰb-'NP)\DJEG@zk!lLC=ochNJѱ 9zGy),ۏtjj IZe8b(rW͏Y@_W.Mť6q(E\& b:;0f 1 R8L2OyGj'= 9ֺMQʑm[˥2FF* /|$diJOW}=tUٹ{sX7s (HXQl5P; TƐ^*qQ.NNR[oB Io]MT1JHzpe9qϜ/`o/dzy-۷sg0(sWiWenn4ijԩvt33=[w!;Y\q9iF C0it::ٱc}Vyw_Fzz uxkk{zrK{P!16Y2Vҁ)y_d;{/zԅBCҁ À0 T>RI BV;`G IkJeΪ]߷q/x{;hކ-*URQ,.pk5b Xgk-mᬔu`u;W)**= "i+69,0axh=n`ߍ7Pۃ9(FXȲ)i>SW7V Zk-+Fi˗/6IfEwO7QL0 -c[YmSlh t7@Ia- €Ca T4ȳ Bmu/Gyؑ a,!Ep x[Kk+ny" BH-bxT[aw6Ӷjmm +X eXg->U\/sgߎD.3ktDmDooCFכVO3xNJzéBȳ "P cV$s|5(YYr6HғW_Y5,_O{ogSX+ܸwkWίmg)la@;7 CR#`׍iG'}}/.fjrNp],}hwN~7~{r']]]t4\-($ Q5jsν<&CH8)?&ְytbO_/*7Z/: <羷|;6nnGٗٳw+5f9֌ d:m[شi#<ϝVoyf:h% {ȡ#4u*2AדT*U\&Yh {33;K40:jٵg/In NF Sk\6z)XY!&6D$yzٵw#:$ǏedtZ 4D*(W{]@fia~@Xg 7 (8ɳ6bʖm[)U*\JLIqi._$i6\N9)j8o`y/\h:"iȆQHZM" m򆱖ѱqz{"yj JKa,l pvn J#3 Gd2FSv08Eo…ŕ髜V+%KS0t_m|l,KW1Tʪ?-U ;l( 8 BTN,,#K3vd5JJ'UvkYiM$QV_`e[}mh+RN!(hC $ u)ݯbY)ǫNB= 53٢e8ũTk, e;JB).1;;OʨVtvu ˆ(.q)gk0}_YNdeGyO|8R+5._jr9Դ'HP~esZ=`z 6ok7pC¯A`i)ʹj82pґ%gѫd@*p")<)%˫5/sIV`ÆѶ]$Iq9FmFjG>l޲Z]] 5LbC IDATAdE d ]Os1sujL6ma.]e'OS<34K+ڳn~aavQO\@IF\wu4[-rِ皽{v]ܙsaPm€ ccD4I_X./Zgn :Kɲ3}}l۶\CDHйafjgϢ`ǎm rkhvȉǨj@bLήݻ%gk-H2Geo|ƑtfXY^v޺eiex_Zo_b Օ5ι|ׯ՞HzUַ/Z[vW}zwD"ZƬT0 -\+}=*ZWAJd jb𠯳rC:[8vmZU\%ΙMp-VJ[RqȩJ7wJ)G$;sg|}j'K* #$m Y`MFҤE!vy~%Y&/;G2-K—i %lwdv݃7l)Bq5a:h︝j 020_`iyj7p(z]$ pQ}}z߰m^3_/> n&CAW5~-}=?4'O}ߋwVJ8t(}.r7S/G4qsxϻ0 ^xݵ^ߥ[2ǣ<hYjMCzͿɏ=팾Y=r2&Reɻ?G9z4% ".jP)DpƩm )+YT`L ց .7+T ΚR"<΂A <뜲 E$]S)g1Arumה2 uPR5Q8?K (ј)]ތ R-@;Tn+MS(Q!BrF\%8gDquHo\pA]rgOqg!l#aH- W&y`읥,4K*DHKy8V;a == ґt<"9eey[TexcJ2lz0 XH;+r!P ¦kpApjBV P~m |g}9Eq!\-n.osk&\ ,__~Olٱ};GVQ5ͲrKtvRVW_ ҜRI2U书=L]v:_lPX po#w(TE@c65mFؑ|@zR ܸg+-z 0BgZЭ4s1v=q M;{T4Ib$*_/S9~O>unyvʃ=DJy;jj'm O>$?y-G$ € T,,T6%b߻yg~nmٌ9i+Ӝq6 KNC!== :%,.7>s~i<2{{;{cǘ|<:7d#$B:;IV3aԹ 06#Y#3^-@tvvz={{?CѤ9@lEjagJRvq)uKPhĕ26KiͥWgq~qa a(jc,WPT*Zk}XTಆMe9[7oa<&'\Sgn>0XbDR)i+C%S:} *LYW/f&-xonblN?Ͽ"sL̑iCNG֢υa;:2 k zrמrzz+n>欈j/5?Ƥuc%usuƦݷpGQG!(!7[tU+̯6hfW. 8q%V+ٺs{ȳ%k  pgryN2+s Zx>WhPT8{~ qzdӶ] W:x{vscvuqEVk5f/7ٓGYd 3<2@go?S ZsojQvI˳}=W.c-7}G}>'zvI".NLKVcOss<pez?/o~];4ZM~#ǎ_(1m>B' Y xmλ^ḯgGOSS7&uˍ7sW} g?[~Gt3|1UZ{i~~w~>gy,vc x@^ݩgC/cߕ}}/z}gfYz9ZZզ! ϴs q(Xm~YK}yؑoki쯲_U=R?yvm ɷ[ل_h?C='4WX@^?|{pof~.ߙX~g]AUJ:Ua r*ɽ2CyS6e[k1ƒr8{= xX*@U2.][`HI\]StA%q PNlp+[6Nph -e@ BdYF.G0 :awbEJJbpmmmB V2gf.S Y.X)IYIBSX KDh[G (F ToC0{Ĺgټy3a\[q*'%ѹm ƀ9AdŁ^v];EX u}*[Pd o7n^nM:;Z+I Xd 0FrCܙkX؏+cj>0$r>6)5pt RvPy ޺S"_\m#ڵZ +:}2 h^*pZ>B^ky-<=T~0.t@U`#Xo[ݍ>JIBOw'w}-8l8Zo/]#.J2u (ف@L+MAnj{8N[u7^G004_}VnP^,xH + 0W)ܻp{wm:KSҩfx^*?V9RkN kzrFٹK޾^~c?ǡ^ԉSN?Wkظq3ǟbll#_z-7CdaqÇs4rDJtR$~H>Y0jZH,IE*0amt6Y>3$ L\8AY' "v9yw<f]>u\)Vk57N њnreR̞=xɧÛ߼[~;BDzA&-p)[yOsW[.5F,[tb 7;vpavGXZZd`533S.B!+Wiι2룳 ֩uat?:[mؑC$*Ƹ؇No&ɋNyFFFA#_}%i%DQ89A95p3iblJ:t&ekz :m{#)lY 0YLd=X4c|GXmj7ᖒP*2l͞61If3QJ2w^UX^\sWbRFmA}<?r]8֤lںRgwnΖdvvNVhظyM\ 8y 24OR7ĉ#ㆻXxǙ_Zd)W&.7M9u4!yʉ#'صs#w1yGQ@_* ~eٲi3''ڟO\ƱqF'&.? m0;Z}7=]ݔNaad3^؞= YưR*8 ԩQq@؃ցy۫*o DQlcYJzkjپy]uG)MEC嬞),smKciB2 c@3 yΑ"B ;X]YSE\$J1~9Rq!37l8ٗNXM0:!9v0;edl7}gBEkD..^x۷cs]TVCRŋ\j)az~X^Yb4;$42#9INSr6R(P^TohEMˀ+Ea&"EZϊJ!XC^cM&)Ix Ihr3]뫴u6r:tSG_T*?@+͠T|SW. r El}^GdӦ L*߼ٔ(;WMU%=4wb`(cc&/1i+n6nDgt$)Λ$Ӛe6m"O<(]՘3/Ǟ%f.m| )B6mͳO|9vmYYm[pHҔٹY{yO=47p7Xwvtc|g=,,43d|̩g?s9w7z_QO>utmԺZ{۲is/m^9I~'ꚿF[߼("7<7sټc Wڀ^kO*8"?WL\Ji-|~uozݺ+K>ơgO*l{׏^t~2u5P|{yWv}tZc?/_~bG6Hs Z= {6G}ÿ:Moٜ4݊OJuu]k_:[ԵXiO̧[ |JOg|u?y^8P t)>C]ܼ#4ekkpYot4?wkr,^]}~8;+1ɋ:?`QrU,FA`*D 4T BI0$R\0r Vk؀ >kԩm!e5]֜m΋ zXCaΎ$DFչ_lHNYkt@UD܌!C,s`X'XmP2p@R–U;S.UʀTkmod(%bז,׭5qϥsb@zUۄ 3g2??ds9QhliǶ^|G1Z' R2avΝ6sTYJ;׉ƍzu!p;w6ZJѰϷu y[eKF?~#˦gҬ۰8ѶM#@)>azj\.MRd<<yP$td-8v(|;8r8=<ȣY9҂uxHj@T7ĺMW@Ѱ7.P(5zCs (drlV/xX+f /eߋ{I)VnXE_otuvEb)ĩQĒ&܄2'ƂID,%:;xqfAz-3i;ژ/2׃ **eO( JՈ"piZZEI$2eS) S x)`%Tf-YBGOAssS9fXxx&x$v1׿8wzM<93vk{:ڿUO>==,^r߽&I{xin˭].XzBT w^V{=g|84n^M^\Вܿ Vnܜ)kC#~{,c]JX>xqv?k7Dӏuؾ6 2<޶Wr6g?c4N2K?> JG8F"Waxv.f;gegmhkyp;bǁ*S _q-.o.=X軶~wŊ=kL)L'>7ͻ$ 薜ui_??W*{_y/p.|/RxhC!WNߠb;JL6ǩS'9z0sb 0LqY 3n}Uܓp Μ9]︋gx]Y8A I!Ҁ{.ݎj5)Z;;I!+6o])k CN?A{RR)t BrWq`a^z%t߷2~fgg-#n6DRC >J) Écx酗(Ihji-yȓ6A)E+fae`|ZRhx1 ,I͂f#|/M&#y!< ~TJ%[ɷ =2_>a366NsK+‚:#HGT&H(6Ҟ{ޑhk9/@`җ;YgbaGK|fwtT/c|;y+7r#cgbtMhoT% Zx\g c3vK,,T* }::r,md!R-רTk( ӳH!d& <IITY#X/cO=ͷPJ &N@(~GUY:fC;=rAYtL)KX(Tx(|R8ӣ,[/dժlٺ[`ݚ<V]?vw3?_FE$)Lűٸg]u~PuIЃC|#R4u寔 lMYH"fs?|+5r4)^lvDQFR?Yr57_3,m5Ax)Xmon̩L07=)KyK N-ttu1?;xLer57$JBsϳӚO86& lrۯۮInGMybZ޶6:9yNxh1acX(9 ӣc뿰PTV 8p twuI_d{`պVӁ'GF.yޥT-s\7vzxoaْݦؽo/A)ři|ժޥo[yھ]IOj Cޮ 7>/-5<5{oOz7wlє4e ST"ԜHKGk̗4gİbൕ?lQV= |qO@Ko3&V/ 6SN0峸' QɈOz7׮p+];Bw!b e}5yETe:Eu&]xcTI IDAT2A#aanX0rhhmlZ0®}իVRXX=}-| r*r4RXz@("a Q+& S3]b O3,^DVJYő ^rl΍%ǓΓ8wz3s?yb3㔫Un q]w$()$P(+E_?aSO;/V .i΍$USO~3Fشy#[FX^?;2 ach$R0tHr$ID6A%1FkN[n"*p 3iXb&&f6B)8bun!}5k2tz|o3OukWS+kLvxsI}\r@Fax64^mnMPe%+ejlb1 l 5SsEΜ8w!*096F"ƏɧX+;ĺ-DV*gVYkط՛6Jv O@UКڇy񂀟ٟft a*E*NLpg?+3V^[n_dλ+, ~_+_e4Zd `Nbn~+Wwc6PYoʯ]q$ItWb#;pk/}fwOvZV}^\{W!Wn iBju,bЃq[뙙SZ[oNz~VyC ^Wϴ<+Ozme?azM[@?PgZd62sfRr(T<3j}?Љ;{*,:ש;[<;&Pzb X(]e]oyMdWך|w{.^Tg}_=qbK.JU % o"K.e+5}v?SقFi뵨g=[b:{l[ʾv^z~ vݥz\JvU+w">3˙iīU:οS{ӓ ?&Hkv~J)*{IZ,^G8{`\􂆊֪ c<ê,hklIMUKz80( imKieŢtj;I#j6.r9@Zi-,8jQ3CaV0/|e )ѱ 6~%< P)@i0eA Ԣ U xO킻06Uz=EXSVK j]U{o)c>!ylNL>6״WɦsE5֞OP)WP&Yw\*Ilt*CRq۠ڋ.B GO\֬hl(-.mJ*=!Q QBiTʷf)  pjI&Ƙ1:hooF6'0pK,%$ Q <)lްOk$SmqU Hpy)tƁF[p)H&Q.Kf02N&V]mUpĎ;ؼVaܚOHG8pߵ=(:ܸCcV+O$ Ǐgus%Mm sz,3 VaX>Q(*0 Sȉ=GZ gp-^쬞A'4,`a]\'?DYoO c!S޷<-M 09h4g~&&XEeTc%6o Ҫ c-q[U=s]>{jEK:̵_KXd\殻J.C<KlZB4J%Ԫ5ZHv!c޳uWi^>| R.-&6? (Vwü4O|"Ȥ4PFo\~R HRՍg1;~bDH752O?7mKب,9d {8;wa b9x ' ߿[ʲK8ze#~m|KAٴwtpq 8IIj5{v93r3xb|lVFHk#L6c߾lܴe;3F)O6'*V$lV`jz~[(HSH!eIֆt62N$Bh^^6n$/uՌKB&| ? AI$֚BLO?'GN>a*¸|_CA!7nD%ky\6ۛ:]#_4G_>`_/AxvݑׇH߀8a0(g$9;ʾٹ=Dss C3>6Nݝ j"JJ`J”% ߪ8yV dR;hmkGjLT!"kULI>$gQDX{zzhʦ8;zֶfcc|G~Olz%KfQ\#? Gc`WiشfcQJҨUb&ˠtgڈjU#̤[wG&'yDD]vT\]%NJu_ƃ .XD_cZŃ2m ov$lR1bB.!Q Oͤh S=Ck{;Iӻd5c ŬXZF'v_Z^2tQʲr(`#(tv$1P$k5zz8ujcF-QjY:;\LLLNtRWQTYr 6_ß7~UkS,^S;aaar+sseT.7|gG#q|_RsW}dT:z|.O__ҙV|4mmZG/tŖ1o=۶l%6ll6K_O/>w=7єo\3</z1pnHxQz vJXb,|u _u9|pͣ%'O8p2bɈt(X4Z~Gf ##|+4ϼ6nwyO쮾*Z7Z0,U-iU|3 6F6ً?~f~5y}<J]o:jM N\k.(p՚CVh o;_Fy3O1_ӣ_aٿ.zcʠU GG(O41= aF*n"2{82 pC{Aؤ@HOvQJ.K5)v*q*F_8(a +@bU%kmĐm ANU,uLʣgbUU~3??OXf![/LjQD*"R,کmnv*=Y"LC)Kzv~mujY[)ESS,ZښG ~Ib4 'Վ~,!uW-U4ZH)`js>Owg;T8-)Ae-lAKhCX$#vXP]`up^}y~ҞW=b4S,Ca p1R4ڋӤ ;f-Jga~;zkUjD%| hc]=vml۾dæ =JDsS3F($=رjƦkf޳!MMM x.\(hCR˗-箻==c}ieZ8|^O|ٳǿ8jH0MZE b^`wZͤ}p׋ъ9ccv:t8QxG'߻UאIgy7piN]ϳMIVAkJXb9{AWJv<*@h|!8{4c t[ =kE\-9~+׮kҡ\.066Mq֭_vW>~aRe5--IdYHZJ0d˛o(mΒK~{َb5ZH27vgOCPNk=10Un-DftdMk\e3j(߸mTtx ҩ]]+ZFG{0YpjԶVH;Ga~kXLwuv=<7;T(-s";Qgf?WlX mbCLp7:10*BKRR) <7rf2ߡϼV{Uj ǃ/%yi,|Fϼq`ʡS OD\!̂ɈfT?AmVyɒOƫ|~uvk 2}o=?urůt}/nM[)Y̪/$E]yEـyM^c/95a9ϗRz|YuKKriĬr4g/~+=E{(N@'Œi5SF$IX5I' q.AkUSA@: 8@w7J4$Qr& '1B9{LwHR:{jc^cF4@5T5uq6J)ןiu^cb0ÐYZyRaH6gC}oXK74nAٔ{ǓvM)QVtu2N4+c✐!q)-5wRcwNS+i.uYS+0=E ;px٤aN4apsN|  Fvps&ԉi/t]Zgvv|_j ЧfppZJVkA2+l -w=Jt:BS952Bf QDU"MV#ъ0N8o/k֭cntίғZ*Cq[z$<$qDR!l}kQLKK37½׍{Dfg(ΗC_cGG=(yL Jx-Hes |Q#ڱn%˖r˴6aTlD:\FD\&g"n{ qBX"q|g>BbfLxk$ C)ϢEm#_u^. X֬{/1=b}@nKeP h$ғ$u(gme MY QCUIıbbzBi>?>lfn_c}Vox n@Udr9R&(Yc -{z$i͵v,O)Cq/Qy&ʥO7@oo'qT#Nƒ_~L(x;nH\%ۇJɩi2GEEt6BAVka IDAT^ؽ[o0|cr;xI4?6+Wo=AWg' +>$) xX2<|.xI֬ZE{۫PX;52tw\v#—H>mw2Bۻ~095$`?_ro<. ׀g%|,& k UC#1{G|&y̢ Kw￵ʄjdoP].}Oe$=m_kހ5CUfeKs5I:Z˖|r{jUlb#^>I273_|Mn(Rr_|mn*p j}a3ZZ=Ak{{XhCl&ȉS˧AJ4X,ͤ 9QoNl4ۮ޽{){-63;;GG{'BkҩCLLO059ݽlӵLS3G(,̓ɵ %Kp124ӆ>~iFFN!HMSiNtoʑJgF"(C*ecTÊ;3=]LO#=3Od(N|q;tO|)fgg(/jmc˶mH!Yz5===y -Ζ4b!$[ I@wFa??>S6a&{ݳ Wl V6BmmhehmiXq#Ey0(mU-059əIv˳;'*hʧno%J1~fZB[K+hU% BJt07_$."L(T*a %aiњs a&@ˤXd1mpÛhey)U TkeN}ZHelv%{ Le2<7Ya-b=mzr|/@jDOw7tHGHO pQXt:=]DQ4C<'ٷok֬a}10n{(KSU}I;3s.2D$1ZDR DKGdgO~gY)Z(T MQ  iyoz, @Zwv޹}Mo17Ls9[^Nכ$ީ: 3;|.6k.,Ʋra]d 441&We"2JI6wߍѠ20ȑCG;*d-v Ql[r0N p $UܨCˆV`jʮ+VXJs~z ]JIW]kn[-prM%9kqIܷ#)sy@|, 1v^~ryx{8t?侻%N:Sw/>{)Kdhi֮^Cĩig>Yru  +E%!qpp@<=SIv 9*ʂBk8G'~o^v.Ɩr-RT8~_꣼{OͥIn&yݔ.oo^(/py?Ύ_@XtMp^A±N;xo>[V'Wٶ&f9rFn=y|NmN_)Y|)'wyOZ< ;5.orN鋒2T7=wR>;Oq l੔Ǟo>4_8x+ꡛk7o9Gx6?t_`6loH* O79xZ~W_>W~]ʅMۯ/B>'|6i9w)g+n5\}7r>^x)jV9y>뫨o|z#БOz[~W iv럜GJ׺ģ{wֺ{M(lCnl0g_!Zmψx|sX%:l5TWrX el?$ec,G2auPgV`&KSe]XX"zn`#yDf)Ql0Q%_NFIBR) l4ҔJD9)2lK(yv.宻!VAAkEZ^p뒼XNv`78o򚥩` 8NȲT_hWUyͭ#61n'dFnW\ DQ bzns dEv ΊV/B%'muy\0䁐,̦$8z0^-q\Fi歛9x0xqAY۩׫b5I` y#-i}׬eltnǾU^|a?[nZ_T.vUPǑۘɳ T{3`S&YS"MsڵHsKTbh寐v;b#^ #ٰV0{v\y/< J}0~;.jF;>'9֬[KemiBtEEBG1Ok{ ϼO>ŎdժEzkA8ƹD3}NZ^ ٿ7J[?̇.!H֭yOOvZjuD J@7  LhJ:je+%VWԛm|n~gy/}QJIDhY2H+!N(|Gᝬry%R+ڝ6>֯[P5kVyfs7fhuۜ>}F:].MrAF`o}[naMu2:#u9NyWn<>~'NtRlBV"i]Gb-di΋/<Ϛk~5Hʒ=0OKlھ+pb[a,jɬ%V Ve/aOx )by%/xv$V-まuH∴A܉j:x<\{>?NcG?h x%v_]^ WM@?_<؟f:7nn|=~c1MUJ# C1\{=7yo׻yZ˯op澻lۚ{6~m}z=5?—G?q:K?szWTqmb7Tྑ{cǶsRοImǖ W 1~Ͻ."ȿX;k969/bzUgQݸ̊%M1߷yuU>y >OToL=_%p5߯_%v_IFy*#S,6\;^Z,~|_~/gX4檍oVyh9>ndK/9mڥ?a~olAZnbnkByu֊j4ؘ`E' ,@'5%$ [ns z}EfVQsf)U C-*a){D%vYW+^솽"xdiֆa"#Vw м8*`(:5N@[BNFJu^kΙu`-`8^.XaYi\PKizj`#p( VjIVkh`i!e\$QA8.txGVӧO|RF3tٰiOsUW3:8ĥ:YS6 QZf. bVuR =B YZ@ :2TKκXx}LO`# Y4qTF)%w,U\(Qim: 0L-d`h3c w\Ƚ(> P Kr9 (ӳ3pɗyF$O; xv޻h=[]ٟ J}5P,ăW @,€{qҋu0 Hה馬]˖qYΞ>)])"-\,1H!S 4eff 4^; oϿϲ'y$Kfvvs =J4^ylKٙY Vܲaz$9ǩgD1c Cr*[m DԪU͙[P/ mh"p6cltJ,Q+邸@9(J`…d|6_ Z+υ \PB+-}'K755Jrh#}[ˑZ IDATV* VE/:.S\/E%5]uZxXx>@rD p1癟g_}OF RxeJ ssHD;4mFF~JqჇ?BljS9#y,ΝBK8ȡC,_ xb Q+"éG?J_sIs4sT 35Y6\ٹ9Μ>-µNy+._؏ RBEg-ZW]M+(Q.UWqnF{9~w~zCᅨ~׻9i͟_D|3338e]&quWqXr33t;Mvf(.+XlRN4q wTD%sR\")hwɼc?clܸ}a/hQVشyww/o\I_]Μ>Q~?/?SNK5$(?tab|J~ཛྷ:uozU'6D1CR2Dq"vlںYڍ&?%s~']:FV/WL»LKa練~zzYP{;#0b`~Ge&.Mgy'J$w/)!ƠZfny Ni5fkl޺Wlghh8W ʍ.* 8x4͹7fᝬ<,[e+ٱs½O?>cKGh\suU~_J\Ql֪4Zs)71N,B{~T^׼؇=yyCy.B߽d(dB^p <np#Rffձ'g=bvnB=qI!&&ټu#33\8?E]D8N0Fl"ǹpݵW2KJ»}+ȡqY,< Y,&}^Hlq֪ŲR*ᝣl|ϻƛ9,>jQxiT)hCjłk͌-[N!M3L`L:,zO22dt( dǀ,M)K-crrZ!lm4y.*pCVh(ʥ3{jϛ:eXagzl}˦Z 7-b-_٣]͋Z<~XX߼u80AAdu R.Mlqq?曈c9yCH 8yGF\lN9W63D\,dG1:.ufuqyp.#"N;_O}/>O]Bnbó{vmFi-V-QHFhVv:;ZM->wYlSj}=sg^y9Rgx _dfr$zw-uvڃ2;w}]{䀘$ c=$Si>&su-+K(ch44z:81 ސuS&q'"i6=vml޲n˹_yczI&xr3ui/=eض+.Qr?"bmhsp-kƖ @r*f88zF\gJM{u4iѱQ{~fg\KoZ}YΞ> aT;վ:+\%R<ٜ,OɲTDQEwqK\8 z&q;Unfv.\zY. ׇSvC9wӘpɭ\*SIJb kzLmp[Ջ+ ZAq [re-5"0,(nڈF6JIa2Zj굫<4[|36#hηhlCsn9˱y ,ˉcx/4fI[637`fjr]FqV$&Ky[y(Wȝ-$rLKWgҤ[ ^Ǝ uo{[b4JG(ep^FS.ګٸe+l3?z;z w;k_3טek,%GN 3A 7]Hi2|a7L6<|mUZ;{W4IJLriu7λ߆V7eE'w{ϣZ2+ٝQ2,0\rQ^e/@)jv-T(XwcйV{Tx`vfR)F 'bA$4KyCcne@)Nw/+kEaO/֛m "DZ+b/6).7fWXj_We.k[ZzaMZk=QŐgdvZ BIǑ bk+F 5V{m X ,u`*Lm4k,}x-6ωtb 'eQxS$ ky\K@#Ons!KH^[qJǴ-@,")+9Ax=HmlJӅEb"r4\f<1;E>gAeQw'7wGf:==EQիEF"f Lΰdx<]`,r`^_D;x=0N)ҴCeT+ Me`κ bA+.i L.ӵVƆRt v֪4 tÜ?ov;hEA4M]v gTne`,\/biu{Petpq{xkk^K(pɂW9GUV-|%Jh"q- 8mt,5aɬ%CNI(sH. ºiLD0iQTc$W?e,bp ŬR eW< %KyV@Y(ܿĮZA =,qaMPz.S5 |PB_|м%_Iz:k>1w+ ZEgsE G&pb=KNy(6$4x#c)ff诒w ёRNY'!q)`/-QlЪDUxQz }yn%m ( NJEc4pNe9sMFF8P@4~ΙsXtl~D=d-b UEnJi mP.,^ &6A(f%*;-oyJ{|f͈ O쒝̓dZګS Vme\B va)-{X^mH;3|`-K Ko 8I8*%|`pӘobHﴻ%6n@Ve\7)ig@F@SfbS ;htLqz^U{~!ԸaD{'6Zq.ᮻ䆛>L^DRv9waSt##.,r%bˬ_皷6O=4ǏCѢz6˨qNr !R:SSݵ[$Zcq6nZ"0&~qNX HIV7|Z0bv㈣1ǎbrf]O宻`N[֭[ǭ+Rqk;[&knx9z68 :ǸсGYX1SX,=y.4J#*5Z5[fk8*A [ľp; bmhkRjcZ%W^oM_ |m|wDZ"!ML4PdigHgQThQP)h^#Y2rع!=B$)%6PiW$ {i3RZӥъv'Rv}o1tb $)sL\Hd \s:@S.%E@108VEFc?3plܴ\Wb^c*hm6g-YϞcMl޾G>(=,3S+Wb"u7\N>Ͼϱ?GmF1Gfs6l[n|8w;w q8NByr(LMMsV$2IO˖aP8b_dfzגv;Fj8xS3=X߯kǼN`hynq |XG&B(ڈ ̂T,kւ ނ׹r7r("#rQJbj2q|6ѿ*:$(M)(GZ) l+K's9K¬r`e94̵^`]I՚Nȝ#6rD$`V)18X 66rn\o_ZZZZZZooNJ(DkF%OF+⸄Gʈ.p, P(+Hc|vAgKpi'340g9(bC뜨}ƨeYKc_0.19 ;[QȐ $#*N]5Ɛ$Q/Y^׮o$;zD)EsDAUYl&6JCT,ۍrh]GRyOe5omh9ҴFȝ6iN[mLfvfAzHJe \8mb:HmlrKWEDݡHR 4E[A1۳ plK#$)c62(ñ֊Mx-"ncsGZ遦wJ9JiUP@R2Q.\E)b ,*-sŇc#8{${zIJqs<'.d::6X0dHylmh kP6M$+н,cQ9B,\yno4hZ<'c(RNd,ヒ݅'_ξ|ɉ ;:6JvVrg1vEZ(6MYbZʹg1Iر<[^u7 x۽]x6oennGИyGg;PĒ:LMNsivV~W~SVOGh44 s£MHG$avAݢZv:$q0Yٳ\}Y޻3b  $%(ɒ(xLcR!ys*V%{-[eEH81O8اV7 EN^W%8Da|u$!Q+wo;˻s3(yO^CTҌMWȪEN$33dY>7xg f^w }9Vaf^vZM~G{ F@])SZ6(Dwx_>p]"jJ#s`=!INĵĭJA0ӘB3Oץ[EFN79~D$"M$ZX<ȟ{~n;K*izƁ}s9Ϝdr+{$JqA~ܼ@4كR I A//[;TjuoS̅ ٵw7>zIVVX\^nӄǎQucpZF'lnn:׮\۬ol\r Z-iJtBsgiwz=v{+\tl57p_ nu2"sCbW _/Vi ^p^ ? FQNdAeQppw9NK#Epa':tk $߇dtfS+nc/Ġ@+/V+MB]]\acs _WڋC g8p3 g8ίvù³.ZPZLdGFhVߗN &D4Mb#RV#t}FuL:>Jآ/h$(qjk4j%C Oa5kB*"}+Ro=QEJ'֔EW" 6.buE#,U !'ϋ 1!(( JTLZk ƆK!$ k!:3HCeI WĔex.$bT u4oGRb%'%KQwm:&33#!3VWWbc}}v~uF&}Ʀf)JO5I`})ML|Ax(Ⰿ VJb@GA_SdI?4JBHs}h< 4Y\a~vF5C¹ `SRJ-*E+0\js}n6ۛyzgK6wo?c߁ln6c'tgF ⥋z0Q!@ Rv jn uol0bH\-BGv:\xS=SeoMn\c/ JL " ONr-:GG)ZT2EsMa*Ihu{ޫm$ oɲ*O>i kIVop9~299;9~QU*l/1hbTy}+KL]Ofn>be}F0$ n$zmD-$IгV$ij#)f,^(vg ~ʫ|26:/Glonu]w(*I89 {KKvM$EkC.j v(b^HIiҚ%e3Ud yQcR9{?9C\LVIPB@JFF*_7:tF#<կVse$CE 4@w$iz?kojw8uQę4MIb޽;~]ĉS\]PU4FFȓh{\_ hnH7 x$QFPM$Ve9)!MRڭN4imũ38W_{-$D3:8tx??}Gxgp6١p;O~& w8td;&/?{pq/{Zd"?IIi}jjz>'$/x^X!nUnߺҽEW1.R nAYdIHGr9iwAׂGNdyykK5T*ck g8K 2"mYnG x1%†U% a`on##/B/ %gr"#A<7kD +HMZ7:6h*TRK'/9#ܹN7fR**ic,:Jg0e\H}^x s eZp%Ƨ&z:,{^2,( g8p3 g8ίz)ѢCZ:"OcbWDbYp*vexD>[!#FZI?K"xtdždzQRRU@ T.D8~}%2Edk1HXt*I Jb^Q+-/@b1,h)!X5%ƺ(xx`Σ8 J݉T[± )'9㡥mܥ V?R"BK(LA8kc(&X)< MӘPsxI%d~s.#\Iscn7PZr{wnq}.[[<寁jFH016${29$opND^|@[ʘ 'B?v34)ann 퐸%yaV22Xڦg욞bq? 8V8+ J+?)K0eD 3g JC f:Mt?)Kv{ENU266Ѓ-QpQG[:*B%CXEAOsXQ"E&ߑBuy7x7Iu*q1o㡇fQ7^8[ۜ:}0|/[a|m,twZIIZ,:8i7BLLSX:8I44ΰCRQG%&w}S&nuH3MQ&͝F2 :Mn1RNTXCCV"fz =nA}d 1,/[[[hI$iH$t;,&88gywJ\֏=^7ÙgQ$IJ?{4 EQU+$ZSn+%HA硻^$J'JzywA{/g^p\u3||>AVھGǿw~\|G8Q.]LLOOtDtq:jFݎBuonZbnP%c~v7ɒ n;oS ΕxqUYBсFή=(A_cӗ "%\\cx=TZh}t ^o# O**3ӓ!&N CS._}>=O<- ou8s ӓtE45_صg}yLUS3k9CeKjmģȃ{NcTk klnm_/3/% E7AߎeG ZbH]nA-O,JxHV2&Z۶, JIGjcE}dQ[n1Ѩeam|,?;,T҄,ь렒t9g(}-}mHjEL3s36.iL򽻌ah bp3 g8p3 W?ZįT("PcY6T”eHxKiC*RгQ&q&;!dǸo 'T8P6b#'~7Z1iRR4bP r$!):ا,[{PPTQe &T2VWaa=T=033_cav<4;md.eA $D/g5_I 3&|s;P #<8/oQ23=xR`$ v{ 6#:#4|fTEx~ &+]՗_̻ h&)J*^H:e7 aeyW_b[vm@hpgc iY!Y)O>P$$ |V JkƳ.qE66֩2)*rVhӟam}~"^n^0==oo(M^\zQ(ڭ/a~vMxf?q}{36RlR5lj3gQiJ&JJV9,C+ͥKWٽ{WI4lqR^+bR21`jv7Onh6Û<5e 4_-, v:NTHZHj ϾB!' ث jFt{=G^LQk'N"/ Bk+FZa}uLkVWQJRoI+$|@CWiy0C#$!iL)qE"C8D(ZJgIvEc(-iuJ(E͛7/g{׿Fs ޽%:z=cZj>a15>t4|/|զ풯gf|{wJ2Xyoow^mZ6[,18S3stwα27?BfY=Հ:Iqp\ܔdTP* i*I/?8Dt =+MR.^A&;\tG{ DP%w>n^'P(Kg0%~B$e|r Z&0RiwY2/)z%lmn2:2BǏͬ R$i(J$=XkL i4IbR=EXd\3MmΆc@:?'061:*Iss4\t ,-,peu6suzɩ)FFGBiJd֍;'qm ?~NxFd&9ۿ|8ίj~)8,q D2t8>."8 Y)$V46}9qL\V\|pldYBD| > GR^Љ N"It+o+ ӓcԳQXBIYhQR m0ZSKA5Q8(gbw$r BbJ3-a6Dt<i ;x8#1Zzƽql=4_eϞ=ܹsz=tdJ031fuvW,-Ier ?')5T (H&&m NCXё`rrN"`J] p>x? Y5%;;;tjc̠Ghb*Љ [ $ŻbxyH)/‡֐l􊂅;wGܽ{6Z)kKAt~f~)}UGV=FVc}m~,޽c {k @j|Gr!ݜ7KVWoCg|rb|b t;8y B`p$ulѥ^5>3_ ,-(ɩ16׺(){9Y*Z;-t%#{T PID)NCV⼥({(I Vctt{w贶śpZJRTYxbsszүy8;p6S_^klon277E`37?[7AP  R©DLE{ U$i 7nrj#5<J&ډCSTvzTk5W.]噧 [JJѠ^eÛkL<KuLLL yO"E0M S299 })E4J |)p8o 6)FFGq Y;ꫜ?g~dIC@iK/ݼtz=x ObzzЃs_,EQү>F0ew9z(-=@')&o5>8Κ ;v&wԙ(ƳeFcpmBezE7O㙽R_,Jz.R=ܼqť{ڽ ;LQ^ ^FSg?ERAx,^~q?rN(}2zÔ%i.{acRH$"PQwRX{HAjGJ򳟼P5صkg33aM{QZsQeû/Em!ȶ믿h4nNY:9'N@)8xW>[Y(V4G:8GV tCLN -dX[׎ۥlRJn)sҒJp̣"v?ōdCo=>r*1cap3 g8p3* A}31D3Px O:4ZcIu:Fx\,J(D U@qC$։ji@.U@JT#S2J`"8`j eb_!m[`kQ5&QA$娔ċp@Ц:#Fr"Q!J, AV=RI* )cZLx<' O6>kdn )9GjY f&X[%K+i}^ }`HBFA X)fZzs,GPAH!^is?N)YX\a z%Ckkc ǥ!w!IR*1ImuiԪHSHB)K ظڕ<97un܌⮦D$ օMKhX)ʂD$|ey|(־4M]WܠDiTӈ@)Szvv=.^(Jty:ލ1&DәfycyOաn/|v=pU'Fi bOi_:F.Jtoq&F;m3y Z+j,]O(ZGs{JZ6:ku3>:N0EcցvMk+(gllt@TB05=]N;c> wyG/Qؽ{N,j%`gsV)ySEVCh15Bx-nYl[:`QJ|T'@##LNN?O2eI /&Y&Z+4 ;`puV+^XÏp[Xammv }9˞]{5o-]DӦR(}㠴$QA76v1>Aի7x98tK/xwQ* ^gbbRa|vZo?D/ܽ~.rA¯QuIP>7FGX\Zu:СpgA7j8k*aa.xC<cAAu"+(}waGg lKk*4 Ξ;ϝ{ ܿ,T.][C^+k{-P6ay4( *yV2ԧBrunܼ~h͍u|(5& OéPq-Ч4H6$e4`oe`HIs=6y)ܺyFF"Ɂ02vk];L+4Tk5n޹˵7ٵ{u~?],arr2_2~\TUFFGػowHrN?ƕ+Wfzz`vnywgnvQ6,.> RqR-xy%r@%I%P9yRH{wpQnG^Ŕ9;͒^Kss{wxLNL2=;ӟ ]_}8/7 D$/J$I5lBNpAxSv06\Hf+t9օ>/2p&Cn` (cLbgq>tEdJ%#㇅3$! AHZ.O͜N"+f)J+ eY }HFGb DG$}7qQr~Μ=ͻ9Kw١et;m:.3 g8p3 g8Q" B!SRbL$t)) ?lk"RJB+5l͝(>R ApcH R!iY& !.J01a7<bRFѿ""i=a+U8]7wHD{)]RĚNpքY9ֆTn?>06yڰVJ.>E٘ );ARRQE? fADQ 8zn$2iؚ(b1MLL)Xׯ!CHGјtA|$آ"Ny**i%t+[nQe~^JR~ =|=KAYF=(HGBj-"97S7ĄߒXnpCV"SIv{NEI#&!%eY8kܻ,Sh<+\!wܡ,xS"D`6&gvr z{J&d)yYĔoa|b-=^x]033KT6jB ~-zޠ G^ygꝧRRI0|z g k+._D/DXhb<FZ&ZWWHa Oq}{„^_[֔T)$fO? P֨7MZ ܼyi~~Rܿ+OvUpE79y8mn޸ɉv9]sLLs,ݿԚ~9|=X|:oJ2J $tfAJ$ G("%M*,{S.(MQhP%[M&cJ8Fnn?)~ CN :KghwLLL@ /g}#NJ wnߢ乏nGk[8{p#c!5;\T 7&NDkz瘚,,pl6{-^ [h4xW/P$Zc6`~.:lF: _|5!ndzvJH"RV5}t «/AJ*4k/?/c̔{322N1Ns[o_gjj2@ HKM8y8> ?XDHv.Qש\ziΞ?#(埽NudtloF&ٻgefw36@ l+[_^^P҅ 3(4j53y/`EԐBbe6HEʞ=$Laem>ޛ\x#ǎqԣLL{1 Zd8Kȹ*1JJI&!p)D)P>8HhDx0(qaDIuc vCkR "Z4YAd.qs/(6R*,=L0(q|QZQ?dk}*tJ%Ġtp3 g8p3q^p(}LaLL GEƄt;dsCb, IP2A>B0Z')P:AҗXq X=Z"Ec!G7w biYZX@l(j]"CW"ExB1!Ul|^DWL:Ȭ$[\| &v*c/bd.e|օj(kbJNI7/q\ >ZA D vYKxSeB>` ##(=en}''y@6-Rfwa4$R$MRna Jkkr4^Ɗ _z-BC \fU*4֚`qGknpq=Bduu(@2u%i|ojJ%Ѵ}j\v- ^g g;MZ^gG?!wn K$`1oCH$ FiΜ{b}ܼ~Οbsw]nݸޒUܯqJku!Rz.՟ >bPMTrC_~,HւynpRVV!#Gٵ?}<8y8G FLN;/_?{ =JbRY6Q@@Zpgⳟ [MVV9yϞ62GR2sܻoZ% mjKF&fkkc 'iw:aM舘v3(#*_Vsm[ߡPW.9n]"yY.]Yg8NT)[>\d79 -Rm.B֔e6Ʃ&:~lp a\ɸP./PCQoTi:$EY6J0:6JY4[cdIJ]CT=H&iޒ 2,(-^)KZ;-jpH1@`%R_۝.ޅ aYDq p3 g8p/1y XW)a4{=ۙgzZ ;sb#6 ٤HEHJFhk\*_Lj`^>N IDAT :`2 wbݨRbTD{!B(uN@$#,8ߛ^VRaڭ3(2 ]gH"Ԩ(m:HnF)/,t>hz~K'elS6^'ҤD`,i^ZcPIZ58f{y;wHH}&DcX eɦ 45V=E*a1R&g `h !:Hݏy<α0lxaX bO1>YK%Xvdeu7=] ns1</GM t<Ξ!:c(B IV35 5D2nS\kb%}lڼ/y7w g?Ost Ҟ"Dі~'1cya|X]Y""i%YOH)RĴV#kl6ˍ뷹u&nċ/s%:z SXsIU9>9>>1˫+z9nOt-fot_63AQVq@+T?q}zru?y[γ>fz|FH)Y^iQl0~ 9jO{ƑV+9})gO!c:.ĉV26=AC"^?{Z*),v F(hFF^okk}b٬HQUpNh _`ee \Jŕɴ I`]IQ挌!OOZ5ʼJzQxG}hPC;vw2B*MF'("aaq ]f6oӟ/ k-4;{$PwyN7w|އ<{v{J℥Zmǟpa=6m¥KW1aqa'ԚcLOvUbG)tz2+9}/1/Eq@< 1 QB%&/4_'yͷ77CFRPISmaui}o>j>Z!`"EY0}QN(o۷&Ɋ貤lTkԫ>_0%). pΣz՛Jp@Ebċ/DRadl4HU!v;hWvjI9ꫜ]8"3G֢B wZOPZC8Sr9qrݺEDu~ۯw4ýx#Ν> zZ5^5C#$x1aH|u1TV[?H: .]ZGZ MLrΟqdhɩ [pYeqa샑Dq-2tG)Jؾcv:؎E~71i0hKgq=ʲ6XGAJj8Vj*J,]ޜ*+./SbNb,/btp֋IkQ9.3޸W> 4G?̳:;EX%%H< o2F[G:lxtxBzlO{?M(,Zkq'bz<}ߐ zxעT> ʰ tHt4Qt/{ } R$9R #<Gb;#IZ%NR 7)0ܽzp?~Wp3 g8ȹrWƇ?޿/_:6]*Pq6 !yhO$RHv*O-lCH6P"v=66t*u5:rX UD `S {(%0Z#þO<{-Oh^OTcq!Y)EaX!Ťmpc!%BFOZT==VZ!q(%  9d$qϑǜ7i#HbRLo4#`#`>= 6E=R؀vA4u ԫU\Z^][k袠#nߣ VSuvvGVkށ(7Kҧ׏?~:n/ C!3B q|GdEFNǯ/$OLN"bqΔiz#LNM!Z&,//% ]eRH/K'G(t;veD I5 ^8ǖ 8in^?Uv nOc^3eֆ~{"# :AY'#tsN2i_?#lڴgm3?KR}qfLHɯQ;_"c#޻\rV%UvrYzׯ_ٳ,-033C::ZDvC(*G,>cyDq.7nX(3gpW1z8䑒tZk1agϜ ]DYo:uj-ejrETTJJ?h4Gt4#:TjHȳ,?OժH' BR gf||Q7^{gO!PB{wwD^+IMQt]cGX[^#^KI}\EKId8A]vI8uLNNg/~nG5޾^ľl$IF|/Ƶk׉x}%#&3rȓ\v[RT|{0ڰDKާZ~i*Uvj Å8\ $Y*$|I>GI|`׮];;sett -Vǁ7G~,hǹgklڼ#CozHswz{w瘞HRǦR _6o)zLHWK!uܽs%,EqsOp]^k<@smE?p VN~BB ܠr8Vk_, whb؋J ^KMT?Çi=΃y>c/|8I(]Eή;q İ?9 W Yo\*k5ȋȈA * ^5udq :G Dd|rM73wwGPVSlkK˜== ?Is_*, VRR "H P ~Q (KM{u9݁bÂ#748,QDi T}*[kkEYjQ"KRtĀ EX 0% yGa4,K^MB0N(AVd9`fg8p3 g8p~"QؒXE$Ę2i)_k"ZqBJ7㠩Zk*"}BȋBD!%&#Eľ jQ ",D]0g\Y#Nbw ";3 &lBzU?h1MЁזƮqS Jk &,cHbv ^/Pfm;@A Άd/{1֯啒"$r>!$ uYj:)4&Fe^Qz6~_J&f649},35&FktZ=֖Y_bӆ l8M+TI{XcHDM9yYe()i6T*h"y)5+,Q$X܃=!J{0EC0}vJR 8(ؐsS(w5/# 0~gy觶EH[ߣZIX]]BhRPISTB35!ӽ}j:6eh1;KV3{~2.\f{9p6f}RLo܀r֒e'+/+Ai 1:SO=[o5Lmرs7o>q_VQ蒑,P8#MJ5Kݷ(}_c9+KRT)Vcmu u\8s{7k αd9:݌Z_I_BscoSǣyՔ8QYߟR[ZEjER8x(f{[G?f|b'/4@7(oENRMM EYܽua|s/,K/qF&'Ȳ/Sf>,4F> /~%=yz"Tz(++i)!#XOh1/O;AҠRM _Ȗm[1] Q=~y` 8!.i|E?/q&# QvHB V4$R(wh]qFmL^cÆiP\|"õ콶v@uP蒃qįEh/%:*qi0ZQZOc|%{ecRw^_ƒz=ּ;lٲv{i^Dqś?gNE8~Y$/t=g,wnca~HI5Åd}>|#^ghs%^JQizĀXJ0(ՔqYVWiT1α}v/~Khܸ~-[71e GaQ~1 YG"Y^\d΍|+_bv~{x3ZVޟ055ùS0Ͻ<۶ϢAkğy0{i˿f~3S<1Μ<ťKm֨n1Z!s6,PJ2: SRcjlɉ/r)D >J TI<ŋt[WpƲL"I+XiudyNZE5FƇ3JM(\CmpRI) +8) `mp*1(A)[QhM%IN'WXҥ+#k.FIV: Y_4ĉHb@e9JĈX&Yhr'c ((' 5,v^Ǜ fddǟĊatF Os£\;3pP$˟fmOi033sϟ`=*`}>I󪕄^uH}nݻwW.\O^eY:YY ,ٳ!t*+->wǏR%6$iJW^^Z|IB'Q)ȋzF>F $ƕA(/5>y.l4#$jˌ^ۧFW\cljG?2N??{ްVu{۷.r>ȅsvbYn^NRI؈byyqڝ.Ox0J-}JRP9u/ #&B ڭ>k%OevJC2vm@`@?Ç~‡|B+NGB {D1X$iJhmI∼(ڿ"~HŠKЬ#8 ]΢TBAd28$qW V|471LWev +-zGĆ]DcE4A,0dXFGr#ǔJu$Ç*/tn4x}ܗ8@{l.rj,d Ф, b m룵f|b}OFW~J !umyܼvߧ^Q6~^EIVxmyycld#1j'?{ˡ#ٳw6 7o-~-ص߼Hӡ%QIqU9LE({wn\e5ƚu"#Qo] MQWZ6 .5ZH%MYzsO&2o*F2^j1:k@J* N#7nZTSdYNZ#/rZN?e$ĞJhk.SOs9L?+g5i%ڕ+^op5ff&V-_Ŗ3Ԛ Kz7x1BܽRk6n];X]YGQy7[j(z|^ENVHT Kߓq\8wjɷwLLMijM^h `NY(5?j W7삣́~%D&++'tp`"4CIN/;=bNMsAW1c}j)JJ]Ϻ Ie 4I%yBpf@8HT0b;&H.Dڄ*&/ 2hOGJĬ>A%BY$IBQҕhl!I"ڛW9qEēGW V5V{DVv:m_ܺucǟ8u4Zc<u rΡ$< /F(B*Z'gLŶw{`}d!R#Mi~ F(3 g8p3 g8q$BT,ʂZTTίu!iҋZpuu `$LDO+(h j gqRSR \@5 zp!qq{reCZzw@~`*ՆHM\ovD{z|%EG=:R\~YC,. )u\wMVJဢ )$IYhk*CLkUƠlH)P%MSb(˂HQHfO f·*-"Jid ͱeIڵͳ59K m=΃/t=:"R@ӈ>m~OFFERx ^]XQմ\!QCG2`r5{gj4X!#֬:&MS{ZKKKq~nJGs ?s7op fnejbcǟ!TAs&-[9p*Nx3!T@$}pY M^YǩON[8J ш) k "Sbey;wu Hk<2;<vm,4jUT˂Gh!H*fleff .^ Rހ }6:_s%Z{#DXvrg_#ǟqYnayugFhb謭hqKp?nw8}3i%>2/auuvKZe|rܟ;5?X30I z):k䤐R<{nZ6KƒTW}oxt;ɆNQDw`ffG Z&N"q y٣Ԟ`%?/f֢K}͸Y{S#eد}ӯ%R $>"$?vV:"˴KH x| "sh]>[G:_ _H'o_uc[:/7ºaI>!@z3h$JJ(./IA Yo" у50Hz s~U$I/mkww(aeiHGx4ʣthc 4wοs\Zu"tV^lfmr+Xā};s:X,ZA2_&Ib<W*$2qdR^*ZBߧ^R?!ܣBH߇,|'/Bx_o_oZKW%*dYf(^^qۀs^7ΧF~xxߋ˔3OQֈULV,,2ameCd㶭\<[7o3cqSH< ^d'صZ8Ҵ»oŻ?eCu09::.#_xRfc#u LչیC(JFM.Z TuZk-J!h._ĩ训RK""ەYiiB26t}I$2V8&16>Nۥ(JSdie):$%#imjfQ̉_${_IsyRśC'rwfvwfIQEw X /헂$Ӥٝ;tvn'swzlC$Aɨ0}z o*LL?]_*E1JL]YRV7UN"/ R|~~GUۛ],crz{D]pbr|0|!@q,q'7en~IYl%hj Rc%Mcn/egvnWU+W =xđCY:H3n3;7+ `nz7_RTs3LZE1~?8r_%)A_>8Vjqq:Fnos [$e}u}!@}>cx?;8qZ-m,ݥWӮ((p}f=ev9FEbkukPVaǝ;KT6ܽwgr)QEN(D{yQp,o&:,cPȩB^)WWU(Һ M`+,iGN&}x.sYg=Ew{G111#lvع{&&lqkϾ٬+{6Y!ȍ&s²1/^4mjSӳLL{^6;\|]iݷiMM9aLl%//>ݻ@rNȇ7/Ϯ d5Gۏh2XWb+OU%8G9șg替-kNYƵk~Oyp~Y`Zf)qz6;]䥥Fe 4Pa-`׸7L!4c0Na#엪B`[ejeaT"2(Zibu3dzxyKb佧,KQ`:fNU{,-ibdb}ܾq;牒XH΋|+٘):?˟UC0 (ԗm\׸5qk\׸J1iCeKrJkUq)dEYaW4ѕB C=bGЊzUit4Qr5B uYm9()$η8 HsF&4/Gi@bւJY|$Q)%Vjc/M! [axgC@Qg8y?1G>3R<.ֆZRA\4}PXkcPڇdnOѐ*CTd/?<.Odb NsD9xq cfʓowxx˷sdJi1'ʹZA-<_e0Zr(ʒ8thLb;Y(% VjDP}[á"TCI/&cFA4YIQTr;Z삽(㴎[p_s5j0ǻH9Teq",Hc8IWyI-7\5˝NO/^d=ˑÇW]ɇ#~9xpok׮s=ܽïݡ512'O_\FB`(:GeE%WqoT[\E֜LϢMD#Nbdc=x:\!qDi&زBo IDATG&c($Iɲl㽢h $.N(*DPVlnmp"-GL\Q UUٷ?0ZqUz.eaLAlLrRK6qk\׸5qk\jtnZ ֤C(f=9EM@(EC^bԧ&R&V!pmHA 8kXlc2u^ 腨&Hf (7 `#%FL6+]G\dO{-*ݝsm/nQaʲ jcEiu -Ԡ)! P09~TE^EcX*EQEƍƹ C+c`P: VrTYC2R8'rux,$*GT Z,IcYRE`UIYjJ yOSܾyݻws1^M_~Ɲ׉˴5$fwdi7nbvvGw'}Dʩ5b%Te!&bkcZ-V㈍5$DYX+M`Czf!NEQ􋒩 ]:0&ۦў$2FN-z^Q#rDp!atR$k5(fbr{[Kx)Bvyhݻv/ z [8e&fGDڊB)"D@NciZax\/Lѣ|g){W_lnnq ~o}-j%#M41؉]>㼈dvU$@{n~ ZDQS9|^)z-ӔWxpsEX>^@i Y^g_ <Ӭ|s5_;U'^g#猠*qIuBs&\:w[7o՗^dnn 'T(JwҌ^\xGh73vcﲵQ4[ZY2E6 LLLqUu_xŹY}9Z"bu$Iv) e Qlz(Ӛ_pa.6EĚ81a y^qk\eV_Ԏ+O 9a)/}&ɚM^XTYAi>Xb$Yg 6 &+0O]x`<_6]l2D,L^  #;aYNp>'/}`h$!@ɍ _.5ԧ߇0qk\׸uyſC?'g5$:n@ 9ZiOxBh2ӆ^56ƈbviE;Ԇ_ 1h- 1bEwJx!va,I J̕f'Z/@{UZI,.l{hIcZQYGde{D TU9嵎D=6D> 1d@Qk̹J,WmhQoAÒ+1:fCunjQJ jc'v&yJq=z勗ɲ+J&>r$I(ʂčhP뼐鍉GdANq6Z#Y I"S ^pސS3Sl6LNٹc'%='lmsgicǏ m6EQc| QT!cr O:'֒mM>ys/t : sr8V%[[DID4oȲ< WVX^^ܙ'' &Nr"FV#زdӚbjjf{Bǭ-Ȱ|oM1 qB_e5&n5ѣ4 8 ĜGQ, A$z%1 A,K"BVW)rq$q ޢCg{}Q '):2*D;lY_[(t(#kI67(,KY1_ xq(ʒٹYv#9笮&7`TR( e86 8Y[ $8{t8)@YYw;kre~W?b=|wKBQE % A`;*P%xeb [Z+"hj\|?dIfCGկʸÜsۇd9]l$ɗ(k-'?gc},KI:瘟O>c;n$ӶVֳ|:++ ^xE~'K3qWns)>ǎ!c:x~Qp ݹ9HOr~Z<`{kAo8I[)朗W|_{(|0?{\pk 4 *Сglmuؽo?i!ҕwԲ:J9ܾpáÇMܣ{/ҜfnNК<;iOV1ܸvf}cPquΜ>Mj$矣ٚ˙bkkkzXܳY, &~^ yG.lnl>ꥫJjiy-$^Qj$Y(I8x w`/'NgTlnx$+^o0uiOMw|̝K\|QKRv4ZuM%aQV%Kwo׸#8Z 2ڛ-$2h=EGks&Je0b`[%y*l6`n[& eY;Ҳ뾔dlPU4MC2FЉ}Thq4-!H^ A<j: Y`1$W{+|h$6Op׸Tcx\׸5k Q[w/{*mE~H[Ę((9,4M4zoپ{HSz'w,RJaBs~G!a95ڊ{M*JLƣ x(`zp'FKYIn ]΍:Hh:|Xa; xO h~.X_K@sµ{ :m$b0qONSX$Iٙy:zXR=srݝ c|qZQi U13Ȍlh -dTygcxߪaX&:h==((rjfV\=n5fff9W{ iXAT6IkTU sϳ{~N8'HkulYcXdccRdi&&&5C)E$,Yfgrr LW(:CDqL/WjxխC-bn޸{K٤=1-3 j?h6[?;\:vP` ϰFȌUAj촢ϙe^t?G?VɾnykLNhf%Ik R򝋂1E,A^9ZfyfZN?Pe^1JARc4InCEI༢, 4##L$ֹ~.ϴh$1UU2XE%Ͽ"O=X(D:ɬOs%)E)88U@QyɣGIӄ|PPUQd(+˱'hxg9{iR( r`lÕ>-c0;Zv(MdUUk =G(;wtYRmd>v9%j`}Fm.^Hwn119|W.^ي(˸$k8c?<{.^ mr:ׯ\OKVP ,9"9'#Bpҥ0f ?fq&gX^~D*NDQʺk*+$֖LOOb+/ʊ]{pg(:=>c߻d}oz{s LfzvW^}Cp5W=ϝsܻ{ӧOν{wq*N[+}Jkffgݻsr-&~4v๯֒eXUUPݮ@hd,S#s{Ǧ pX$HaSV_)ɜ! GQէ)HDa'(@ Mn2yGdždTE~Hz sEQ%8(u>4yY$`6`^FT jg .|w5(e.(Q}_+PUFvZ~8\zFR'61FUk-,ݥrrM.‹pEeYQS7P<σb9\f 0,rC ~=ͅc֒[mڭvm9C"x#Z4Z-L64uWL`bCΝX4ǟ=ss N= LNMQyOC0 F_K?BB9'FԠF{?੧ y@ bPKUc0I("8} ZƁŀ^hhOLP9O^4M:[8a}}<9x U4Zb^yʢb}},IH;=3+-#MS2ծØzc)E6w ^z Nz~VbsܺyCGc$<ŋt0qD-GJ?Y㭨{>6ׯ]jQ%֊Zy!ׯ]gO7^ \vCz^((*#EGU$n~LDxhM58vO`]SV%>X>}_ N=E^*ʐ P]SyJ Mso!e xAd*y嵗سg]~mf).W/_xHAƩS'Y~p^G-^9Y !+NlH1ut]5qgTc5qk\XFm? CզB2#0 ((Qj(#ZY* ~4*cVu5 ,F^ *;բw֊U)]嬸]hyW7!;Y ($,V&|R >?,K :4x%J ֩* pX%[.q!Kd JI6u^NsP~)mBR@TbA nj&Dlx`tD'$i򉝇88|ΝcsCoP2$؀ 8 (%{Rb" x_yrFYNkXQLಬ$'Z…21Z8:Qic$5(ޱc.<2+ڽ$عg/ӢTCIoI`0Ѫ3=;#>tP%k>t@6ֹr ;v,P=qpM>syYZ؟ATVU1?ͷ6gcc(֑%*%)Il$w6EYnC$erEM,c*iV#NRZGտ>bUA$)w-SOƵ]Eף,YA^l) Go%zNI k,D9 I c!LMNPUY J2')V6Envt4pSrϗɣ78)Q$6=~[Yz[l7PFj6h$1U%j(敗_a 4թlRpMyQ0(#*([|QZKYV=z`*volknOx`{0 x1xIfq.Ν=C;Fwl) w:Ϡ?5s#;gm":Ee-fF!dmmw9a1 p*(˒^g޽2G"cXXXduuG8┧~nL"Xǔe]gg7Z w\177ѓɚu$T|gܽes%^{iZbGܼ~vc=carf}3=3 yڽ6Id)Y-$&IVșajjz46m^ڽ~?gP 0i&"?2zu>|Ғ5ߞb=8ĸ5;Q`$X1`M* ;9Kji (GQT 7C&Mlʲ$hѥ;>. 큆6'.X!Y(&IV\lelr'cԺnDH3(عgyÿ7oJ !#HeH14Vg41JQ}5qk\׸5qk\%\}hI&=DDJ1pt=@VvTU:!Q qYiLPw'"鹆/ g \ pkC=1F&d]j)临(/ri+"IjdPJ%X2+X[${hqJs6 Iᜧ ()*KG6@etP8FG8+*Z9C(҆ A' N]$kZQ4?)5Rj#H5"Gwa ׎j!XYKS[h*u9p?Syի78~ Y4(h ^+p>3s8&#Z %|IInD9(@>ʉƕw9{**H˙G@hw6ťnPb%m[]P{L, 4S'r1ZA4mp{(bqnr½&fm}}{HǘQHlEA/<*\#<:t)VW^ϸzW\׾A" :hjYJk>|>~W"ԉ3|Ugzzu&qat /AbֆHkv,,0332bЧ٬˹EUr9^?ߣݢ*\r+?\噛gfn{YY[e:-~ßw$ 6 )1#b9BirA#9s073}LQvټM[-58[oͭ+׸z"{m9vi2s[ɿWZҬF%"/xknAηR|)޽ZAv(͉Sܜ_JH(rDhɲ(z}*@k,ݣR9I&G<([+*0{8|hZp2P e(gk+:n15`b%(zosgYysI ׿(6ɽ(QR%&F᪂4N»57֙[E@ǏV}6esgQD8멬' J'O+/s&CV @%6Z^C#]+EyiQ *%kyN{%#WeB#uml@FJ\FSֺ1He8VW)5?Y+Ƅ8މБr X#eOkF63 e)(n}h`gCz(y*FXLDYJ pA<UhUT xG8̗J `}8r}8yUsjqXg)%?? 矡& (܀n}IA?CtΠ? @x} GJ]T#»Al"ʪ @OEu,&K"^)%ڈzVSU%-wMO  (Z9y3`ԋ ."lu%ʳ0 `0}؏2KwtW^yKvܢضhͮݻ$V+gQg\[煯HŤILexϥg\{y΍7ɋIgQ>e۴@~zMU`Y-%bn6rh!567VٱsGdeaa-1;=VSH&QK2Rlmo61J%(brf\ҭjyfqYHItr'qh }@ }2@#$qNmYDQŢDY$XdU3ڻdF'JS{Z>g8>*kƓ1K6M0)K

(۱,b Z) V da>TGH á{ m0a궙q8,h·ΏW<~x{{{ 5eib =Nd)Ly,S )ghc1C9T,N$uZN}84XKq@H ( %w1IPY.ٸf\,m ]; #0:UUؔb;(eCӝ}^U0,wzb)R7`-\&Lp0`OnNsuH<  UkX@t'CBaA{M#P$/fiCzYtM IRa߀MQ6n}˲в @;v$ iZ7Fd1)R5*\ Xc2_YY1mS]hKU/ndZWKc'ֹȢrW:HvU\ X"n2}?<%c^WHxo> eMFOOI!6ָ 4?%, F'99U PNr!۔dZ ڔv!|-3F!`Gw~ýf3X |o~yMcn GiܭSwb3J C&[H5/5gДC9TD= /_U,80pkAW8,PI_޾1ǖ4"xvrTq=|\6/A־a9,3c`UgF*3䵪*ϩy6_{^E?QY gMk —Z,U4a|P+nnŪ5gW8d׊5pJmp3;;CeYp8P`XO+|8Zhˢ7[k@)6 u? kV+j++/vӍ:xuvZʅdc=c=c=Ͱc8B+d'M`ɷ4MhdJB2- QvV!L򹳎Z)1z5^Wime^cjkيMv 1O6<5 ҒN|/1Jy~+7bv: ,lO 40m FJ&UDd^9<62xW <9 =Do_wG-hmA[;Yp6PV,wBm6>VIꔋZDTYHyV2nFu] Xl$0dYVWMɸpѴfL{ZI%uQ+*H%Y+V'VtOfFqxU1$F}lt7V% \ -r2M6*8Z0Cb*9f^ LםJ&3l4jC6ٷČC ~$*a`65d2gZR}P 68ǚ0T@P.%aSU(9ilyP%FL&11{"`ˉC}鳜>͙]}l8KXZQȍՓ Gց .UMoKV6VRA|>a 5}J-NɊʳWOcC)/0oNlܚɈM=Alp 6,}~ӟAv7|_-xﲑ=|x}#kmOô_r.4k]*@К=l)QO`2/zoFs3O| *+w>i0]\4ס\pi$i6l˼\aJ6Ma2km^@yB.ETxd.x`O{{{FdTT#U F `4+P@GRG-T*s-b(eaA5/5̀G+!`'Tgz>c9y8Y2= &QG"JYVȄE&E0U.RLi,rEM!mY :]Sf˒`Cd%+/ɽ)edfK8=|(qz{`-4*G pLƠ X5UKHgmX%mE0N] (ZSѝo̮ȣEfMcSKK5Pjw$haK6[P7lblw+P'` +N_L& H]jur7 ӭiAs|{=^Ϝ6h-aM :I8 FgsV)(쵫} 5 ,!૚xtѹ?x$[V6'P)wFS̐޷ܞ&虗>, ;'YNL)fZpdCDJ,7 lPxuYy6Ĝ HZVp=>ƻkrlk*_\OyB2;GGٻ2p>ǎ>p>Pۂq}OL }`q`8C}Ұv>Ng޽;}VR~p׸<+kM?(㩯ӵ4AuKʃ źdfK{{{0K@wdbR*eDYhPfoJXB XhVR@YX1%+k1~'k+0_`be6Y!۔DV`r\)ujQ( 쏞S2Ku CSk*v+ub; ȍ[bʔDdV%P0Tп8b*@DAmUz$9,BXjj UPdAd*% u`9}0QʻQX(GH\s>A4둒llw! q|ɖB *YSumE>1VG6wbj4J?Af?нr& K"w|f Um&s4A36"_$zpސgWr6PWSƀלK!żm0cDbk$֪nS`P094#_\91[\2a׎a@->ښ|˲QRJA;p8GJ ts`c`] R,0#}Z+" uz8b3Ԫ&).\CA0luIZ!ev,"JE]P z~\;/@ ^x'&^x.۳уy.By"ІsLV+Y㓫 6Z2MgLʸKbzŗ?-'P3O*ZQ{B{%K%X&o IDAT2YC4\eḡ㕲9X<˘2T\[Jj96' "r#Un 5'Ԙ(Yw;Aŋ!}c.Lx@R7o$ ^`;R*vt|>}eR꽳3Y/@ 9p]N7xr{#*copI {9bƪ# x!s~-N!vnmbY1Q%JP0Ynf .O~`XA)AĊ$y(7X gQ`YżК̪=jwθ'u\C@'S=.@:kkLxÇME$\L`UR)߬2°ARa%\`~ּD^UxLR:HjW3.0=v޺u'[6F.M`e`xʚ.S6& m eS9_ʫ\-j@>;@b(oh$)Xj:*x)9Ȍe^vRB>bfYw5}i`C}~7@R}h{zFʆ./X-<&>p^ LM-]UX1oOlک,8u,vN vuuuy kP#/dlPUYnXב'Y8YQz u8,dwӥ9~7k8.:x,3.or7bR_ EH`Ќ\?ܢ?6R\vyjUE>&PbXއe2y0̆+横F hsMUyWPÇ6=j@6򴦘@{)fK;0%1j}[uJg ;$_5c=:biU|v kNs0VG[uQ7\57t^\kE[w(7/zmzcx8`P @UrK)WSYǽ,b{{{;"'X V`>=62edN )iYVp iTIfU$X` Ci `g&->|6}g7n)> 5_7Ŵ,f~c'Y8ۏ0bأsV'²LC,LĠ, a3 =#$ zw}^H/SH{{Vnk:ճpN$'ZK|)OPfr>ɨʹV0 PKRK QɚɄJWI&y/<ӆ%/sϥgk fOyb7 %8L|ABb$%/ɣt-'$g dк-WۘϪ2Iih٘cg mAAFY8Z,$71= QrJ4S@>ϩ@`l.\h0}GFkce[Knkp#p  D4o-fqRY?ܓشVJ-̱d#9A_:a Ȳg"8w?g6x6}cYV~įߠD$G tڋQr6@NS 01`! `zNz>/`6 !2R&lF.f3 O2йS?Oڋ=s]5@cvW)PQۙvw(ِg~:w˙qxI2e\Q:TL`R85Nv2^ ̿+Z1oLZVJ) X 03?:ӡ`da^ux-F0Hˎ}J@en_}UŇۆm8s717HX{c8 2`Y;0~_ښ!c`a DzһL8%~h|+++⊟w4i)@l6 ņ5@JfTӳ&3},ÙӃ͓Vv@o 0 t1d!+Z3c6еeqpbiHXB.W)S3tOdwV)ヌdel(K'0R=CY%(T0%: 0 \%B}Gw APR(5iHp>ZtXk}}.Xb< Y @/`źMY?AKxOE p${ԳbI;YK:zzA,X5<|404(td) v=?Aϫ^Z%#?AObX& IZ SA1\r[&0`ݹT5u缛3pKU؏ 7DEHz[9Hv-Ғ@gs _8A\{%̱LRxΡq=G6!J5G53־<;~]OZn<֙nwz-Cu+7Ȯ]n+ۆ? //idAqk0 t}]ʎe6ʗXs<pJl{Gm[pm譡köXQv}üdnRT3sv, ~y;e9XL7+۶ knYjW1_W\qW\qW\q?"<(KT{dFYJӻY|xX̓f{COFP#bi bhKGtmcf zHg rKo@K%mXrW\ 2 a6)Ǜ~ZkSF6d MN  @bkYDNcPd *;"'[iCm9FRi*(  N ~͚=B8'%P(Iα+[Q`,.L-0"1},ϷeQ7sAv¼dX|~ȑx=%SI鍾NPM(wݓ>lSM\o&TpG& M`/ٴZ8N|,#M^"iHBVS;= 1%^1rdsX&ba 3c[@ =GF |19 <'j(l` \ȼbV3eN?+MPT `;F)[¯B:_$=A֞cʑ5U]<521c?ǥdw OZR\j|}~9C??syw `&pt *0N:.5Xp)E0##Y-{6mpm`٘cȱ/0gonKJK^N\=ٳ&\vT zk#8flXѿ_G\9a/3&kf+ƪT".#j?F?ɇnN%γYxڐ*o{5ؠ+מ@\Ԛ̝2s^:s6*(x9c"z#$9'@NwgynޱuJ~#8%',qH+Nyֻ+Z`]W|rö fvt|zw9m`6tl2%೗ܶ}o[ `[][6 Ka*v-}%Aޱ DۆvCmn27|x}ASAo"ǗkX5n+ {Úrv17U< A:4U|:xM+++Q Uu>zTUEwhKF,KLZa'pcŒvQXH$!8,Bf ZG TKi+ NnJllnN*Zk$d9"SW7m)l"BeXS8f CBHuQAYN:-inp&smh""6 Y,з&о~+]O Nfʏ;Kȭ畛&l>)Ŝ 3L&)Nib2^-j8Dz? BMOPBh,'05?Y>jk'I`dG'˜M pc@+HzzQDɤwM8x1& pm1/,}y53~&jRj1 V"׬- ve??Y}G_ ?E_7M~9HՔ0'n6ՔC=xgH!̽'<L)J%: TX4_ ?\Ud+|J3湣r{<[#rx&q ?THUptz5yP .%IYϵ?K<V DҞ- L.U\EvH6|{=)TΙuW|cGSrs:Л ac@RȃQD^`e56 P>Ob?L{2*3kh~2^L:%=\CzNFHByNFU6Mޡ ؏E}UJ:,Ib6CG' !8Y^Ib0 I$@Xy%hQi>P2"Ps%q.ŘL>ֽH>{[$E٘숒" G\))ךzٕqa{RM6R)p$6°:9jX{|FyOYźe׎@O y |b³Y rFYL\/T~|Z|^sR=V/f柒=|ث?Ѣ]1'\|s>Hr^7qK>>OZfS=pJz璷\ m0ú,xy`}@>ұ. pF ,ۆwhvs 3p8|Uo<vm]+1C`NhuM5c`"PЫm8.&~ƃweU2)ZOy|z@lKQSSj̎ÿ+++ N ~")lƂ9A* 3ÔcXT|(%uQ؀ȮЦ> `gOdh<n,ApdICHcP ȬL6t^CoR5 F@b`mX4,s<)QEr,Hy:,C'E4Y$5OVhM/{ZF BO>J4JNT׈H Dս% mX:=&3@T}REJ&dWN LIHӋ@(}26ov*PA,H_NuvSNIPHӭd̳/)#Wm3-v&^@Hl7"SS%U!%?P)^6(-J@RA:rsژ_՚ ҭ& d9D q+SByLs:Z*X)A<('Xd{LjA}AўgeYLGpCI™`py ATi 'σAOutzWc@Ai4y 01ie)Y?|7$W}j8Xs^Mv)+-uwٌP೜R8x/Wc`]on7s\d3#"aGã_8=˓U.1lԛb?ƚ0sOWA7)m(+" BnŦGzGq=0a]sGq/?.9?Bo^?!~_~њh>sFLlrɳcȼq'r#x~H ,>]O@<3Q;j!7A5' hWY攨Άj`ͼs1 xܻ+||uT;JHK<7mI0A5~ _u7j(l?@ӎ&9V|LOP/+⛌KqǗض%7ʸv VR6wK?^c#NL114-ɲJ#dۆqsE K xڌ=1km!cek/ z~&U,1P̳DR22A^O ^%ΣdOu NVpy$N_ʻb,Rfx$hH504X)i(),LOR>l=HkjcE( 5} gTS'P dEB/Д-A:8'[M%Б̻\= {[==eJ2 8>e-%6=d"ᆺgoMb5R[t>lP"}H`YsFtr{3".#}cUR!}< (-[Z?'CYv"o%0n\CTxfgRSޛ0gÁTӔjW^H%nɤ UV)_ŞyLFy딝Thh0; //8TS@ZQZicDQ {Tv@J+z2ce-m6',B1c  ]bm]pa؛a-'rE12%lN"4w5лbY:|a'߾e]k]=`n8"@6(Rϼ fg*=%p0Fep l~(D5 Cϧ(p t>@5sAdK^Xj&$xeT|~+++⊌bi+07b` |qmɂX'*A{N"%-E@ d26}zg:FNU5%X4ƂD*u`t'aTBP2-”= @R82 K+ڔVz 4`fc^CW -FFH;(%-̂LbQJy_NmٜNuPpYk2Q[Uu@lWHqGk+|Pg"tk &dwamK8-YfRU8I`LiEq5A#}4Y/yxb\nD \@B$W܄M1%O,gQYp ̀DɃ *zK6XbtN aCU D?pSnHXKY˺VAy+0, <5 :D-ÑAvJ2' 76ēGiB%QͿoɐCΟ59^yJkڍ1/s|toDCVH7xR%+Rl M"j㨆()u͹@@h'\H~b`j y7uYǐSFVNq$1XtASaz9ӵUA[|Nx!2#R2l\ڡ*M,|&JKq _D:Fr0SSlqV~ h^޲ +?1VK]/u `ñ~p7_x_boO uێB[򩦐{>~j(T|YjH'l@͵O$sm#g SNx7ݰV3Y!SfzNOu9u~ϩt6=%nc^;{];~Ju3TL?`R?4b\/lȥ>+⛋684җ_7%X/u&j]:8pUE|ax ؝rʞ|]XE/۲~0scAKHдwMex EN@o eA_負6RR)8}d$qwk{}ݰzC++++~֜~Y(d!E.sGbA0pp؞,G.t/>f]4 6l!,#ƔTQY_SR1޵dJ*OghzAo YJ_t_;k62d&8Q aO84Jn]AZKR:`Z6)'Z( SZxɆmI9z2D9Vm` t["HSNZEa1 l+<Dw ˺ѢpCWk59' S̠mV\e2I0d=1*P)u,j+RdKw `J% 4"WE|*OieV`n JvI,TlPiYЦyמf@IA/1[K'\o %06%EL9v9`){I\/@Hя6E2UCJPQY&996~%P̦)]"lCl x} d*dLG`cNK0aydsV̅U5 d!V!#jgVj 0 [=ٚdb臞LY' %p~nm,AWNЏ OU\]&)SKWkߒ"Zr&Zcq OF |sZLs7 &4wNEG@[y{k]Ђ 鑌%RR |]go + {~xM?_Uxn\{>T`f)~іRs)S4G~==+^__~1m?xW}]yn9[^j@zj>4K. W,0Uh;PQ{'ia~uVsyC2Ie\}`հ6M>\dLcsts_ȁL̗G;Kqs͆Nj:?smDu@^zPi߾p+kmˊcc"з5eIsh7}6q J3|<1xGmxM:`fxCl/Bu/n֒ l]@KX~9=e[b 붝nضx`A2ai:`pH5zxs}`tU%岌ӯ+++~!f,: Z: 6В4*YyY@TE *phxVIW;d'qM[I‡uY !/jҸ) %{@)lЖ>z ɤ ٵ>=Zk8@gH8,ᷳZV)eLj2pKXSZWhu^<~M 7v?:`〇a pF<ZMphf%u; u!Tĸahc@8t K07nTvh] R#plؖ[Й=k~ $ cIֺ J.ƈ8i{v߶/_O뿂=_lݿ~1,N(͹PM_3(u)%$jJvmNt s{ys-f{0B ZN;!֏Z{QY/ڙg{]l)u/پQTff?lNG4Ж# qF T%wFR݁gq˟M?ndrמȇ07DW\q7_ vmY@)ӱ+`fKnaikϗw _}|.%唣d<8*Yc@A/ "f8lf\enmEkzHɝXî/ۚLe/ Ex<#pn~@5uvH3@%mYdW\qW\qW\q?8VP !Ze gZ;jp;vKPX% wb% Q, 7e3u}QYa ɞiJF/yA`U,8R u@dx8X|2,D E\jEWPNVd,b#Au}y)΋"tL&A^-ydQHTH^z 1z*BKYAY3g#B s!Sq,,L 4eSBRȄK69AzʨZEMҹ4e z -H! == ]%B2@KH>#T+";P*wEVnɒӞ@fhsN=jW"'Z1ɒ+C|bz"^5? +>X &(Cg;U=뒔&) tl{J{UNu?d@%G[@i6_4wiAPvVH,WeOD{JA fym|$)d~ɛ"R5Ce+gf)+MTFws4 e3 ':RѠ)x @qL;=ۻ$k_)Bh>_~H<=@a~z*-1 }H%. 82+&2- R|~ǁ[k2E٨NYk fC&g \ D:R_fe]~嗈 mKpP8[g m]蓟gѲ*ge6~Eu ״.[Sv;sG*K#hT5C[He%s, ҼyRCQV3Yk_ ] zc_qL|-vDzcnkv:4mcG)Yx;,Aϑ;oy]D#Yc Zx*pu]om8Ɓcx 6pD*+쬱u b }*h jvI.ij!UET,iZ9O`R)|POFk~G>ϒl<7862ql(PHV F4n& `ns#et'Y =ϵ,uo$kEmQ!e:&;,3 \eC1ˊFI,O!C6q6hg#d.Fa%^2  6\r: ݌&]SنoX_F-WgJ|U\uZʽYxۂ""UGf?c7xi}}?} ş7 ˲`6F(ͽ+"MAM.>"{7[h9wdbQ癨~@Vn=̆p^()eN J6kl"1 }å٘E1^S Hlg܁b@j%G)u>/:&ٜbH)5]TTe`Ƃ֚t܂ H6MII>+z |y2 Y˃Pc︿q u]Ee[Pԕ1mCS!w[c צְ_ǎ}?p[;^o+j]%Xu´%%>]q `;(ї7l+<4DK-_qTΙ+++_D cGy ꔉhb@SMP!wJ`@1'aTZ#xQއm"A=8is>E[,b,]RUtKA&TL˒LbhB) j'e)j$xc<%͗ΈD"zd! zaL/axdǁu٠MG4CJ@ٺc8Y^}hIޱ1,wxPBi5L[!c,H s. OR8vo ,G!1" EitA>i lVћ,gӅ+d@ X1Y} ʂ6@OO|b bJ2ޔ#,eY6P|`HvveaqpX6A8 DU qcP u( <v{:WMyZoohC; QM DPlT$@cq YyJ i=K%Y d{llf#' SEb&W dT=׶*1MDF"5gd Gӎa6RS2R]<0ռK$(j'^Edg9">sGB p9?&0]|&:A-_K2W`HKEyŞ`F5m {,&lpg6)]sd+:fN M93J+KI''@I&'3I"ٍ V'q)U=A*8ل)lFV-0)Hpje>WL 7#g8{r-;dUWZR.2Ǖ;()y}Ou^+7]ybK4Q>bI`Y$@ )$qu u;nP7<D pG_:k*8{^[:lEdV}ν i 4!?~l0ldȡ8$]UVd/͡K9{٧vUVfdUXkaK>caRPwW\qW\qW\q?_ۂ֚X,~^RvޝSHH7,āTZN1 T2XQ{|*Wyz_:BkqUTBrIVO^Y%?Go]xE|FQDM^onǁbY*⨵(:8 E,&tRJYе`,oE-M)`]< -omO&$ oX'; vKY94V8&([O8 = p3r/6 o>fL/>b6ILK5p鄟d]r' By!@)~ H0PPs Q.fjɂ?*[8548,HLF+wyr]kZs @C~f H05Ai^҇3&ؓk"f'Du-P@a2>3C)Bo=%8ӼsSYrSvu>Gհ{VDt?9@qs2d(/v.Ȝ ^k 8"FzE˗ɪfFST[GT|WiNlClQ'p{^rh.q3kہq "&sYL j&Z)?c]o*1Ƥ1jG!B1h^e F'8^J@f@ n;{_ß =6_ぷ~9s|p[h\`l֨#UH2-A6p<>sBё~zf3~Vl1"Ĭ?#oOg`HV\ʣzd͐:#M爥$\|>d>œ>׎$ \@umiݺ&D%H=ӦZLECF\qVчRS!=r$qta;6cZ*Jw,b?嚥prRP`+Ȍ֥٥ŇQu߼v^S7vwKZ# =K|3u'1mׂ۲`vl!̰oK"bB|aK>`߷+++5+ ed`Xڶ K)MY,;&BvOTP )X\]<^<,N)홌GkK}$gaӍ@|*1&@Bh.cVqOo:OhPG'dzNJzPVݱѻ =G(=Z FIaU%{xbRL))ہO(}߀**W,BkQqXCx*d=#ju`:օ7_7 p^b{h.-/7 5$<LP9˹F*AŚSMk|i5(U6GvH XĂJ R_T A;`@o@W3ϫsZ8,&_ /+@ :JӃMϢ uyƞkp }4!P9md&Sw0 '@륐~v"G3ϙ`RN`#Oh9|{G X- ~;TF'@d&lWّ2&u'$ { \dW'o;ḼM@}&s=A =ȟG )5䘎))a >YQcN_Q3 ܿzO aaRxSr"wlۺRa)O?:Kl{KG-+6、`|6lU﹢`u>v@ q[*Phh1: V_?wW? ˿?s_ѶO_O7X^_4DME9Ɯے#y Yɸf'=1d~J)bIPlȺ6268,-! R̙e!fZkUH0Aƍlᚷ& \te8@3bq1sB':x \fTTsR<ԋu9M`^c䂿+~a`3.x‚NݰtNݨ+X@ꂪzhm}|/eY؝ }?|Lc|!q߰mzmYQ*ۆen+Z ޱ!./^+0e u/?c6K)Xox<}qPJZ }1fPyW\qW\qW\a"`W69ZպхPDϦd}3&gLY ur* NiU_D#(FFJE 7GR@;R;Gn7!8"kGL2MN_a,/Ug0s#嬻-b0+b)4n)ϨLJd/8jJR2@Ux97ؼmSZvY)SQcz'5^Q+>u@dNWƋϲ[g=R`eBsqZ]Lћ_YOS>l2$6Ny.e=bEp=AYt91) h*FJicЦ?%g~d%k)#7O5V\rɮ6XXdÂM 3WZpcYoʊ:Ά w 4}Nr=5AdKu6|U>7}_W|{ſ? u|1o~ml7;Wџ)~򫿊ϿD|93RAS ܳ gF4g{z( 1C / dz)HVm6`KCͼp$otT,DS#s٠2ߐ ɖĹs#8tܳi>neWЗ&-?2f3y0o=]q?Vu&S:K-(00?[Ʈ#؍XKQ po۶avZ|8xJw,kEm?w{CY RF|:B>t<}ݢ&N?c(hclr㯸+++f$@~8N&F-mpk, @^ l%3`d:C_2Q(i6.UShFfRP%C]VOGOv$*!@ jdX^ɳo}RBasU& thӏS`K r!  c7޷,u/ @ -g qlh0{ IDATOؗ^_aaR6XYDtQ$nG)*v 8Z* @ԅ˴N ˦;K2 ocCbrYZLĒFb!^#c`ɄKЖbF05r"L60I\Cg)EHK v^#3GHm qQa'!耛<%9Atd}BkmZ h*upKn6,@F)my-&U`˳5#UZLm D#.Eq*@}[9q٠h/bؓ?;sgx+s{ɾd'>^b8jLqRj%(65[ϸdѼ}+(M7el3Jeʄ?+:OVT񜟛搠Y|tC~`quM/k1c}c3Fq;1p[W4<Ǧ Zؼ4%ϜT#Z 9W5|遗ۚY%Zɓ+jQc%O{v:b@sF@5_*ocǿ8ǏxV|ϱo_6'? g8^bf#fg2 MVRiGodgkZ@")Kx&O1rsxNfhlBNid&"`m>9W? FouaDM1mo ; "pw5`h}2!$ց6.&;Ѝ$2^,8HyMb_d>Pʊ1h(q/J9Lsae]QJow^?|Z ۍW ׏m/?ti3A>H dj.)35BreS"C六=%GOlNIATa.5O*37mo 0_˦HF.'V$[_ˉXj c6]̣<08ŋh2 pIҜJ';aq5FlJp7׾際\qvptXqteV1@̒$YFysZ}uYQѰmUH?__?e7?v,쳳6$W^QC/T;{K-hB:8zǾ`ҫciM2LF e,W\qW\qW\q?07(dEG`σm*"ulb蓹d伲QUg)cJ9z#b\KN., 搁8̬7#<WEýwd$;y ~_'pP11ޑ.o- . t2B$q&F-\wzz"T0nPeF\WUe]XЎ`,uAkYǎv ~xE u~h< \,KC!;LYb+ s<tGQYl [YK[^2 J=-d}w 1 ev6'Pl5ӲAwQ Q1B| KPmj505p8E@tQ}w\7yO M.)lz+$rӭu{6fQurC਻!:A"u 1] OЮLl @k {>eO&CdN 1nQ!5ùdB2dkfGh 1yO2Ryh] OAEpd\!5G3 k A@56vbcdu6%Ы|cT3'q lsf2hPƙ篸_\|+x{2D u[g}hw&c!OޗuE __a-ckd>ڠM*X}tܖRx7+ڠtJE/>Z%Kc;R:=\JTuŇJCoϕyEj)묪^+ֺ 0]Ӷ9r+++ ,gRGOfGfX,ě=jԕ,g KX>c9A ς,~t;:๷1,Q|PM`Ԑg ,u@= Sʑb:AYDdB!?O/^d*`5XDdT8 ֻu 5wLZ+*h#&p,4cL@dPΨvvx7|x=ЏRmv|G/wxWܶ uZWxTTPd2V`8F8V5#04G:54^CLG7xr1nQh>OjZL ,Z\ s>uwdV#Yw9{:8R1еf 2@~`2!P"Ol>5xgPD؊) mU@gI`(TCޢmOa1&P'P,~\T?ٿs 0l%2g0`~/l *8) S]S1F DmEODH>_O/xN[PNyebPF2u,TRn=YC5sI]޹55,EOp^jOR@MV`@-5*T`JzS )u|c`7ߔ'`? N%UVd f{xl%zV> Z3z4Czo+ G;PJ˹8Uz>[Coc}m{`q_?/lNOqZ p$C( j`?hO^Ӽ@Ze $y9e6N."M5DNgFM߹g^f`CGy\d=B$&(,xw9C{<&9^Ca';s`8v5\8W[^z R+bzܖ5q___F@5r[p!Őij!j J,@g7vwKOGp80uAZX i`GuZ66CzQY<5_DXnd:gs ,d7QUb%xbV8y|W\qW\qW\q=zk(> Tl`h[ 6No!:9_ΚFVc?@#:+Fdk+:>O`89YbdH&k*dP՝1>f'3[Rʚ簾kOHES71BOs6* c( >_C2&u }c' e8Y 0?d'XL )Kʃq;B@K]q4LOdcFCR!fpF2Vu-`$ \[CHpPs Y&C֍sVM'&0>6/1'1AnpsϼFs=R5T̟&F0Č/gE!e5_b]5CA:z8Hﴹ#rd\'l@!ŵtu9o)s>˕nj.Ɋ &MO,8\E Ul(HOq7N\ G;䡋 z-r"I9F&n|fg,xf6i;3<z*fq[*Jz)`jN 5x18_TU ך/cz]A\+F(W?O_~~#O_;uAxw~C \̃@yu/ivzx8U `^^vcL V9FUnb>EƝs(C.F&ܟ4wO;O\A ?=Q Wg#G怙 5P3lH{gd T1c`l\HK+A 8v`]*_zGwX)𺠔۲ׂbwEooRcG_F@u1:;7n驥KA nt{QVԥox;eM݅l]VulǁCRxaoضhkm]`Ap{Tr'\qW\qW\qWc՟@f&ɼ'| &XFO@Y,ZLLIB{;Wc 'LO;yA*n,l⎐Ȋ 'xJ03πc=}qy7zXpe$#M#zGŜl,P rgοJ{9CW~K( >Sdmy=oGi&aYe!۬'dyΩ7#EM9B0ي9sM4` zK AmJ RHVs @{b2K8b5wڷ];X܁y{zjeMر: pO9 RPX֕vFɨ^~c`;:jDҔMdaTp}գ3FD?{e|hZ;P/u0^kY ~7G]kHfUW;•~68As0g#Tk20L=Ad#dzbnPFeFڻ&6Ԟ@D[ &+A~]WI7q?@ ;Z ;jk R1ڜZO uR;h+9mXʍ9wnUc:W\qW\qW\qW#LXz>TػLj qTTD%U  !?}G]R*@suw2@EOR TBjOY'*6$ L3$"_q@s d l|SZPy ,*R Zc8rKyJ`G<ȎK^s՘>l 1L ɘd'{s&]NpCȾnxO!'{d.׀%hSyoHLfr$z .g2  :&̖,@ט^l:FPӍ|5g`CBZ:O>$i5c'wi#rE1jH8=a&*;1@ sU 5vP2K3';'NGk(u% #A@1ss Rspղ{9]jA6È??N IDATO[ɏ ;|thkP+_%;W<5kɎ!a&z)~]]^930X2s%CisR&_E?O9S^\E~*8|^E?SAɮvI]\sOsƹOp:/-GH9,}u]z<⳰\JW\o&vJk$3z A[=R8RzǏxl譡.ҒFv3 #$`؏/e)oL:A.a%eYq[iQo +u>Z=SX  F(ܧ)q,ia+@)dX }E`S]q$ldZ/9h bRw1p2.я! d>pJiL@3Gњ>Wz'Q\%`vwZ Z]ck;u]p3MP*,a8aDrrcH0J0ݰ,EL1V6c 4E Eo(K?NPZ1!ӯ0>T d=&AHoW% P<&g{9F X> ]9 Rlr1)%K6DCv֭"W2Lm艧{'욍'y'ˤ>c黙 O`Rfvh@m6dd+?yꞠGOoJjk:= P3 ~'# f^rmde2LV)xǔErڜG0d3֙7ɼ,K(ʎl075'<:$'܋ >f 72nN,G2 ^Țu=5;Jq4q$S=(\>ũ8$ԊZ$/ Gt᚟< nbT˂>Y  ī'c5= Tʹ fv)*:y)e Vh?ھoXU5pl '}/w~ SlCuXK)D_ 6& .e)]+ 7;U<}mV e)Ry#*@Qyɋ6$jnN\^)p]jK S&}i! Ie@G56@jYpWb[oްԎ P:XmGo낺Zؙ.wݺhV,5l &|a1X֊Tv-u dϭV`| rL]EfEx}߱V8;Ѻ:·4. xo;Oo8K~>ps`JqW\qW\qW\q3L+%-8%O#jN_bXDA% nإ{ a*1=>E@UhXND(''s \6l+CS5\d  rcjNӧc1)ML8s??ǒEE"L3Apn'6cA.Օ 4ˑ=)u6۶ s ڏ }OaQ ǘlȚ]U{!)P1ޱ#X y6C`ܧ !.3gwǺ.3Άdqtq:ch թBy[hC`sK_Ӽ`~.~Wo~_qXjŰRWٗ_O&yLC=- {$eS܁yTp1ಔR5Wf 2[ʼa(d{Z$K8m*`3LGj{fS#anjr{afR/xk\B)oNSi tZg׉\|5)%|'ia 05juW\qbhGC 2e(qm]/WܜNىAPT>ؗ;ޱ=6=Z#`;|zphP` @ M/6`]*~/bK)k.Kھpޝe>P ꆽ>qkc7ܖ`3ر?ްWz~vxl@OBbAzI@_qW\qW\qWD70Pї0 E.} wv4*βFF ϘAkM Nr1ȳ7\l0yrDMdR1 ɈN'pAÃlbYt'M-p;, e)HW-(]#1:YkwcvzkeR@.,N/" oy_ /,? U7!`.vp͢m123Ç2`KYCʅ;N4)7nDYPkÇWZ n@@Z OAR#: ta:z;An?x16d6!5y ,rOPuKFVhCEk&(11Y(;Մv j'51/7ka-6c6YS.&0ʭ5f7 w/L J ρf J3'ћX0q?󅅼+1lm{韞6af^ ;_l|;u1R .(2-ji2`XP7L-|>}}rՅZ5؍c`:_`&8-L`˭0y#&J=,s:ɐ v$' '*Q@(y~ܒae . O ;Y~&.@%#A~f[י5)kzߊtz yAǠ&]&/45*φAa"C&敏޴k+ F .MiȸFlu8ykUeLen_laS.z>\RΤ.n0x,SawqI{RllkBl^"˷:|xzM't:M5;2e4qTu$K IDATA-pyn6GfAP㺷F)n%)-[?$me*ٺgv..l&S_v4ۗI%=9bc_kZkZkZ/R%SH# 9u;["?&`ܖ2m V5VʄV5怈PNQUVb*badj֥7r$6^CfڳJ.kVTaJ쿽)ֱk:NBatyCvf< aRBLGs̾a_و"~#qR2:wzPʔ4Z]&ۼ‹\=BN,fM;Tfڡ3SGSeRl0ua7;'OaOn`Rr9d׽2e|lKΖʼn0vz" `6) n-AG {S&tNøPiq^uD~Mmۣ"ƖJR1`u9Ch=/̞["]l#:4T p2A1m_Nu ӰM!^:lrͻ8PɄIږ\Uǿڿİ$ޑRVb*Ζgld__=xAKعhO>U uڵ%Z4'Xl\Et1kfgYO#ֺaw-:1 5G';Z{7(l2U"151EJa4j\Bdzl*ZGdX߄k_>mSYMc]i6O[9?' q&湺]3`[IH3VʾFӈgWl!kMN4xŚO@ug4[:9'KҠp[o5F3$IJ Pَվ>'8?7y/‹/سUt hy"{_ ,Q[Zk֏~/8ZkZkZkZ-a#7"k-o&B%+ry4{&\*mdNbꮰ4ƦRKmi ؔRMqOt[Ԕ]06&-'h{"IA”*Cu.vWnvQ0uEwRk?M%5ST7ɨKQ33wnx{ =yݾs{-W]ՙGy˙s6%- O=nMj3eEm!?Ԑ^튔H# 2>Zm(3 ک+sFs^"U?Dbô,q{y݈5]VZdX$IN]4Ɖ7\}ƕ(alW ?b*o)YsP :I:o7Ͻ|3]hn#B ̳mp# 2Xϥl5` ɶ(m[C+ьΒz7V1)y{"B;t6zso6r ԏw}G8rk"rk%A6[$,]o5UPpـ# +M1* M1v6.B4f]+o{ܸqL.) =h^-1;l[kJl ν#+۽)4lT|Ll)(f;=W(ŲS @TmtZ6VW5k ~ŔĠk dH0ۋ[=,al%4W= n[m˚' !Z,*8%7;f[aJ~!/(+@7}`@CũcXr%T`j4k*N2zKi(><Z ΢Sw5 K_,zd}&&CGXwTۣP\\Fwǒ8LUm-N\}:`c75(P @WP ػDUkv HmVcj0v 25ue*%Bݚi|qqc\v,-YC{.Bq CxO#jA"SDv_;ߞ{@넋>WJ1[Pv{6 Igb*h]N$iA)S/L%sZ lBzݠqF LSaygy^p5sy9}Mܹ>yjc*Kٜ,z8v3obW6hj<1컇g9@ lƺ:@40Y9ZDR7f"j7CYff7Ơx&x4H41`MnhуkdGD=8bcvFƾ:(EQ yʔg\<~x滜=~̽{w_yp~vO? " P-ޝg6%w~kyW>Zk}kZkZkZk| _lmձ1[@؆) x/3VmcLwF1WsmN 4Mbm&LNVZX@8i)JMˀFa@ǜYFd,ysL23SY&"(rR6Rj̀N9!HW2f\2+^TiucI y@C%Su⊯Z+ dD.r 8g...8?dgsvCI{<|Ggb˴,=: G'q||LF]{oֺw*.Y2gk*ʓe j 6SdYD)c;іfB(zt-T;Ϩt-Lm{Wi(~&YٰnC%x3ҢWbRƲ()s]9k9Aܚei ZA yd"1FS{&}Q1HA%B5.x+TZ;l/};UY@:3qhס{oY T7A6q`lDm٭QV1UW^]X"lCAq2Pud/GgB1oPɳe Ђ߿NzS3vh)T݀ZX͆P3e41Z=%c1m$B:c &wua7Ě g4sjXrR`ܓ+w!5{hǘ<j ul䲉d7$e'~HʴQf6SKJrw9Jʵo!)YZۓ+|Ie<{aӽ cƅe5mxL$b wwz/DQUʺMk,X ;t\e 3GGǟK9|j92Mu;؎W~V;ysφ޺9 {9n8ܳO1ޚ繊9fj6oonS^:*f %f[vm58k\`T${o5EHrKbҡz ;H:6=f}mywyi_윎2mQdTKMD.34ln'^+TN0`IBI$ePhj-rmz/߭!,K&O}fj<ӪAќ5h'OÏ %r)l6L%,{&28 % Z%>GBť]Lg{ͮBChQCխ`m~4-2@$eɋz`6; TipU A? 80b6mآj.IvL]S}>,Zڟ'0єԱ"urn/tS7?Npc`Q\bJ%ƇfoMvþwAZn-~Bq )>ݱ745F|AJu>7mlR}^GVSf"9T>=7S B(%)$3)77qZ;l"~,ͯsw%z[dFޢ8b[fcdgge&l6l C3}oH>O88QE[×jlі^z's1f*^9b*y͙{='S&sAYt|_Zng:>z7;:nBy->.|H1ҪϹLu^T"ssnɂ뤶Q4*N}| FX]٥+zBgjT^ȴl6W}\n5Ƶ+7%`L;,L])Y]PIS]e$D;}>v3%Dڻ̯\9ʧ_Φ9%_BJ5@lahڪ)~ӔIYkIr@ f֜0-ՌzaDv]2{ֱzq띋 -SJHIZCEHLܛR7d)v;mI{_yr9Ք`"_L1:=p;dE ORG71(+Gc^ wa5Rv0LOK5[@%:ƻ=T{7X3b7f%dV4!8HYٛlj(qd q;8XKπCuBɉerqlVncm&{;f2>x~>\ɶNv< ԬWAɰ_A1N$+Xú8&JeѥqD]>#aˬ"q~gZEst|W{^^ldWZ75j{c,iyij0h4V7e Oe<ûo}]Ar|n1YG6'Wyp!VɩSQB1lpv#o꼌hZ90ߑuN-w{3F%ǸkhGfs_ L%;`mLB>ϴV}qik^VF&x4$9.bsӞ kk6y}\j] l/C>*w!~t]}o돸riCMA5|]ݹ۷8:?a9{6w>{oɆ]{Ɲ[_1~,f^;exkkZkZkZkL9ۦ **(r=aN6+{{kK]uu^(c , ,Ȳ(6ΡbfUJ/P4٦h\;MШ.ŔM)nӚrRr H.f흜؆oT 9gSPٿf1JFUo^x&>b&v}][f=J{MFv -yAf$SjB4,m sʭ<ӔQ~Lwt)9-2.֕6IYl6\qgVi"S1V4iB׶^u}"f*(E\c!yg./wRJ̳e_NY=wlɮ4>2d[hsf6ɢ2)B7}_& *IUfk'e[9;ED$GՀzLT5@9qdYߊgŒݴw*2R:9-[vV3%%]P*TTRJZг76Ӛ9)D U&缷ʝ? cnjTh@bdGz{6~^ٕl}kcґcdYؒ9ڂ;w;Y>ͷo3OMx n֙y?/|wq->|bwS<͏9rwYk>ZZkZkZkZkWn,uHQn IDATDɅޛort;;(Qsɳ"Wl!B̊ZqU2MY(氒LʥF8 5$JOqhtKvٞ +*!_3WJȢ.(*hGB6*<U0X+)QcjCqśl3ycdwJܮ.P\غzqɕ#JtL BNy>jSoz.qi31UĮt.}-m\ -/{_''G֤$&+ycAeޝ}ܽ9vix<|P ӟtNePfp3&oڈ]v"[k1)kK&B.n/vL5(aN˾M1q|p\?`mS*h0cl`Y3םCK_IUc^vQ;ɗxxﭷ8>sO%[?W^o:g_W_囿M~CBj6WdzG9{oOW}6k[ys{eZkZkZk~\}oK?C"#}l. FNf˚|'[cRmO$Q]٤>˂ f6bvc{Jp 2\We.YXYnj@Cuf<@N|qW\ƶjqښWJ.n45d < 'ߥ<\EmtSpki7B]Z3xBkc̠B:ϳ[k̭qkeY͠C; OU0V̦7 iJW$޶嶊5*H%Ӂmk6f"orh{2,-2>$ iٽjho\UKj1_SJ٣@-An?1zo~dP<`}轏׆2099۵8lވ׫h؆+>nr8Hk8D\I Li+I`/ btN-^qNSWXSǾ6xcp:а9u1w]Zfx" prP98kv _ЋdrA{=" &\ϛWh4q\9,,מQ)#m(v Jٟ0q:K@]̯\@#ϱ~x j#+>_sr\/9lG;k&iXԄe7 X1q|s<.QBU)Sg7ߦfNo^| {o)M.wgQ{+"/rwx嗹q?[ZytZkZkZkRWmX5`G *Gȥl)%dq\ 2]Q`t?+F'TYWケNg* CcmJmHeɛ bNDYh5cib.b;0kfh4 &^HĕΆ7܀E/FItZZ 1K3ZCqJnЈKbn >26s&vt]\...n'YF՜@R&Ep?K\f{BΕ瞒z%#..xyx>h߳]nvS@Sn\{vW`psP5aY_7;iq} o1jvxu(f`km-7U>OǏ,WXPޘ{>3GH*a<4A pvPF+5i}7[K֠~_ mܾ,h}&waaޖql2.}m{6̕RRk\HQ~U=Z{ ZkZkZkl?+,V)L [Nm7H>%ɕp e֕55M˧SWU% X+aB*P$\82P i(3!J6g]]컯83T)Z*@rf{Qŵ,f41S}UmʳTy6:xpuxpp%f-ǙϺ."iVsMޕ6VlWJLR2M d>eŪS um{2C@m`wccv ciy| lak1Z iCn%=ocy:,XuX[lC{ƀT|XB`NY 7c^m(z"}{g">tNfz)2ŸW_]ٟ$ԥ>q \z u\s; ϼ=xm NyOkjU7[f'a."VC=[;C"9W5c=EcnRq]Kˁ}_|"7M?Uuhϗano˵ol&DCLqb,s:`PueP+ lgM]llǛ\+x}MI9q~vL9ѵψd6%Sr&,;wwo-6Cs! I'~wx17"B|Qs?2m5{ĘOqŹ{C̑> sEF|=t<2{C^lpztv'n/=~kS_ _uoSY~\~z];z}.#~_&os5~wv׷r?{_?َ,~w۶ZkZkZk֟S"ܦH %[>z^cɶ8lWEs֤{Q- SJfCl+pD'԰ F[C۞7Z uv|jdP;;c#\5mM9fɾq nӢB*Y;9$I9+zSR:9($yQP)e\T!۲:t ׫m؋o4n>w0SJ1ؔBb92Mufk\LBR[R !q=)gW gfCnkc3mhSÁH",f9e*͔h=2+:y"v"8XXs&AH*,,!]ߋ3D7T3VN2bs''Σ1UW ^\>]*J&;ON"Jk3@yXy{3u,ك\Z J {ϱ8վ@DuDٿ%˶M`#yެ[6nD֨fW6 2\ < %hdbՅ&2%*aܝR \؋'>?{ ~SuN4çP[sE9tPZq{flckawav5LqP]Q]2wCοy~#ݕM,FdaCL{K'U u/7@ {؉FV"#:דbCtq{DmjkR>h)H6K*%?d4o`k)μf ]g,'4=݆Y%)@':jپ2s3r)WvoLə)gr<'OdNnMGo>w޹3r|S/sqqf;/~KN//}[*eRRs I(j=GZk֏WǔTp||>_ZY#e&,[(K~~Ϝ2_VݽKTV vdJ=rEÕʝw_g{Cxms;<'O~WM޻?\yӤ\^~ABytq9}ͱ}n~{wW>y={<<|s? \{c޾y$问w.hEmK_EÿU^x9vW{~Wn}@m3w4ZkZkZk[ F1Sr$Yr%+HM8Y-2elJ)ԲR9 m(jBٲD +^+(2dhPƸ1Y/dVmox"K '`C ;k5ͭ0c7e&˵[;Ka`Nsʷޡ ɹV$%6 +K)l&PO/fUm֠`6ݳ U_9mddBgWbqg?*oPl< ֚6S. y2)ƵYp8NUefPcQ5l}b׫F:Զ DE"5Re`寐hmoϽ밬vz`>7)m1M.X@Ϗ|pv(%zÏ $\pxdVǺB ÔƝ(9gu,]Rrh Yo-;P2yB^rUb<Ԫ~~?p 4n<ćAU+U>N*SҪ޻<ğÂY$B|uwس/,sWխzPDz`\= Hd,2Qk)9{GNNn&0/px!?x&3<;jv3OsJGj\]#NO2M |X|>ڜZ<ɡ9a~d#k)yܳs4ĕ#=zrO`n(S̷OKf8ncp8L֝L=Pf\fxsFSkR ]Ǟo\)O? Ͻ]^z䲑) ɻS/w8}+mvw\q^6??es})O\]fÃw{> g$yƓ<k&Z z__$3%o},ze~/ux'pu~e^|s2};qv>_*kDo-[kZkZkZ/T)+}c6K 0 Ք^sM^%CWjmIۻ2MH\UOl, "Mm7oJlm2IlT$Tll0U-,>!m,:l;ymøf*+wLyqX()Oi8xkS8#7\?#aP#@4(n;PqJPQO' +^:9^<uh;<](?E靲_Yfu{ w[U=7͖H ɲX  Oyc?C$8@İPdK-T6ɦfSz֭;syXk5X%ppP]շ=9ϩ[mUsn޺d`;_v(Е2ǝ̹1Pnm,@UG4kr.Kb8ygKc0- xڳ>U['1wzm^r=r>2)=SȺ Rt[N'TTcd֊E~@UUu8$kme"yVU剩F <ʫȍtݽ}#@̨+8>Μ9Ej㉧gcsOwwyUBvv')cN|qז<"9.Cs2%'R2dȉBN*_ fks|'g_$[v[93(2>Gq(B#y?Nي{#w)Ss \ >]jtǕW >NZmZS/P=wsz1e'⥯a4Xxv,pMrwr̨YV>G?٧c',/ YNq%ob'䓊g?A"'=&SBp <ӌ؏8s?fp{[iNWGW!W.g&->Xsv7YʧO<>ýۛ|J \,,,vf&w)F3)F|!vwiwYXy3,ܾl]NyS۬c2=br=ίW_U֯\pF53 t$~ U(ْs2YcAm^MıL%UxW X Hw:`-;QŚw܃zOF^ P"9%RNQ҉b;=+''3pɉKw1^XΝ0fqqH*ڔhbd4^dzB[f8wp@]yݹl6eqq ꓓmocÚ:T`\LsW(sgR,YA`k=2֓-lEp2 ]t._bE$ʻ|ڱ^++ۓY+5fMD f_39Ҝ9KY,u@͛ظKLK/5Ti6na{siiSf8~aƟ_DFca}Ν{Տ3jsX^pq裏>裏>/7[ U(I}Sx*/*t2KcIg=j#~smiZ/I f$W^ק$\v @nSI4xQzM{/?mg,yिfn9bP*_vtU[OpPAԋ *B$ڨiD9LFTIƠ _Tc4u@;NMqH-.dFU*جI8cvֻTw615MKmHehDlRHz0t hߔӶF!T83*/E%*4a-a9팑2jM}̳S) I]OU}>Z 7i[hN/tL-9G:0t %*r=.LT!T҄\ט4,Y2cVT ,Oc)̑59qs]` GuHّJ[u:{<\yF}u]US;C$b\w6ǁ\}I:JKH{GlpJYSĴᦦ,s7?3+ +\nYk^Ac'gT9X>z7kNsΗW+ߍ)ʹlܳG0Z2LS͜KFp5UpxYt6s vqiۖi8L,..4K j~'͌lFl&؉ęӧyY]]!e8>>ܹӬp qzu8qw6od2j qgj@-M+16_>7d^sk*}tﳸw&.HK={4.hxRsu!~g :wyH-s_rg/m[\)`!OsJv)I??/| Ĝ9"+pޱ~dIMωAX;Ơ,,/.}LG}h>i 'G#W^$1ܣ3yѴ|eC冔jv/;CXcAvfyVpa%W?\z[\:Fܼ}a%"E?/XY1m7nwU= t%QH; ÜhN(l9>}f0 md躄XPMb5m6\iI9]UYd"if| AVZFړ=13)䍹A @t! ^UAYxcޮcF#jZ\vMkrvyZ^/f \ZK^QQϘSeX#+M,}d9m!r̔TzD .EMWEA&րbgJB+ZtN'dQ{geU):R ^g9okY+NσPdslS S_3U0&r1"+w DgǬE|S7;فsX'SyL 9AXv~\ 8**5_Մ6ͻ y3+Pb#7IUmkU2̢Yl K9;ُ͢e3a[=դ{u.9m6'2.1I)6Vhg XA0|nܿsp̦3ves)kc~EBtMpUŅ,EE@Rml-,ԣZ|(RTR ֽ+K:sܑd=zb-®]w\[)3iQuo>bUAVgt>R^^||s|woܩ5VV XYw+裏~@/>$m3a:#&2RۤV녚{0چ{8<[_ܩK,=F.g>G|z[qy_bk&k}dHʙ +?wY]>wߥM[α:هpfB*)vݒ*k"=(W%kYm 5#d>tA3R׵$~U=^Ӷ-Wb0*5mr'U)6ݴAfn{g*ߌtװI^& G}vxԗ!Z!XRTvꂠ%E}Il@f.U3$B%}{0 塇s4 q:%hh<*) #M,,.1/09H*38906L9:<`r|x4faq,mtj㍩]7RLaH_r%~ܝL&| b}U=`7w>tѬPTŇ9=uO۟}"dwJ{Ps0KyﲽYC],,-_KG?)&o1GG]fWoqNhQ;!1X2cFfm%MXZYe`W_:칽y ~ş7jRl]`aW^?u@3>6[ UL;ܐy'cNs{8֌#B>yow?k_{˷46{w6{V/-~| 3ol =~zMubw)kO_!q)vs՚;GlU;lGL ^Hd27SӂKeeD2e6Ѣ1!z2=YP<2>9gR`*$ͨci{>wl6e>P)TYK218đ h)5xo^RL 0}S޿ib酬I!$S+!mReZN@wD&=3)?AS"(Sa@p4hQ:JQ v"D[ѪkPwZfQ"3xȕu*8ܟf^@=lcJ,g/`?eU{bj8I=rϽEyh@r'>gQ<D]os$j^5xEV 2;9o$hRon0^Ԫ2eU,s-嶻 8BNYXF~LF(")gYˌ`YUxρbbRHVKxrniBcK4t0hg'\yAjykrD4Ę(08⹝EFcpmY/B考)WC9ar凮qGǴb![9&{Iup'ˉ .g30`iy[G#\1+gpv'4/;Lp<_/G[os9B5揿ܿO=ȏ8ޟr۷>3#~k׹w{w?|iTEq{۩)Y_\q`9&hj/B9 34.5*Pʱ_!z$MJ'%H_5}sv_)[^ldln:MFì\ Zt hwY+ Nd?6jjzTUByNE/.}Im5n&.P2[$#ݻXLS~G ;9sP9؎"lh'8en[Bs@׋Zʾ/8}Nzd[P}05uyQ9`\\z)9a0yQ{qev2g+=*wfـ/G:62k4 ?|{[AMTL)>xVI)3)29>GRT!6f:^'wŅbl8!y9<#\e<k/s,?ȅ#}ElǞx6l1&aP=ճOK̓g\xE9^ϳ2?ʵs8xop?裏>裏>cE y/#6Q%K;1P'*v]R_cp d C,{69>j<(qP,JC)RδmK۶Iiz|t^1FR |:U.;l1Ehj] %kj֤=* uyeYmRКpΦ`c (Tpa yS$ɦb+"sӀO(\vEC9Zz+hr*rvͺXÔdZđ2`@5$'6^mJn\+jNne@B5W<5eР$5ѯ2OMmuV38b2l9(H_>Eʼ%z;h0XC7)9 W)'wKcn{WPCYchtpN'b7]w?BD˹nz? ."}g-Hr\`c&{enQRdֻ#twF-E ǘYR~,5{]|p-B^LW؂:P?뭊}N 9`RczGsY^>Ycv9C^C751%(. {Lj߹ 3mΜ;dFlf6*Rɷp鳧i̬6&N&3 qj}cxstxkk GcUj?i3Ǖ/r{_}eyQ.裏>裏>/?r"+ ~ ʓۈX,*Jһ; 尖1*TB,oSV(m, dQ{j¸b"/+)fFc(]y^5=\늦 vMtA~5jJe jRgژE]m|Z|e=$k`R,U0\_ *SR#щ-hʢ.”ZK0 khs}n]E=mXmKΉ= f4 $m+[ tlcTnIQ-CSIhZ8˜l =GQ2UHYVw}Ķ%OnkJ?/euhvD$*Du,<6Iw 43MGHY T qRܔ ,pLp bFU B peߔ1;*6/Eq-dZ%kP⺢p*,0wKAݓ#Ęj/LD"݋6$E7tj+SbRe\H`}Rd=yעו8pě](ZQFڵEsȀ}v^]kB2.C^Y-e[3\>v pIօRЀRضH>qwHC3FtO"K+`0`2hP3V{\>{ .puIL <{^ڦ׮]֭;=0д< cqF%Fف1UUqvW =a0y=28/K7P\9aa1YKE w)IW 5bh?zS "jus3ٞ"h h(MջCcKe~\8}9r:6L~#'}_qd"Cún6Ku]"kR}W_mz=y7XX^ @B>G?3kԃ v3zמah6KY5) Ms e^~sfC% xavf| .߾,OqaW]`<\"l,3'c/ҷ[O2GSvv0XG]gifynnlӘn%Oos>C޼[yO^}>~W;Ys $UA"+cZ9&V9} 7w6iSrEF#fM裏>裏>裏>~$'m*’WuD4vd8up1Wa"F87Ehϧ(!um #i#Aݩh *q"I'm+ Eȉ.YfP gUKܶdOBT^|) 8Z+`xu$ jǘ IҬ:Di-#*&?+o<.Q,j#JWQF@E!YQOI1K SI1:x%!2iMīj4Ta!PUYQ ҩIŖ@04^qg]0aISjOkJMCXK&fQ~U,)u0/=Q'M`{U@fl .Q]+E$Q&pLQmb HlZEU"P4xSfs.(.e\QAVP%9e#mZxanQdQ>x'GZH!+k :nQAgMKTdȱ^f/nB9eaǦF:)X4nl9m}Q?fThD,W!@\k :ޛyI0HJiY!W'qîuk^\@r=)jQo bV<*_aߏQ,+̇2Y}+2NegmB N7+g&I]NZ`gS@%u1/.|߼OItQ f'"uԢ!mxJ8wdɻ)6C8\eu!c x/84U Hr%Ξ>;oϲ.M.~<ǻ*N)Uxj)h75PW66l$5݇pP#4\C-oﲰHU\ZsD]Y-m}؋Y{VM"R&k͊L 9"/e)xMϹYq\qpfo;Mi%e_ WrB5]PTf{>Z洏tr,LyY_vW9~W~*^{ܴa8G}h?􈣽Ľn}cS+gXH@vtwBlqMQh&b f}4&Yst?F5Nbȇ kNqì^Ň7_b;7`a(''L'-ygSz}6n a0fw?>'>GW%?O?c4۷8lX[9(> /gVpWd/ 7?IUۯpX1 w6=3#.ռv>裏>裏>Z@~Y9X%N+-U%تW L.( bJUPH ̪H f'$W"}tM=2TJ 8 sMff᫊Q@ɨI70fU&{MfR>Şhn0*/XPIAnɩYSD{avŮ4w =gwxn3M5\ )4[P̬mIuXZ^ojG T%Ab[3Ws)}m_d& fSY_HvN~7 I7 ^rNE.9\&;U&=TRT! XlQk8M٩:7Ќ/gzck`sY‘bRzfk C\ӉuKoYSXzTjӭ'͎:z'/c ,vznˍеhs50mN RQ؞)| (t=|9}>9Xd-^Ƭ琻>X_lsNήSgwydܕ-T7/+<˹;o@>t0Ysg`RVd7:N5XNmmwل2)`,:tuk'erSAQ˥`n=l.ݫ2VN["sIPrd+Z;׋E0`?m'\|3U]yaE =y<غI 8wkgN888PuN&'J;2k&'첶̙1%<ۖ8=`9foE]Xd\cfo4hnjl.ŌSz@.03ЂϹ+$=$uvjm2㜪H,(->G])|*ke߳grgֱe}ouv]6O˾!ȓOv,-o~z?裏X@OdH-gϟMln1;KcvdBchAx jX]pظŎ4)[apRX95.7Y8J}ԒfXq3婋qꨡ2,kL'5>؟$4SV:hOXfpY.Pf|teG [2Go}c#.Vo!TO>)I>xo~GG&\}bws챸AŃwg2WZw4m!4裏>裏>裏>*gr4xC Ga :ĖRAc:MI,sjHbmSВ$vT]WdU40[YUZ7k @U4T0ҘM֜ESeZD"p 2 lERQ<6:E(0%κ/v3Vklg=Bpj/U^ $-^Mm%s]sppxb@$%̜UEh2ʱָB@rsC{A VK!EJfC9(岵/ w'u~;f޴J{q!jMO0©SAQfx\TrJ>@s !+A6Ć]u;שr,{`, fsWױ癀u}U9B hr&  b?cK* dϢ̦_yFVu#WypGK;Z萳:a:p)n%g0-\,.-0kxG?nf{3XSO$,_\o̓νsGKCN.rO?gN=1S#8}itvbo?2u[ hfcry8W4'5wߞ7NFS"k8 aq`µ 95X}._!ܸ{ wj -apw.Ӱ3WyW¥':|oF~+^{-zM6l\rgwt1sez GLN=:ίpo;SєI:裏>裏>裏>졪mRKj;&.4-3d\UU,6.fH:5(Xd=t .Q[ޚYp_Υݔ^]uŦ4CQǹUq F՟Dp~(0+>et.elzLekV`56}]Ov3 C0廩ł[,[ޔq@)+ 3ӱq< fkN^r&P՞M(tOXEDe^$V)'*F xT lnhEzuǬV7j*LVd•/0TS b4b{Q[ '\ZUAu>yf~Wp>]y8f*XOn-YlϊYQ&wVX%rكA)w@WlF#wYZL!3gϱ.S j!FXYZimʃxsw! W\ Sgp|lƝ;X][aPxsL `@ѨS˫kdf{[3r푇e~`WR1Z_yj&.LO:#e? ReQ RS .7{g?gT0Xk:ޕQ} .jחsznߒ}{f\ :ޫ]v*"-NUXZ^>x?s~裏>裏>裏$ԕd1$4Tyjn9SX+)B  ASB'WzvfPbU *ij0dP#6EPP,:HoЌ(_I$;=U3pXr}zA7 z 9\x΁ϼqx OPdbi,ݍFQ־dfof{O "$D iUzp7ͬ+94-ՒzQ!DKJ=(hް։NSNDaU} 솪9,K!6:vl+%9Ga0p5+a܆Eh]ͱm(4Q%S2^!i{{1E1XwI9%U{B.`0Y% | ,նܔ fh;MT[IWә*~3;$(%EeP j1vHlvҖ.kҐ4@CNԯMiHhhB!W Ez)yQ@Mm+buHCj@r"U([;pVENu>t]i3bqUfR6I5)K*VB3#JX ĀK Ihozj]*:tP"WGWR2U JIC=ofG DhUK ˢ}[U )2ϣvS7ݏ@dTDRtVi3[ kv®`_n-X_+d'DW,йX!Yc bt)4o]|um:ZlOMmM1HCc4'[ YEH4kLf@[-0 E(8Es Rc]_^䤟"nXgY,~6Ysv6|'GS?˰ۗ,Gkmj/EIs޿ՠmm۶mOao6#9ؽlOOێ^%\q2ϣwiw_#ɚrɊ>8!%1-cRXqqY8Пg9ޚ__5^ F)7mh_0âtS8fZ~(ݘf4e)m~ĵbHM9y|-Ο߁bh׹u&}[\\ǤSV'cq/xĸO{HN|rB闔F){#^=h |ضm۶m۶m۶m۶m۶mm3 " B fTYO( /[I9 ⫐+p GUkh 5ϜV/ju)U%ԻȜ+B%UAyB=jK&d5}kٲʨe"LP$ -6Tnaf֭_WDLbBml>3͎f\'$G ز`7QVE_1ٳSJ y53+5V珊/c͒۠& R7հ(M s[Z:e-$וA_JxWG7>l\1Fڤ&920pggt^7Y*XCc4NSpSvg=dobsպ"Z;7+YWsy;=8qeT+B;oǕkxC-~f sӸ,u(ԟ𹛆yXw[ѣݬ@_ ;bRq(PQ0LM*Pq7>[F Y65= 5C]fߩ}Xp¥θRĺ!@nhŊJ]5>KcB_a+VZmc DϵA,ԋ6 "7ld$C BV`~7>nqnQ6 竫GU;۠kQ> BCzB LvD)O2/tL" GGG-痗9zr`ge6KJ=}, 19g>xs?twFɓsn߹E߭KF1;㖒3]JP. G߯irܾ_zC%,T_.؜`E% ԉ@+>e}$9!Hߵ8ŋ[ޥԽWU{cEoK}v:+P!;Kᵡ>Y冺6Wl۶m]S/9a93>}̬׉#VٓC>K؉rՓWaIXWyAf t݃@q8&=Jst|(צ=y suvɊ.? g~9+kұ^gsz9LچuWvMIV=Oetlt`uލor{+\>ДD!%q$F 1m|cV?;hD~z6/Y;Fsct;<:ڭ)G[Vb۶m۶m۶m۶m۶m۶o O t"hBɜ3 @LEبzOhLT9E1g4[KT{6z婺sJCm TQK1r{8 !vغ(+)R{qhBӴbAa@6(oFd. Jͫ%ؤ]Q cR`ٙ~VԛaJq(\LJfSqge/Rv$rW 53M]qy.W+\2y) `5+׬sVp%d '~#@hHQi.S{q{PoYKժ¯Zيvl)un~6hZ x\$B/فJMc0(5CS3/&9T+,CLP01(+9Kݶ;}[ׂo_[zW"gޏQ6lځs+ ոgOvv6Z@hfX!43vj`VP}"w|ب[Ы/|(Vb\W.Ef}_aW{z@10n~mlz/{sVd%AA4˅Dttogź~ Fz0]knܾ[$jQtnjF*\1r)"l3g;|+bhG?goq{Bm۶7鱗.Y_/G39; iW_Eτ˳SӒ.q\.hUYBҲ8Jp9B:a6BKjLJe>yؿϽs\1ꄲΣ]"µ jlgqbxuzѭ/4i}Gp"_}Ya˄X^⚃B߭ybPkW+@6F<{n\doN||Fãu[ZzVMóm۶m۶m۶m۶m۶m۶m"DTeLU5eF S_BCĜ3e Bv*6 IT$}v(Qm-4Lӎ(7¨mɞW }R#MRf6fcE$[\"mC$ !gSZ(Q=NA4g6ƨ8(vP]6} 9C i͒j|*lP@&IW8x/dBJ#2rRv@ [֫V%JTolPق =dwV;W6a xXBJ0攺o(.ý1\ɖڨDr5 VBvb0}i1f%Q̦;dKf6QR9RGJt Wjvd\C[q`6[.6vzύ}b1TDs!efψ, .d c #9֬_qwxڮ`J=o*ySF]1(@Ġ99H6غeYA#e-vpR Vkބ#ƚ]unw)SZBVS۫+Bg;OYk֯Pԫwzmc{:R1>cc(/lI1.Yeo)+3TAs@=}[86-xڄm2책ju]lrW^[H]| Yv/)#AoZx}! aߕOKRjcQ'R}ǠЏ^@RCwsIΠڃ;CqQ={l&N)+&DZg~ٝ"fyWx{}fsABmG^s/s}Vш'ܿNŜ{}-%GGG'o4_z>%*VxHf1P];9mmja|} kAOf m0󹾧 `\YZrR4-eFr.,! V̒`V*.d"Y9Á>ZPpEw!2j}5CU*C܀HCI?V}Is%%tPXOEm(4ᴈe GʆBc_ʲ66nb1^^l ' )IJ4kQkP2}HM ;Mo*UmJ$)i 7/" ",;/l8W&RWe+i"AmrѾfbYW>X痞+tպsP5t?\ }ʴdOΞJZB2C1Uza lBT{V04`ڬ!"%ug'$%%;+J !@.՟َmlV';8 P|hq.T[j1.vє]ZHmSg2A2ZFwQU.EC/ Kv؀g; ATRʶYb@_*u̥t)Vxz1ý/*=oun Э";t=:YЭ#&vU.UJu~r  n-jP|Sa\m~Td#ZT(-ٯBzQ6` Μ]UoEO%uAZi}LbPc(gW` G*8 - 5j{K}='edrbB? R{VL }a IDAT,]FT!^mKl"mӲ^\ɒp-Ў'HTHle$t5nq~]B.Lv'@{]^z6L n^GHb}i[V9v3]xw?dw-o%g8;5ds.lB qC,ós*Gs/l/FQtUl%Po-jqI=b/6Ä^R,n~~A۫@c۶m~SY@XRc(V&sOl %=+f3t=e/׎GGܸu,kL]{7GGיɳ>|э+agi&%,υ|NhMY\_vo={GOid:,9[e2y=d,L3}>گ3Xsg^O|3_l!>{LSf{{LS9:#L k\@Ϩz@mM~F;įS@PR\D[1Z)^i$UEU)!xYJ\r1zRȥWkΠyf9笇fJdLO͒dXr1nUQܮ1bhOU9y0!8VE=5 r[) Q!vY_|U~:W^6= Bv؜7~!Қ\:BȖj0a ( IY CjbAd#z]`POז)4C$++,kҀHhɬeql)g*8*xgs^mTEanCVK/n2Xj;Ӿ,-Y;ݠ; VQ*uT^BUW^#TҖNkF BV瞫"s˭7oWb}\2ؑN{v8r%_9<+3sc _'üqmvcc  l+ɯ(~g2Cy-1]Jư{{>&L, 볍HYc1X-Y]JhLӨ"99VԑSdFF _qyyA;j5..9{z/NZ./O "tb劜 g98>ޜx|Dߘ°CU؀cu _sVDӣ`2 p`={O,_j>3cx~lEA_c-ʶ_|b?_V+H}}'۶moISgKFo\LZN guyequF=};e×I8z'{f~w?/ˤTHr~N3It4U}tg7n[4cV1D$PS=Xpi<لyqt|ȏOOq|!N>|])_godg?8 d2qt:6$svy;rmptx+_o>8K8r8 -ݻ4է۶m۶m۶m۶m۶m۶m7  -16fSi j$X%QTD7 mFP1 J D 5!RrOJ=})cQuvHP6b0I,~,҄C\T %r QAGˡ-*BU zMDU:z !6)+黎wP-o3$bT(4"fWLĔTDAo=%fMϦ0̹ EL1 !f$=7;\WXW0gЮS`` Rt,N,$8lJC@3A2=nr&Ѹ*ԮVxU:,|D)Y}2( !65#fPкd.j] wDyf4HkKbkk9?L̙Q g֫5>-5TOl^vxT [O%⩥a?|9k77?Txi2Սj*mWfsBqЀf.QDlOCat&d!L 6ƩzMb i [}62'|z=ffU[gʹ~4Ħ7X~_ςϒvP\7DK^ldc[>V46QpѧTel/!H}O}:Ξ} Ⱥ빚_"!ppQӲ^kaVжcMvאؐK`X\,Ĩm O)zE1܊˫ VkȪ@ agaquw[?'Wr]dUI/C؂UXSV!AU?ў^L e`:"DT 次a@mU3gjG5,9gnƟ'bi/})$bhv5|ڶm۶>&O]n?(r2h:a#Mδe?(3ʬn&Lg|t!G] Ҙ9']dR,WϽsϼxgiOy.gLkկ~?_2޺~{?7/*q>=Er&2;Ǽrw?úfCfۜ>{|d{w~2?6E\NyO'Lwi[iNjt+K3e2N??O%1+f't+3`۶m۶m۶m۶m۶m۶mJ,)dR(!fE4ur4F;4J`gg% T):;tI}gzo9'm;kS=;sR,QcTgv",C1grߛY]W[CHc $'F1\U zS~xYU h$ZݕdMi`Pp2Ǧ%砖֖O :LE2}RԌB+ CYHBlUX m2!4A^Đ 5RХ zz_99FLXz$`63ۼ -`}.]s%Jь>]aݩ1,vTh*ڂ)9<ʖO(q86-6عdXz7 1;JA#}=S2`}3.-H$+s.嶃9t86 ychxpC_ QA`խ\;o_n殰t(Xs Y,Yۜ:0 6>ZÞ/W6پ: Z ss_4p7v(F5:(=[P 579{aN5\>qln7 M!a-x!ٍb*`8ս BƬ|OŦMqj湨BKj۟F洰 CôJ?3 - !¡?Sq7`/_#WɈ$+r(X3T}|򊝝1rO;!N-.o&=W؎Xukvwgk0Zжcnܸl1}{\۟X,_]^-yjX1pqzʃ{w5A9EZtP8܀yTw =<o+ W>7BbP4J®?3%-t9YkLu?~7y+ ?{-?>|SγcƗ8}p wn~ Ng^wWw8>c9:GLJ9mK'3L~e//tӟ=m۶m۶m۶m۶m۶m۶Bd$ЗL5 7eMf\K)NHу`($ҐDY̤zPI4S=Dh5z]SUa}TYv hC7]iPsI%kFhL>t'ac\hۆ>P;ͪ iP3g~ HrJ4AٯP()#!RG &FsP})@[ ,lhA44 `)l<\ (4Fu)ص5%کP`¦:PYd܂g\QcEvznaY D$9(O7Sfۊe Z v9gVKڝ¸Qf]QuF[(zM*h.) Ss0f%@kmY^R6fiv@뮿+(bsyQĔ->^lρk^-],<غ,vR}b9E*uX6cڳ=;=WUyT˻S3BIZMKA 6.7I56R _{> VZPE $R*"ofC>eҳ|N(5GG 62_.$dv'cr-a?Y_^­c>^\pq~E{$鈲py[27$`I1'/xg^jͽ{N䫎oGry3µgG|{DiZ[oꭗ?=ŝoryÃCo.a'S|?g^cwhrέsy9,.={=G)9<3OOork^NX-?9Om۶m۶m۶m۶m۶m۶mڃl](IA:+ AR2Y1j$ I$DD 16p#VuD$J:#+!^! 7)=PD-RN ;b@6X= @5ccŏU209<+ETUw >+ ;@W%Q"Yd  Td)UZY2yGAA BaD<1jM a*^Ѣ\2 %/O(r!w;}VvZvS*-Q]\xf{=#=".Vap[BUdjڪ`!e# yP㠴.RbՁbp {XRnV4h)< 1 JyGf{KԆHճ׼("kf)ժHg@&Ey>D|2.•`s Z$pqqdwl:eZZZE7=n?<3"beY ?<:d^3%Kh[b<3̯x^ktLz.ϙvi[JԳ\8=?g''e_ 6ޚ7ݳJ}bSQpϿ-nDZAa-{)dǭ]2Q6wR UUϣ@Vjǟㅾ _ "BlkhíkA]Wm۶ڧ?uI4'~0}!7Ge<;NVr~y&{{Y5ǙOIiţ{y ܼ}s~y_޽xow/x;DWG|p'-Ͼ_o&35N 5tɓ >9xk/_|cJ dSf݂<cg5vF;#v&b3_;8{rFmlo6jŜܳ0jG䒹Z̘MwcdgLBZ uEZ/!MZ~#F1|sVfs4T/JlL Þ }PvLIl^PWsc"X_سqBJ.fQlM֒m0ZYJϡ\ID6q`*e sH:氠۶mm <&=N$YY\=04SV#8WG|W%|==lcQɣ5~NY7W?}^;1W'ww<>)}yn޸ŽwxzCzxesyΛoy_bdδxC\)4r> ɘ)0p3n{|OʗLYvnɄ锔,bE V nŻ?>auNLJ=^{CB:CvZEz,1!C,C4}`P.qCX)Ш[t(M3Znʪv*!?<Ր㜲zLydR~N=%H̳l 53T_mc*ps'=zߪx)Ј3A5U4S"~ jWb8=z4v1~] T#EV9BGnp>YLq<r*AĔhN845FĠ@+U \ L1*GJ&A)} hI %-D󚣔z_|.0^s&m B(e b$>!B,b*$z疫R$S)hIʭ}W@)]j?d_6Eֻ{pKh'Kænh)&`T ͦhX8o>J*[Sv `y/H>_|Unϭҫ} T~^i¦5v0\Ȅ }ז5D J}>u;Z'{fS?+)Fm-gW˧FAE( Q >wSrwhk,7S.[ M98ػIs %k!Db;%XH%rGӌX%x+,h!iBl?wkk},+-ʒT%%3+t\)L'#dXj^a*"SkBC:表 A}Ή-?RmpBlK>zj..g<߽ӹY ?i d`+^Iq:EE5W ?W0 ڥDxhFl۶m/}*ܭp@ZϹL7W\E\3z+ܜ~?9c*SV;2{1^ ||>|s>ӹrllQֿ$dU"+ kmuQQj!|r]u͡5>B]4G>Y@kN-!4'lJrI#ЎZKnyo||5y˸qmƻtx=^}R|k"_6F$n\N`^C(u.BZvvFܾ}S]I!Mpo$[Ꮝic}s~C+ sO9N bo8Cf!7`3Zپ6} (@InD OV&'ZA@I$gE;އlvw%<'2e뚷"OǶm۶"ڧ͈m|G9sw{KɅvYQ8EӒk[s-CE-n7 .~`_׾}k_׾ Wi jr")Y :kmffJqCPSNq͒I&6Ϝ)M+{zϺ#e1j-':I$ mk qKIIn'KT I7gϪfm ]| dB:q$jIB'&M*f'&oԷ{ jݛ\i@s"G&]o k[yuG-d\pp$=?`*6;Jw a*b*)s7w ܀vXކ4 lJ(VlH RS< ʮ=-ٵVSM*%I 䄔4A3Ƴ)b: 8VJב${ij d3sald@]wQSl:RʚߕmI!)q=MKW&8 'N*B1V]*$ie>I'&\ۤL`9W{ vy%Ĕ2||j*;L K֜8bX~^K1w|VW%;u5rJ"Y#s:YU͛ x&jBX'K xlYcR)}q|b3[k86Z^ }Jd 0H4ĸpԆ슬p2 +u8/Za9F0 s`,g: /7_tw~1]6u Wc{cln3HHC!Ta` mh";F9v}Ol?`w,}X(Pyׯxpzveصl9g(3YF q$w.y^x٣w|ݰF\Q-dl8x^eVkikQ_?E{믿j4M٘śvfKu>b |*ȑdubܵ4lX}dz4I؅sFsx'jL61%ޡ^s=NZ݇0'O-7W>=\pWKs(^{h9>?^\_rZq{fupDrg]åLW׼ܮonsu~o ˣwvOEz}Ë?Gx̸W;~388{pɓcҸc"/y%_yJ3pssCr}s͛[>ywI[IDc;V2GnύqiÖ n_Wlo}WVozk_׾}k_׿K@'[&rҳ*Ȓ $6.6ڕ`i<*TsmmHwŃjE؎fȿȾ0ȀA\4lq MLY@fۤ QCŠmR)R:6 t uM'ZeTuZSZih PͮIn[i`=M(Wկ1 N ˕yI!& +wm_ů]N(T]Ir18cfjݔC:#PXZY-ర%!w=h+ raB+& Im$r d6=cTMeSV j)u)]f;ljqt56W 'Fido_qc㢍 6+['%e!ZGRN]0lm Z5qX6C%ҤԖ& ɅT-8kkK1jN}if.{m@s\7ƹ):[N!UUְ`NRc#nC0pYMjL՚d&[ƬDjuh>'(1%bmv ͡ԛrF:/C&'.f/nh#J`o'*ic\g 3]{f(lcAj6g5R4CPtِ9 bV՛D&mt܆ LĞmsӏ:x9_:{,-ZEwnr56ZjO$,w_r770*< VGZ}u'gn;Gm 3ϵ9Hl`ZźGNF}Vnѡsrx m, c7{lַp(52~o}<|r%Ϟ?cZyݧDATV;* .!~IXo_ߔH5LkKXas#Dt輴ػ(J%\淼y&,%bi]"|Iؒ 2'wD꤄Voְ?a3OC^,$Zp(-֞y׾~kÞr'Q]g=.eǷ?< C6 ݢ4ry%}/_lnw톯=\v7xdjyo ]~1ob}k_׾}k_LaZGZU2`]%\tKf$3,uMzIHJmՠFdW Unvm^W:DUߔ\E*8$a`FN8MJBaR"X}ܯ]_:Ε)M/yPpgRuR 3옇|UZ55ZTͤf魠cӳ9XH0@?M7lA4F؄Pv+=8Myԓ nKaݚwujL#]_\5RzˉMI-3 #U4t%xI֋Aq̶X 6R+-9'HGPTiVɚ)]6pe7&BL0\癕S?G*HGF(0Ff,aim^ Dj y}0~arHF?R_#$Ʊ88J» GE@Y[thmkWKcp?w"5C eVȖ٪+aǬ93 7-NkA|*q NV,m6]ơ9Hß xɉ _jU\4X u"6ߋ+ }]nOOi.!Bdʚl=MdL ~Pg?W\& Yě1kD`n{el7ϒ'îLcO|.Gn/X#I4BgKO{j 7G шf/(~%[kjgf9zOϔ%YrS?/o< Y{MJ^e7܍cwSBn͏7\-m( -\\ >|gycN jMo}Gʉ[ u' rzCª_r-9GǼ>N[k:g'!Zkd2<LgLH i>z]*1`:ͼ}V$lIҔmkzr"؏ CYlcRFZv}AS UO_ON _GgwB/|W/o7!g_|n)pthn;J)nX.Vhq%g`\=݇lyr| w[R.ح+rw\p٣UX,;zgxxz*7W!o/[W,W+6 y`e.mw4_| 5=W_n9984go TL$:7UΘفL;Գ4+sÛV/ܳ5&-i4I1 ne&1)+3M}St\4tvk@f2#]42IBAިL42tET#8W7wcSJOKM3YQOvulhC QS'fF!*z)Džߓvn>JSH|_\GY- D7elT,=>K)nGƿg .#,*p;v3}W 5bVc~ݖ'<:=O>=:óSfuNv.ǫzS:LW9#G\}l*>&gv_<,LwK \C"sOl"=ӡy\Vv7la*6sA@kX۳-Lr׾~kCdn_< .S.80}{?,9o<|;g-WM[r|xE7.ʓG^zp'ܾyrYRrni8zp͛;rƃG__sڊ6dk~}q˷k^^qrtnKIJ_ے͉ÃbI~g<8\\^,g(!7W7Hr)Hw@? }rq1o/6g[#d{-?M}k_׾}k_76 2a$bZ])]Ό v"[e߈TA)Q]ɾZBu:E$l޳ ^Ko"BEkC4@,CMd2HbjfTN"ԱRJ1{bI!Gsm( Ad)I(ur{U4k+%Q[L5{Ȥ#3lm?Uՠ&Mӱ&)}z{B:PJ7LmdP?O-X1P|̡3e}.:RN`a;NYYs#vФNpZuld: ?ًf6D2TRRSjkk7HABIiF=c{|X/3|0{Li:[G+%:$rl56SB45q#%42WƸf d;g[/ J+ h(Kvu !ƕݮQC`u#bϤlRG=:w3T+kpnPa]r|x0c.eÆ$j+)tY<-gqp|4B*~e~g hj&>GXtAIhh4g$M[kӸo|i&1JͅC'5p/^/l,hӜVer01n_CT9Ysd,`zk_m֯ϟ\pNg,XR=& ?_sҧ>{oO/V[V.Xx52|cc.ox ~nXϡ~ξ>~wzK֬q5<]6c> PePnǯ92fNWo2 ;;%1%Mnw@ ﹹ?s:R-x!咫kj^pqyCx;/I٪,=װ]ߐ'ggfG}k_׾}-3ͻ@ce6J)Ӧ)g:ՁrMD*ѦHə2VG#xVTW&,R_)-MpY 5W fQ[w6E-bۆ:ߵJAh28Bi#*U\Lh6QMm˜Bg,k6+g%c7u{WSl8ua; )a Zs8{fiDl76eJ_l}d6=W5ԈjV̙8Ί wq4FSI#n ݎ(2% Al `ع"nk>tR/!'<'wl~?}8v2\1+z śTG)!ɀkVd"n;)۝Y K)njp})Fux67uZǁ:|L С}qU`ԧz\ :V˒L(#_Va{JInͪ /$\%a!2hjLu`3[2.SZlh3'/ =jt5KnϜ r2iAcC]Z|*k?t!gd-"/2}J hVh9u Sy݈O!OLl~qܭ6S*ӵ{ɔ{???zZXJ$tv_CSyD 8DGCt} gׅ)B&BlM_h=kiZ >>0b CJW7oj!LW1nRmÿwsyW%ceQM*.m`r۲8Xq^u` j+J`g|ULI`n>=t"A@zWDݱB2$WW-y{W3v7M1}MT_Xp;$`k[[k<Z̤<%kt^K֜GlN's-'ZHXdu|MϥJI׾P>~UݧK>^(2ӟ뗯Nx<~NW2~#=؜GWIey|.e,TyKn'X=Y/kAl{R/)ScY[JLku yN֠RJ[C)aq98yOo/717o쌳G7ZfGS.(wU=@97"Ct=N/$)ejۑ: o}?-}6v\軎(;NAknGh!s3`p(!aGymYPpmC9tOŖ]]e[raȄ֡uo4' wX:ת:Xo\ -<d VU(yJNVm1Rf8گCSvU_{n$I}5}X(~`Ro/lp|7?o6v;Wb\,)hkC`:)8Հh#QyGlCƔ fn{k>|ϟp}Z [H4(61~ }dk-Xuh4u-֦s!cTnw[3=Q.Q~?#vptdZ*5JN]OZ.K6ywYפX.88B;[rw:kcMf}Վ䂃vWUǃw,=ޮy%9C[w#]N,DCiCeYzʲ8((5t]I)'g!%fe۱ M)wron[4AHJ4}[_c7]X_n!#owyT*ww\7ߣ;>dۻ+VDžzwG#eaw304n,7?M<(Y__So %< c9p+Ҷ Y@i^3l#B&+\ :2F VONٮɥHfvH@Za}{fGV,J<̫[wI_ܒʂ?vǏO|o=ǿ}k_׾}k_6 ,vZ#ۮޔ(YXNWRRw(ٚ.Y"lb8WW>A IDATO`* .q((eۜ+M]2*PhE\ئ7 I\9FFvnعM PJ4%hH)QkSpS&TWTdhFmd4% fFf iZCmRO PnXHlS'm!LY$amVTPW,/Uu \Znv<дxn&7:9nuԲ[$r6kuelT(%^z:M͂Է-;9|j|)je+)sΞF˒NTJnA]"},}o;7xk`v80sT_7T(3l7q$2[/yXϐ?8߀vcEp B#2a@ @-n_u) @A2,꜃;0ӦfoFh7i6:m׸HMv1U*% $je0%1[{.鞺,qL 6@1]f&[CM6,~n5 ĻNUV6y PYQ cp_o~-,8dU떏_&p2-[svܳ:as='Ox~O7Ӈo.nX,o}k}mBjqfnC^Ytrxۇnr#6RZR/Cvoyq9U䀓vn}`V`\p{{GȥcZnN[?π%Yvo܎0ll뜃Sls#+EJF<@E`6p99~:|]'cE3 '[4᧔H䪫SB#F4D6]-ք VhuB#=$6ocb]*uf/YIϴ2f;dnvMuR}i<[;>3j`kl܃h2 t6]N\ Oѡlu: ۪lk){V|wPʦd2S;|oL˶qx@Lc qEC憀uCnMDU$86UstƉ FC:s*srtܭ)XaXk5(w!G[:n˞kabw[?O~̿?CÃ'ްۍ.ų"4v-˾gsFrHns~S~ni,>fģ:Xk[=,mQ#:9VMBN*>4O[*$"`hŻNsa㠈2x XÄe`&!|n\uvL dWWG:ޕAsnw_Pw׾۬_ [OŢ/t£K޾ayx b7,V.x/h2\)zf}_,[" wwo^WEa88Z6V֗kmO9ɔJڂXoK W':>xK./8>e7 \(9swFVJ Ɓ[j<~al6t +>^| JI<9;k_}=~ן'';Qۛoxk_׾}k_׾6(V1 h{ٗ6RkH$1 ֪o[~YjU'um3Ibm斤I(n9jcF4l-(ݚl/ڱS4J-XRbJErf68r&pPP)9Y2]gwܶ'c3合 jIW"MS>$4Eۧ-eSqh܍2Y#Ĕa<h8?1%xSN ), NL fka#^Y5c<%eL5!ᶑծ{D̹/}5XzLٕ`ۃ[n̠NOw9ցZGjS(]Tٌܑt+pg+6wOPR W88b=~ozk_׾}k_׿5JItn}gv+"Gi4HIeiSfDlм`a@C[(m1wT u:0'EFdetea5CjUDV))Fݞ8cPLЅ9:`ub92]-R(r|CSm=au u nm+=PAxsMfY=  KmukbF:R!f:2mZmWQJdDm6;4&,GD%83v  N`l UMvh)#u˅_w웿8tO΄Z,5gf լCɞ 3k!q5M,ŞYv=^ qM1%V3JɮC0 h(x q++Ɨa=6>/2rs5;CC;v5+5nbo(-ϋ}` zG KE !O jgHD fӿаպƅefu3rhxBL-&ONz6rg0O±UшYǃ#1;1FgGʈ65Nq*~P 9TF9Ì TPkb ݏ`˜:|^͹B_ NkE{]oPZr4z ֹdVpCK2zBJ*2m/kYzeeI)kf]Nj3 {\xq=:^GH/njW$Ckf!Ќ6֙}D" pQ{[9|T85 aGFؼIۏXaai"є2{4gs7slrMwLt"&+Pd4IQ;4-&!dFHO^˟?pW_VC;^&'}+Oq|e^s{3͐yv]~)}|Zֲe-kYZֲ,`u-G}3b1تD3ŨId@1-`QC$Oq*-(IU9}Ѵ-1NHZI+эf -Ci%g@RhtSj8d(e!{G71r&6jKOB3X8UL!eP`!$xӍLd R }/4lSU<#CmZBt9+j_/ͅNT!iJ1$fK l@ +}/ vu_2MKB"5jJK) o{RRctjsĆRf6#g0=Cl*s M|]!B ˖ ⦸IaPBoH *x7 tB/Z*'5{L\B$6I\!mE,Uw)Y7+(yCl<m|/((C$Ă =\U$C^Ix 0 *@Px-7\lYXwz>x;f5W]x Rw[ߊjWajMXh/u{fQ />/qAc|-Sn 6}! ;ƾ+eFbshzBm5(wGĘ?8C6q³fbl/w=&k+ VWVIA Nb4!.<$nnq?@33?,aw-զMyj j!zno+_cCPaz݇ƂjÚusww6e`dsHm` ? ؂:ؘhQbYɾRrG^, ,kYz%e9v|ќۄ8"*"ortAh۞^Y5Bw9gOs䤰;mݤ/Gtr{фval2 {'2O9C/Mϲ{'FVM)f<"2i8u|E#2aLB"140&G~;ᓿ߼|[888'_=xs{";~Bxo>Q殍5&kGiFH;e|Wy,kYZֲe-kYZֲH3!!6 ]IIsPڱH Q㉂sf=b.WjiMIV7CIQaעjg"EՑJlOSKSKDDa<0?mROTKEg@c걦*wm;RtɌF-"IseҶsg\ yիVVmjsFLN=I~c!Z HYWg8]/;fO %!7Rr^ps3Whόh~[;\7SGF6{846usL 5ŬEw{R-5A)zuqf`o`Yzw<+;wP{X&1 56p[ k(_DmBR55ј>Ezyȳ9[s)jt+U}7뫫)fgG>hi"O ohTb jaᎏwyaa׏\@hT]kx.`gDVGf4-1%F/\fw|7o3k\x}DžBkό IDAT|IάI$}vcNq\+FIo m Zֲe-kYZֲe}5+,ET۽jc(4 Ty*Ը G&5P7@P)B۶4DȆl7sɌ**YB`1nb:tpU_ j[ S<A@I7}+xsUZJBLa&&lNYR-RZJ:Ȧ5o%DԐNE t]Vw͠~k[Lg}HTЩ֤@V91j)Lږ=FA_cJխ<8} 5’lF|uEOP@NFMi q1+1w& tѬb+[)"zSWW7qn05|pi _,ӺIiBT@ӮjWw-"DIr<ď1ZeDGMyP);쯝ҺnUՑ1DiFE?#.Rs xa6@ALUWأˌ;6qzqH4e!`>g)av8Ѡ6uS:]<^55%>Uƥ@ fsz`dt;=yV`!%knti  @䍁ݩs>@{χ7Mu<6d r׼s<)eh1XުZp|8huwP4UD®6[s@HB ɞTPcRǚ J7_d}ָ 6ZOEی7H l( Y)T(ThD=]̬cb(js'.ܷ!A(qmWk0x3 }Il&s9?ƥ[68NZhc[Co6 mC kHж#yȣoM(C瀭ᾮx?Pv[x^bA\04/@ Yӈ7@`-`A"9p,cw2qh;|F獏}9<=j_ּpqVgz:D{YZ+U/ 33^I>}̍KDiH-1w:{+3wg\rs`<^ƕ[&$pe鈐#֏˱MA;aeewHz 6qGH֧clo#ڔ+2(th klnn2;:$;#=g )3:N>S/n/^f}ea|= rVy';Wy,kYZֲe-kYZֲ6QM* Z wm(d鳁WPUoD;I-P\sfݜh$FV5K&b4M)%Ƚ@ϜXm^|+>BЌy't9ɤZYJb~η$9pl#D3ʼ`9٬ 3M$8u)AUmc7436ͱhFca.hۖ"ϖ  l*H9M'QgS$rɯMXl}f۱wXeMJ& dJPkUUX^hEmD!WȥZ|OA]C,agE ooȋjߪsTRr)m3_pE1,w0?iE`jKQ֦t) QIpSQ*l4SID5DX2)Dg n 됴M1Ş}n0-ӭBPUrW1-+ jCpgM\8_gԕwb]p[p+!h#ܮ\'b:9?Thp=\ZT; DFgΚ+tLփTkچP=Ae!?8նe;y"|녘UzCP.CިۖB G^:Q*l5H狁f(2u<[˽ ŕ16Ⱥ^i:J  mmgz# %h#Ag}Hda&1|wqLxDb¼8em}"s8y# 9;l_h ipёhb.~,g Y8l:V.b4{5)56*0ӶV!*﵉ڔBR}^oXtfA?}v7 R jk[w^*3͜:&K _e-?~,=g};WzBӶ ט'LporaiUz ݼgrCAD#;V6}'7md4ß{#f Vʔ~ZHqNjyw{'94rf:r{{sY}h<ȩaW=s/^2W:{;;n>yvпe7o=WoorܵFagssVpƧ.\qñ;!@Mw[|֜L cݺid@'ܞ2ftKiD;ڔHܫq7Ԩmp.j/Xa1SKsJ0jbӴL$ i}IkJ}'ܦmE4upޗRriƾE(S`lb18jK!ZMB$6^l\A1# >dY [*,33Z>_\qȮ}$Y<(I=H+6-۵(UcUDMA(ey.t)nRX\g:`6 g U;“} P,Fk/@0p[cuFǕ@)b >CsIџ 6ETlc@,{P!~@6iȥ/rNMjp '8?`uo|7oy1Q}in^<= /_pmxϟ㛿 >'O=4e4,u ?k*q;ͽWqܷA3rUZֲe-kYZֲ,FX6!j)=i`{TH@1lb !nBPcɤhg$Fm|N3@ fmLX/ǩ*۞&z]jJr3PLuyűQBby ЃDj.rJɔ(,/F]HG)jӨ 64&\)$F7N*KGJ^ LI:|s<ٓрy4` D ,i=QYo=h[UgbdFTV yU`fip'HU o,F ּ0i-;iVfER":`.=!4 %XSf) fS\=7dϑ``%ԕBwIB*є_Blh,u+v;eUzB֢qY%H!u X uu)ѮWs2C~ur}rpt-ٶysA3+tm U[6izΧ 0 ޛjGuA` c jޠ0^Hz=[yk8yl ^׽(:{OW_E\GÝ*,Q[w`9s6OPӛ 6ލ20l<2@!72qYye|"?W  )轩DtM822lؚ{G`C;j%4 hՕ1%bl*G }qfGaF.xѬ^؉5XN3f3)Y:VN@պ=㦥6o3xZﲷĈZmTF{5 4M1)Yj;-F:ohz)Dz)/9Q炙d[5qr;=?&Uc}k1}67! ͑CʲW^w7Y[9MΙ[? ʅIf= /?C3 fGLW9~,7su]gosS5c4n t{=͞M}*'[OV!-^Q+}=g׽WxO՛>B9ybʱՖ{,߀xlvWݼۼ]}c\~wp1<ۗ[l^:]-vzؙ>8}| ^;Y^7ұ2N̤~ Lo3nׯurXֲe-kYZֲe `WlՍ3Tgc5_X4ŭl06 ]V`6]Cl&ˀL ;3&EMPR9ʙL,Pt8t'Q83Rf =!ږL#%J@EL)6 &&{T:D$Ҧ@)n vے "; J43MV\6")itTNTLʚY~}ljBp(FBD~w]oRBPh?$`*5VYX2b)늁NL dPg <A{# `R!mɦD6uBڋ:)kC0PJ0Cd+HIhE`-fm s0h ^)&{ƢU+!M̖ }x{[\Ih3\ C"L)OK`vU!h(g#$H_;uE[. _u6  % ঘ,R X#CUP/:&j 9 PJ9l &RD]ϙ&Y.kQHqPI{ꨠEj@U1E]#|uߛ{lLW^?!nbbp>ݮi6d+lT*q3$1[dΥkzf Jqg)64T $5֟&+ ТU.alG<`nvՊ,A@#zkv 7X0 kRP!ɹyѢWvM$ z@OlFĘSąn4crh!|iGRX][9gjRtܲ\BTah)%[ ̥uٽ"<Ȩ w 9HoJJ{{U}~EWos F3boͳ e-kY\,|f1^]롟o@{ ^Mfܺ44i$1Y?9szQnR{#o{r8⹫/{rxxoozFv<%MVy&)x{4 g.sĔgOO>MBW.`hZl~awuz\"o;zxɈ\˷m;ao^hq1O/sswN_2_ʕCN96ݼ-->6y1;x֌goϿC`YZW}0f?҇e-kYZֲƔ&Vj 6f%3J- \@hZ ~;B HBiE@sQQU8*6PP,jv\!Ŗ&&WmMc)[ gF a>imۖ\TmnFbLuZ7ag溼sb`ej까%Rg&Ĥ疋1(1 %SBCURU;hBR7+E7ҕYԴQrJ&Br6m3UuCI-r+)fݝ[SHw C'΢*^B45rL *4Q `ʦdM%"\GW ڳQ")̫bΦfI!4ذ m2dτFcɥ7 hbU!vx g2ٞa~%m1-қv&rGh0- VUzf00Yft u(N1*@-Bp1Wo$sUۛ;)E.I&Q*4`jbWꖾXSY+x޴ UTwP:xp08 {x .ZA=Gmus]sx`|$+X 41:ۛ zOT i#CX櫝P^qܮv,3 M X6Tᯝ@vj;+S_cm@ !g L@|h+ k*T>ahHA E/IPJpsL:>luU˹F&9su|)T9Ɩk;(7 ٘IuȞ! Y,g.gyZןHSJ})=a*=[k3Eo 1)JHH0<||&۷)}澻ϱMuHiļܾ}d%g1Ga>qreյ~@ DbHR n5s(\jc\Z`Ct;{b*x_ VhlMp#U`&KPGGu-z$bv2E,XZj<²W^KxS*Cnn^bk{' IDATUa%1]=FJ;uה7pEJ9` k#a;vV9}8͊ph¸Q]R7n|N _| ~SO3]?KWz|>gUNup2Gќ ]a.yiHثٝ15 =Y2ή=ϴmYt??uΜ9|= o{77_sO|9mbrt:-e>Sdk4^r&FNs$:oz5411^3,g&+<~q+~xw?׿W/ɻԧx]ɻops?oG;u[<_~MVo}a~k/_a>_NO-_Ǿŏ2'_+ϲuw/^x)qo~'?|+;7/ş{]uoC{o[P{~ _C?|e-kYZֲ`HtJh4 EBoYmSJw(TיYFHU*[[-u3!juӷ)1U}16A"m0#HLQM{bӪcaH&cV]\-+EA zSӅjk 3PL/8ɳYTl'lvјlfg](u•LQXYK7 6j[e*Jɤ"crݜywTAF0^BS (H+R AJ&1Rh &%bh}Uͯ}tKW2UՔڋCGޮ|sdx15i*rTe*9-#WS#jVf-H_NVhG-PbԵTخ u5Tmms;%gkQ4Uϣ4?d!XְCP`b$f-K,sD,(cJY^^ z1iUA(,TAm,RU"g)Mn3A@CA`/ؚjƴb⺰F,lt8Pé"J:m+ R!M)#t;puop4خo5NBcDQ;~X-7Uq,gݯ dG%:/|qvxӖ7_5];K6 Ϙ߳s) ڄl]ZD]}l>Xu)A}Ad[R/v~!1f> 1uT̹"Lv7)z?ՙAjw E+$,4/yx69:iJ{ךIL!J$ʱc_åcﰣp{{> nnq}7#Ã}@E軎ptx}'#d2/R3+1M bb>*ы¬1Im!5V El;|-kk)6ۺc?kuaGIXRMjW/Ѷm4I4$&ZmbzlLUo'y':ϙsdwgK0}yO/^رuvO O)s F=ÕIcl]yի7s9{I`22ӔlZˊا>~_[66Ɉok{>/^x{>sSXիcd1ά64; mC3G p޻Nؘh|5n|9.7?ʵSk:?~so\C?@x֕tϹUU|Cofu1&/O~~gng-u>+:oK?d#?{G{<F5~ +kMv¯~Y {O?Kܗ~/x)۾w֬t~j_ S.?)~f4ѿ侻7zȿg?p=|hFw_%=e-kYZֲBm*}gv*eF"(B{Bra |!&%@?Ϧޱ iHMCͪEu+bP%b*$q_iؗ,I ]2a Cf^5uZչQ5*Um 8s&8mU!x4 Tx1"I7YӦG̖RʐD3 .;)6R:BhcJtc*|>sW+l]mi[ R!D"S}3.:ߢ]&S2t{cdױ"Ya)ssQXƔgKU{@eQ }Aɚ eF 9@)S/YQmpAU]-cf&FtPaDTa D9;dͿ 0VZU hhk5D ?#I-bJ}'!0Dne%~R\z^Ǹ;uبr`g ^s-"gn*@tcUT%_c}hu(Ϝʅ쓜<'N3?'@4kU^xB`:]z1ɓl<7U[U~%"Zcko1y#u̺:\Ǜbk)wG[ft")jE^`ƿ`KøIlʯqB 6>/z::o^QW p|ɚe-?~, 9җ -ӕ3trJI- ͅļ?uf2;}`Lw8ܻ9>~Y>dM֏ѬoC}άG~qJ;eܸ o}SyyQ.Z"SvAf;[2O=nl: uNzv# FcN ^xE~?7}Ghts?c~g;V(#adi­} .]WN77i7{n>'NY8q-![_;y~tP?@;rp|7r'n?{{4cU=z\G8y}U;xI7}-<;χa/^Ѵc>Kܗ~/W?sOK=GL6U1?wgUq񋟸7t|.='\B }~gї܏W5ָ89~_{iYZֲe-kY^ UfZDTE4nC厌X. `MJՖhYMIlQ}j΅l^d|ԼNUc*ʥų>T, &\ Ujh,;4ZIss1eKvEQ КJJ 6zܦJLI˚]m{Aak R2P3{Fm%+|Le6v]A"x.kĤp1ńnnGQ!tVd T £qn%36&B1hPSn~e>3X)$T3ﴩIۖ}דnTHoTMYXV*0] IJJ!)&n|]<ã-Ocd:2Y?'׹ g^'yly !񅧞p[7r{ zvnHs~ w;>_HwGhzΟFp1_μpz}i)!q4;O}w}}O?$wS9preLW[O[g=7} Μe|n̓#x96?xW?󱇸yPc?Ǐc|;9qSyQͻ99::8 CZֲe-kYze+!Aیt*KVSL]Ulhvl4 J),gų7uCZ7$ rsxt I[tӭ 16E# S6QU@LMUxNl[;۠͹QIHI\֍" kR 5"U7bUCK. IYt1$@"eN78qԐ$Qf7 }?W{Z\L{ڦ;63}&[EshTN1 40^VP*ܭJ8UV7Kkcxb-0UƦyQ! Y^n4f=KYk3D濖@ )05i!=gT8k wPWb9WfuBc$OɵK0@ \2(2EIyP.*3sVp5Ah"* 9<ʤfĤ * }GM}?T粠j9ӘufS;ԨwldκT"%cK{( ~m\,U*`Rb* qY^/ 瑕T0 gR"tdI̲5`\+K}ƽҾhe:4 w jB4ǝbT.AsO}O8I)b ϮV) -;&k~Bb~R5+sppr- RPDy ^Rb3z7RU~k:qP/RB}fS+Tf[[xJl",@J|oaa>,|N WKp)IVp[0ԡhRUԜb7,3872_ΕYAK&T-!n 3EvE cK%J*ՔUYs|/x='"wܛhpVZ;'NDoŔk'We]eO %e>C8%nNpKgY={O25=DžsKLOM16=Vgus ~W\pYg=bnGA$9jʛBYSRbpVZN$[I4AŦ_3='fǠ`I*w7 d\ƜZZ|v3F tdd]%ؕ54pۦ%F1Q|% m>E,pK绬['5/NѮZ-.m2mj.Y3>]W{ncz|=wt8^Yl'5@36[^oN?=|w[d2^vV8K\ksjyy^9b3M{jYX[sܳx>'5.|i֗O0ݜ|/k{w.{mۮ,U;lx᫿ϰ9mg|<}}>;y.3+93i~ ťW/^;w?S|O}ek}^zL0=3ßsxlj^[y+\>x-| _ >qU(F1Qbŗ'b~H)tWy *'EuMbOb-t>AZ49Up|K?JߪkبE)5ŔwufV'K=9&ZUK* S8*S_'bfX6;JJƵ S¥֠~76Q-HzD@AV`Nm~~1Ih@@v:EkCA_6QC]Lڊc .n Ga;YAm릤< BF㭑Q}/1FBF{tC? TER[DuAS;ת^iuR96ΔQC`WFEI~ZjO[l5SxMn` 45c-}Q U&7hJ?fssq:6P[H\Ux/3  {h]P*q*c])*~ IDAT\nv޲cglK5 ( hǞfqq 9DL)jmKpY 5GKh"!\%qinaJڡf0ǪHO,(eKdAq(NcҗG^xM`-`"[kVv윧[LtZ-^MnV֘_$L#v)X>Fn%݇l Z)˽ܔ5Կ.ܔBYIAmj3bΎJdD# jMT9ݰ$nd5Hd4QA (F1\\~p5y*8(9u}ȟ^_~ Yc˼`IL]8CN9F:/ri%,௨y 8{N^t)TmNeeqAԟ>1pG}2ER$,ĩ$I WT9gQJK@f(:m,Ȁ^hS15'Yw-뫬VΞdyu2S6 :XiR$gIS5@!!c/~\YE?ivWfB#CY$/%l1\4, DJhMSPuI} Ѳ^r.޹uuI&ʚnߣ(rqU8X-ȁՕ.T:0KTY6FLi ]/Ъwp8oqʇ.9zf݋SܽG?F{̇،LS%:֏lYp{woXܳ56/u1y/ȡ=8rKx|]s3ai%^Z`8Ο#4]Zc;f'?~+s-z~?~ m_Ü[d=橧Opiѱ6.Ⱦ3,:N_XK|Bx>v_;vrm✧> _<}v+9G9.dݯ>onx3 O-B܃\^|sv<י78O?ڀHR *HkL ZHi>p̮R0H+\Gusi^it;Hn5 4ԈuABR#SU*/mc4`F[VMk(wQ[%&Z۪BQb14QUb'FrTך|leoVK0h9N(1Ջ Mie'vUUDpnv+` Kb2dTY.MD v%5̙Ap.7gdԱ)S7 .x*JLOY]`4}LJN\k]L4K-Y۪ V,5h{{WcExYlK*)6 Ne3? hZJE;I(r$fGߗq3 +!TN45ڭVə& H@**)%(D* UqT< &e}mS9;sx</O YU61T7ռwłIfuR(8RYe벤}>Tk^x$R6_t7#UՉά=6G$9iOjߗ'CȚaҔQ;4IIb%j2~z *L!:l h~* /Rj*|ب:2<`1Z93X/P4{Y7yg2ؒt0JHk KAU.c789}?;kzRNd ԃ*ht옋Eb(YBexYJs.b./;Y2sN(t%$ GΙ(UɽN$y{Y]ÇȱnX[g=F23102.:5HȜ[~JX~ke$rodYPSR$?Ӣ$k2gehYZYlO3qhY- K]d8ږⶠ %2 /ZCF1Q| ݣg}av&s*bq^=g $.{$/B3M;C 5cS<# c9_8O5Dn :;ojfNƺK{t:T3sx<>~v'Vj=8uf[W|=߼ʃꁧs[I:#=FnXصlm1n;vtW>so/xFpW6=1nkp>=Υu|eD{?sǟA&'4w L/?Y MtwmKjs7}?Kj׿gL^wco} kYz}l(t7t#GmwxWھܳFclrw?$>g_yw}÷f'}~7oڛ9}A@am,w Ħ̑SbOi[*x9x /0/rQb(F1QmGRgLH C xGYwj ]gV@l5t] Q\dc5Ze1i]8aNǨ^5em@ (5]hZ@]B%)a !P"sfID_dp*ORqQWT* dQxUPEa"7 AB` dCcC_UO"GQ^{f6j*3uJ$'FCPH^% < ^!uZ(U@݈FTX'Φ)v5M)>X ZG!!5'yVhڪNX]Q 1+~ȋu8fr|B@J6 K('A$yfKGV5ڇ;WakQH ԵnslZzLb7H YUZR(l7(X~MNMdڭԟ#M睖$XH]֯N6Kp$(T A#و9Km_w#mQAw(@A,)mec[~-Y < aXJkFSjqu~L[1gRrRQ/@ 彤 DSKj-nZSܘcCh( gl8JMG7f<T㼧(IY^p&X’3 'VeY[R:/9B_܏dv"$5\)ݹBnLKq@)c!-kK>QFzȚ\lzlOls C"Z[m.sPT5(&MBЩ,&FZ3;g6d͒.r.R9~'ĵ0}9sNѥlbzjPnkIsКC0{r) e %3.X Mk6sI0jK3A$`3'G?))J)&ksEAYIk'uqYۢfT1|VIQ9Jy;_s^(F񕋫.;֗1?;E=|du,$U[o=}nf r,-{?s]&)5nv>-.n^dFgϯs^řO?#N>x}Zav!r`as.~1s1O?IΑrqZžk-e9edg=o5ыPZm-Gw9}zS۶sG[8qOo|ͫ9'i>n1;3CۣKjlv]SHȑ5>lEήk8tcǎ211=>o-NW~Z飌MdMv쿑GO|A{cs_k1|(}7rkݓLnc̱g|7~~?}3'>D^^}sv<[ww*Ks^x|^A~KWL`>#|m|oM<ɓwm(F1Qb_<ڜ!&&\EgN[^;UNEH`#6V:F)t9Tm\=Wv; P E#%V:GI,& ƅJ$ZU8dV9Ef=`ڬك"X 420ƚ*T*lfUԂn Ӵc vKbܪ* d5$X>{꺯j9*<xA03)S* 'Է:fLQ*#9ZYuS+X'hզ+DaigVw'm11VRۗhNmaulZ%H,EΛ%uS@YM+r %!+&54aHDU= R&5ƼXۤj/.sV'ںi.y8ŚK03xrhS-&FUueL]7@ʎ4^2X%:7ubj*&V;UաyFA$LzfBpZ_RgUnS=dz+?N 5$!Ůj_cRRڛ} 2 ^ְtAc 1 `B%LpCG@$ *m % Ueޕv=aV[mغ+4X?cQ{l-~0/c61.̀:amLEU'n9('쑽s=vJ:釓VZ(9 qrCrxA4I'*7f#\aUYXmܖQp.tx}h<'u'qc*ܰ"=ABHȒ憞T o ~Wh mV)E xc9qޠΙ >Ъʽ[׍Z}K.ޥoՆ xC`y ؘV~-郘guI1H^h"5)'Kyɺ\R;G&sR\LKrdI>3-OrO{R((F1P\gxbUIV-qTӚ c[n^VٱgUn >t?+lr\9q^*wwgO~3K繦I?t'yŸ˫lzVOcM|]]=}GyG߹:crȧ\\[y;g'9{"='8}m~ ,ȥ3^77yRw'}?0??s}yⱇn")mcyeMV8{i|"UO9_3vҥbyLv=>AsIN?0˧@6QxSw{ֳwp6/\{~ǨZɹEYN>gWz+m3ܳfuW޶:ݱO94ŋo`ۆko)_E>y?3W竍n81Ƌy1o&t~I9Qb(F1/OԧU#q.\3E,T Ie3>xQLp^aVK^6d[dJR爵m>MFToA5It"SO:Sm笒@QjU49йR#ozF+V9z2qR Wxi @XzQp*3g-x*iRiMN T!eJz$2jSǙq~R6@5rn+Z`'JTQe.JAn^Ƅ 6Pjo@ǔ|4*md#3_Fmɝst9𞤰 ^O]BE49t{BP(ծЦqK1DQGx'u}2G0eL%oI,Ѯ*ա56PTؤ ܴx j EڪJ΁>Xh 唚XTkBibC~ bCߐrs bV) -uOs.G )D@5(1&$Q ;Uݩ *Ξ|:l۾H=U$[4fAM%o@aJ%DzUm16ϰ{),KYT/kY<ŤQQz& 8Wl~anNJgxsZ:R jqK(rqUo߫iU-RIk_@,+qLutiVZmyk{fy"S;$ۧ8Mw*/{7~3t/v{ i-Ӈ2i17XM{|]rlt:>wvh3AN\X^u'`aܮ{ ɽ6fn%oޓ[]ȇ[[Fl.sNf2_p`MϡNOyZf:7lԑmmαSA'&S06IUUl6 '/~Igrv; iM0 I=6Ewo)uׁM\{K|Ew`w]Un5oa?Gk IL9LLhsϛ~G>Ǘ~r٢wONT^߽]isW1cS>Xq}ۼyꁏPx7|;S||ο&xO?k~#~uo-/qWo`M_͓/Mx[y#x-eSh=$2oFx(F1Qb4ĆVŞ9@ X=)SuGUr^d[HoEeR8U jW6_Xj6c}Q+aA7%i8ONKJMVEbCP[K!lbf:_/)p4lVT&qPrS6sۨ9Q'ٺ Γc@FT^8cS >"ڈ:Zw*T12SD8UĦ!IR#oa2AYVc;l$%ऎd$5HEѩJ@9H}Ҙ! QքB eM/iSBDLy(ȩ;`H9ָEi41q(6nLsN-8;Jۼ/n$Mw4U݇0(` LjkiM?O:IJEt ',` Jٕ$*bameMRZ:Ar* YEx%AO5@4lj8vȒ >g)s0hVc:u~9gbH>ą/T=(# 9S\s͜=s^cfa֗˚bJ*eUBz:7Yy0 A]yl(ŭ@m3P&,.$tduRFt[bD `J_&⒪ɤ@FQiQ1U,ɣ"W< ;B=NiO'Fw VVlϰcGy⡇yK_ž=TmQ >;8ˏp#spM9w9'9u 3s^Z_G:,KL϶`bbH\R˅MLk~cKk>{lRMNݤ#cp-/~)3sm]O~'=ƭwބFV.E-86N`پ{7,?ȷ|7?67]sS|?6{kܸssŭy͍M?r]S6}f&&aq~|l-%ס1 _\AE纻g^GiM~~T6kg?kɿg}e+w?vU/]z _dIH3?[?cáS>7 ~e{ǻ]y|Ktkoyx{KJx᫿N? ǹc=?`bf/{1m'+yʽyKç}*z/qO|~`;,}>㍼98 ~)`|W*^y(𡥛1}+Q%W@Y(~qZԩav mQʮX7Ɣpٗ`C+$uhCvo2PX-@"-Sl &gӚmNbn%u% AsJRUjO;)ΰ y˰P{!vα/މ:*hV <@1ڟ*ĨXF(*=7U1Kw XsVu%*FW(RUH4z^Ω9 &8Dѭ[UiqP8juΥ(̸l5A_#rK]u֝FpnPV9 ja@Ek6bL_d%|6;pb5,v

ԒDŲid t&R#:f*SM-).#rݠ xzƐ٬*m|hY v+B)%f.1%Q$M6VM,uc0Unm@KۑlǜZV9. @ nW,͖=8'Pۧ*!sm'N:MƋ70uvx{g2D2dԥ,6҃K+6*]Sky2P|:8F^9X՚V"C cCrfm.$FaVjk^/Wa$*W$,.5AO@/s;$Jttlެ _ =O]L*].`hF A{Zp.s_U8fM1g(J`<:o (vhy7k䘩Bٝc#lޠ78s8>r]yIxOz 3ҙstWrQ9 >2T!xxOm~ ;s勐35RJ\ }iwogmK#Mlh֖>[Ǿ@gX .ӏra;wqq>rj̳Nw`c#?ַ6ŷm3ĭ5O>ʱ36Q}MK9BXW{Ujԛ-Vэ*hn`Gj>t#?gdԨW3эpQh @Q$Px-u Ѫ/(g;9vHN{꺡_Gbr@,facQ-H)R'W,@ 95D!]rEb* $/ $iW b*x\̉AIm 5 €Ҧild@S!=PjVEVK"VYGu<) o**3ސ;fivI륦$5SLsVX4 *Jg]/b!D*$B9Pn`B:}]oVXBÐ:1gYKS{}]j_y2|C-kwq{Amb:Ta- ra5T6lEErRe2 JzLuӱP(jkcXh$NhN0$|.BBY s" D:6Wۜr }nI0MD1lqR[w\GkjZXz'˲@P\><&^,k޲ N6IКzoiYB ڧR.)UÛޤݚI홃K( (%x60*kSʍEuؐȖナs6u,%Q'OU03=Ek:1!6RvBe $@#1\>X=`;vbҒɬZ uRX5fu-g2O@s4l^nyI]Z%$O9SU-~_x(|?U)!GXo'5pܸv&9z0;_;^@籇;Oߧ jv/x}2f}mV8&&&u{\x$ Ϝ66n[v ̹/N{~ٹ/o]s%:g*>C|pӵb=A9AUE K.육ʑHfb|fubmus{X^ +=:Rؽ0&{tXػiQ*^nvv~NGxZq'9ɓ<>=Qb(F1hR@!DHWl+MU1wRX$ 'Qc@U=LhM[jdQs* d?PFq+?TKy +NYMA>U8Gq:ڇJܐeeP@k#pQb!dC˙~ҚŒ4 ǚncC`Cd P5/SpDXT'NGƊR gRVnD;|kX$Id\6EM6Q4NvR @L]`jGJ9 5&hjI)Ҫ*U&wLM))JK z@+5>]]'6/F8vp$̨I Y9 WlER؎[~T%sEm8c 7ziJ`mŒQ`TXUT%/;&q6Xf2dUgbܚCC 4Ůp͎kW;WU ~.^֊ΕO&wdt}Ji`mzg47ttMp dS.}3 Z[CT dS$ZGYoԚ؛]J4~7Hp_뮋[fK u=+!F1w51BSSTVFB Yg)FJXsKeWJ!$7Z{$~1'k5}"wYC5\y>o$ `ybNuni199Nֵ"a%9{JWx{9+;/U6/ σ ˜{yt|sSӾ*k\rY^0u^ǷsP[9=&7sZY)`¥Z<ėQb_*\nvg{ٹ:q [z"vd~~f}٩I.\XbvvX#ǟ>N cz[klllПbrrK&]wZv 7{ ;h\Mf׎ygɝ)67{bkcfŽlԣ c43VeBk|'>$_}-|3r,L1}|ldbaz=dU塇'XoV`|Klsu;Tz^x w`r_KǟxLM>co%^wb[j7c_zȍ7SQq?_/vx^+Yar;g(F1QbR#T*QE՚@nR:JRE%KRlA2bDlu:tȥ&mbrQT7 " Zʦ$;؜(6 1K)'uFsԐE W4Q4b)9 ǔrG!Hڵ>Fj: I9Uv1PXfSۀs Xf\5U Ɣ@m7ʔ !}C暺J)$Z> 8UK1)@vA&dOYQc-)@2eU dHb+ #A6G/s!b*^QU?ڭQ;W^6#^xNTt5U@]8AkZQcF^^1gI쁢U8Ƅ1l^ ^ˬ ,B?jm\ RQSp㐕iNRw<5}Z-Ri2VH D^?U%",G)VYjRkV bn ˜80 a"_^K CzUYjhƘd8$LJmؙXUZڧC՜ bCU|޲uT5,OI~j0F-eM˔ Uk!E:e(NNZ4櫶͗|0"˙*u>:;d_VI톥1˽5阶uYu)" TZkQ,rYJ}W@RHc+6&rlxjֵs@7Jj!ɶBe_p?C9)CE !~OCQ-Z)  esr&7P \gK(xWt2+Āwr$Օ)(7HZ5~fNq:c--C`29bdKeM͐S~ IDAT$I@8M1kxĔ% )XuK@5ޖZ|ɦ4(7Wy\RodW] ~Wg^= 2d#͹Q /j("qU85&j/t$.wik^|N ړO=En"~L?;Խ4%Ξ9'HN='fSyIxǏNysʡfbW)%5,hÀrˆ!dÀw/dOn [=[8ts"kr7Vp"ǔByr#k\=KX'|xt Wn磏xVqa}K\>:tɅM^~.qX߷y|$It<:OJǃ}#zģIut_*{Ι݇_L)wL/?s>Z;G-u; *rc:۟OcӘ41iL_bt#%{+jo]YYuRBR̈́28S5W!0P@k/%GqPdyYRHBΝeશbЛ=eN RT%uZz|sѹj1[EB%e(ɛ&Ӭ=mV/:ܮJjPYj ZJj{nK^)ȡC-S+k5xfڎVX\r %y^&RB9y-#8 6a@f;l oR6x..>O~XR\:L`kW͛B-lk֦Vk"oWuP. ILJ8wb"#W8`GuJʍfoxi^nM@fM g1mmKpFʵ۷mfjhzQP$l-׮=r3* 烦%Y%jyJRsdcp ,xbMS -d)i񱑽#o١}cgz5x<6l/ x(mZ$'7_ARUr ]9rwp\g(HI_'m=i]˝6@ac<b$ʧd벿wY#g5ޅ5Kd?ˍWVϼdbfqZ[@]F<Z-E= Z<LJ'#V2kkWVw3BqS1X I,d' .唷~.qrƹ5R7t`}ź`fOu{%]y/q->x}6B]X깋,v٧т?G*2IOޥ#ϱ:X]]ahb{͏Ϟl 7~_%uԾ0瞹ʥo#Z\uŲX9'Gym#<= Ppw?a?n^[\9s^jFzg}ڏiǁn<.mӘ41iLc/MݐR  wsϒUXD/XubwX!,4JՋL4:0ui\ؕD))va쇦sS5eQ7SkqkS|f Px:T"kkru)SF 9H E ð$ygrH:hQt_)\cqhdEpS9Vc( |2#@Nܦ8YjW Xa"XFQ U$Z ]f]Rv{kRB V+R9@٬k ϖVXs"Hz\U$wh0pRSVhsM-@_g)ij1+Y23Wܚ7QjӞb3g506Q.ꙬIHkJwͶpF1g6 CzV/RM 96Y4QY:`93NR(WAYqKa{KquX\[jVr`@վ Ղ0_ ܎[P=Td|)ril {g@P_<ş1q:3q0i7^DγFVj(PŪmYe__C>+,r:R͍¡qv /mT`ן\RnENvt1lƘ={F{/do=0bZ6 گPFW)q! rbSKhpuԚ{JiXJ?U?kbx>1q΋7ٻ=_G[Hړ˒w30fɨu6-L0JkV:ZAhp+آgr]-֯Mg$a ܙ+C+wSI؝f@CFi{TvdS-va.[9e?~:`36\,Ɵ@[o:) 6hr +Gn;3Z\m c#ma+A*A7aQWvf?s@w4rq_RZ6|^Cmmp/qN@^lXpaѠZњg; b;lEsFk+3 n] s1TQi az֔dhUښT&M( +9ǯ) =$BR-E$@ęϳԁ,tTF#J/ɜ^) _\ͦT8::bue묭;gJ̞`=Nyέ!\.¬l\atBf!:?5[~R=f8#jmվvV4f|6l#tyWeosq>\r]x\x/#厓cʼK.OG~kH7cwwM_B?첾έOuֵrpt̵We(=[[Վ˗/*e~Վk<ݣt"|x>|9uG]޾n[o{+/˗tO +ks.<G'O8!W~s|K8=f1/ss}=z9/iLcӘ41iLcӘ,L1aȝ[#Zq3TK\.Q`e|<X"+hX!DŠe) VSߥ\YAb*DtiƜ=wOMa\lxMahoOʬKJyfʥ*̋;@BjivU H6옋AK]7W+;p$X`*^4 -,C*l?|>gm&rev}?9_`gkO]aw]m;wy[ Ò/_b.%?~K/!݌Cf |NN!{ SVVl~6|/^=P3oshq ě lM% 4hk&h\ĄK`{U=sԛ7$Q J '^wZ Ih$p+X+ovښrW^GˠT1ѮUůϡey|;5 !%~XHnրTxDG^Fl@Ϫge ewIqkm2vM6"Ϯ㽚5ou:,9Ug>֪-cpxWY~Wo@87|4[qV#ۏ[_KHkA{46޴̮? z$mCĕYY-t]&)ek )'{ToCUe(?9b#1biM .sklnV,[=kv&&4R}{]4T;L7)I{;˸j&gKAc&{s|W)40)~,o_"}4 }s NE ?z1Wo`.++srz“=a`mveɍؼ4S;D o|_GVVcp+oן k,;;O8y~&)'Os%Cdme1'̺9wi?K{>ynW4Wo~.M?]S$!U:\pQ>>TՕ4O߼'|w~g/ +ki^Ә41iLcӘ41 LK]əi( | fsFR =n@יz}=yAE*\%aUj+z43r5<$V,0s EMʠTgپm">!cIkfPD躙]cq%fVŨ+K($t)tV̮PR 4uJ--bsД<+ز}$c2۟Wr ˈYr;Zm!jgj̟A-7)T2wy+exF$M̨ R6Hc;P1]ߢgdS e:dkaorkZM:z v`0:t4$s\쾭ɠJØ$:0tdvd/^GeF2ZL9k(sNcxj4>GJ+ BtmFu0_f⟡{&}&_?cFe.':WTVy%^{9 E`1{`*PJ{v)-6RC)>?X\uhֻk#bT]!d "c\#WVTs E{0Nc*\v$ L=><#|CG]NvVJ<ɛ?ⷾ}~/~EK`Ә41iLcӘ4 re0й1D1`/ZA-K_,gCM@-V H3 h5c7RT( 8*Ւ,ݦt*doXKL3-iaH%B%DꖮJ8]t3RSP&`VR˟cV TjEs+Ά; Mbj"␻K@E4aEԻ2:Q)| m3 'Ξ?πj*?&)ଅ:kRdHYi`* aS6M}CY^"ජamRZ]uVC-Y#Vj\BdZϓMY:f:unMDIMYrԱpn{GMI\y[5p":m^sfa _ +ga ;ۀ,zA8jv97*boи6܊M+b>Lݮ 7 Q㛅$mmU}Nqf? gGߘ͞ݕf Sb^3{GSgjY[HzAe!bEπ␕;1 rB}*7?fR'Mw%)[jk9}JvH5)znjb9U%B- Gu*i?o= ^-vd gsS @z4GByA`B%0M7LDk]Gk~{WmupKz]67ygX:K_y+o^|+5Wǟ|SkSf/st"27:=i__.5j7+o"O߸_!,+ׯ^+px7~>>a萇'TӾ2Ss6Vrz9Aׄ+V9zDv:ãwzi^}e {7x+  1iLcӘ41iLcg9ufQ]&UZV8wu0ҋo]BˬB,/+3Ö@!!nnR ֜:h:f)aB?AyJEdrt-,`Q˥+UhysghL kʐT (X篈@*^QBk2ECSWJX:IBe0u5~QxLmUZN+J-^~.T2:8ąe)E˭U5)ŕB^rP}tSJcgoH84{`* 1up2Kc7r5+X׮?@Ur4#gVKqrh(z x!Q!ՖwH_iv_A氨dB:RX';0!3.4oeW1gaS" @ˣf|>S?tZMoVZH]{xՊi??VF 0Mhu5X plIH@ɤg)hyGoy`/1pH쵌a&w:Sj)Pf9+#E#ij8XS7fH&YC{f\헮=PGiMe-@xC"j;aLnJqdv3vR gDL;dQGm\Zyk-ﳹ*%SKέoXN}k 5y5Kԥ4%A񆜔(}OrXܶelikwyP-a~fn||}'->IBg_HR4 ?w&[Y.,7/7FUu/)_M(ʟ[o!+\~r; KJ?sٜ=xփ,N9Y.e3˒nx [{9\0:~c769UT[n1j]H }/J)ZҘ}͙@R6Ki\u0M=r7wC>u٨HONDiCTlBۻPu^(vJr`+L3p\c)zm-kVRyURV-Kusܚ՟[k),ϝP4@L \Xްᰯe4u%δ6fiHSTۯw!49' (s a֕9`Nb}n5I=5KZs{lo Hް cGD G w`º\exf{g Ͽo~k`Ә41iLcӘ4)PA I:+ArJ C6԰IWN D2}?%1:S%FM y"F uEhDYq8p}~&D8cbIA%j1u38e3C?P,%Ŵ+rgk[!v.-bP]}X|)j&gaP<J kT]H+miJnyY9,]%"zqWPef6Plr2T~Օ~x!2{;EyٳMC2R@UH DbvMn ;س`R] < *M D8,] lA@WӔy#_Pv )0*H?Wg<*Mqdd{ XЀl $kl6aYT2]˳ĀDlnJZp>'@049C9RE@kP{ճ=.VΜ)ֵ9fBf`*1`aV540JvH`75dN _4X tM_YvkSWonIȢWOǘo xg{h],O$˫si{DĔ#Fu.p23_iӚ^kq~)(H_]m/>Oֺ`Z?qbn;lkѠx3H8/Hl[3Ѹ+?KNؙXRJnۭZr7 ho(ȣگ]0-}U`F I밝!yɈݭA*$|6;$UB<6niLc?#q#t^y;?^?ky'?H&o^bKmmss=z\\ʅu?xIN ~O/r ڭ'/Ny/pzrʜyNs?gxpCO }pyOv{l pao~^},YȚQ/<~ xWoZε+ }LeNu$iUuA evynPUOʠfkbS1b$ڊ9g*>ŀŘiz=Rz j+g$eY+X+샒}؜D`M]IV̦+*)u@(GJbeuЛExRoJt \jv l=:`6"~jC@ŀ!`tm [,\h,?B=YY23JwU| mc8kN.6voRRp0UX+lzJ H:MɊ:_JKi0]ƈ画Md* 0uc>W=6-9YB͸Vb륳sqJ?UN>7#lD-R7-o؛B^h*dVW,fArg[M|vEt7q :opFl ;OPÖb -ݏ԰6V U.s>37{>b HklQZgAYڠPluש9rTn+ Q).4~by]'CuYg1igs;w81yWy-ÏɷK_`qz}.\bڹ+?NY]oyWW.[?#o^*rifl?Ǔ#e6?#{K_|?iLcӘ41iLc1O:j:BuJ O]fZ\q,zfcמgJvhu5>ϋC?n0[oJlŔ,֋I]<v{Z*͊<$ vSńJ=sk/BWH -MgunQ@dкng&˷tTgU)DAf+Z[b_mgU@:,$yduVK/"lZ鈒Y.v"WW)@rY͚bA_EI]r]RP*'[ǢrªZŭ! ]TZFmqkJj:WR=@)C5YU|ŔeD"ZL Ҳ pJ`]m*Ib(0eW%",Z^iLk*ep#@Ef]*ydF,ġ+\A@MVun6 KjXrb&Se 7H SC=w@+GU] ݟ46q h}ۛ; 4q\%%F5ad?z*P1#τ: uĞ{0P;;d M(a}^jh UAse<6kl^Wa IDATPٯu+Jn6ݚW; _G"cN̩)-ZqյCLɏpb4D|x&ĥ[0E-cwsr0%?֘CTX3`(zU,5}XҠ{3-;ܮu߃gOcWcxYpÓ%W<ۿlu׮\DUN ܻO|CV}~̳%`ʜt- :/%?;Gp_|WyWOv Wy%VVy'?ṗl C_͗n?Ǐ~o=+CF%'?|lMX97cqR$ߜRbyEecJ]UfC+n3'rC{1iLcӘ41iLc,e eYI:V`.DU= AӘ: $0uEUɺ.rO\AUWV/Z!w#36~MmUѾ¸(-<93mϕZݗ.ggpHU`*D1aͲ3K&0 Hl$Q尹t@6j25l72c]+ #< @bj'Q0N#l EKٲo`ӣjkNg=+CBf6 BђP͂S{S=mR pہUz[Z I"xð{ \kuM*N$Y$ZZԊf[]N;ԏ^{=kX\XKu+s/DqX+k5~0e@li;F[{:TSRm+ZgU%'I2. GzšY! BIT+u3H P[Nd3dTW݅Rـ5r|&Y9 jui#QXevv7oww i9 ğۀuHbKC;<6,n)xzcQ3=66;k7 Xͻ9w&y5p/-73ukԵ&S.M~EȘw؃U͂lm̠dNk*C8g쀥!5HsnG>W;6ڎ4ϟ:@v̥:Z~bƾTURCgٽ$oE!3=%_F;-(k_V{DcMсYNvj0svfDuֈLU­<5{ߨ5u>y#d.۲w>*w8:ڭ[կ;2cumKgIϥuέp+kΛon=sﳹzos7_gsuJtqL9G;Ŭ[/8XXt)\|v#^$ʍ/sMuXm^92u"g{}'9/iLcӘ41iLcӘX!190j*PSXV]Ax,2{2$lɊaOYhe9!g]fLNX]!Vf3,vۊxJ.ZH_~يu\-7W'r(t`(ikg>U;A>WJe3:B꼘g\,kg +,['jJ9IS4JBkȩv N 95{Nrz8އI[;H@XS%up>6OPms)9U*V`N?GX_?ڲ2[w3!MA ]=]mG\$͖< }}?;hΈE;^_E-jQ?a]ҩ?vZJe.$?xOgqCJwoq=VVltx.,ul~CrH *!KRB٩bVkEPJ2ms""s*`BD'S_՘CVnhem `]] Sj6k|=PDCD1]D;ti\dTiY0fCyZԢ~2#`zʽ}xeʄodyΰq8KKK\y>+L)O?ϝ;N7ݴƚLX;ֽ,pp0ˉ{;`>TfO '/KW~߼p~~PݾU撸yO1MC&^yUw؛v7KFJr A3+-'NM :ŬXTtO 3Hi@߸W%ݢmw 549m|Q$LC6TBs)^HJt)1T@xK9*nCkܑi|Yۦtځ ։SהTMzeG2 v2BDJ@#$tZT F`%JCvyi`tSS˫.#3>V{t:=3f Dp29L8j5dn:  }eg}|>[Zr`SRIek~00PDqܧRB;#(fp#C#?Vo9x1"M5u0+n5XغU%u4WpTW6Q ޤYmY3MM?Ʀ>OsG!q^hs(MVU_w(^sH[|ò88BaMq -/84T$u~Y_C`V$7V8;Z?WUJI Ʀ֨:䚺  i &ƗW)|Dƽp&ǏOF7ȮgJ\`]:RBu*+~-v~?|\TRå3 S]Fb[3TisRkso f?^Mq3G>=+ipq:xg~u5YH-0 5<?f't(ӥeY̹s|>a~ዜ?w=E-jQZԢE-jQzK{Ebj?l3ެ IA?ER@, ݆pPDmVSM3d91B&[UWtPI]FYtaa|6 T.TSrցD6XRS&o#i0Y}z:pTٰ. :,Ӭ)fg\r!`A0HK>L<t\/gh)z*L3m-)l/KGe`C12f?'S:" f+_%Wѩ+K '*7JMZ nMr:FW)#$u͙.utdH GS*dXA0=gfK SPmj! mgoTPہ*+tįo@*Tȓ ` CI KRKL)֗Cٶb)2BE)sSؤ[aYAN5tUWRʘ dtXB:Q%MY1"kdݶ٭Q]-?3hXb=ݬ]GQdTC|JY} FABC*8(SvmsWS&ϳnJUuZVzhaW> |b°mj֘V`s:F=o+^Ι:ؼl똯ϨYkSZ }c5y&ͥ4* .N0b8+`y㸋nٺ۩Fj-rsyn*bV~<_Ir{|kUcT:[6h_oH6kҡ㩨h93[x3_ݶ^ ZF~&9E|M~ md=3*fyHIZSJ4B4>8Nc4T⹹"$X"xFXYYu֎,M:'KB3"8">77yÂݘ—:p 7YF {׾{NE-jQ?*Z&Ԭ|pgXsgOh-N~8J7OۯW,G>sgfk׸G7]W~|U&S™gf{3p&/*e(q˗`679y4<}׮sM^kfܼqݝ%xDas8B<9=ǿGwypc>wa8`z,vdƒăY?:x2Yf&=E-jQZԢE-jQ@?6ò+|&i2(:'O&#|p%k0"W""С)Wo&%)-7:"hS.Q|p[ԕ & *u$;2եo"@%up+ԔȀvᎠt8#?{I[.{r5qk;Z,PK6+KQ:d-<+$ : a`xEf:Qd@4-])\% $vs:b<W22mzMh!=ĕl5Ư_S \]8\J P}c6G\m`-KQ;}yӖ6wl|H~/nBdf!;6 ܹ-]ɪDLFk n2Pg=^5b6ӡbkHypk@H% 1vJ 4oKILX7k ǽ UFgjZl_U=78:9ߡ `ICcn/J( F){ƴ*d6kH(zHkC6;71978 >⺇ߎTAjy [l[/yyȪjNs~A?#È͑&sc%ruX,VKL4uM:>LZ ՟q5!Ɣ:,ΟUįO["Ag8g6.{3`qb;^FƶErk,Uk nm^V+;;hR#HŜF6 f/f,z8 w1Js^()ڎR lyQ}$VnSz[ :AQJOh%)Mokpmc_ڸ|w8J5<}VN`s9ZdTdԑjeŠs#howTa @}kVJxmC6s50cMuQ_Ԣ ?,eGjdA9ycn}!|$+ݸ 9xw'#,9/oG ~O_~zNdiڱo~-VVXN!s$O?oŃ;5N>͕[{ܸve>gK;LS8y17?\yX;rٽSgpe޽͝׸?Dž!91ۺG?;q&+?1E-jQZԢE-jQ#}/UeJ}?4clZ>c>a f!]\e[dB C>ҹ@MUdܹ C:6'QnB-`֮]g58Q*QTLB?Qryҝ44WM*J)$^TW͆+ʺKHNx : 6{{fs eZqɚr6u##m& PO+YAP򂎹Z e4q-yXQIg7Y~0X6 h* Ԫ 6A)zݘs!M:d+c2؆ؑG~ZĬ1m*ۼ ׸| !oAj&C$p6Jllo\MJS 5&M{u؟ŭi%V@G}9Idp$ [h#v-JaC @׸=KYYA♡_sUWś590ͦ5D+Eđpq envu{qe#*tj85`jȮ|Z}mB{N)赎ub|P*x/x- Є+I?UDOZѰhICmf_ cEdj>y"*LnޜpwLqO]m s^]Cn ޠ,Q=`l}JØxƀ.SN q(xs;h`56-jB`_*jef\y* eyn CqwM-cˮvlƎXumhv%Aw6|nihEGgr9V' 27\ԉ)wn?`oa߿ wǟpܺunO_ۯ~ |Ͼ4peߺ7WG3\t.>q_/q7yտ?SZyS8u[]7xܗȇ?z 6O$/e<{O+g.1!1ߟQ~DQewl?,>;~ ^`euպ=sOpEN\?>r @7Yb G77t_"xQZԢE-jQZԢETl*(l``F.wcVI,g67SZWZg)}lM>,:|̮AV\J Yla6yOudYm$' :rg>I/d Gɤ#eqiojVtLfN )j-2 )1ZR6=R! .R"O<3Vā vSbf,n-[V7ê6A=Ta@MPR9(7ml*5lT{A+T22UĭȱjDBnWr8ʸaY8XJ"Pܜ`v>_^3=/,l*#\ѝC\'%PWF5c]d-ϧ6Iɒ)uQǯ hP%98PJR#kٔŦOh6$eܮ <턫m7ZTZnmr/I<- ]tұ++67$i5urţ1lS\=erbXT]J.8HWc_1.&.a\L-=`-BMlظ)6aQsŲMM y4V#4KihQCEg>h.ָ.)JQUPύoU,7x~ʹ3CY"9`$oH=5P،3KXg=D-dvi[ǿq_E@h7U4 kYCcʛk ǬS4' j֌qhaNW;DT,zB=GA%ȠV% h\C?q$̒aTZZApE]9^9ID6/ׅ*2PYi(xC-C8D}P[t g6NdW48MJ",YZV#sy&sS3?cG78F71kiI=>+k_2>OZ#c;~v԰>47%jsYhH:{R}1dni||M>zܼ.w` ڧpQD_K/_ema6kG9vt jd9qJ_gp ^:sw|z%9o~O2%~ ~vҕ8~~!9{̏{ ,jQZԢE-jQZԢH*Jf`h9.!yYi 1orD[8LIam`:cݾЩˤnjyX^) qPϨ,Qe*X.1) CA$4anLI2 2b|CWفRtAU4W4a֛2f^N;ogs$%zIv(2lE0;[rgމaqB\ьW!D)J-/bBAsH-DdLOCd4uDoс'{xHdEQr VkBVN,PjmK2&24`Š< Gգu07rHhߓ&fdS' os2hSv9J҅(mWUHn^AfA*!#N:]MEfYա^L&a q?#:5ra%5unЗje2ȓI't3s}ȱv[j1ext]nmXΰ;yĘq . C r8su{ =X"-o(Ҳ|L DVkܰ0jkM , nq4ں/Z4s~ZH|nPUuj3js n%9/{CT)Ai#rq-w=Fa5FQi,B#A9"^?^oBq}ƵRl%݈Gptŭ5j[S{v5ּ7ejP_QaCX(8ȭ{='Λl}<l(Зޡ4($߇::b43Mٹ)3הP:a{Ml=:|-90Pܮ= ^wYeoqfQ>|9'֡hRѶ$w@Rn*do "֡T}DOf`{cGXge"ܝ$+(9'#^8ZtBNnϗx-%٭DW K׊E-jQ?pa'[!Lt,ok lo=BKT-c߿di+WғOcn~sϳ ?ja68~ >Ef,.s }ύO]/"67}ĉSu۷p)<"kil?ܥ.1]YF.%Spg%v>7:g/\XXv,\3;dv^yᕗ ?7_O O?\_cMP_/q\{.?<>2qE-jQZԢE-jQZ0RG?L 5R0u&ө)<.O;0 suٙC˗L:jNoPβ  WRT]&R-I!Mڹ+gEESSݦ4͋C2 AM( 2p2HIu bdtٔ0Pt1 ЕZt2{E&mQS}f B,TC0PJ ֩6n u^LC1Um{SbS8f`/* V a־ax'|ۜ I !Um`Gd":7$I*z0! K,ޠmt9$,`FQ Oʜ.e&i b:4%rJZМL:˱y8\ [aˉm߼1nJh*#S\MNrYwݲT -{MO{?BrG_F+fKm\ r&n9.'Jy C\0S}1ХyYDQ,3<PNnW xkuTy} :AIj h.5F6͞AaoX<Jv<Ɔ/NLv7hۖ2kU7ŇF曜y[*4Z<̕8lwmfxDC'6^j hM qm Vg%-CZ+q{9ۚXͧf_Ǧ FE-rU<Aڽ +eS=B٦4vUiI@~qUJ\O ʨ-Z袡A=D:6UkԱgOjt|`W%\hH>'j|ݰgeSWm-!6g_mvּa{n-J' C![>U^M=]C,%߅9;;:f*bn&7fuVxc>jj[1hEޚDtlSlǹj`Z'G}Esl=ť OqQgэ%^so?9y<ۦdlJ?d_4GCNlL[wٺ}:{OLoo:{ZԢE-jQZԢ(`4)3`îjS$Am2mPD!UelϺRvVLQQe>/TZnJd'uTY6J m2w E+e(tib氤 ;w]6Kk4fdS TSd۵9YmuR $HzU;-a(L^-|f)%S3)}Wm55(" InYJlP2Y:1mM_r:4He]q4fVll%Mi#'7b%bc`(jOKt2jǾ!`2gd5K12*X_ \M@uY f&ߴJ\EYz݇2C?0N6$ϮԦ 𤄺]I2͝DJ^`m*Jr0ක} PmL[ݥbQn>G6k_ohCASu,~AC-73,C@_)l6\y@nja۹v3*ު+D6N!aW`_猚!/6n%[i֪vGa?[wJ>۞{Rq+X`b &i.63P$,n :Bf[iS hOZknF먂nܯK M%593U^WR$G6WǐJsU3{uQ׀TjLڿ5ꯩ( m$|_[3=Pn\0M$cv|F Ӛy. t+:v>*bN ~.)5+:ˀ9rgHgq@%$wn?dg 6P[ӋB!|(hTqP*v]KU 5.gz#ZG4&?|=jS}J"عAQ5WIAx}+_,SԵsv_YdW-}pXk*PZCCNAydKP R6$H! ً4*TkL}QZO~$MN >z̼dnNYX{Woqvv8p&o^Du`&e"l?ɱ8 ۷eiWIun-=*}}yw>2!;yʋY'@u!_?<:sO[!9,;;t9y|GOe7x ﳱyeun|ąg^'ogQ{?s=yյUstBe]d20E-jQZԢE-jQZԏ$29kE" 7e> YˮRLT(jyzZ,L8Y9ւxٰxހ: 0u˙dZ IDATKv8\$#};|RAm1,'f(CCAMn6&C?Tsh-eKLSP  )Q{ȹRԔ`P@%1dStf+ tġ0 3unƈc< $M9Ѯfkf깛^T><Y̜:5nZ*2S6S15YyWNpPqj]9T 86*4R vNnE f}]j%{#P@4x71͙M͝rEbw츓&:GNLYWgsSL7{= -ߵT̚Uz2EeTB7H<@ǿ>N 6G5]Z )g;̠s8Os7\ޕfSrfH!7P Ɂ͚^" ay1kk H‪Y)edk-#(4__)Cwmā6s}`Y[Ӱ;CpK(5, OCש)_E~xRod%{8Oq{>tf,/M8%JBe>;`s/ʵw,sl(/~Mv{|Y$Nlӥ%3nsl, 5Ұe{:w\Yfo|Np}{ ]ŧ4Y= ls9[l3'c'XY۠˯qer9cǩܿͥSϲ|(?qE-jQZԢE-jQҗYfS>2/PL[je(I#ŕ(CER5ݔF( 4B^0džT(!Pij7q\aDJ.rMP!G09M ê U釁I1P9(6tE$ h5,f|@uࣆ+lT:} dxU|̱ܻq]CŜ;J_HPv_E Tj1]-0Ko*BLZMMXN)>ϫZ=/5 OAư&-Rsh7X0nBRMD@al&eH*}toS5;Ge`6GI,RKi0Z (jy2 T ΔbŔɝ]᡺:g)422{d ~p+MT s. f !|T7_aF^bLɯr}96ܫٳVF5j\'U>Iu-~ :xIzוE-{ԘDccvcO^^ܛ}*ԕVJ-haNZS8l+1b˖wzc5&`5mZVj+M)/`P&6 +>de;.fCm社dqW=4&}\Gv|D_ɨ(5sH܉&Gdoh*`?;5eFm)YI'<Տ M78w`Pi"ű5؄%w3w~lHpX)>麖mU[FxX4@;1;`1G7pd}t9ӗBbww.%N:͹9u7n~žY^?럠TX^^ŋo?"KQVWVy6'yYziVPG6Ope.= k`!O)=rIN?XԢE-jQZԢEee"ṿŔItFj1QͶ N42"mKa&4 Y .3 =n0RDen7g|fITʐruSM9 Sؓfh >B4hVTĹH&ԁRm?'8XN)˒`ӜF ^rR3)J"4Hw>sd8\2{[۝ORyŔ|َ"b0 r4`񇭃MEg "ի5XsAP )@Q  ΎsO HS 댆i&{fK:nIVRȓ2[+v-J3SPMq#lM iC WRG FH9i_$&w$PPGb}etX_یuD}S%b j,E,n6F׼:XR(ǵAh83s4H`S5MǢ5xC+}3Mi`?4z?]K9$D/[[q@$la$G`sR~S"aƮ5Ȱ i䟫!׾^VkDBP_GƘ]B67h evq>}Νy[¿|p:Ӎ㣏npiƛa*ϝW̍7G;33/yBWC=~‘#\[3*ٺG<3Jt<9}(UnfusN+GsdyOӬo^kl??]wy}ļ(1ӥ^7_pXԢE-jQZԢt}?cp iꖔQ,>s; U8y `"Sx._ΦEk&}/vL],ߵk) |p]3Ul 6Eh˞w+Nb:%d5M`95(ßP' dCJ:-17+EҺDRFr6Np=277MULI$vs\*DipџуJ bqPS6fxhxva٢:^i`ASF%D)^Nyܱ@LAMۚqYˤ)JP9ab<ŀmf#fXyGmTmn|/Ly Y$C9g_A6&{SzHvO2XsΛ@uqZd8 5x􀃰% ;cH[[b3ÿ+9fYxG19/{nbhݘ} Q-T]눘 aTmj;jN ,dkSsM-khJF+hJ|( xUDhY<#S|%yni{X:F4]J4{,W|nk&2oRoPyzָ{F|Ң7XX)2ӓSf9Iy#B/l%g 6yaS~ɢ.a(_,kY˫?ۿ{יoqu=3=&r;7ore f/sC6Kׯ_f6y.GG0#ֺJ2I*"=󡰾>]b-_kؽŽW;lmsewoowȿg?w=䛿 gg|rbz12 367ed}NN;C&:-/&'Sfg~%V.s#$ /k\b}{1}qtrƍoev66蔳震OX_g$ uzgK6N8O_dN,kYZֲe-kYZֿw~m=`Ao+/neW4a9jaFm:{)%d\#AwRpCM9oԺSmFg7}l\&s8gӜ:)vՕ}7bX}5\?+IaT7ܪWVE]u `k b?*@ƀ1Wh U7߇"֮m1Ő_+]ưec 5 $~Ze(gmFoj&vuFa\Gt0 6̂;vTˬLb{SK57j3s5p.5A|mksήHPmhuj6t1+ 2$W-XȦ` ֐:2GiU}=E<*-z 6; ဿv i̕vp~?%ź2=[, NM1*1~O9c@B8^쵏iz?R;{ ċkn1 e|^BdG?s\M%и,G zyN6v \|dB-T߉xKrxOn!nU]Plg+SNmlK)~oAUEcłC _\ "l怰^Sְ696w'(8Gb8$h my-U}'ـz1҅v_:5P%{(!&ߧ6A}#3R6' [ͦY;xڰzХLw I2],.)XĈFqW,ܓB"Kj{.-_prrl:'玮l v~e{4aC^YԭY#9$v`YZ_^^[׹~>Cde|yer'蓻<9]9"%XV?٧3Y K像>cuuC{ٳ\a.NXY])?0c+Wv{dzcrmo巿]^{uygG|}}>=9 ۛƯ|W^~C77ڽ>2Y]kNϘR+o7_ĝ>_:Wu\=#97_jgyD++4'?Ie-kYZֲe-kY._]l4`YjEՔ.qid5Dɬ̎1K(%muW6vEo Z ]&D`RBmh),Ceܨ3UUj'rPL)1E3EHD販zǍBt@skQάճH@ Q|3?=trC.9 )bٯa D|R@Rnh>lYywan)!Zйm)E3VkfՌx~h!of' a%i`9O9 j 6*fOkvː`QلDr%CPGpo94Ь1?H8Nl6:_yN1BꙝV&+pv6bYȪvslHbv20[DO<,'}oJy3j?'P3bmm&8ٹZ|͞Qݮ F;pNѠ>z>&ש5a^Չ8/Ve`Qe ,߭OeqX?uE}wyt)Kꀠ9m@8/.ʟ`~֥v} ӫڻ@@jĞ7j>>XVW:Az Tahk}_Uy8iWyDcBu< }n@Th՛읡ڮo([¹Ʋ iY͈A뮁Pc6Pe#$[h;s)%{[-@b]7ݶvup/;jrrUa68==윢B}s zssR-$P;|MKc%^ֲR/[7sc^.{+{I[. ~'ϸ{ͅS1Ӌ5ݸl]΄r~w||1?oVrr^7@|'-v.mpUn6/*/}uV9₃r:=l>08g6O?{l\£svVX[]lяy㵗]L9=>c%2Iefg|lyIuU&prx˯T{.k+q~q IDAT>ѣgq^}[ׯ/e-kYZֲe-kYsJ3IatnlP&5k?LҹE}_1sonf S5j;*,ESSWYmd۷V 5aQ`]> ell'WEֱ%ߨRV۴@]NC.3̫30y{iU  k}ro6+eIWZet>f%9;Jck0=?''z0 +}Gr lGHB?f;0hܬa*nqhX¦AvW[^ @6Qbm5 vz ~gÜwe!yncUagau8sj9,V[W$wf3T0 빩Vt(nS@gNP3̙T ,Vh1=ljjҔ gB'5ub  w)`w HXCD*jl?Llڔ TUƣb;4Tpp+ V1֛l;¼Dpj!9GTqvK眲SksKڳfd,A,kԱ/˯? Ԁݷ_%`y;R|a;)@͡TھF ]}ߪ>-Xk/HrFw67̆?ԡ _h >xP\~<0T]5셓5ZQ\>Ҳm_kFR4'upF&X_Ěr`Q"K{bIQ5h6:+wԚ )mnnɛ&[j ѕ}2T*U(bT5a{;%VP1!֬ѴS`H }MT̪Bd,dI l'` "anѠZ 0I$ZBjdovm`XsL0yh![6t5_W_7=J).3VN9Zֲ~T\'{uEA&\naʍӽWYM/X]ѣg}yP'_q>;t:cgggY+e~۷/Q~veۢ]G?.8;>Ӈ\r'ktӟr~Ŕ lt''\7qU惰ƍ_b uy^Oznp]?ywq6ofk}g{<|vH_͓Ã?`N}k+_|ݍ5Oyx g{,kYZֲe-kYZֲt:EU67ԛmn Y-5g" lj1N4%rئbXr(`֙*9IJ$ge(u E])ϣiF7ԇR\l*>T'mD0uM3s&"'TTYE )lY0H,ӎ)aCH=2Qmpv]r8PlQh;[o@;$P:ovwIZv"-l+l 8T4'BD;E#v:KOAEĠJ:*ִcdhjm7?a$vRH5+r b;b*`q i?]nʳ0xCxmV{v3\q60roRkEI0TJ- mX&Rٔ:w=g,AIٮcb(FsS BVWws5vǵ\qS F6f?*EdžDͶ<߸_J +ѰtMbud S\i 4w'0ղZl r%hCk@[*2A$GC1`8(ߥ@ápksq~ LNahsSTK/FܴC؅:vj#nvFl.;CMz.ZW^ vƀ7 w%`m!5b~®6l$5e;>~-;:mn'q,]-xBl`rhYZ6c ęr,d& Dh!ml鳪y$oٞy)o#t2ԜAHZS>{Ainp; 9U %T6 $6J)Hk~0=U d]{RS%1wԛCBfLY+T)V\5+SF53?_S j\h  2Q{$Ҋ2kUh|OcDca؄fߟ3T]=Tb60;˗/nqbN&* (c,M>❭k벖_t}!QW=׾ y}spzx/kϣg|*}[Fǟ}OWP7Ro~-?G>>=Rk׹xub~z|O?RˌģǏy}+U^?)cٔ>yƟ~p˗ؙ\%^y;[ln!yŌm$C3=:f;)/:C]e똤,Mxk=zćGJS6l_㻏_~u?;ؘ$n2w>gwe-kYZֲe- 7(9;H*ԧŔٔ3簹F +[lC>t.m'{)Vʼsb6ad1hWr@M*J}s2QQۨdRm3>3EjU|W ͒вl2YqEgSEAd\c%uVJEr2k\Yf{()J[Dw\22}*s-uc# f*Ze>T? 5M[Ns|]US:tF7ҧ̕>6ia0_v0UgJjYz` -^+J@a_Y|#[)kEx~_nr("Z,[Y5G5,|χz@J-3A){jf mmc9(P0fСBJIqs?fjَY&\jksrH#s3ىiؼ7Slȋ}WX,b%_L kO(O] ޱ,$oB ްS5e¼ Y6 [ĥte. vɡexV-4"QAF03sCޜb|4~&pZ}Y]@9~w.kzV,T?Ȫ$c PM;Z"rSUrgSț*j-OW_Js[B 抴{h18lCy %\yT.H Pq < us;R'?-W:cjhh Bs*}GXG3<-}) J{WNwa=]s>#sNOѧxzt:/إ 7y2{sIKk•;mg Wn]gg+u?y敛\}A>ZW_.O5=%_79>?/锃wtc+S^y%o~`YZֲe-kYZֲ_U9 u>,ʴXggZkrl.a^NJJ96=OlmSTMeZU|Xu? kJ1m bQ+E̞jz[5#ΨfgSUWCĂ9CsȝەaN]ɢ}T-Lq{+tCu訨 > ґٲ5+,~ʮ@?I.vp1AAr 0k*&;'1 }vXm*|p6!'h)dp Sodu)42Md@1z9wd:T}r\|6u0EVvUqSXyQS\'a0{qhw*c@X2!4PHpqxq襌x'q՗-KΕsck, )=ԐAZR@S͝7>Ղ@d *f[r*ZͧbWz-jj> {'}px u2;TRX:|-23X\U foYBY[* <*Q8u u94(Xc5hhpbAFUzPИ8J#)kN4%A=C¶5Dy~ 1" YK,4) j,>w:YTu;ׄț&UoБf1m & Zp)}g4ZX_|HٳCn ,Jf6@ě8ro# Ǩ{Ř׮L 9Y᭪\$x?~^7:l(F9-|+os曼y|vv_g?d8;bo$se\M?pl{~B?ˌ?+9~lJ3gLJwX{VVue͵ Jmpx:̙\Zekg+7 /C̆9ܔC9sOݾD孯Wx|Qqzye'Ϙ0O9pu''?)e-kYZֲe-kYa)]n,"0T)3EnB)g쪕2wT(7lCJ@ $WuFΝ ٔL ?Ŭ?gC!S,PJ͂e>Jm2:jZm6ϣ T2T$YTFkUrGukj0(Kխ۹/TtvFVb;cje60YWyF"I-T hDP9)u-%1lþ˘8H3N;R Y) @GiEsۼI),]YWԪ\3ɉ1$PؚjWDt>9uhJ 6Hjc60nSe-$;A LIkj1fLe> vd^WQL-ꊲB{T+(+l@`>N Z-& i6U\=ZYh,a>'e,(ȑ튭04+@e%lqSS%Cwi E,)$0ʟFv6"×c^Gq+µWR/*4OGB"'ˇGF 0RP՞,|+|.Ji*{zpN9'UyEm!ᥝSb=WXݞ<|v#ZzsU?.Y){R\{bn4yb#k vR?R摸G: ?E9W;<`Q fz,5hng}-u{Syյ[$Ԫ-5G?{6u/c%ߡ+sVfo9#~‹Ѩ2F$G2CDe:OҚqx?UrϽG[J[G0XÂE tONX=/c~z|`X{pWjm3 G8>8fg{K$u=X @6hhgZֲ~qK_*k]ǿ[wvxK1/r7VW3g8?a6do/vO>oG?>'g<ڞa IDAT?g|oq?Xߺ·;l߸çܺy7Xݺ̳gBq_~Ӌ)~)9stm9:8D˜<;&=DY_K\ڽAX6acgV{NΦЭrӻl1h+7n.Rڹ{k;LY]9z7xC)^^_9>{tKW66xvvjdy ,kYZֲe-kYZֲۀNB[+)~ UQ0%`ƈ='`(T1Mr gWC.'M˂PTs\͒Y09F!ZXrde3¼j@$a^M{ol5Y0 jfLqBcw[J-]~UQS;څr]K oꖡ56&U*1xkhh雺ɳퟶY h2Q]s1;3YC{-aISiUrn|P[F-l.eX+ !i7,S2TދW ;`CTq4Jqgġ{sYG!~TIgo&?}\A|ٯ?Wlimq+ȷ5(mPzoMVS;N,k ų_Urׁ͂ɛVBG5[`=ޅ$,0hzg'w\Lnp奷=OٳYK+?/]g{YIΎN9'd0*PU!ejd ޕV3-mz (HbvߧIٗ2&e߻J9'HЙrLhꊾ0#ȍ+SL܃.ڽA!T+=Qxf`o'ײjAwwnB>E1;A4_)ZdN1_=.*z`cJX ',w(Tu,ҥZs; `1" ܚno(X~Lfn:qdg}}wJ]J4ZƬPsgZ-rWx(tP l)`h[޲hlJcȨ %]6Vs<#QRB*@R[Ud9C P3Cj`aq5t\!P=`iJƟkPzmU-8FZFq5zCɅXⱕPׁh)5?WڻXQ5Bq^TGzwtlcYZ/(k/ ɓG9׮h1p>֯qC7'>c{\rO櫰1'yMO?S='"fw8<=*'3NN(g.N8'r쌽'OfÓsiWmnw}ﲾf/tkOl1ܼ~;l]eeeĝ>oʫ<|9cmeo|u?σgMPX[e<|􌳋=.1zb{ vי Nqz2΀e-kYZֲe-kYZ2xf+`o\ɀSl-`r-ϒZ@n@$D%Q kA47QJn[swt"hlڔ\0ARJbZ=O" ɉ)ecVU IjOo:T65,=6*<㵺rơIN$,\`95dPpHu`:=ORru]=+1ڙud:=}uZ߰pͣ\NG(bo,-5imvI (X#NCQsRkR cuncMf'U*&'jKnEijSFu4 4;rֈxF5W \ RհV{Àdu:p_ P۾ !pJ{ Y[4saa4\n*O.`@ZHb7s5YS B=7$lkGg[SJbWK)Di,~>ɚohm81T!&)) 98s3Δ>bk4hrl_/k&\loՁykʑQ[ 9wme5$ M h[5W˳f{g:*NVe<vnno{O|~圭 u-bHK~-śԲ(G2ZRgYMr4`W[S.re6,D-gykpuHVAvq6>Zv=+jxJMxM i#@uk9`t8hfl6Z*_ kI)6PKwrSwrk粖j9-m{d05ݮx YTm{) y$"Ŝ4B=H{>:olp,9,1ecSv1no_Rjd:c\d-\1a[[\Elkt<|\ǺɽZ/SɹI*om q;lv+1.O. ^~%ܹK;[/2@O{^z˜ܼvO2ou![k["s^|rCZrIa2o1Ǖ%+DuV}5؞\K5[Ka&uwf)eRj5Sm|2e]Ѩɀƌ2z3@%2ZvۡE-f}[綠u)3 ~N}%aEMuh5U%αxK1+r9srJ}&ǜ\ky!:55dW3E3-54V6ZlSn ^6%UN 5W- <[boСr8|āH+9jFRD2RL!r7|(2sx$J}mq,GjkdT)HiĶT(hXkS .z##VC6 |Kv>6Ǔ#:@JTB=mGZ8Ƶ< u]v'n RӬbcJp*Ħ:xÅ+\ڨS¶1FCyG[y8P8YՒ;_uñWZ "UqDpܟ"!0>3-ɔq6ZF$j h%()yI;#I @o`&TMeU[[cOO8N3Ԧ+)0G+(9~fJm6 RUEoDz#;3#2dRj*U.zUjkQRJC*S39d&1]3;8:K lpsO.t]fvw;BCvo7Co[Bg(k5燪'hch)3Y@Aƿ<M M͎~" Ů˾6pSSk6 A8>>aʬ,fgkjZ+`-u*ۃRW:py[1/yg]d+Ο% 8sv< 3V{+F '{ܾu߾¥K ]Ço/${<~x\V-a<d.]n[W~yq= OrYq.u߸?Gxkk:WĔALZJ j'\fiKq+ÑݘZR0e+o+ Bfr+.1BC4jTW@2nip7F;)`*؊rRp:5j Y}˹;-Bꮖ7XvEB"Rāx4m٭Ur{H5x%PzM9!$n7W<-c_b?r lGUS͂֓>U #Q")uqdB@0P.sc`,H4ueՕn/]+۪DA5C41EP(,T۷d%&jdC=71StE DҕB摱TSLŗ' AX_[c Ė͎5c  ,m5hu8,-߳YCq.=)w1S4 Ԝi<NwjY R,*?ՅW`-%$D!]Ȧ!\j *bJoR1-gy>bA Zɪ>ۥbnWSYY -_]\ST,7c^p{qI%3 Qq;SV |A3~zB`~$3en`Jl6ve*Iɔ6;g*u,o5O_`o-MA,x ImTFVovAj_f]\5@tkjܶkfݕϤ~ݞNl+)^UhT[ IDATUV-yjdꀱѥ9|Xnϛ=qcM0Z(^ad0E*4v&n'\#5F7 ]Om:DSgD&6|TP66o;_Hѝ /Z#F`m_A5 éjy*y4> XZp` J *ZV6\ellpΜٞ8\sWqCkkVsgR>PNYOb%{'O~6Oq=zkg²np<7ڵKܾ7_踢n0ro#NW'h,W+OX ko~R{ϳR ۬2so~_y㵷ѻ?GC6TxY>~㽇|vP6q[q  00m'<+!R4O8p=DnCn1lYeIxMZ~sO"rcN;~ÔsP=qSMh6ʈ6ғAв=Xq ɫCs1poNY^NS6KhDz6}a+Mk,w!CQ3R}Nw= TAO+(2镃c98wgp'ˑ]/!oo]~-x!W_ɵ; Ovo!4Jf~;wQ0jV\l %7w(r՗Gi];dfSZOsIhjeAB´NZzk tqTwkWcZbݩaU۟u~o8m_U)cvup\#Z<)}05+SV ~[&,G];GOiiO傷1;h`-sc F;)gH\iϡR`Q%8(/cKs =S:v`9"J758~GT2Sc߂;ֈ5ӧ[«Zvmp%| X4muzaVbq!׃^z#5 Ղi?߿BM!˫Vuc` {k1($g kPCN5¡֌۾P}sC5j1WҮj[ܭ-*7 kg4B>?\sWS0 żr\uξ<+ϣ,?em+]u'{,6\ܸ/g3_.?`#xm~uwu6u.]8KRhWxZL<8??㕗Fj-5V?UšbVβ+1, @ǀ6f.x~A7 q =[Q]dv@3LTWPOM<@@P-@CJTĚr|yD%A'8ZCLԚ>Z[cIuҼo:ZsPmM N-۳Kh6(}hsV|Ze"hD8o1al{KA(8 K?G&{vk͹},7<>1\s}Ǖ˒qyVz1Ͽt+7^ȣxng,66b<"UX/uX9Ok @:g~3`kkk.`f BNueyv¤Y677Y a1LvWƌB)[s6*8f@>D¥X`),*d::p{C`39gJqFm6¦Lh(H2@'10Jn1[̒c5鐢b'&TUƢ~ 쪓2%+ L)@ӂ+Am`P-A=sQSxD'8Z]1B}3qV-3@hA1Z5J-䱸)"@B .p@/{~/Zj0EUY[[' RѢF'IJPSS އµ_z.a5Kj֪lg̕VWnJQO naٲCh>C DH sR'0#b?()Eb -l`YMa+vKmA} tw):U Sfr"YobjN]=jH!:X $bVc)ىXC4@B b1c$G:xQJfej (!;X~10AZM@ 6mMB3W([sV)jpgm N_,2Y=ϔmsK[okU0ڮ?J ЛX0%êH}D\eʲ7o*RZ\;)tm_lـ8A])ʞ5 3`V.q}skP'~%\xowmT<^6\M'bDW=e BDR\le՟|19g%juyYO؛l앣{lE:Aef{7:mG'@>NԨփxÚGTWU6[ik UM6GoUNжbw $*L=ӚWs^c߯`1U"xLSJTiMyus@ B;\suWRX.3C8 !8rN+pݻlJ*/\#၎\POy)׸ri{}yϿ|ۯ2lnqf,*l9dg?lp3dyqEU8s*|J-lln2 k,K&! Q2cOW|mvlA /->g$'?ѹ/?xΝ?S[?c~~_ ¸:F);{ ֶIk䰎=w%nms`kkk.LYŰ08ӎaHvڡn"فcUwlZ18٭J-#ՠl-aXsBtŮ )RL!0 BnUk!StB6ün Ę(ut嚝$5]K]$ӀޘG$hɄ,VY}(4ݶST~Ep:UvqϦ >j*BB*]&k??8G\gC8y&4"ep¾4-7Ŗ߫*D Ii$T쁚` 8lJ` VRAbDKa\-QA3 dfmZZ~Tm5+-5zh`~mCǪb!;p\zOAX) ʘJ}v׫CɾL6kV,Ӕl]Z 2Vn3JPqWfTњZDWtKs58kj\\mŲӧ稰7pz[u@gylf/~9Schk[>v=|gsI(Nf{xuVۗ&;6 5W=loJ:'0MYh,8K:DT,ef_ެRqkF, 8vmm5jPbt 4b_Ŕܡ[{js26'oriG0 f2| hD%zcdoJnܞ)l =e(CLdoJQ[#^ ĸ@D{޼R!<3TGqG-hş%5Lx`PrkniٿZ3mϫ*j,s5S_ ostx0pń'`,qq3s: q#VX[w3[I^H{Gm~K^|*d/q&T{!yx߼go7~56'\|/63lsfkg ~EXOlllQUs0ZlɇK\:3 |_gw֐'O9eg=D[ [x;|;ُC.]{ﰵŋp?`h3מaHC.\F Ys5\s5\s5\sBvX_gACZĪuo;̕\#f<),D ZTS<2AZ5Ha丫Bs#ۑhYJm|~ gHHb 8%ZAI1T6Z q쀰:~`B\+kxC)lpT\Çs ֋k2SJel)"G Q=SGU4VPSJ.^!GԚ'S,SD*9Ţvh"!$+ {۸eh5q0kf-v{(=q?έm@Dye-x6`WS3Tk2S/Yt${vn;Vɲ c IA$\Ƴz m٫AkRA9Qk2U3br +5]e*h]UT!4g?@f8-@ l>g(*U<֯M(%S5~q` 3RnJGS:jIWd6ٙmj5mg6B ``P-r]*^gF}G}Lj R=2}+dДx aNx޸4ph>mDjyКz[m>61%Qv>u7-a`m68sSRV2RO=GޣLCvS (c IDATzja>Ju#f8tg{SF{xk?4 _^[ӘWI:?7SZezEisl>wEWb*֡7a Ş, `;UO)'<1:l I f[G"=>YojM,Ck2夊ߋ>(\Q뷽XZ M4k$ejRCz|]Kk`[e&k7<ǹoB`f f槪GqSkk$ 5U)j;ojc3ϕשfk̥s*5)ꈃ=65<8$mX,R ,@N䚕fS2ՒS:w> _繗.+G7;}`gh&_Γ;sqkk׸w1%?Gۿʋ\:Қ}6)3lpݏ>C_+s5\s5\s5\s-!8c5*h&5p\M ;lYg`r=j) j1E<]cɀ)Djrqu'IR7VBjq|ň`qh2cdGFѮqqEʨ,+A"vx'y0)*2ujg q,\=OT00] Kn%b@`hC(bT϶V%uo b1 0WE?\i,>DzRڴ~rbi զ` U𼺽smG1;kųf76[4٪* ©(UR:M*@T@%BjMgl7 c<5+qxۄ:Lu_Z-Q۴TPa^p] lVԦX%J`1tݚ3hA+XŋCk%**ԜM6ˣ+5f &:`E:Umx<ߵ_rء`tKӦL)`c{ruzSٖj@BpUi6! U&KośLt])#c]l~՛\spJoݥ-0J-6֩?/qw[wcGۄ'2Q CMM:լu;\p@δu{3Buja ;YDϦ-[q'Cf[o?V?wipljA`1ڳ;i@unI̩ʆ96p]MCg{>v3Sx'5_:Tm3f1{Kqu͙fM5unU-"_+Yp{τ۞ݵ2[K[[Ma쟡Tk8h]NbpQDP od$kUo{~;'oWum (fOÞ;ܞQcw!,}?*E@4]` 죄hp[6)Q\35{FwP}wgygeC#!sf!Ğ%{sPk *%Xdm %g{tјyzkןK )Ń=r2^n.(15ъ/<&˯ҫsR̽>pyt4r,/yBˊ箞k\66lmSr&&1E bxrpxruu使[}!;,9xz7,B?[ӣm_|e_q_sfsse.>2~|woK'=;wׯT@g>kMz5\s5\s5\s kkĔ\b k! &"᫦vmN-H^ ݘ3G6K@Σ~HQ" xfZeo5;W)+@Po-UVWpH,3xN\0BفrCrYͮ43_C1ة)ogՕa9mN"-n1})[yhPF(=SO=3 W-YeUEɕů5 nmvY4-Ŭ ZlȃfU+[`qբY-O"nj (ӁÌ!)\ Xxh3#)%O)*.H4kC--ՒɊ"wuHlFa (JWvUCbAR)'́) 45C-vʸUyxm%WZݪXҦ:j~j7ך*ڔYjrU`bcZb* fM〨)ZÕaCX(" :j@:@1jvڵ8=HmK>S7 ydMpu+BU84unl$7[ ♲m/> X&SNǷ|o1򏳌kmH&E*Z;(ݚ8U!~dSfۻw16u66Sl?Zy`%Nyͳ7ARWQ{Vu6@8t ~(n X2Ş{Qě7mv*JQݡ=}'g4,apJ]iޚA!DncԚD9$nMEbn6Wm=exs93i掠5O˩5KZ)n#WmgOWoH2}*l.G)-[wͮo0bkpK2]WƑlKfoߕl {fLv*qզ.~ R;5;0LkJxsk=>s ~mݹG>?s4aQHD|^!'.\™ރ=xG AxY^ܸqpt_>csf-D?esq~saD}W.fv~N]B?ַ_3\\1JzjʥKXllh gXq>&',kb{{nsXrvs?ɧ=OnQ'?}kkkoZn{Bp2A<~i! rc6b JH, [>!S 4ёŮYn.vpܡpSGZV\SX2+-a-4 h0BhvsH@3hJˢj0? Abm@teCcC{g1AʸƬYiHl`RYT[**Uх6JWcSZY:vC^bXB59`i-٬pWW8Y˜G?nuAR(o힛WٸgHr>4g|rdǵx451LY q:/ՔNE3:p6Utϗsjj D6UiE=vLyiX@IMN䮜,̂2=k>ͳNPOψm׶[g}.LRkaߗ?i$Dz =Doұ=s t`S08in20ܬ"9ny7S_#v;mKq FD Bx%5|Uoie,TreIeVn=hNT "LڠF|c_nhdXT}5kSii%XÞ-6AHh{\suWoes-ֶ(Ÿkg^8s+.l} 7._%_̙\2grHJg1|<{+׮]6|~w,+Wli}]z'g.^'|s~ ^#>|k7e3;~ng%.]8ϭO?e׷И)eUρkkkD2gV .O]krGNgPT@Ε0eL0*dJiJk >UT\]c b*sj?Uu8}j׎V ܰ[S4ea%ǖ+Z2VU~rJq\#BLV2IKL즮HZY ˦⪨޲d!$ϭRY-G"X^o VaVaFSG -Vp~R{2HTfO 5=̪מUlsi-e+$ n#T]U}Ɲ[Jv]*7 \r8H)1EƢG] uR>VZ{vrfSnA$ 5; yoJolrȺD@liZ+R!6^85Dn4"/KNébts2)c/Y8篏UNHR {SBYQOEmksZ:'}R?ctx ?2F=To*: k=lߓ >O1вŭ 5 AdlSR}0^ַ.Wmqv`jsrސ{vH53{&hsFX#vsư<«_pnq?CO ]9߸ K7Y,O|zEZprrbcU_X_gHKWIyA'W!CL|~C~ƛo}O8|R9{qdk,HϩoyIf| j<6\{G g8:>"m!?Oxo~??ggKu(/^;.sumGw3`kkk.WCx~1{j[?mr lv@T:5nP[9P,gyIO_ e;ldYD=9HaX.qeR8z/> ZFo1˙ (fmv@9ks'oжnqߐfmizRT#.ўtrbwBi¾zӂ^{ǃcWkFc|=›?wOOx@y7%A@v$&\l(r{M݀ 9bykh45I뭯Cmor+<q63%ǭkܹs[$y ~gν;X,GlWz&Zbsk;g^>9<|hł ΡѬBP+*.?S_ٳǘ_h6fP upJ̪fHl(zy IDATemxNsk8ѭ`AZͲmZRv`ט=V@H ~/vG:BSX~W#>_q-Ӽ֒bw1'@tI9Eǘv*xS09n/{y깲kfm?Nb䭾N;JiC-MLsm5| WT,I1kMMXMs ij Xm!b|.O '8ԚjgAƦ\nVhpW'K+{Ko0w1U$T,57voirUx $kF+݅)ۺ>E!u1٬n qR7pPWyԿD%누@kXh`Af+њiƞRMQ}H! ޶Gh*(-xcD o)׾6ԣ֮͞.X㣭kJ8 ¹ 9YnZT.rR.(reǑ{cYr}kE쓙UYYk߯$[MMҀ] ̛ 0`Ӽ €=a |{ %chd%$lUu̪;3pv^ άsΎ0~'{!1yOp3{sx&H _}0W8u*<~| Gh'o*OpeIgW^:![b >z}6sloũSXK/! p<~c,%`6:ɛz<'P.Bv&'eў+AwqTT;lj5U6Yܦ`qA%8[YYFyrE9`jWnU4mP|?v$]HlVёɪT5*/fiʶ9u"&\BJ mcz| &ET$$EX>r }s'aJ CT31^9E:(V>Jl?yYK@6sIxCpL6o hWŠWtqXUmyl~޵$uUX+c# 7ro֙p= m{_+.ݕ׆ 1n,>f,,+5ѭ'h;uhjhp&lpW+M>x_T +o\qQ0*`&+s`j6囉]!޶R[*{8cMoMp]$]M۟lc}w'D8~1?5! %61b*zhy2"Zls5חY_o&}Xw_|}rq 4TixGu.' ҹKxr w|-K8ubNo?K8}={\`n~v O_;}MXǩp]|;#/NBDpb4?3h@~&NofW^{/{{cZP5xXku[UJ(p iF6őLe7`ߧ2T`  n ZCFu`595F uxt-zR\ue\cR  Dk+TQ Ʊ2V2BmM:Ri[B9a>2'3^9oc) @* eU)eJ뙹^`~93]F}$!Yʔl 됍 W8 R g?B:UnW " (dg %8'j^:7gMj/d1Ogt&kw##P? "h2)Ƚխ1VLLo! Mpb bԮMWAJwڐr59:$st*ٙY־. T*$ FIn/j 9=(4Z`jX ղU A9 粗EǢ!V!i@(Áļ +׉sec<ՠE&3/IJSF ; P]/}l!3?/T\iNEtkt.ec{{M/| >#|a5AK ˭/`Y Ͽ2轟+<4x88CWM|t1!^}2^|~W>G;{kjKuϝ&a]YS|l?>07l{;x+Xbu$NacՅ䩓д7nÇwbue#wG\rO? >ĭ}ܹ]Hn>VpE.qb}Xy|vm)<3=y>;O9'ۏqgqY<};*~1<8qj / ś90\s5\s5\s5LV4U,[CVa,5dk 0W IHϻu1BB"Qt"IpLW>oiT6*Am-ܐ\6,2Q5!3?pY b bEJ*UVu+_͊zTsbR 0dHZU@# ,@mX{$pvꪮ2w]U Ti KDК/#Bx3JY2aRRZM] nE]?wP.&@BA1v@+akn_ ձˬc!+KhST ձ0&9J&FI-Y0]t_?pU4W%`gZCYЀ]Ei0߷8Dqurn{sKdj Z!3ϓHfЍ*z&( BueІPx<J.M <@ {YRo$iVZ"ڛ6tścTCW 8*LstMjiPK&JE#U!¦{)13Xw5 mSB&KM]flh46PcD-ҳƙ`9}rTfaş4jjr &*ʎ{`C Dh'![NzI*I)UzѮ}lechМ\oBZ sc^%T~MG o:>k:=/}SCw`_wGTIߙx XdsEbReBr;lVs{h~a^W;*nHm hV \ NM5s5חS_/kSw6M3X]>{spMͿ>7~slݺn( G8(ml{w|믽w~, NY>Ys"J%%6cA*ur%z^wCPu?Ccءwi9bth<K}ʷ? Z8_І-q?W\h췇Ma=J4EW".[0lj|k{bs4MT KÂ{ӤU#C}}HKkDambvg})SMn]$51||ooJVmGIg /J>%H54ق,]|_~U[_@-1HX@(C9XQm=Sg^xIoҕ:5`v]Bh)1|S`T(PXo}Tk/^]_[X[?EhX8(;W׿?w>ϽN]\oxk6.?.]{[ٟBDŽ~{ ׮=x_I4ak1nb7>.^fSh>ƃ]|Um,dFK||vqM Uac XV޸X'?y8DpLX-5+Ư~[x筷{PG*x1^y%|_ǻ|ݝ]w~gO?+/Ks5\s5\s5\shRU5Wt9*ٜ R&YRvg3?MCphk!%H:HRP6ăنeu;J~ৢCB؝b\ X^K$0;* èXzٞ3+@^Ն$ ۨt{PB d(c%\r Zءiva귍PWäb74 Žo`P̞eN }L*<a5@\)PFޏOJ Bd:Y8oҳ6qc=gŠ4iҪ@hvNLvA7dy@E5Xf*46u\" 0U6ł|]'ڇy,~B ׻HF"m 7\о; 4 oxH)kSh`KijKŨ*-}P5oҤTFI6nJ @\؈m6u!ᤙ1dnܤŗ ])JWp#0ٱoa=lsPu^Ժml+Ui xGZC#bM6/83ZB!e,4dLOޛ~?ޛ@ 6\{9/ գ/"<ֶ_cufڛ[C8eWM3~[Oßl36T sk0T"8 ?.GaMEdյBp MU |ZE7 Y/uZ@X+MU~tЩi| ?j:`h,^LZ&B 4eD< T>mr833\s}ybӧq.;Ooa{e}W o n }Ow1N \; Ռ{7[cQWp"駟Ѵ {O ͳx[XC [?ǭ7yg8uj'όX?/bh{{8::½[Xa lo?[gH&}\ s3Gm+k&<9!Ο;XOq,w>~{8:8C9D.#{%[r ~OqxoGo~=kkk ի[-@- @Np{KJOnYn6e|3rృK ԥGTZ?+}[qkCm[>/_~lՀŐ$T1qtEl5CGH"9- R0ڡg)M9teY,Wsxk&pOW%P=?2"R=.vJ(б\ðpY81G`Pf& IDATp3xNHo9A(u'm[X9-ssa-y*R%UmƌR6gW0Ba(PgU8QNճ&hHͬghjߘj$6?*f OJl AG솕q3IXvТ&TPm*s¬1C&v p^o(Tf 0up̆~Ar u!@'G>6}e<@+[cn/ޚP] mRjohhV hIC{Je1 :4u~YNHg47C窃hfCߗ(6[6‰͕vƷfa>nM%ތ5@Zg꺶"C+DlRhp.M3՚w̄+E.NxF3%ok/~Kx{yg /4cMNBO0jø<Ĺ+W.k+U54~?_CnëW^o~;}C?駷;x3xyak1~}|sVX;sg{[w{K9a9VܺuI}|zg f /1C<}"vpcx#xep&8p%ܼy  萱ď| Nosa_Od## 8hypfe=kkkg&Qk`5e˂qZª 8 ¼M M Z֕t@zۂрlpu*gTrv/fD9b+-D!󻱿´Fn F$D8]cJUyPN7,^Ǚ\}kdZXvi5 @$m}M 0!vf! Lm޻f *4fӱqn 8d OV$B0DY\3H-jcY֡jX\~'Y?FbqNlvMոm[-+rsaA{JrWgdzQҚ+#[̪gGlsf@>~TrvYUp}"z\GJ{P9"7anR-b ƽ@ܾt\O7j +xީ{aJ`a7AX8'quwPiүɧ ;;UAh{Ut l?D|}q U W *$Ton9_4ϵpl)J|ԛ*rX fPͨek/y><}NaX9=:eѲ0d`uH8y<^}Kɋ/w?|O)N]ǫO>đe|o}yg|7{G?ш;wx 8Bkx 1d.<Ý=<\ <oyodYP;nᅦ+.…K8E#^` 'WqƧ~K׮'2n߼';x?W^8.pXtk yLHo\s5\s5\s5\YAf2WUZX*R,ǂ 9(V\.RfN/rvJKJ&@dtEScA Vf: RpFP!gn,Tn8hjT{I"kR/6*gLQL SW`U-ꪒfiLݝ28N@ \APjQ fNR \}UX|\$!y mB%ItemcvE LsR*m,*Fj_b 5$5`:t4WpCb~PôPJԕD]rJj%SW9$reM*g2G=WUsC00R" o0 jn5TjVi}*ZfakB yx8c($lo4M#iv+`ڥkJe5W=^fr6"BH !w0[@AWqO\)UW: RL+Ը?PɁ@$[۰rU  Myت(P9~Jx:ժ˻'r2X[A*:=Ԟ]i-/@1 @@gT#W=|h+j"8rVJWBcyAM(g1~{ g:DžmE*s\8:PCزL *8;4tU$8="ޘj B-kNnr+3a$@! ˤeB8tw_ W g\;Rf쟟k_vށMH s ԝ"M*So,G0yd>3rwAb ͠Q֬"'# ,m7.2cnE! Y3TT-h]8.Sky !6+}ߗƞ˨/beNokdZiw~_<+(xϰvgO~9[꿉?v-\~i67eʯO n<{g.Cཷ~Gx,wo⛿Zi?œ'7o>G;իWpy<~^y.\7=hM\y%+q.]m<غӧ"@HOIO­wă3xq6lb}$n[x C'>B6W}Ξ˝s5\s5\s5\s|I5Lr$,|y ;XPuWt+r4TjDur0SJVO?ơ@ (9ƣPN-9\v~ avH4Lb6p!nś:y=@s6A|w)zL5j| <^tQv+|3yQk#/zc7|n H(l2f;Wwbi0x)iߛ8Cތɲ6rx6p`@ozo"~-_򟣑TCk4nC>&tothԿ ajkB_ǧ?RIIw^M2M9w~3|c1ܸsמ[wpڳ?Ç3\=G٧s:{)|+'?ظOm@eOqI, \rۏ/=GƽC<|w?r8vOLNl@'p._9v0C,VO`wocmzcxx+X-ҳgz qwчV +sWKbkpGxg8Oc*?/{5\s5\s5\s?+Šz+44m x\M!PU"qjX!V[UL~▓"&:nZks>ZCU Ip].[WI Jހn H8RR@C\ژGRPpIؕa1\~jꇔnrgmFD&_4@mk+ҹzA Lj~P=AlQ®&XY]>d&hJe\"F#8aRUXq495a Zi1 q MJ9*#iϪ▕9e-4++:ݚR"`d=WA@\p2%HY1A)֖IVC֠U2pҭF1*\3`la*959 s MTmZƺ<1xs4~S֫.}h͡B^J8#PhFc@s-h&(ן g*CB߯oϽ"Z\C| 1 EËg&E6gܗc AJq| QjuZt [E::r WYPyn5:C@’m'pYkHs^U"8ꎦ r<[J6mS#1ִITքtx {nc3PZo'5!5lưz;Uyբd4:ƽ'Bb&th̹'vPzCLNxTJv\NknN7X7X;Xs PL o5Ԭ3'4>*sN(7-5TQZ;i`Ш ʽ[F]IX!8&p UBR 1liXN wruy(9k/~ppDgΞTꕋ?#syk xu.O5`:~wqEwacΞ:|!0԰O_g?w gxNxOln>O34no>>0`mm 67էv¥0ll"Kuܽ)8{ \A­-ʩX9kpElݹ`X[H?i& *N}ʻ vpO]Ĺs+k6VaؽyVN\s5\s5\s5\C*Cs[ͩ EJb c1HBՏ L25C?UXkWYk.jxLKƀʉ!'HxBՆ$p9٨lRt%aʛsךagtXHJm66zd?$ )"?VHkϯW>5-<׳T©wJu 8G"9ƐخkCL &nMHg+٭-QePRܶ)-!%j47kT*h| RrC"k ɌpU5\WIjs0cPC9U~Z- JEjs{ [ux"24" &m5ZyR}Y>έ7iuIRC`"Gk[ZeM8 Q`eָBHz}QyXÊCa4 SJ/5;'|.YWD+"8quXf2(VnAtź#{` +B^=%BP&XC-gbUhAwŐ. M3b  8tBqlKC>>HBpQ6 l&X*A sY3Ama혵rp aAThIV#iNplNXȎ x/JN!zEwplb2Gߏj݁?MJ) zWQ;k;@KٿDk-M iXE#W)* pur½yAJɛ>h" t}ufn} rf$DGtž65Mji$Z jnlcnmݼ3k/kܕ{>\s5\s5\s5\s}:y?wo۟p8! ~ RBi G%r0JBsr@؀0tdWx, 0AJp]aV[$Wl::bsm $CVjiyLQ"]!g,̠K*T^ [5B5ښX [6Q(ՉR+rqܔ/M (sFe5LB4 E t.;r%Ճ0$[e&;&Xi)!P \iSmj`?=T0 lJ IDATZc Z9s7AUpTFfҲ֥*`Uk1X>-8fl>Zmǂy)GƵgHB`+OUtWO  T6#˕r88tu$@#յS. &fՄTbR*:3*}M&; ryKDIηI[M>g/½Z,^S-*(`)=39W[W{`DsBX{CJ9JȞLឭ 4 u!:H>o1KWY pE7̝ 3c,`YhABXQ7zPsv5~u(u+UCYhLxhR| EI܍,8ZD3̸º׌Z{7V5a(=]+lVutr3zԯk4lD3M(_?+ knt~qמ_ YA?)9C 9ޱO{di/SMˤ/6$nрT-kAJ``ƈ lo&5:k=t[cQV)&2U͍[dowk=f>*#c qr8xV7D[C_m5\տ~!\s5\s5\s5\s$XC7B5 $ Br%8SJҨ~9&lsZޢܹ Lދx?#%4S;@syӈ7ccixTeLkJov1h\+D룡  ;T<54=zPlyĄ&5\_jxkkkpEC$E>fzND+$PdӅ5 !"SSyΚ.Gq JclPyagӆb%PU6X{^-@6aDY 2d(INZehlIPKA1REIt54 Yl<IPP填?@I0vu Ҙ[i)eiERsdxYCTAa3J-qŏMȥ ij"WP~j_kA`.+BMjAN "@J SVɜ|`URPuhc3!BOEѯ צPg)EANJչ'% AF`TXQ-eD莃1$AKcwOMfnKQ3{̩=>gXm'u |E{6pcQ(6'''Tfۺ"zF~Cd+S:>6,y`V;TC+4xNؓ'oh},lo'H}MJo֔)'VvEh, o6hl1 n b~%ǝ;jW^{+to\%(nl%a66`i1]3X6ğX$ּ (J+r7{¸w}ל| f .՟ۭ7:O&wGF8UmWO_ae{DI,yĆg-'lPX=6 ɿ zRks5\s5\s5\sygh{CPQb]~u˲1Z;9xdFV3].ʕ*SF $#dh ;np4܀ Y!#B dx˶ʘ|Fčs֜4Ƙk%k/)22{cU,}&Z+][KX0Jz?fGAT,uw)2Sև$#DŽ TzYPu Bybr8,PR,Tbec*wMGf`điR1UywcWO!Yvu ;kF hѺ7->uۧT5sڦKY!k^X `tsfkЬT=Jvi)ݷ3cf܁@ 0N:q2o& #=(pd\D0׷3@ys2Zc OI-hq#56r}gv:s +fV.w:#\ onjR{gś's N9VÀ-j+w5}+KN]YڶjXdAg x@3&f31ng~׾Ut[u,8KN'r!ߑw&&9 9ozz:&>4ځe Q c |Ө"G"{nՀf+, ʬy}Ϟ.1uH_b0d}kY KӔMljЂaZL@ri\5^,<>{B̩8/zj۳1D l֞9qs89qs89q`Uݨ A.˻KeTM5*̙&1-0V, @XǾNҹ"oT ekh`m 0P)%c^ki3i%<,֧;0DYNXVfQʹޅ)W_Cb;e+UC[`*Ƣ:hsbTcn;UrIVn_0^[o=}gӀvٸ`h[u3ܤ$3sX>_{-mМs5#LtREU}[G67i_(pLhgL4T]|nIF^EnZiCdr. 7C)X=~#$ln3 UwLLH "3Y!Mul u&(-TP>`Y Ԧ/vqg3b +UV#,P-_M̬J>;*<+E@9Ķ[RhOkha6HQEsC=G`p uFZ9"3$+Spt&';`W3J|oMJgZ껔cB6$>GNL5+ɇ,!;bѴEx a*%w`q|⅀h+˓ S (vGeIL d}ZFcM`aDl2?(c&w#*xurQ7M]xXܯd;?k1М6TR;;3̯\5 !p!-\`#S)-#;B*{5}kB[*Y6`;5 bJE\䵶2ԣ)+p}N_iRY{^{yy,edWy~X֛i/nۊ@:^LA\ה"\孽L&jʱc=;E&z@b#!ј;m,g5X֜;mM!$!xi{^6ֈ K_J/V>AKh(ڠt[y3y&,%-X׸2ıFm0l'wt(z0o՜R-HK]_V.g66T*t٘c:8td*͗ܔJ)UKmZsY\{@Pjԙl%0xkM/kz3\RO lzrXj\z\s"exS%g?&?Vx?ium5?Tjij(kgKcfwPwYۺ:1hYQk}ݻ-QlT@k[,{@k|:;ap s}Ҭ] vy1Cons5ldFjrRPq] {W~otU x F9NJ#ZsNNV#a[:eEnj 5blT  qs89qs0@Ȗȭ{||ĶmKqѽ5f4mc-8fh*!,'mzoTʔ1d mީ jf f5` x&nY!f; ],zW&i.GJ£TA,oM`&\\ *!BTL{D0V"k|~(̥ nx|z¶ ,$SGt BKm UmU2 0t?TF홵\КQ9l.ur~LXm|Oe68wc7UeC)e[ 2A'Y&D$h,*"- emk=A0a_j724-:ʲD7`5e)ڟskr9 oNnpZ #穀RłU^M 1,|K嬂?Bб+ 0($etXorOp nɼNAjBeJ>GNٌ?ךvnVkԷ)4jaW !a;c.[o`|@|֙:8֭ j_@*Rkv۾q(B=u..GFR Q蘵|nӉ6블)ٌ\v=83D8>E~h4)XSe_tOq}/8awLg@4?^F59pLTZ#,a<uM=s\E%=`+~bb׍sWϸ89qs89qs89쿱OL*t3mBԘ򀘻p=SGgUt&KBt .[kh0 E+]3-9'*,A3- [!zuG5L iWͨpi ݙQg&"v`. c>KFFcL`;op6"+ka{z8|mbTx 6Bs$ʠmG@2ma߭s$','枀ԿRuӨٴNT:.Ƅuިh#UT`YN7Fn_k16 x B(1ˢ`RLRo6'ZL^ F [LsPݼrʞ@0 `. c!I9|A}\.jL @m2 eu,YtnE2(x H0:&+Z&As'tZyG]eB[@˽cO*>ә3;$L{Ф*0y*s cS|esuXrv`2v~W R%&oUJalui V=ˠ2A1R'@r]3u0eVX@# l(q5)x]vܡuҵARVSFYZݷ+*fbn[[7hΞuvVۛHM IDAT)b,dȏTx=IAy~:+րLyфsFU-PhN]ڛk Kφ8_wNx]~ )c)gZrs36Fh;x;9u89qs89 a+Ta+F;Cݒ 4i5H@~h(\Y TX\, `l3559fR%9qlK gY*+c7j e=H f;f:cwYZ'f3(kTTf"ǐu"@]$2sg]C*:hMU.dNs;zc0`؜V7lc**PGo ^. s(slrLu,Y@c5K]Ac:MsmqIe nMٕ;f6~nHa,hGn*D½<0"}\K%FR)F&RaeƤto;2I5kewzel "1%X3PYg< 2&ou:IVG@j]ZZè ^ A v!pd`P;&}JNZCBe [0;<kU)1v\c5Ȳa $hA]9@ZY'\M"TMޗ*l'1DL,(CVf \S=`T:<䰀探ɳ+?RdU`Pulia1VGҲfL8<m) clrj0)p3r)כ;j׊+`*ȩ5[Rslelg c.mخ9" +`[M@C=0*\RׁQ$ռ?]ʞn1D hxH'KI5lcyrXV\sxR\ ^[@@KLn kY",fcAĩ(`CSZLh=MݩfTҡ(KKeO/h7t)ԑkJ,:@:!STF )jlDBi}ESM)LYuY$B g:us}z.g\ِ5]Oqs'>9qs89qs@zbNBhʚ/ktf;u #pwŸ툡2TBw[Bo.UT@߶#& mnĜ&ȲHeneߩ׫; )Un_PLTAIERND !fl0O*#`Gʮ"X֭mp 7NLR?Cj0 t`TDPն&(-k)eh-Ew3*Ѩ2C6zr9oeH,UJ%ƬW~1 n8%'Į.iX6~,K簣l1in;V>' pk wl]"4hNW5j4wьnm#uyO-ՈAtX&vSg@γv|s 7sk'Ih<'γ:)gBj(u RF,&94ް5WeXLhGTjW[matD)!Z_Q<(+u{c&9eՋdq%(>e-gSgjΡ&!?;3ּAJd}}2eO?sAV*bAwW3{O5CRNyμ:B5b"Ք f(GUG?_PQe}XXcH \Mu|y4,};>@rGZ cX /1 ao~O#m7⧣/=! Cq_MXhKCWx7[ۨ`Mm)B-!HN^^H@E?ʙn֭P9c]¸ ]{m*Re6!B}k{ >:`S cPR|i*뵛=&zYGn8,ggVpL1#',&?&&+WE 3ل w^;.KH*@IP^ Rc ݘQTQ1c*X Vߓ1&ޑ}[-qw OT غS],)]Mr%՛ "B&r$3[\ԜTQ9bAcw¨Ĕ:5'1 c\YTf[l#b|8Q<Ssjt"vT-o`D 1+ܽD!0 rh킜;L/ $E&"}ۨS)#' dH g)<(ר\PT_5ULWͽ\6~Ӕjv^|ʞׯ1eK)gg2HBYę -{LG@:m w0 mTj/rP ,gFf'\e xkln:ix== }-פ!> z5I*U1yRSg,U{6ގF\c׾*`DBw}@k<Ϫekմ{=dCGvݤ;#& }5] 6uklĨ몃"j[?^}Лc.lq񈌉_'H$޾&W]s4w_10O7?ӛOp\_.W\^~[6=r__mݰx?k_F;=1pW/p.̿ tZpYu$N`G"ƎyKر?=a?[}7x"pOϿ{~OC0Oq{?wws89qs썘1`5B!m0Yv0aAep"! * \I!1f f9Uc:u|Jb)3&2[/{BK[ʑr볳cF|OLqe-ðN0I8W97:ȯR˅3 `9*Q7HsdoT|([F*b a@1b .ȎԦ& T ='A'JD.I['\m 4 cJe%\hRFz#ʎTH2BҢf dp.,(z0;-XpocAH ڗ;Ԁ&8ճwLEꘊvB)[vv^w, u)r ^v&zs):쁺Ow5.UV^gl^&D8B]DKjwSMX IPKAݝu~2a~/A,l3h`95jPȹ iopΨ9Kd7ef͚<\*V3u26oHc^=•^ e9o^q=xTӀUSgLӚtټs1: m9qmLx7v鍉 9~w]^6\Xoݡap6V)}bߟ0^͛w[nNl< 8 ]_®&x-x%ƛO옏o1ߡ́o>6nxkov̧nbGC;xk⛸$`/ׇpGį'/>۷opNVˇ{]12/淾w <~/=^~k/Oo›/C]89qs89GalfqLd RհDߺVJ@*S3'j1h|zĘnKuw,\112Ycn0o.St)kDI.n-LrIw W*J6Z.hrDRI:Yܜ#"D9&jFZ'`em 5v(X߀} DD̕J̚sHd-̳*1fVC]y]ĭuHoJdz\jTo s2uY*"NY gїϳLXHm,qbeQuJ04(T r!sǜ[Z3`UA )i 0*eifhhF)/kMw(Z_2-Sh'U6U)ɴ b;m4Cl knBM! 1$gs2Xqo.+F/]S 7chg2r-<悲Xu +:%BEcxT&!ff(Ĩ!0G_9/̗U@u\ΩE5RY2 ZNT3!Kݐ)ZTe($$6N!x7YZa܃ `5`ϲkvg؂A DIPha\"E<2GǾߘmkaUK ~eTՀm*&SF让4xR>yT]9'\kX=21ǡxi6:^s& nCrFVyWGzeYL7eώj*fZ 5L= Ǣ)Klwmv}N> {k՜3~&7G\H j{Պ/Akr\.1hL6pCKcDZ"V] c.9H44#sd3]` ;jߺ7uڇo9\<Ґ!ǡX65הwd x߬rƛs_pSWӘcꇔ\gKG1~a7q{xz|o\XaH]ozW/_zoxxz`c;^z="2fcw۷[ظݻwǎK v\rw⿏??]1#;럢ݞ0/f]?x(~+7o ^B3m^z+^O_;naߢ82&.c{L|=} {#۸n O~Ň@P9qs89qsQf30tU6ɽm'u*56B *6"[BkJ6130&شeQֱ]Q-ԘYv@H7[vL3;F rRPWVKTSZJasClgKӉ}iqq.ǥK*u2].ȽwBZ~*Qq\z*OwNeQ5\h/ؒ*F.F+`!fV\} * e٤0*jH:N(,7)R4 AT/Fw훡9cᗥ$!؂_&HۑT4UMhYLV3/4&naʔvC>'Z|.Rd,pkP߹g2^ J'/RYTRB;l@fZ̗vOpmԜ)AغrӚi807F֤>ܐukt0tYKo*7c ݜM4/H+0ƘtkO$[~=J`޴Gcg矠I.;R-`ց1cc9-sz#9aY\A\[o/w\1el-ؘ@aeºm)@.X)ԯdY6o\ 5  onR 5eBF5Y6TTlF{eK)W (.\ޤ=V,X,x8~kk=2,k3w4$"85gD9x'L׷SfRArkkVSOq utN3KO"R|qXY,w(SؾyW2'3Ӗ׼ eݼ2O% TD@9β3i'ͽl3;:KK{0[CCCjz}gS uń|~32> qs|}gp{a뛺T> ;.w⎾UhYfA>o`fRa.W]6<ߣ^[xC<|=mp:. ~qxz|/V' w)淿,B IDAT 폱 잉xѳ3z`7a0޾{woĻ~Ë+޾@N?O xzzoF@xzG<>>_`W|\_޾^ٿ=O~7~?uws89qsFж::֥"UyuGl*rfRMZJE7\TK*0ֱ@ GZ>@o*]LÔ8XOHd L[1YP!ؼ)oN9W`h['DלHd1,ڶ*nK<#v`Uz, ڮL. \ 3;nc"c ӾQmnjg9i{`3Lls,Mk$ M-Y#,6)]WAx]JL3)0f*|9«w؂@l`/f b6+] 9~iV!R#j=,ڇ12.p@P3ġmCt$@UԇÓ74wl$T]z,O)A1weA4 ٿr_1N :q'ߧfU@͛&ALA(7KEV^ (j:u Xu'ԝ)ER`Yڌz hWI-CSp$zߨʅLw9j1x *-Zg J-[@Qc0GjiZtN) EL4-<\?_3;$לVpL/S7L?,wk~Yp度PljYX-;ۯ|Լȵ\m8˄X-2ih0 ,N^ 6T̨Z0u6{޺Fkbt_\yfجn Ϛ=׳I׊^uXW\lNAŅC6f@kaughҒ8yw7g㋾S&Z/9wrvc׾cM<ZkIߌ,s)iSp @rЙa`V9< ̚_ig׊@k1]'̤rx+1zc35V_j@05++Z4sl\ҠPn@689 1pwސ94pww?Ƹ?l=sb_yu[7ӡ^ضGz'2d[>tȚi| |r07ބZl\x%h9z?4v^.wÏZ| /?O>FLvLo6޼7's1'#{%n uaێy{# | c\WNBf X{pk'fMyzo|;/W7ܘC>ç&~O?9lqs89qs㟵1rMوp 8d\Y#&,8^,`Yee~*^WA2Do}J6iϹ,r/*Դ}gJ*2,f &I*BCJ T] 9TT79b1dwؕ{#YmK dRRAFs0{nOOXiTKt6;h i }: 3Jlv bTv{t٧.Dp7VL$Z "kF)"S!"@RQ-T $J5R4sL 06i"C3`I db lMU93`rb ֑s2KTu)T)ջBcSׂ%M4U*>aMh9T \ߓeh50R 'Z3e=6N<̐iJW#p۟j+oֽL3om9 \< #(KZJ1?#d{M{:"Хb&PfMNTXok!l=Z q)CG*7|}~RܵwܜM Z1wseF&ȥj :ׁAg^eޖc ܁nW@euKIx:-kkWfi9$ +cE{0)Rk&hk-?&ٔT&1AYS;{cwTe+lLB_}.h_z)u}x4糌j ]/w\33iHO`noǙRUM߿a *O  a) CW3eʳ.@LB920!YM:#LVYk}ocsN)0V@ /(Z­?IelW^z*r gr5PΗʈ޹^۴Ki|.1˂hrY1^}pn%r4tR\xݡ}a9v6E95~~sk?u@9A; wxx.޾]p{z︴mmz_!qZcy1?ްOraܱ,PzCحz_: MAV ǎ9o|/_/go }۰].̈\#ܿx@ 1Hc3ǠѐS3ÜּJtk+rw psaRA7lۆ'a{Ç7? =}A|yvv~mҟ?7Ps~qs89qspss h쑩1[#|H*F"T8?UA@K:L6'6b0 TIAu4e&h#ymT{2t `cTe{ - 9*A5x&|XꙹZCʞM@UKFFUr,7ZP)FIfhU ‚-9e@5!EOTf>] qBjR2/2/)_CeK "{o{Ϥ0ؗҳVM-\3-9'3'Jy¬^{GsȺ?ĊNH<gͥןUGMj@0s}.<=>r]meǯ{q}Y95jܨ Pnڵy/v- զaI+`PDbELZߴS]Z0Hk#\ZW^26ظɨny֚M$zRV^8 hY.|=3J)` x[ Ȳ&}~\c)h},E1Ԙ`Fw֠@+3/r1e&"38?6]lAҹX{&JT<Z\ǩ>93`Z<3yg'TsG+oOxqumwvD>+3soFxl0k '`ƇƎ7\_PYK3tcW_70 J֏%#{ cߣwRQՕ9)<"۲A9;g|Zȼ1c;غv{˜ PǬlp^} >/[?'~{hpw~-f^~r޿k;x Gqs89qs\2L. "h}Ucu3s\\Q_cёĭ1?tO "샪Tw)'*\0eTA_JMD`kTfNš*7îA wB9{qsDHGn1/w&xYMn#M֙dS߳[LZ:U @MvP7CLō [?r3`R;Fu6ARR28UxEGY~ͪBRiw1MٖY=4Ph2+J)/_sphqYv̹$p @7 "cn1aDG"3LQ#7*ѵ"ө5,#rA/Sa=Q*C)ӽs0C(`Ѝʭ0IA^?eGt^H@5UGeVBXyH6 $PsQsBA0=ˆ ham7 e1sȶj}`NYzwfHw3׺AVkrxs3uaq)oyH5(`>#TʌRP RܗM\vdr c :$aB9tY`]I}T>R_fj@.3 $dʲTeo7Z"ikArYaX\W*3֚IܺnR]נ:9lmZjQΐ3Fu2yAg=99!y_q! #Af9@,ؓp~C er9.!(IUoT*"Wc X3m\g c)f\sN̉IBYqrx8$gt@LY0BMHxG0*dp뵚=aZ-T|]ŵ)jcIps?NwM0] w}1޻J]}cεsw M"e+ y` S'%J pò,)%I廜K^kwN%ITXbu Xf=$䜠Ә۫ihoG){̬` \YyY`L{E `Q̰BE&EL9ik&)R帾 %s/3wwd*rYTUXЂnr)\A h֊:Ea9g<[  *8ۀӶʄ\k(HV6'k).8)1 k.d [^ÈF5ԛ+D߰m .R3XԥsD * 27li+;| 1mY7[ A ?2ε<$hwjr߹Bn6{'lkna(v)ӦuHM 5g r^_iҟ 萞/xBl+)kCnc lqO'('G_t  7'EJU;TlF: 5΁gO% mLeߤdk##9곆!`c|C SE<6 ,Y'>FVrk`ʩcC0F@T~;`~sD!(o 킌&fa1 x7̤er6uQ3R#Y2jгϻӦ A[ڜ]u)00M3r^I]8չ]QU eo*ٴƓ^ :\Ipy{gx0_2.20sU8`/˂w8Ohm~]qu}WGWhQy @׊z85m8xx:%iCV.;X+Ap_|ty_~Ut|a^jg6<>=a;=jv}IƯ[>Ѫ(@nꚲec3,9|CFzlZGFJeۡv+ueWops/^WOe;>`% +-_;ͧ/?޾g <2.2.2.2.2.2.: zBڕſtAL,PYS!?e.u4(`)pvȾ a;GaΈTQaP^qNӌi.h hWT y;;9,v)p`zv'@Z3Dzqo̪MI ܘjkdr `*֚l&Ȇ8eQ.gQnh[Pi{(xUKQ,@րPf^e-JeG薜]8)Jjf@05߭eCMy+튩LکԪ4qa*PL OܧH9l35)F{E٤N.O $(^ CCFp>X/Ht.MVѷ@>7wq_mM lRн@SVϕo䚣}dK\:/׎`v%tv AXsCIT2HQ<܇t/] s^zĪSi7h,J^ Z7;ϥSM3A?PHRZt}g7vMv Cspz;գϞZAw5`8*d3KK6'y&gfPL&CH-&&sduC{ԅޚޗ/XWhu[~f>[i?jGlʛ`gW] lXKFG KAlj̱ v1$gSt3YLX7 B7܃'ߟs>m醰sXM*ZXLMz)EsL,yh#شOQ@WAjφ;:Կj5]bcOuz01ry6Z@X5 0:./^Ѵ ;t*(K]-[V|^( bc&zВMNt]˸oo|#^ւZ+vnevB /_5<=|LGW2j&a8m2;no^x:VB ^ qlG\}- v+7 IDATMHuuVqZqzx?E_wh_K?Zßkyqqqqqq߱zR ˲uIH,,'1G7eJqc^C,EJ.UHCtd)xNXa@Z2 ցnjͻ`=\Lj#fc,{! ٰutr:m;fs.2ObKC̖#S;mSbuߔeeTˮ [Tg^ BM6CR\{N!ܙYjutKN4,iRK%D %p0O+xkUZ Zt@ , m!@ŪlM륡8 r&YM ]P0r^вs*"?ΰ\وqdKbkHޢVC+TF'O4a v\ ##p׊\=Te)2K+gȱftlJÄtu42DgQf"PEM9mؙS ,uQM@L# <އ@,M˪F $C65;Mh8>УE9fT._s~/Ӗ'浐5ΠRjfexl!Ks7S>V9`=lA& \gD`'tge3Z6LM-\w +8~rtY 3gF,ϟTk/2 h;g/3;`&LPpd6ӗZ'` ƎTS@%c(1rf튬6T7}nכsa1|]g{DL seI塏6`AWt2ayl@9Jy4{Z8]BEVn=,c,jiss @F+a$ы% 84gYmYx,e@"F#ANHgԢuM se^g2mS9B-M <.g\ɴπM2)3w~ٍ.t_e\Ʒ5 -pjxq}+usG n,˂pHZQ Tฝ,+8+<ݿ/ ܼz@B􎞎Tue;;vuaKoZe'N# ?̐G@<<I\)ƃ!Q 3$ywC8C_n;J-p- #\uYpW쳏qwcGӆ }?]{Wx]z%~+zu?Yuǧ_ool]e\e\e\e\e\e|Fu@ 8+Q8 4EDY+e|ܧ#W|E3U]D%T#&,e?c ><Μ R0Jm̬ț \qs{hɆZv]HS>ՙCÞ}N5b(SW > 킩-se\a:pd0̏ @8gݻ|TuZC N?sGqُ.)묳"Q?"13B4CDFR.K9Tqt[9 a!=YtS֩Ju7l:_GɜeA7yOHIgW+,ѩjJn=꬙3`R\Y(ZçOp9k3 l-#M(ܚk؇w\ݜEs:`7 ]a_X~e ؒ3ݴPkX1TR&l؈vj1 hfAyj³'{芀HLg6qok%i۔3ʮpz:>|ǻ'\]a] N /_v`¶p[$cr՘b(n*10ȣR! :hI}eȀg;a7? UhI$'S  @W2sbE:?#{4Jq5DQ& tŧx 81 c:{₪1,! vYqn@"|˰9WUeYYݙlp97FN.'3M{̧!f: `I9GDٌaN.kA'6;" s*Av3 lZmDž2uW 1ރFεP`d'he/=tfҵ* >|Zepd5> z뉥|.˸ow|3 ;v=]vK7/q^;uŗ#vv=ۆ5ÁW^v߾Ŷm#+nn_P)vx]a {,{vF<=>nGq7۶ŋz= ?ܣ{Nk~Á_3;z'uWwح+zܘ?ZCۘkBzЂ]q{zQeݡO[úVܙ^ovGݱl*wtB WW̋WXΫ]A9n(on]e\e\e\e\e\e|Ʃ7Upv8c=aՕ'+R=5y7XH"- ѩB=V#:3H)xhqPȨF@ aا7( W v}}2#/3FUಚ(Eg"F ؒ IPu6) )m,}Z: ŖJ~;T5@t~Gɖ2-cK-BUnYQ`m3TsCVʧXeO=4Z,` llQ&R*܁iCYVTq#TJ(#Xv[\vTR]3 5~9D*Ռ0쉇 3#n#@k gp)c;5j݉>撏\U炪63.cfVfVpTUy躖A Z۴pe#Ya|:Y;, TѲ,UcWb6}9ϡHt`3Zѡ8@.5Y&=Kڶ^x gr{Ilf~;\_R޼,s4.p]9霽>~CzV|LaBXnt?#<=,ۆwo~>}qqqqqqߥᎰ?y2ƕp/A 05T*۝i#L֐Dٽ^ #R) Riڷ%8Q%܄;z;iaՙ*n@.uMC56mX4j0(gukqgCT `ޣE'tC'Qj:UT$z#6$B DX >4C(G^`P"0[w0mcLe&N'T'<88ZcY!EkǎUs&"e_ K58T`iZKIk `<5Rmd'vSFl3CrmguE"7;:.egmHSYmο-6 "heu02aCX*? |KŅ+yQ $xeG{~bŒݳbTĚ0{jK}wuEh,Щӆ*CP=fy:Hx\N]V:) yh% %P~ &&ԏ":u{@f>cM2{$&l㶧]5AEY̟sN $7BZ+; a{06LLכ ӞeBFgШ{eoFHKMjsFx0i!c*l#ls61ʁ*TƄc]&Up:@U.m=Ϲ:r|v$w=3Ջ5e\72/+9hВܥNeqK>_n4^̿ZlD2fߴ}5ihlaYW-˔tNco(J#ZQjdw.}g16S1GMp@,( 5 dPz+ie\e|+?] IDATu~ctmiW(nKp57dmX|EtVl kd`t:"Z$O<{e)0+@INVvkw[p}s k7Ҽ|Qx<ti;bkdRACb'N#v(/^ f\-\bYysamO4|心[b?`k __>cy]e\e\e\e\e\e|GqŞUEs68QK!Fyg.[uʂ&zaV Y3 xu֩u٘#IG)Wq?zZ ҇bjC@Ƣ@J^0-okLXeb@>TN[a ~Y^(ӥZQLRJ GqMB &{ҞpF1N\큚A7lF oz&^ 2 Ȣ*-x'ϲǮ#wW-w{лg=7Z9Tlm":|tOx*3RXw7ZKkgQZdk5L,{^QL.Zٮ9@XlH04Z]򜟩%/rXiМס<i1-f ٘O_N.=SBy: u]͋B1z837`C?Gx'央qwҮO=yFI>8 Qd=\f&8멨yF{esA2!pK96 |&1C34xwAm>׿3p*fU?SM$UJm Ѵ D_B| ,FBMYfhD:e| d$Z[_0WMZ+OU|v(Pse)l`"a YcDgBg FS.b07hƫ'pXJ/2.\􆵬XܾH;',{\\ւub?bmH=ѷ ;֞l( p/[g ˲o=eQ4IRq}}3ٳjb]WoRqu(h>ew Ovؗ#| uwjmᣫ_# (f7YzE`)c36n@puJݡ;KŲx[ܿ{}9Jx֥(Efxlk>-2:qs} {{c'*c_{2.2.2.2.2.2.2h$[W^Op`gb2LPxwp3fI.0ЖiY"= ]:HYKHRQe5'E9m넽6X'DL朄h(ru{&JRmNGu'5 R#Y(31 T[m2Kaow@yyNчj@;ZзN5(K֙gT p `T&ʎaʂ*;X5%46BSzEvqnZ: | GTuEWNb1"hlyش=.R9́h#7mդQYٛ~`8F9/H1V |(rF+7 ~b4!P׻(ե%2jCآez+=ۄ-f.3Ye"\;J,/$A6ԍ)!3AQ#bcsk)$0u(uEu`cF5 ј;_"΍^a'Z7+zd%K0\`g-Cn? sj@HO@(5+jJA)TI=AKk1X_!ÌRhf;( k 3 qẢ2g7B;LRe4jYf>gj$kgI6n!haWj<|R3^DNF 78`RĜ#<ൎ,lMHScl4K&aūyY 9{XWǘ3d4sSdg~6ۄxΠWzCb bR0̙-x=,ۙ[S:TR6XL]S˴~goLgX"\w3q Xc43مS{B#?C&bRl*TPA5}]kN ks-MnS|A|AUc#9YIl3`3`%ZtpX7Gu1#=1cRe\e|eWdO9ۆ uKf]`5R+Jef > :itBv>=cp8ۭxǺûwXW=~ӿݛO}|ۯ{<==Ԋ6\_]/ ;$pOT'8 ߽[D޽[pmxǷ_(a} K,Ʈ*,#EYӶ"Qz_ w/?\ì+uE+nuyn5^z@dqx|j8eYxq8쟣^9> lN˸˸˸˸˸˸ DoR~7=BK1ExS5F8 5.(ʰRE{N5K23Z*eӢBlTJH+hC< "7rPb9\^-+03O*\i[} p"R,/js)7JȜN[^1">,LeiTP(3D} 2?>(ck t,hٜjMnm ғd}cK;MCȖև TGViXј[g\ɩ@sMJзZlY 33FVK 6EcazhP|ErD nf0Y Lp;2hլO[USjdBHUh C60 Zީ8G[R@WAR*qIY e;½pX[d487[kXʕ Y c@РuQ\oM'R~ . z_0vMC@ E0N*BC qJ qSa1 5Ums dȊv[ % OۆĄ.sJ)΅2cBo>3kE`3l4??}F Xs%Ys* si^j,dj9=/FSE\c/L`M8t hOYg:\Bq4썆M6hlL8(@a ,bm-LH,'J[1G8O6`lf9xY}Nc 6_([ט1dnROZ#:>C :Ҵ/n8uȡO6e"XH6hoMD84>Z "/"l^  ^+ZE3qÇX???|Д2:WVp{qR O,]qkLWح{|x5}%N w^;nxx鄟=ouRq~+N'C:.y~;VˊQjՃ)_gY*;>2ƃq:>a5k<:NǍSu xg>/ϏwqqqqqIHȎ3R&#u( 麬0RJpPQ`ؔG֔JV1 *n}ybuY)I 3Bg![a KCIP*/8yñ [ 8!UJr#Cܒj wkp,EF,hY3GM1m~b6J06is[,mSQ:O{R#zRl:yALxf $Ѯ -=b~ Ά)ŠF2a[Go}ZZG ǰOw)^/]V)%ΪHadtshj>3^NK?ߩi`8 t )iVdK]܊ \#rNZO &8(r P 0HCmdv6СD&=Gh{iuq_3P|s& : tttM)HtGw|VO[;kTk_(#Z.&b.Bƈ5s,@Wv)p{]@W6]O6tk>`nV ClU-wcBg`̗Ydn#8 pR:`̵DSP 1+[V\0`eWjD0c z0:dr$#ζk6 [l|FaR}r@xքsVa<[H'12]N oggiΆޙ= e}Z+oY1_ AR:xM;" ?䠡uC xLF Hj*3ޙufcQ"4r`?0"F'qв1BgP&49nLϱ;kMM93u i,3qoûźan,bv#C ZYue=`YpW>,+ZpN| 1޾{ԥ(KV+ZÇ+}w_+nbk ݊ `RW~u>fm(^"~?im/ sǧG\߼@-V:Ӱ,|xvas7ۛ>nwvb^v늺,Xw+=޼~CաzYC ,pcK~i<֊v|? qze\e\e\e\e\e\wjƼIYb atWu1% 0ZÖӑ֢h%* w|HCxV܌=,F+m'YT3J7:qNI|b>*N/"FҘ TL O}6)ZXbu*E fRA5PԤf٧"7s`XNk`n< 0R\H]g@^ʼ7eh0`a!Sl<D [@|^`3`w~x}fCDc]ԁ57٨(l3BMr8(nj^axΦ|F+fZ Ku= ifz1GKqWeƉl+B#(""E@na! !!hУ!M@ t"h( Vd;PUUךs 7>\tKҽ{=c,W7؃@|?#?ZmM)w>Biqw$0]jAN./ 7m!/77Xx+%.+`]Xaź777xZ/VZO:bFFrs'ʲ`zqχ6/(?[(}F߹us\:׹us}UUӲ; E$8I[Z7=iNm[a] %m;,{"K|RMf&g ɆA$lp.){Xj$7rcEsZ rtHE R{;.g K$x"e V.@4sFg4;@y' *= HH^ X*5= {gܧ cikn$n/U#.[蘙NpatՔ_KA2M}/Hf[YԊ5XPI;_I7D2|^Wцհm,:p)px-5лέ#Q ET `ͤ} (Ff^ 9Ks!ndRL̵?^Ti e3QTnL11@QL+Wy馵' fS͈(d :{x*"_'0opS5"a Ȟo=`Ey]ej6^eF:hK+FPյhK<@Z.7N?/ doTSe 7ÀyR\z eRu,`U+Ϧܴ8bN c @kLrYg0 IDATDv~X6vFo$yFɸ&F;2HQk&BsJ_0hG/mX!k9~JNi"H)manZc1DctBnge.ex*Xs\$X׵>n|T#!Ћc-;Sǜd[>ǹ>N}7~aZO"qZC$; 8{[D*֣hEgP8~ӒK 1O`@B1 |(Y&PW9׹էK-uT4O]}Nnoa`{xJЂ`؎x# u]l}quy/|+H޼o->~1qX6A`duX/. 4+#?leYѶRq}}Z}W'Q_\.Vu|r-eWu"eYG>ZWwh!2ږi:uRQ6eYQkŲ8h9-7p+<>MwJ!1՜x:@G",Q9#SN'c^֚K]зz= PkE] 6"zM-Xq%2i"j}\R_&bܩ$}Oن\ P5tu"26zb zvX:Z(uߜ s'a N.-*L1d9劊N@j  #keaXk+{(}M:HJ.*wDH*6@Id1.R[j$^\W1s\]}*pp`Opc9bXꊖux=2bZC ᰬX=7b7߽2'Wp5Y׋wˊ/|sq{s23@zTЮTb? ~m͛w'^7ؗW|okXkqopbl%ްF^oط {u>hSUJ3^*`]\J` x hSQd$ 7iC}9޼y7^?sD:^ߏ\П^<׹us\:׹u3$ZAY ھ eR+(HLjfӚrd:mG޸03+;05{pRSVMvZ YeО4 e&V/* OMS % f8-@%#3 ؎VjGڧ'B% Fah{LuҾwl.=klֺL#K"^hq݂z5W 6u߂Rкh~Ip Vqz  GIwCŮr6C]bu*,}ۨLEN(B[TAà6'0iN80Bt 9F>#U *iMLY`.g$:?Tڃ,_V z78ez{SaXsOuuY8&8@(=+:סX Ă}q9jY`@$:p}xa}XZ j.+ :LfxmG(G"Rٺ#Du1LAr.Ru ĝkb&%ƽaT*c&Dsi?UZ㊲>&cv &eCݟ1WWPN8XbZl#p$ДZ>3@{r%N`[&%12K݊6!80YIBRtf$N`-d/ƹC=K>[Ѝ JRa!4" hP-@aF`뚹;z RV>uJq4`p=@mL+p lRs$7 pC}X>38FN ޷.%yAm?ɱœʜ28uy25u H{FpHߐ pH~SU+CmpYřO 6 *2 ac'UZ1ԡfRߟ,T u9]A, $&4,lY$>a%K]6୮e՛n8n~u)(Ruޑ-:$~V)ؐF P`TY=d>mXI͋ uZ~gH%ZVܢsm4LJv|96P82sZR,$:uvYwa('d3⃤q`:6ZuWwdv 1>e GOӾa\3j9̇4 F~˴r&%z#cK)'ur뢵ZHoŤڠ5ZoP wP񉓪ܓD-)vz5頠qD r;$A`u{6. K\yM ݾ(v/,:rls 1+"c ' 㮹E?唅tEk Q昤 Z#b=} PL@ϽH F]h=UmٷRRW)]f.aOyI|y" dCtCEdާ (kFGp]Ňk 4\kUn2P`Sq>493us}v`TZ=7C- м2GBᕶʖ@u.R  Ϟ?Nj/ų/quW^_:##3KRp|woe=2q80/ú>{ĺ_Pc ^}ױ. ^yÊ~vs\:׹us\h!, S6N]5K`q;rׅMb]tSct7D!XEԯUgW TCܰȾu4[{"`hhR@Z^5D22&GFZњr3:J5aۥsS.O OF ޓN!ZLV#RŴfmh|Hڪ]9$KH*۾jD(!39UV8S%FDH㎺,hAP+[ޘjN2:57(i֥h4lbHf?ag{L c(i@jT8 6N [iƴ6O8BeUt!\9[o3SǨ" rd#~7Fɚs4` )#vLqv{2߶a V3GJYQjm- "i5M d'Za?n ^:|c$R $:ն<CUy䤕ã5OfRRe JR8dFtF҆x3L0 RrK7AZN@S4EmRǹ=(tT¼%D)r6,6_&;y>ďi;\(|R)!TAEO\1LnKkzݭ|Bd>UI8%fJ{' ?Q[?)u ]A{CNUAH` |OI w%U|TcM\ZݜrN >c J(79=:re:ø:׹>Tpz ĺe o]{ò:Z7-#ߣc+ q8xmǾ7ܽ~;YanI{2Upz Qyū7 W\Zk޼ycp%^oul[O$Wë2noг×wǶ%x]d& O^`y=½`˲0Y+k oWsĸf ^;z譀quq yۑp<V4K)jrBuX9zCE638,Kf$עDKj0oӝ6^ ͱo2k,ʗugذ0S 4Ki^P}(–9R`ݱ7P3=)9lQ+AC AHMb@}-{4'B:Gs@ojCd@jdr$D5"} (T1 R;HfTȥ%JMkF:A'szjUf^ `F,J 0 1?cca2<@jBX 7i #: ) nUʬalTvE H aT'=#@] 艽QaǾ[IR{A]WF,lތ>Z!{7$v@q6C%| 7J7TFz ecI~v{ñQt޼7q9y#s{ S {doSxkH 5WC8oAXaVƾBUlihUsL5A0Y)CqLQSzJ9:%%/-fHQ]y9ֲZ8~d}iiyk]H06~\[it ) )ٰ72ȉG0oc{L7ܡ13$yP]ކ-~"D! S?rR]T&?<=KpgޱĶ^hKRQJT\^\;iAcf q۸:nnnpy#.#. Buu]K[)lĝ\:׹us\:i Ipue7̾צ*:uʺZ ,d j^`dw;<OJTvd_tPW`W>"8.#dz\[bMMѠgԥY )F#9GsUl5m]hj@t,unZ m۟d̅0*;F;w:\hal%0C Z+ ߟGNi)c;?U&f4 `eC*GJ 3g8:UnX>z'pQ63/gl*(dkmC{t.:b,=N0@Q mGSuFz䴎ygVvt^9ř}u9֒ 8)^e*`7,30#XU ] Z%`3ݲ $ TuYP>Be4*r F?TI4qVg {XxuZ#P$uW&U[ԂTIaI+2Z &@lTX{]ѤF'r~u@Cz*1Tu$$S/2wGv"FO"qSFG4 ʊކ5:dsj?sfnAeɪW?Z]#x#mv IDATS:>s㹶D:?RSė4R#.~fNe,1HM+FpV'bo(uAO攂g6\GPe9Zm pl崺<)e Tss{L1玌8 oS9<^uƥJ=e?n0h )10ݐ`}P@Gf|dFvDBqs:-5B ~rS\JOѝ9>:r=GhTq%h1L)Nu]9'2 Ғ:tzT@4Uat+!Q`N"Ƅ8P 9zt [Iq3x-]Xde[I ?;@RhyO#q?)% E~|v8ZY&2DRX~G3T8F=Ǜl٬Cs>ej\LSw\%zohmv#!i=z8 ]/pyqڎLJ{\\, wi ;ÊϟJ+x`C +x^+={Nk7nTw|!pѶ7~y7:J֘hnZ7{W(x<Zú^('_K(,ueK7npq o-|t>A`7m;""pȀyf#|imߛws\:׹us\g5HMVR+j,`t9 LСJ,$Ѫei'! d) LڸR-E*xl̕Ra˰Ef3.~-GF*J:פ5Z LS)#' ;-[[*_Uk~|;mO̱Bq'Hpm#Cpk<c׹us\:׹u?lRQ"R pY  KYws$f,jX*!7騥`۩r0 pu6Kk/\ Ҳ&Pk {N u'LDbfBMPeJY`[#(/FfpL@(Z ȟS6і1#iAH.szH"6jQfl=s}R,Afjb֐OږEnS73 .7Х4a= P +O˲P]l:Eێ"'A0C]z) S4-Z7ZzRtm-^`XeM =4b~j= b.1g̰yvtKSG&݀IH c Ry 2Ky]f׹'a D$ B=^*$}g =C=35}()`%z ϰ ͲJݽ1`D*![\w$~S>3LᧀiEEMhスr6#PGIiK-hss#Nhȑ TD ׿x=u6b/ʠհo$ CDF1xZڕ ڧ۴gf܀C]K\bsD3Z2}q&M&88p k$S@:;I@ZNP!/fLt1#N޽NPMsRV&) +ZcZEy¬C5p V״iY&@\)0p{EwBI%t0?gb|p T Ǐ[ʵC1ǶVBNf^3X8 6:l\haY; g5>DX@QN\@tfDL,i'.r[2p8M0C?b3`-Sn )dUAkiNAagus}[ý@Z]a!Ʃ;7;Fk5޾Ղ4òx65'YX>|Q`p2gFqǾo}:ѱTn} T{}|xw^+Ê?_Gox'd:Yaq%QܰRd..KR(W`]²f; za xx<ւ}k\^]@)#cRRPCG-u=|޹us\:׹us} PB BúԸczb1)S`T243棂.TcDR Mȼ43ԥNU>mD &Qc4NfzeL KRJ/s Y+IʮBe{$V{?ۂ`W` ca-֏FOTʼnِεbY.[rk׿ոv "Q`++OS,j)٪"l='Dqmp33e5͞R |4\xc]6xO=$J23rWvb FPHg||Ai>Y&иOnʎufƚ2_;ljW8)2RrK/X6 Yc剺Ph#B>@'lٿ< y90}07pL|jY q1d2JLΔ < 2 hK]1ƙ6Ax겵NK40.fE!1ƜZ"k]/E}X3k>ޙ$ie:׹>T . ur$PݰV̶oط#2wo_.؏Lۛ[\\c6Z*aɐK)#%79^{Ö<1׭8$QD iܥZD!%Z2{,b%ʷʷ2ր#ԽLks3->6"rA ZN٭{0K&)"A2M*sk_+bdu33HQOkyXهa9\ϥ|d+CgqO`_ģyr(!NDkǞ,s12]BD4^b*Zd4:Yg ٷ۰.H's؃ d>Y(t-/iT;ֺrANWE.EE-wo_-^+ܤ...//nnonxx 7p= w:RƃO+ ScY*!|~K./ 6Åت3V7_?Z:.R+o %x~r-_̱ZªYqLeqEt +6]ƀM 8>,ã;ʡQ d"p_.Y^/RF3w¬[R,04vJ$pa%ɤC~@J0)1{WVP3 4G0!Ws[T3TjS Lא`P`R/0$Zƣ  =Ȥ&2BTH5 5Ja=׃4դS;〮 k@Y8eA-qzi"1\H;p #?{Ni ]הi䈑 PFj]bV2i Ƹwtin|1" r|6LlIPVr8)zO w{պg" xW! h8(;im|{#rG2Gv}(\UH(jpG /sߢ76 3`!4X"Pfl)trb.E46 9S%`RB㸋 k9!21]{΍ #.v>"E-9偲/pƱݓ@=I-"[u״'LҢzHL>uaX< V#<TsL0"M$|LO("ؐdpB &!s|\:>|uqŬwx5..{ط׸EwoߐgVl fxGp;<>z,:.U!(8w8\\x?º^>+~˲"`=\uxx| ^}!>Æg#8@kw^{xE6¢z؎ DX~'7ueEom;ꡉp]j\p|DP yq#X?? }k_ ܾ/[?gm_RW_ijwWk S/?;_#wۗ7?mooM+Oy >?'^d~]wџy]ٟ?^Oy_Yk-~ssz.^-T7V_s\:׹us}W@K ~ZH&`+anoE9E;Z$+zot_jdJʾZWe{;̟4?KDNZ)3]`8SX:F9q+5@Fh)Ce{J{-"4NM<\2[)p0®KeZsd%vR IDAT*R66ʥ%͓]cQ]QI ZդPޤǺ?>MIB4qnp_(`2s((.C%'aha@)I 7@dñ +HNa7jʨl 8YMҞ8$(S3'Pi|LkRѤCio5V6u"POV+3d@C*v>@yf.$w/*}\ɞ+ Ne6ssS:h_^ >!2]V⡼Mcff=EXl[TgoXcN)Gea؆%bovN O``[){3zj\ z$^~WX|E JEPZeQ~Y$:x (K )>Zރ[Z1rGjb4"gfqɥ#{c>| P61w? aفi/< +ryZ?t_NjiI+(EJK3t-E6qI8|S c`of"&|`(ÕLKw1=Kg՜7GHjHR L2#prQ4p;Vb) I6k6wLt(caGGӽdRyܓDy_l7{omYVy~pνoyy$1* ʵ ʡZꅥkUR[Vq@p@T,d!"#232#ś=g?~}n@?W.y/;sp;xZ!lu|8!RE}O?*0ٛ[󺞻2Qb 轺uxǽ!FͨYg_mlnhgUfYbǩc*$1)F jGMTQC֒yk^߸:/xscGĦA.rWih0vɈ\2:ˋ4m$ƖNY__Y\\f40|4 9ķu͐!l[vmcA_fsc QK;^d%=o}Bdiy++++,C#aydv2Ͱ:XlWݻw1cmJ%Ӵj}o˟Yͯ):rh#ox[Ư=ck^׼5y둕fu+ڄطrArw=C;%kJS4"lBe\C#_!,c} j_CBѪUd\,NLa( IAgV8ވڅwnm⅚+I o9kpP+3l}D 5uSu٪Q-xNmhLܓQqDk:S(![p٩*ƪ4Ն !|h iN486.\<ђ @HI ЉZ>:;ڴ*Q0 Bh+ HI8tj=+|45&˙ǀ𢖐M}٬A.r`YC씫$ˈyClI_E/8 䒕)d>%H0wF7l f8{2KŚKy֘)TKn*Bԙ@)z)4% Dt.DS+xִ-bS$SD_Jfbb(]И25Bh(iHHA8R>$cèm`MXZZmGpNS>;Y넦A>,.,8^`}+P,j!Oh[s1vne]۶.peXtsU4n8ML|+ųo |XfZDe#M;͸,.qO>9ei%! GStSԂl ?n/p9pMï_~Kۆ=yW>Ԟ{'_\=yi_ 7=%g{\u3 y5mMa}tZ>fħz~}kmDk2i" mkQ͗+e06Zl֡m^,)QNYU2eޅLR%C$} _B2DΛjԜLYv)YƯQE6{3ULHU2!\Y*ȐIS6TQ8}ϑƶϽ*yL4ѫ*HGJ(xHxL;᭑]:,m^5o-`T|=knry;u`|U{;fhѬQJ6'MY5[QhZ# X4Bj%*|{sVPDȵ@3Gk/4dgsQ[ugsVӗo؊s:zMwFHm%(DԍAPUI?Bh=ߘ#4͐C\3̲+ }hbC'IJt\~(;yvR4'ޡ }̖a$ [sꉍfd4 Zc37#{8 d9cC{p׼`ɇ({SuSH-sľE8',--1`scB#©'8hb3{vv9cpb m}YX+mVWWLzr1^\d"k+hGcǔnDw K/-/us޽#s/ dxqv4 1DlG1ttݔ S>s>Kn4r듸aسw])]ض}'ܘ._dB)fU]rI)88 ιM'+գմ 89q_-|-uOyڛr>~<ůٶ~wMOcԃgꛞÉaǁK'w9 >7}MGy;}rnz9Ѿo_O7'gxo7so_xRōx~|vfn&wZ<AI}T)wdl Zv:P Qe"qD Hqk 4y2Xh[&]2pgى tCV$:?|{ji킩ũ2!9h ]Dl@v O1BHdcUk>hJ=U,f휋h+/ ݳ6N RDWpFyJ77𮪠k'*6~T@TK~] j+Bc\2N<#8/t}Gomg<:g )E-}LSU ) =G싑&Rחl=YZ``{l2G_vu*R"(SEH]Q bo&ZTqD3fY~KQk\u\ثؙݭ:%c趣jala@gͥl+ %?9Wj,FVq,Aesv8{v]$8ucan8?#y7G O01}"l+,0UD, @B*`Bgs\؋3voz='/ wguHE]y<],"z7Ů>W(ad!Sa~] y rSG!{z>;]‰cw/_I/._́odKx]x\| kky߯U+Ǐ|&Ne.q<%?=?1{fŦO|o%گux_?/KXml㥼YW*3u +y%7ox'|q^׼5y(U5kkZh1Fwyg 9}\D6baZz3^9]  >{=⽂9kCin&VKMUȔ"9kMiI޷8m*D_涢X+Ww3Xޜ{bA^`'qʪVu *>gJ= ,,5{ƚ Mu Xm/TͬWA Xـ30W IA';9q &ƙ2/Q][ީ8sWrnȯNe  =/j 4H{ hW$br,g\e_mPEstx7а*\{*B tIUwRRKfU9UfXs\S֫%y^D֩2lWM ~Jmn }KVe'Ou:p0^Sp)yzʠ6&"KVaڣ= HV5hVF$ M'j| I[f2AUκy00Sa`Puu]JT09TGZ`sjjJb[ί*6sΪRb`*X1ƒ0Zʓ!O,鱹=Rg(FjQ౔B4Q\ D1ņ]FM9{DXYrN<>R՟վUoBejfYy1F*]Ty :ͬy-,3iUC~M@E@xOhHVhr1%d! u*˃3A:S714c1 :kvkt;GQ]'?[1{{L q:vݘÄwԭ 1n0UuCU]\Έl}.8ɸ{I1 t5{gy:Ņ!. B9^3ChmU1{Qʺ&YDER5(5/6!j&@1"΢MDrO# S%^ztॐ]!8g)&%LƉ9kbog{>2uhsr3+0Ԋ@bAz}T11$J.Ь(dk)_lJ˄O׼PO-޵˜xF}0n[e覛\hۖ=]߳_XЇEQVWNhxey1V9kz' IDATNF-Srծ!Dޱ}+U&&_r{8v~W8hc>;^.mxi+O{W\=7}+yO_/f}׏n|W{rx"?G}f 6_}v>{ZXbk.y~j'pk *\$YӞQ)G2xed9sfLᅁ=UI]]EBL>%yYhVkگ8U^{ h@4k֑)#91퓱" 9%vD7X=B{R9y!JJlvr]Zh4f:= 1 ̶mIwCƋ \xϸ {p/2 evn kyԳIDbl_nisU6O=Ķ81 sn>ۉ意w_ްOF 'צxm9Ϩmɹ RRkC%-ˋ4Z}vW&G9pMlٹ?yva`72 ޟ{ ;>q~߿#ĖO?޳ ny׿exx;mu+,!+r?uN>_)(F)@jbG1 X0]} Qmw\  J)KJ8|E ^3smJK윑 >4S<7)-8=m+DVq3=:NՖ.klUzTќSƅ.ؘ 2L?6UՙEb 52xKe1L'RGx_ 0 Ҝ:}>cLk6cɚ[D z O&FYJjF7+ζx=wIvcXMY_km/f8O)E h 8v Tqfk\͋%U J&E4e e+V3u˛V@,XRbYPj%} ] W0U @5^3ž :׋)xbc) =47Pb. ެ`5YAǡYJi(%3L!aO#RI&euSa'!%Ӵ8UPFp2}vz H% hv'H1BH5>_njd%r)$WFVu+HycĢ ޤqAȦ*R][N-Gw)YmD\7|확ZkjHWZ(8,هXY*圇akL^Ԧ\ [8N(9ٚo`93^QsEBՉDMo @Fd$}س?s9 lֺ0ت뚓d4vbn+L"Dȫ굪+9>+.%8\E9u[ h]Tq1dsfqa9f buq(?E̞Mm<6uFLjvۏs!ǝ3rS]Tz[mٱq%Q,vAJF\;\m׼u^p=9ӫkL,,,ѶQZ>C.wј'Z(״- LΉ>eRi]=v1ߤqF*E텚MHY7RYmϨwH)QC؜"ONS ;v)OCpuxRNoWN=aNJɨK>q:s|-˜ɶՎ'x,-6V.c{?mqɾ:gz3d& |*M&t>r.h~"uN?{PFt:%ƈem[6Aciih&1D6 H)G1엲c/j=YU ray/MlZn㹯IFK[y}Wd}2T6VOp˟s/ s9}9wOc6׏}?_?oVxO.{ gW7Y#w/@?ؾs:dm "y]?G;?%uomp|Yl2b41>ƫxO lg;Oɝdi^׼5y{ϔ=fHE[gEIB+Eȩ'Hlb .e\˙!;grUiw4Ѽ8stb͢a--%yTIRQj^@`z"@Jmho`D1(xJ OFdp$ЛZ4/8e#LeSA<< of_]Ҫ6ϝ,5G6XCU:yvj\TLWALe}?ޙG>je^bS)6ST nbtHo3RO#=x1(a H.fϮNv*$-z>81t,9)6O` 軙շ9b0[seF*D.+ US@HMʋT6Q[Wfժ~W,k ,Sܲ` dp*Bq)bkdCC3[I  DTKI49dc4ы_'xa`"]Rxu̚b81 ) h`Y\{6+fP ֕^{ozlM~O9uL4*/=UJ1m_tJWnm?3 szStQgpkpF""xWT,fc[kgrD᝷ut\ji KUxnQ:fQAtVuJ'))'VَjЬ.hRAȀǙlͱH,2@c\#ĸasex7_ͧpבjWz*ܷmc0Ni#GO nqn}'s)ǻw~\y-OFn:ρ/ ^ӧ1,-Я&1^LoNzp;/+ws˘Cӎnsbټ=Oo?1\{U|_&m^>[iV]ft{yʓ@*ei]yZV%i`csBX_= ++4ڱd#(?tsafĩL?`|MS_\~gZLyҷpmû켗Xܶ}霏wcnaރY/~Yk'`yGpήeߥ_ vD?};C"l03w|/o ߨS׼5yk^'[>6Ki Bt&D}2 c| dB]˴I)4/E3 g5c i7w7 nd굵q,"@t]ȥ2v=E9%SGJu.3bbϋn"\g}ɐ -J+i*Aʗ3,5y}`ЎSZ7޴5:GM\W{ .j29b;N,B|nJ6]I;6Y𺳧T;^\${Ozn~\x^{ܿ][߼|Im1^GbXKw2?g6~s\tU zYeg7% w^xm7xK.Oܶ\')vX撱w| ^+~-ρo1| wy Uƅm&{it2e4Fc󦲗DJE͍M| x9}4Y^1\#wTHꦈV˱>cwݦRGkny>k觛/}'7?;x>NOoWr/q.[ҧ?p{/Y>/mKI}ncp_":pm˖]Y}>@mϱC&>Z޾S݁+n"S{q?;wrއ}׻or</?𿳹v?V$P`3c<*9L)5yk^׼)Fn*iEצpͯSO"9 >(৪PȝgM"P%kpJC0B>f B_UdVqԨd}T>Q}fI= z浖"f*lT٧:` .RO aPMzzm:3b|uQYj璓39#Xde8K+m\Tu*ޙ99YQ}PUk&bK0gī";6cFO,Ӭml_ҽ$jw.8"Co%`T0%bC>\Yv뺝 ~.@%E (UIn@3Y_ǚMP x[AN&l|h#c IDAT9âw^/ҹs9C/:WXT]C YS* !""+3a>['՝Lݗ}#m ׼S҇HIذDf4"=MFm&L77i#[6-%u aaQf,{1L&}_ceol 5غ8 W]{gedm7}¥{Zߠ=x/;Yp`g-ʔ]ۖar/}"\w??A!W]ˁKE .:W\ۭY޺ѶO"ܷ$>~}X;7_~?x{Nw~ 1j"KNxNI\xRיln't]>EPdIv .OYi#L7kF ,m hj;^dރ~6 g{𿧓5`f?U.i| װy;_…W?ggxopn+&~w~.q43y3';UrlO?t,y9}K'ŎQ{8~ 7r{yy^@]LW8s'y_ gyKO/#x_"/_oN>7dm}O?gu\'GcH#=^<>Cۇ}]xo-q8ŧ,pK輙׼5yk^_Yb B$|Qu4TŚ(o`*Y, X*q!RA{)z1`pP&6JHԌ(Gpq ;G0Tw9eb'5PFLۑ;&UCyLXL)1jӪ))!PMPqt|S)UX t ˖`V5E-k)C yj IM=`S~fS L3b`L)Ȋ6 )`zrde%*1ހfjr!gUE:>{P vM11UYN$=Vp+3`/ @s ^:v\_1 5;ɬ}FAUS;`j-)hO굡 j|SVT{Uj9emY rnUрfP{#(iaɠʖ^բ9R@D}q6QsJ@]/os h8Ա/|dڈN뻳JPջdeYe?8ujr+Ňaa8SbQ׆0b4J~z\΀fU SBj LYAO-/SɔNi"y2TW 6AUJ.RZ,5g292[u ,{ƾ=HN1Z'O3^OMWMGoβt8o~?ٯqb_|S~_N+Tؿ ?~;S|C}_j[ [x;[` ڧKcO6y1?%Ğ{>/s7*[rLJ_? / 8st?_Яq_В~ms*WǞ{ zkg97oÚ%p?wq?OM"'xQa>Y{S OtLtLtLtmɄdQ|^A Q١Y 7wJkyZ QE Eq$()mסo)jJqNXg+ #lNOMe0vڒ)E :fS#Rpv>]qe]@fLkjdA ;֢$Wf5PLveJ"14wVLTz0fS+l-9af8$Kk;r۹YNL^Ȍ}M^4dMJ.Lc0ܙ &b^)Cv4Y€nj^R"r:yzꋁmMV]-{#\ՌJkx0妁^u^U(mk*)5sK ?r`sg_2J䂭KQshozS7~߱ڪ1{ӨOtLwE;f`l Uစ ǫ1;q;pfe8l"xD.pfAӰJԧ͐?8=-\}{XZ>'`my@[Yp |oe_!Pwi5 K+ǹ+n؄RDA3 3Ғpa:eײ y>[,s _̓r]ӐM3dv ZXZZfMoW>1R_LPaeee* ׎[i7'8KXk;ϟ^Ǿ={G+?81Osl`Ư=nFͻU~)q=g?Þ阎阎阎v~?zo%޵ӊ\Ԍ+aI'V.][a[-TmD솭gChI+' %j | Ue` 5Д5\Ik֔Yy2c߸툡}Ĕ A*?e UŖj~ɕOCmFbY!&terjP!A$X]5xfs{oU;Tb0%Ak1f 7'XL6~l_{#Յf#Ey߸=BQ z}9IeFAg) IAPZ&jWH m v/ߧV5}0y޼]p^!rܺwK myj7%+*5me:NޓIf t. ŭ]qq[W2ܻGYMÅldZ5ؐ^i?;$ tٞ++QBX>tJY__!~?31?юH3UN:`_`e,)%ԘQu/cm{1 |Eo׾5|;-u4-/}0[/A7<~˘:f ͐22w-#/yͬϹg_dFFgG[:jǙs9sNܑҀv]ރgx`ϑ:?c:{]sgx+ 8;阎阎阎阎FSbE]&7b2)kS"0B-LXuq8h"J K,j]Em+Z,22%a.+bG[2u_3\]ji6ΦZ Mdeb(fh2H ڎ;/wF?R-@+J !beV@lb׻6Q+\̆V$R!}. 0Eh50U ?-UG}*ڊC<f'fVp[Lv*"*o*L1ݹ @]q):ª KƂx.aɈV(g.}p-)5}`W\Sf<&-^(Z s tZ[[qcu:(hQ"%+"d"B@rg9E ~n@Ͽ25+?G;3E8еa S%SB$[e+Q4}ڈfuY+!@<ӱm0u)KЧ(u] ??5R]!Mpu@IM`7ʥؚ!$zDmK!xԪnڲR06j^U}m cj|Zi۶- Cmf !2n[R-h?حQbl]J;TE[;Mܺn,ݓCնE2vJv(]L{W`v':SzcFP,yTaPĠiU%N@MIՒ<'Z@bfe]@ tޠ~+@p^A#)>֞I(hyPWW}OTٛcRͧujIxyc#;X5S@lC$K_'[FRL7(WN^኷^],ӽr\tLyo7-ses 3$SGkv tTnr526`j)rGP#*p/5i@)RzgF}t>lycq=g$ۼ=*MCu[p{MQ(gc:7Ni@1gPrYt~)+kZFA+%-3̀R:Y#1^N f"Uq Ͱ0;'o};Ͽ9| O>7 ϻtϲ@?6߷giαz.#e|g`;ue_yob~Cl_Gm#|ß~˿ -/=rӡ8͂rk7bW֢}` :Ѡu0QUWux׀X$Z*ŔBUzC BMF{W:)VsXΒ=碣'{?H56'm<lHm6+[qjP⠜7t IDATOUɥΛ TTTXӕڹ9eҎy.J{JI%&șk)%ӵ#fؠ2ܻ1L5}.>=خs(,m4HLNԿu__k 1Zbo| kWU*h*ژ䙮*杪 1QJVխAf`?^o=Nb2TanbY5 U.WcB-[U7o(A"uWEj~Q R weҀT&,Wy<䶇^7H"¸kM4 T-wjd_C~۩3&6 }\1OjM͝D+;Wukb)@Quɚ!3+Hv~mQEUP:o4 { ,_l@sśԛ7n\T' B5׾ -8j>=R[(#&vזQ  B.UAD  01?93`03ˆ-;X_+3;7xuܮ1Qu4p 2:v ) 6mfq}lf0ͮcc vn9[~żƫ3|#o{%_;es~r曷/~?z,W=C_[.΍{5rs{~ϛشiXл}w;繯}#w5^pÐx4ã<ڽ;{qx'γ͉3gKI[af㟽 s4 8wv_3r.jfqfx̣{r)N=űg8s &uvS,,.7q]QDž0K=y^1yo= ظuSg8w|ǟɹ?}L>s9hca1̛y3]3k8ʟr=x:c:c:c:㿤ѕL!PsGՊԙQ@Օ4(Y3djВ]aʔ-dV}ZlbXlR 6<Uj#R?c#ESfUdo_ s*ugyjqhJqd/_-[iWKgw{*<6lPj:|.r FԔJŠ4N &C4iJ ʩE2=WmGtk*๯ .dcQSE5Mt~l"Z8(W(fX8րWU i=W{E{T+3Tehc1XkR4Xni VQ-[P]T]mh } hq &PZ"w|+7 i,?8D]v}7Gݽ74Z~Ml:1~mPs&[=}g^VuM,5LNjIur|Eu~ިRtҥB3hljuxEe_@,[s1mo`Yh0 -{\) jEm37 x@ 0a@>x7gXMSSWOtLg<-Ӏfixb"Ye-/1^]e6E2 2",MC;n_`܎Y UQFKYزcGذyg Ο['_w 7ncش} b0p7\+6)i݇f{/qݕ;}%\uNnx 9őS+"/q16oE\ʑ}M翆hǜ<N9w 6,4 heVW;Ξ=C뚛Ùk9uaw}|?m:c:c:c:o苺bEO˨hGt34!Йy-QD\EgHQlM1 :zަ8R V/> ]ΡSpXZPJ&4 mWhۖԘ* mšBӘ*-2&*TĜpFjhj jfIbenێ4Xy)]j+ҫhhUpiľ\L.f9vCct9Ӷ&Z ̶؋ǮE }'՚V?;&S%0-lza-s9 zC * i8C3]~<]Ě! G6sʜG; \&kӛJ}lH *V`Uz Q! ֊{R[ Y3X7$ЩAWz?'4 rl*f-Nz,ɫ~ST~ \&Ds5Pwu9A<}zizj7.Mޝ#T`YM\"k阎ވ wҹ׮tm"6n&`vEbF+K13hn]$zHlfh3h'X]],,133džE? aBq6p_ct ^{|9{9c;ƆK.eYv]}=;rC'ٯ?C psk+\~e\W1wᅄ!w\y~q"o{Kf9}Flċn[vnan@׶<.5^n5xIvБG+,7,4,Zc'7y-7sݏ`fG|v4F3r7]w/K.AL rIhwb3ܹe[]c)=Ƒ{YC9uS?811111`;G&ٷ7؏BέDZR<ڨJ UTz+i/:Dc bb2Y-[gŹ(+JʲHX}]h;-|+bf,wW8%{Ʀ/tF tUpk@>1zf0έ\Zd@y+xYڜ*$rR(@- ?Ue Bv+@`j0%3RhjwYK!LCD1+̒݃+āEa419rH. '!F MsƷ TVժʢtfENTdk 0xRs-[#t8_;BH15AzHfҫb4)M%=4%sS7+b֏)G'Iƶ3E)AkWσݜChWW[ D&sR9b *FӏHd~e)=EƅSokp[ tb[?jdNSULt皪q6ׂk6wݵaPBBB]:%`K=|5v>^Rub{ 8ƅήYof=&et%}nq>T{u8LYj{}!+TTsTkX#Zj7?Ѓ⚥lָX<^X[ ҟ#fR&AjDmҨ bb_m|~馎 MY~ǭuDP V`_~GA=Fh=;sj͠oD?R͈zQhK)f_|aR /bG/[ǔkmHQCA LƆB} jٻ>0p*(Wo{݇g^}*fNLS?u_ lڔ?B(]}&joa"=:WP,Zy=ڼw bQaB *Yu zm-21?uW_h 1EN`nf`fH`mGhסݘ&+H)0^b800,lHێ ,;?.۵aYg^/Oc:wo?ɓ>ᇟ`+xϽf09ʟq??ʆFx[ c/s>nu8{$ҝܻ8e7h̙U~ͯçǏo\sg\Æ%ncϣ{'ٲqgy[8~6ҝyo\t?'0ϾrD} Ca|&#g2 fѮ2g?Kn`ӂ󲋙_\dn~s'1n }-Ϟyq:c:c:c:c:c:6WW+{m}%on?SBeB R+4 5Q>:ĉfj6|ЕΊ&#HmܹmSh\>SNx_JHHaZPSb˭TJ{_ϛ QZzn < 1Ҥ4):ֿ7eJŢ41en0JU?V$єcju} ;{.n^i{Pmb{fϙ g^<S2 $s)^T}f_L?VgeQ˯ Hf :w07sкT->@蕙uON6~ͫG5ŜQ}Cl[LF-|.WWհ}ny*Sv=nz70?dMahCY߅D hz_ʔj]-  iݾb#uUm;Veu-A&s5LWklnֽuM\8l bzӑ=j}ommJsHF~P8Fޛ{ȥ•̹ޤY?阎t#/y037G3r1ڕ%ghR"g%7bu4Q{<&Q +֐ٴy+߰[~}pɅ 2O<8s?W]s^N?])ӷr߽psw=rN;9=).:N>/39|=?={9qǟw={OВy&^g񒛯FK4dR.4g8rz9WFev^ O9v ep\{UP'o I|+;/ŭ_*]ѭ-Owz/pa'q+_Żd n [x 2 Y:Ψ؋F9阎阎阎阎阎阎 *TR(![^,7gٙ2wf0T#5O&byqL"*Z v0%,+:B.Oi82%dTbf8@w %Yu)%opeXy1uE {)Ʉq`Jqֹv*uJe?1QǪ`ӕ ]BLJUu_JUo Y<+dkR0Lժ^~~Hű2iɦ֓7Xj,?]mh¹Ϣ jB}bkK{n?`J5+S1zD/77BZOdړgۤ!fn6pu.=O%k~}b4uj k gCZX=[kA@|^3 m@o_F8\AG `p^bG⤖ҲTiRZBdhs u[F*Xq"y.OH1$r#So!*&g_C')g hfP8g$w?u5^[s{ʪthQAtU33it^ عd,w٪mJQ]k4S qYM O5*b*""SPEbDl Y9^U,^EQ5=D)Z1S+ib5BW&Z+4k;Z6uYAJ)ڜx/v1{|$%#'r%8v-Dks-=7:cH=4Z*mwI͹3.ZH7TdUY-a83Cw"E\rU՚}o'Bێn]3g" 1O)\ۚwk F rO&6v]o.;O?Fm<5 )UA]_.u&+ \hƄV$P,.KVZV=gqd&gE1kj.rֈE ҫz6wt#@ l/˙U"ܴ0lhhj9rs(^f.OW#z2~ޚ=H0&}]]5k8n ٳeCIh͛/2 +fJ5ݠM 1[s*!*-c)zִPE~^ j_{l}VXg[ZE5 4xFqep*|8 so+\M{8[tXNxLHug큺kdgGR3RЯ[) +iOeHWӖ)?U6aGKo#~Epmz٭뜷c96 &^s"Qi<1ҵr6/Z/)j\yG5!zYwBmzBˮϭb Cvw*+l5nCٟYͅ55IFO58阎xZ/x ђiGkfIidd!ݘԝc>)!Ynpp 6sjϿf;yef̟~O~]r]]D$pyDZ3ocWӜ>W<ȗ$Ex^6*pyt vsUײcf+_t#6~ŋe_[6lΎ]pm:pcWy9fwF}.@>wY~/ϮJ>kd5Sq b zuh'rT oejF+lŔGz&;kVY &=jM>Qib+yC, (ۤ"U$ksiCZnD\:ߤ'_FJk5TGR1"!:sOۯ6 )iN L5>oP~^D!ϖ۸˓}&f>m 7waV)JoW蛝n+$׆UߪޝmM).W` a}NkW~ 7o9{Y lh,>w#:y7^ڽ~}+ Wv!&^~߂7\)JJϚ?4خ+Ϲo~0 33Y=%KGNl%ˎII).[bE-AR-f9$W,|fqZC;?'~Wؽf\Z3əl W/\%_. hMӓ GNc[<®L</~Iu'4Mny9ןwx[0Ͷ5}r˧|3̍[?#˯_o1΀H*c^E MTPܳB Ɔ`0pO˙ikR Q9I@u͹iA1+oex=g7M;T>[D%J1k`ۧy{V8T~H@&f,9W%~5?H !XBv4{hno1ՄZiw=DRpT_T]:mVNJW m" ]u`՚A+ =Pr&&~Em͙E03﫞ƇA3$g 6 xFtޚ`Q!)=# }y;X$hG?ſIomxy`JU^eUZbj ƑW^:G3=Gbmw :}OٹwS>0k+,LDV6LpeϓϿ΀Kkif7m ;>pz2?ˁcg9Iyg,smGO›HM;Xh:1έ{wW~#x=˯$Yعg.GO|5/\}}#|Oqy 2\™3;vi>tܳ&.>'طk+wޱʠO.~+xAN>ȵ+XdrPL2>?d39薋5V57Jn AkZr@aX%!T4iZf(wɕdX]Ʋ\femj^EPEm"*Rl2lc2HIfޙU:QjF:w7@OrU 'nU]Ѽў)2\W:ULVeMxg=ADSQ}!CUUbRQ$yFѢ*dxejYGE"d& HzwPT%(x~6??ܴPpFN!RC4P`_ MTUXA Kv 4jH3TiT%U!r̀qU-bTX9\DU-!taJm}\Q9aYҺBL[zm,HQJTS,@TUΥ@lB#77[Zw`nP-sկRtNbz\$=BA^P!6 {Y bzٺ)AfT5|08{ݽ}}A'1LߞI0R*7%fRl\\%|Y9 gbǡmM|x[ܾ=i)JH noŮWP{@՟SQ7TE+*#)=Wژ#YE| 0DMs6vT *dթ{aͅd4ɺ\^) qzsٸ>GY^]rߏY#v|wQƤ {)ך0kl6S^_;9{H:zb~7G 9Cشe+NS+i2,--1:zpeVKqE.]ʟ|s*$m67ix{p&ۆ2S3lۺAyaf7p˾[m^/౏={oO̓G/0{NN_w0qEnu~!fgfI<ܫ\]]/?c~ ч_үځüQ=|Yvoʁ=D`meO={ٷsMܱgx O.{ގ\@Ι--扇dj3.;'y;\86L IDATKчn;&7-s;z2HSl/icWgdži&zlͰu|>nu/slln3UfeeWK;{3Gަ_gz]d?ȜѸ!?#Hk*@$UbXQ   yޟgzAm,AU@$0D1UaS &D]:jb?p`lA֩:Ʀf5jF-ʺέM̓mȝBtASի'r[@OkQ"0Bxn_2SD!/XBn*.uXl6oVVpv-jר6rO4.;2y$67d A݇9LVAul90IT 5MS-sɤ&k5NlM-~V͛›*jִ*`0>M@(e0`X ` JYAz=,?y,bb"Ƅ[)^DYu6UMif3=pR,I%w ְahŮ@s. ]mnqjL=m~ R36Rj|ziw=[_]nqTu~G,AkB1R!3]`fiRkp*j/u*ΪVfO`0N]RM15mk=bCbG>SzVW*, G{g꿯fŬ;tV#&?T[Z%wʗ *kڳ֦n a]YkFZHXs[!3 V~yI%ݸ9_U4]Awu)tm A+ D-C[1Vy]@9f6TUj1w1!o%5təh5ݳ9G&3v͏.RzS^,<t&PA : 6l<x8'}_c[O! m[k[}mlm{&evjwގ;*r{%b{{%w֨aS(˿hhܸqC_+?ϖ1&zpURۣ2A橖U?YzMn㖝Ħ-MЛfnayӇna+,@|ux-e:.p9rn_6}9911111qCg06Z$$< `T-`sɚ hVvP4I)%\%54HZQ=e!E 1&U4UUVU v@-N*qSRaCYKQ!Y2m4G-NJQ5xYB{5R &1*I,RrFU?U"k1;`ZZ]c!W5qdS ^C$&jWr0B 8ʴJ+RRsZ@Ã&&dzZ6xH=b~ubz1 bR5[D u]Yܨ@Ic"V]G fH .9j%H4BB(,s]iF~[mc-{[sq QmBCuVMmuBrgP* "~]ZJ5ΝAHɝ-@GRe]6RCp圡$nV-b͎>&Y-sUzH(Q5q;D|Xjv(,%F\Tq15ֶ:}ۏIg/)4=^X';f9 ss]kA0TUA-1@N±~Z6ナz#E*ys?U}qmG/Z;g7#^X׌h{F?2TjMl쓥tvְ$U16V-J^6E]5`k>,zu~Eossxs*W10,xL?WP qnBk8tQkgRwEBU9zϊu]#oz)T]mJaKJj3 fAﻻ,U 6:f"浟]bqᏫ=7=%&3  bDo5D<`Mћs1 |rhXtn ҴdXi^ __Ѹ/]bnb,pI&'$3#\^^Mq#f\SMqq7Ҵo:{d'׎_}?*Koqf4|ì\̙Wy\̻/u)\|3=N_Zb~r"|xO} B⡇~pܺov|m?t/c\>zv/r_8ʵA3Wo9Ǘ>yy, 2.B[y ?[|w={o&eq6͎sm>c-˼t}>J0z$,pCG?['_slK,Nq7{7F m%B7@}ȅ́W_gib]9{~Lhhhhh+u ZU-F[e¯/`8j#|U ) j8Mέ)&:)Z:S@B6F{JMTl jFzT( 5qXDW\΅6Fi;$5T)Y-9+u O=r"Ͽ.yDҎM0iwŏ}3\Dos7iK19Vڀ2>cc-s pZ[>=KfL)hhhhhh'8>(g ˦ռțځhZ+)In\0)TMj!YLUTAcH %F vJSDd(eh(@fMA2Ϣ ވgHZD'4UBkUaf:-rU!iT_NUM}gY6hvkHZ<UkYh%- gMAUZ WP mٗ y~-;$,}4a׫ dS6q.KJH1h^ 2 m-8+ D˻¹:AR@M% #b"DuC^,s`/:d_.VlY<[."/~1Q$bMYUK`t h76 Ěl +r n﫰Sa-i.ho8$qIUkץ3N򱆒\0Vg`jh !:v7m E7biifl Դ ld Ե(%Wx͝AK0BMh #* ='ЪZ NǐAѪNKWKi1$BtYjˎ%Fb ֜S@umyzLڌT~`sb{˜\;leBY5][gK` `šK+XYCpj@\gbX{;`TmI4[GlSX4x׍cz-{XΝ{M(߲*)"@ uQ;ko:zT s7klYT3暂_V|( Qm%EU; 8&Uaß7)UϏHm H&+Nj=C=ݗsm4[aN$QC(Rj)Da"{W f   So'ؘۆdw.F4{)JpAG[ۣkN A=#ؼ æ#>~۳.-ovq )[~}<qCM`nvϟfǮ|03mI)1=5Qr "&O<E5~̿Wh{c<1VE8v [(ӛXDS)­[isę8y閅F .q\21=]{wgq9>ǫoNqͿ UR;FC}>G'~i~Ȏ&8z,S3m!"k痘det?gB;Ƒ3X]=OǸ~wHIصs.2Иhhhhhhq:U@A)д 6n-5ˣB}#Z7 6D9w*0S7hSF .*$6]@KHg0r2p{:3X$rEd/ 1n`ߠcL78 @1wb{gT"B)^u;k shMF牱ytB FBuCPW)}!Xs6 K]oڸq 8M-bv\sɤ@rmŚwZ}Z ^&.4BYpaKa8+Q&Yqjckn>dХ{krhOԆwpwZx}>7UmWڳf7Sf<&嶡K!f5+n`R19"䜵!+@Wtp7\ D;6)Bi9 k}?DB7DI1eWZ3\,o$A7uߋE!7d lύB!Ys9A@1BEm W3 cc`0V׷?(> PUGW9b5栈+ݴBl(:Kh܍d4Fc4 H {]oaǎA6c2K~q.\Y':}"8Ǐ?t+FJ0*dx;qYX_a4wmevz#mDO|Ay-w{[g??ql"/_!i"/vk,pϞ-<|~N:w_?3'/0y gq2ٲqə ;;u;.rO'>ΧY{ef[?mć01-X:|1vm<;\̙+<NjgOxM,n_$ls ?ů|u^x8~]nb3/wamr4^'xƁw2:S?hhhhhh2 xVU  VRŸSC'Z|@q) )DQh 1"-EDIQDqH BWL)*n mJZS%KQŊi^M^jU-"Zhq2LjmLBgHU- ,CQelYaK`4f(X)q͘UĨ,φ4kb\'xj(*\M6fLe+ju@ QЬ?"%\4KUMe6aJ~b[Rh۶d4)J\#A53&SgBR+lUQmn{EEc=)Ī,oR\bUղ+]mɔ`ZUF]a}oVEh'-j5')j-ҙu)Cc>1HMCh@9gu)PYju+T5Y i [ ڠvW)Ba :*ļ(]ؤhuY W8&/D.P Qj ʞW0ZUΪ%1Eaf`b2Xb4תb*Z/e4hf&+]+ ͲTMmErYmBCSJ&k4;}S ! ֩ ZH $z6S g6MAKj@תZiV6XU ؕ`Tm]QQE 4jU`@EdhJ9kD{RX3H]fn BxN6 b@c֨YRm_&UŪigM԰ =Sepfl618ط,ͨɬm"Hmu4mkRdb|dVx tͧ -i3RE!`sSeiP*yxAP1LPUɔט-[Í6dhcXÆ!~ &@cJj"1UY*-yLQDͼ;B`.!|YȔK(LVґ& ;KgeK)u `lUHP&'2) ^8.b =] -F{%w T%c[~<)BEa 6,s9DQ=XŽz0;lfxx\5y:fdݳ<'6`>)xlO1Ycκ ?RqwݹR!ڻat=5ͺ{_T4o@R){p|Q\U ; a`;{w0f{s0ݚJ~{Ckb Şڜݳ 0z 3Pl>IBm(:/,Gc4n!_\r`+\_pyN<SSjET/p1~w_شi {~d):~lgne;7ϱkq3Kׅ7 k}>^xa}_X5qw;u- ]X3/o>ۧ269@zc-ܛې[?z\@PX]guV H0{@ y(pYG6^cE]FއhW+hƍ7_\vsNzr<[ؾc;㔬6QRmbg^~^}ՕnڹHPxWE6ΰ[ؿk+;sVn۵7w˼!ݷySާ/q#|G:Z ~7_ɦ]711Ѳv861XYx4M˖p21EZ8u,? KuB/q%_m.?m=cmb|bɉ 6!] _̞,,nŋ4!JCtw;]bfQ\WF 9noM֘hn\d=ڵ JF7K!_W8s43q #J$+k}ؽ0\em:+Wv8[|ÏCqn1ˆNZL>^\X&&YZpri^E&iᲘڰ0 hV3&KL`-ZF㕖Wj0I!l=^WDaZ1?Davˈb1 2@$R6fqM XO)9Ztuٶiz AmZCѬ46ԒS`JA9j.IbRO)V8S02(2@$r1 !`wQ*%[^Ӓ1F0Fȸj~Q.Ǟ@`SH7j󛳦bތZXq :yhLІRD+T]_jfRBWX[b2դٖ).;<55zK0;:Ġ#zW2 W?-2nc V֔ @~ZlߓB[Z?7lcp8 4|.B7P4GfAls:D]w0G׃b ,f}"=bS;x`Ǣ35@I|sɪw"ڭ]uILoR'} ) $͜u7ۜDS_j[aN=*@jL)+5owK:W}_ۭȒ7* v ҢjDS\Wu.C܎#&">[D`aFf< $:o Wn\#l֘4:VRqB=PUzZ 6k6p .xkEacHvR> }d]ޣD6B0~j [?.)O@(E>Xwjgޠ[c$w}kFT{k-As0a6gE0|Z[g1k{wL3֟]s&_}V$BݧWsGc4n!/? s3͟S2|ʆ ܙ:9ת)L؆4KY66xc Pfc4۠e^ù`ccnM愦+_BŬ+ Z,؁*]PU?$Rܛ\g=s}RlAY͚6k&l]JcdD3 hp(Sل9@|WxDhX~a`JBrQr6|̐Ty" v !5ɭ]( <\jM]0P3)A:vlϾT "f#[|q.oK㎽}~wgw,889:W[x1K+//<|Cdx?'? w76^sO[NJdmL3؋Ԟz7nŅ+\=wܗ!E-jQZԢE-jQM}?ڞ `_bF]$oc f9AyѲm a@&m)CՂf>ٰg#犪CFijny>nSWEVm6sm̱0of)VJ8lHM/zz)*CoYʃM[\Z;lj!NtSmvR˴s;ٔ[kJ:0Yۈ-L,3fη2 F<얻ms6m> BEr)u7bPieÇղtaSR} )NtiIS5*6Ӡo06ULe [4Fb~oh{_`0))b6ap Bl:9:0 v9S`epɛh I=+W` `[b1DbJk`+rv%Xx]r=Ygq"uU đê4@RupK6',39Is,T5TMclyǭY> }6nIs4dɛ@RK>ϤAfz0uP^K?~]^c29Z)sQ=Un1 P٠m , UD2E{ ƵSv|zǢT4WBԨCDkr%_s`șWoigj7@erCf옫dv v=ۍktʺy#C.Ďcmmp un޲Uiw٦RR ,T)qҔ!I7yS_-BA@w2W)Ѭŷ5䚭ifhxXN)=I=kW)Rf Eٺ*[V; iP{S֔fvo%T9H b͂~I\ղKѡI"RQwYԢ{3T ;t鄘:f%V^Ws6G=׶y rey4&twO؝ ?29`7yo_叟~叟~g)⣜Y_ů~o=('[{w $~?x*_= ۱u.kYu1ޙr훼k,SbĵMٻ.ЭZˍCo 1e֓6Igt($[ՎCM1{IIO}q^淾<=|.?`rn F1kO$g6}MvXxxQSby㏿UWyy~x79wG|'crQZԢE-jQZԢޓLV)fAyV.Mr!$޲ujOm6+e ٭"R:cJƘ˰AۊFRsq jM}9 !R必V\) ⹍&2R[KH[پb*ܪ7b|$xo0Ċ)c Xq{f@J"04\l.aZ ^q*Qso1r_CJoJiݸ*xP90q@fmDnJm y)d!YW "]G.y^Ɯڬ >u|ڠ)ŕfbY( TjZ\)GՖ`fMyMs@96iOjp!_ѠKv!\yn<&BͅA1+byJQJ-`1XY@֡I!HS\0\mB ە1Pappt61{gj)5Tݒέm=is';OԚyӬ ĄA+!-4ϲq E z$AH1e Yys@͞,l @^kP׶vVW6XWN |M48 ]4l#6Zl}*ޠTif[W}QnVT2R~ lb-Cl%E>xRd-ʠ0vRNֻ9244ErrmMaOӾ։0Dsp+d)1X˵u(yZASik3q qnns=\tC n왫@54`5ok{֓[[| Gi 8q?khjaN"яC|Mvp斢$i r!kfr-  _n-ޤ`8t1;VίhKծۡx 27ZSH@e'o3`zc ~c^ԢzO?_f:0X[Yc:08(]בg=e6>[K\xKgX]a%hﰺu^|.vy_kG?¥K

ǹq{ș%bL|WY\#X}l8~߽HNeL_'='y׍ؚZ{Wip1bS".BzӉiTgJ+2}V H0@F\I<@v<hȪ:i_&4 -րoa)=FޔGSϛl 8>Մ?;U8ОsZO)ٱƱ{-][7cuS}],羭q·vGQ9BrK8/JSEC\׮h5峯 wUBwSĚ}ܷ 5H7xQzO=O@Ε>]BUR v2ã#}TY.qˋ|>Q>?~GX[[e5e+/_}៳s)[n|8k?sgY_Zwdqx q)EӞ>6Kg}sx콻W)eTWUbsIYIX[f|j1u*-7eDjM ¾YCv}V}Cݲ94[`i`nN.r)&RNUx=ҭ[i S9?RZü6m5rk@2;9װF# ) aXLqh?eO͂fz3!Ao1l{i]c*fsNSX?& t| It\n`y7unioZF-9Hr-6T\?rZ32smρnbn (o\l .$Ztn_)ړ~ 1CݚZ kO@F7C:I֮!j zikW#b7u }@\q)ʰTj5}IؤI~Bk`^<Qhƅc1y{I788vuq3Q>L[,G`^ԢojGۺ 8pq<&%z2Y7iL=ezB8~cn-{R?1$&f}vcYgsҨAcv+?Jgǯ/߸{ |;s>}K]I|`ϳ~WJӷ?}?% /qPwQsܼ.y^{מ.]}CvnsUw}?.wnGla}ug%&;[vo_Cx]js?*'!>p9gr2kaƻ`uam_|CR> &} ?N];7^̸d^zog,jQZԢE-jQZE\ u]`7vlTW8ь}C\9(*ms5A<P&Y.a{K>%e.ԟmmPEErm|͒϶I꾁v1R 5@E&e5f)" J!3g?ͶmS)JQ5VۿWG->mWSmn6 Ur+h3HEC={uVJfYde>W "+1YSCrKܔ:9S}4afEl!cmSĠ?cL ]a ,6F)Z0ĕNNƼg#U5S_Pê)wQ];ښ,!t5ɦRl М48ě&$DZv\gmq%jSuR) *6E0\gS@ƌҘPQ<ѹKEK&9!s}ZSI毯;ֽl_}UorZrBE>΃CfQopc1`ٳG1jX+V~-&bMc .5bH採ГC<92">H+)z.jQַ%Dj@aԍ ]LTH{({2Asxv7_5,Ȁ+^[CB;}OlV$2?B tݘGys+llN99> RKZ?x<"2` ;hyxp _kG<9egI=Gu#=7:Ap-PȄb}wlux΋hx|rwop}yċ=c|nL_c@\-׹uxWnM8ue } Aؽ;`g__b9"3^[y㍗pgɵs ңy}q`6s;o~FߢE-jQZԢE}UM6Bg2Wj6HEdjmN)j) sժS骁jG6+DXU)}o@9+(}sz, ǵ*1:J @HH#4Eq=R:Yئl @ځaQ3mjl!%BH"GAՔ'R B:3SeDif!0eRů)̊)!k:"}U6x&}ݝ]VVZR@gi e \HJ3nLy$"EDnQ1)m_U)oj^As냫]JuSH$u.M]o(54- ԚzVѮB1fi@`T% awLmYBA7C3$$r?M*5{Ӏ)>>_i&nWM}R.s%d4[\ stӳ5u=Ԯ)Ϛ3\Oo˰.T2rP2  Ԛ)}O:bˮ,= F"Ytn[zwѾ'4%lE+`"ybkwv: \2@9۱5Es3D{I@AER `cuG{>/"C^|0A辰l>bDJΌk T7En(eFrքQ簕Vמko ԜDqwm }wͭݰ_Uc m$!TKl\M}:wʹ̑ X\Gh𥪁{W|ZY5i8Ɵ: bHY@T{4E}!&fydBLKرۜ> 9sUөwꍠr-Qp>+Ҋg؃b:9w }h-LgS{u#%JX߰U9! mXR4g$v,/ԝ =$?7N/y+qOVYlw?K_A%n:%)xc.=;gip\>CYɋL_гuĥ'޽6gW!q5='?7rSUO!lv|?›z{70SzMػ3!A&/gk\B.K=׮>H>9(}VIws@-3[lCpQZԢE-jQZԢ?j"5`hJ@nlae&`g$Q %{SR5c"PFIJ]Y3QFP-O2zKMإ.Z+3s-U.zm:KK0i;EzjyQ.$kϯRr`fh(tCcLLsT5Q˽¶A-0mЪXV]/X+_7䫜4EOVlTrvh* Bzm6@⹴XkQ(+KcBBHxS@k dj4xNnk Qp TW2*001[;blMx47sH mMO{`}TJ_H)U|J@=<ׯ*=Ew<6ݎ!:8Y f˄VSETlGȹXCaJ>Rj1Y鶶 J`c]os@B! ^ҥĤ`b1s$vJZԢwm n_'jN|>YdYY9`י޹2sI=&6W#pQZԢE-jQZԢOː6e%S(\詄*H5Ε=Jֹ}UGol'H`Vf%6[MAiAqc"u_bٝzv.,m1rZ/=xh( ZZUrˊ$lm@JK!ktjZL1S$'uo܇ȽcтESp(Q*9#"rgJHP  !`ЦsHd]Ad)C)XƟW* h:x5PT1KTfݢjUbYoȗds\ធ̮5xtW|W5Ĭ&[jTZ]j˜]qeaH%I@SD6>jK5jHWK0e}m?+ ֶzgk15h4eb^SX]֖aЉ)ktwm:.6qӛu4:Wϫm/F4^hftԱc.1籪 ءb>@2F;V5[TSRM_]1jRq#Вm=uZ%RabYsq[nlV6@TnYJS'p.qy kMgc P`hUSڟ'b 9򓙯s jEyEGaB]ڔED9CA1v}y[س@7mOP\|kVqP[:D*4+pwDs$B~6D}|Z9bطYH).uR|]Laޢ+Cp8@v{$;Ygb/=spj7XcԂFm- 8dSW¿:hW\Qtjhk3}]g+UՎIK_iĕ!qo2! 6R?}!ZD0+~r7t4<(so"ڻz7zq[CxKr[O0Z˚EEm/3mҙ3AM9lsIlW?Ta,jQַgwaZ_cku3hrUKj#}O?b u9Qy'bcHFZFnHSJa4sfcuhb7Xڼڥ5lϕUVֹv!!z믿7;o{O/]\a}LܸyQvLx\xg}u2 LO`mmQbwĭp|_S.l=ƅYIg9 wvvyOy&>ڙ-Y^sx2;>׾8F\<(Enqڃ2wf=j츳w+W(XxOB_:>Zz_Wu{Q3XV7n[SpmnxR7U) 6WnL{|= PUio(Wp\Į I"ځg_<`ff(7Ml7xڲeͱ>?پ|RHnyCDS7j٠5K0iDU!I6ǭQȀ.2rnO@P1+[SVugq`T٭kUR0:eiv g:TC;>~ bZ|Ο!!ۙi MnyB#Z' < Pq%w<ܩ<=zzД芤DupIi!G=V{oV8&9n#jفehc^{4=ؒRD[PLq]1zG(.Y.o~|f?ϚKs~wR,C4JqwΔbj-s{QZ-|oZ#)EF+=zg&%^;3_|3,/%^9ro-wѕ;5s|x|΍=WxPf^\eey]޸ ;R8֕sMiW2X9$K [ Kt#Nxc `Y,״ }@ɖA6 %OKRB\Q&[-ΊDk%F+5.C!Ŏ,8@}s ! ۠u<71pS9WŬ[ou5R6Z((7͕6:j)RjՂF,w[< J(--q߲ij!W c됢eEBɡ*f)bF}fJLQz*Y #"dn*.9@H+c$J285MS)xfiuȒ!SPv}3lKI}?5tP!=Z)D'rmVZ\JVɘ "ӶQ1ZNѡ  uZ3T/HlnⶲHj$H4`D6:$BJvm6C٦z9\ kUBS:؟;6 ޮr9l7 +Y"Jcq+N4Ķ ܦ&Mi3D:A 6Ů81"DjkDE )0J'37EnAhkP[k,"t_wP Cqq+^uuh VDِ7g=1{v|.?gj1 bq dKn!8\qŶCֹUT}k_MW]G\Y}qZj )ZWj3wa4i`m P9H_\(h #3^<+R e1b8vzsSFlfTa!B^е=V\/4O5QmEhص*G{ }-},~{Щ|5nm b?vE! TVB+Q%s_g˄wݥ}/tẐ whv!Z 3>eZ mj _iCş {j/`D}6ׇ;}agՇF ]-j![CDʞ_ݜNxϿdBH6G@ bE?E=Bf/*$ Nsm01g3cC'?w:&|% }.!=eshkb9wi.{8V؞=}\=~WKwS4E|RLί9*Tle==#Qhi$Zֲ~S>0OY[XK++~S>|C wodcJ@ypT-ss6GLcrF+0Y]Ba:z8buuGԍ8:xٔojɛ+faӗ<}5V'cR evS Ng|]~G`:ͬmM8<9eұFTnܡ2%+;+ܾ͛tr}k6|x[7oqz͘>>#]2ݝЭpy~5.gl&c腃7W)1^eB?<]FWW3{\|-(gszMme:hfu[B?`z6I! 7H@WHơ,sQoЛYآJP5gY JU\ŪCӹ9,7X:FФ q(enjːH];񦵚buŕm0hJV&n5s&1*Dij] ~AEWX~@;F2^(!-zoSAYrW3^GlkOv#b/HHѭR. GHۮCtKbRu5z^E`vc%(b0ȭC rb+\Da$-[YE1#!հP5SbM蹺Aߺ>u"ڲk 5[Y[pT5eD-#|_BpqF`XFע~*#*j6jz-rs6*ps3Pj&̊DDuJV:ؽgU[c@klw8k1!lJSU͞j)b4hD;0XFjC1+j%g7 frqT 5wչ=M_5)aR&4{oܒ^dٝf)MNJj{XJhjdNmĆ+l/bjfy+웥7l njY+G[{\+KpgD}j6uGFmd ux-6QsHP>`/ЀŽ6]2(cۄeRR! a(yt#LŮ ,nqm]6B>Dmymk\|58\,X[˪Z2,lv%x$E[u'[9-3Y p3HuԅͷgЊAReކ ԆJqmFJ!HiBdtמRZtXN !5UkB컍cDahHl Jf1Y$[$g"ZvFW yѿ>Sbd9n.=O?ۛ| \a  h)5T{ҖWI/4lAzZֲko~;zn^Mo9/|nnq|qH+n(rxr:9+k[4bz5z9~ɹKP%sbyꂷ޷#8:<;{Gl WCg_0=8d}E뿼`Z<ǟGof {kLcv'T!nnKFQ@V z}{|}^}}ī73v+ooqtvd*y1}׬ݾzuyӳc_AlrqvɝC43/flܸyZ"]`~ymϽ.׳ k66ֹ/ͭW2Yo_\ֲe-kYZֲߖ2Y҆j hdR=z;:|9Z. TKEU٠eD񨣯)pՕed2Ϧs4ɹFvFR4[kFz3.*A5"kvިTR0\PX\S `JUW WRvZ~ H2eft\)u5 |gM* gs )1Pro]h[2.ESFk\✳'?5+8;;`|zE~>U\^^J|OS$$RJ$2x&!%^>xz;? [ރOx)kf5_<nWx~KRem+o^:yj$9+Ur1=_xYağo?gmkLsbM8?Oz6W$#snnV֙3^1O8`=a1 o_vv8|>ikN`eʈK6&WL֞]gg\.}gc.8Zfmc=Dٻu]xXrrgYZֲe-kYZ_JUi(s'ҠgBqc֜b@q+֎`Y*#Si1>1[emns(\,FͶXM(zǁ`JVB #]HkDkS#֊JdN@B`91&J6P*LŽz0tHEG&ۆ `yn^N8ǮMLU` TlͰ:4LCvMMgsMjmP6U5͍U؟U+m 1Cz֮7:T~6L[nK0 /Dux%b"qI)Y]6roWuvLT63;ϰgRog 20Rmp}\ ]ڞil ԅ{,}mH$0u/Tqw}{R@J UߓR}JB|dewZP6DZi޳6"c+A3l (ϊtp00nKoS tΨGղyCLVmV 1%F.VbƦFަMʶ/aHD:>ن=|Q/kYM7yq<58Sw>[?Jz&uNN߲{un2;\KO/Ob4f23AޡlxKfB&_\zW aL|CN{7n5W }}ċn?۷>?&1X٣G?fʽkg/+7hԟV}rHȌE?x_<>OO !ҥ-%k\!_9oa>K\W [ֲe-kYZֲn5R BfԾR]Hk82Y\P>h%A-=4$krp}_h !ug髦D)2S?Ϛx5K;S@tqš)UW@`;S,224?&'$j`Aՠ_ŭ5c<@ HJZCj1T j!7dR,7Zg}"]="͝3jjHB5HXc*TЂuSYw>7jSZ 8_kƛhMRl}[؁qmVjJf˂t7 y=5՗JY.p<-B{QT bրXmiفb1RsϨKf媨T ]r.vmsfJAZcy&m4a󹫉֪bMgMqheŕяԝ"ԵɠpKignM T_VZ1SH⹩vwPچhBL#:WLk/ـ]}CeJU[mon6s-+RA a88~!DMlSL綪SRAF(MtlYYb98l0*y^ uPw ŇG6l=įNT+a.%r]M%YWB!]cІ\kNv_ŭJ5Z)ޥW(b8 4uF~= #>1-^ pj{n; J˿8Ȑ[Iѥ!@3wq&!Z+Z t,n1gޞc *pU?$տ]3i{nPsI <;A{J8a M ڈXLFnG߫L. ԖZ]HQiγ}Q{;Al@C8o@Z:j!=DEsp!S 广z\J}>Ȓ츋n;^|`YZo>| 13%-ܹǏؿy~ȟ?'$(s~?\ |]?;;&۰;&{ۼ}n>am} <~‡;ʄ2-%Qˆ9 6 Ny3N'|4$\^I[7yRp>o<6_|6V(WOٽ΍w89DVG”<3&뉋 IDAT7y6:<)~mY'a4Z\ VJ+JfsW7oNW‏{T~3&iDI0"LXYXv`V"k|SNN)OfvV&\2s&q˫_\ֲe-kYZֲߎRˡSUV7iKQ!P-OB*T.XaSk`S1ɁXp1勄P-1kHܓ 156|Meߜ jt]go51@$qޭ_#LCn$kT֘.K*x f͞[XLqTq j?.մgG,6C%H5Qj&[突J}Ʒ)_=[$0XJR(`ahW'6bt찢iVaMh^)\'I4ښG{5 @ (zCb1 )S4*H25XlAVKGþz.A馨R$[^uի2c>R2 (l#2JEf6љxNU8HJ *ְߗ+2mU,|8`) K UWS·gAn75( EVOA3un[!D KjD\U S2н6]E+fU(Uzkm;!՛]]Ɉp& 2Sh$˽ojJ+hegű0@HdU\]Y$k6Ҷjjmxue )2d.}-U_60ߵg `Kron;ێ Dl!`>$uϥ^5EqX=gmk,+gъ8ؐ71vL)m;=ϼo?W'),mNU})sX|"+~Y$7ne;b=YQ @׿I,{ u{bS@\tjSCB|"DϣFwjHݧTlcw}8!-?~1yD{?!@I?'GY ] .4Xw`UsObŲ[zA%į;6XOA#!ItsneslOV9>L[K՛^^P~s6py2_wY#ؿu;wjF0s現M'+: 뷏98<9MV&V:&\ήX[]ƝM=ydꈗ n^jΘuVS6v!ϟ=<~xa4X;->CSo 盼r=#V:9~};?)Gp6U4ޢ-ev&my'_}w߃r@?;Cfx {C^3+3nu712;6oXyhX3e-kYZֲe-kYkXk".4:QmHJf)lUQQD!~s`˘"A̚7 Z e2uԤ`XSZ/ n8yQԭJ͔l/SWњE(abvW*Z7,Dmmnj}.g=}fr b  :*CAAш3*1HCK, ECSUKO꒙:D\s6ꖠ1ڽ Pڥ갹2ѬR-7HvK5jJ)v=ԵEK!-GH+\7Z>CAe3E[֦5k1q5RejUdהF@bLjՁKg+EVٌm.W9h,N\lu zycS-c@/g"h $gRsqZi]CEqeݠ|Hk1uŕ^yO-:ޔ lkؙ/ͷ)pUPڞeeHm;] H>\jjkS]mH Ay~Q{F 1ΠbSѱXBzTx nt6!cbn'-_ۯ]ۄ`XQ{Ł,Ӧ, d_dW:TJP/j *2 @U wx6Ry4b3{h17Rz;j6-N,rr QB=r|l6m@-+sCmx)r^% ָbR(`n(vm϶Ϩ, jPNgU1`ŕ>Vھꃯڵu ~lFƬK{vaZwmK7DJͮ& :ˍԚ!íU=gׇIJM1MelT`@YAȐiŪ`\|a*!J` h&ʰbB:_cL4 !~ND-VP,eSg"0,i6xhjP ŞR왈)~N>>#j撢RYe-kYF tWǬm>+L=DW1r]&)}_u^Y>{ӈ^ˌ[au侰wWkkmJ*X'La(cShʣ}uF7Nްsg[fcc])য়kTf+$S7E9xsÏdc|].K;nWh/~nijOpzu֘?ٛKW`CD8{z{;SŜwo/>8AwVee|&+*g9<9CT9x›7hX9SFoqgy k^Zֲe-kYZֲQ nlYh S`қ ,pC1@lF@'cBJJn>%tjthjbZ[Y,u`)FSX߀Al SDnJ5I5Zc2{h6%!Ƃ7f]$JƘPDfN 7C Uf}DmZ k#Q Qi tZdUj_|rNL}2yY WAbcS5{Y~NBBWWZEVKe܉7 T-!1yg!.ඈ4q9,lL{._CnrP!WA)(~a[\9^SET](U VDa&hB/vj1k(LIlYpگCA;@Cv|Jzll665UWJkp|8eRua&!RE cP Mo ;e2u Q ][M  E"6Z}nU)t@e>&ﯥ: oV[Em{(RZ}0¿o=[P9ϵ^Զ  wWR]MX|r@yǦήذq CmCfф{m_j9чEDe-kYFф/bkkӓKlw|q|zɏ#Fkz]n=9@ˊM fGΙ:cn߻O_*g͝-&묭qwȼ ?;5qLXe .lnv_d}N?я;g/gm{o[9/x]as6vyrqw؈O>0L밑yGg|r60p ctzg?{FaB roryz')p{c+Vo=~.Mop|t͕-6#*?LBxs֙Zˠ6]k[0N!x.%)̞4,M5)"Q1.:dQݪfi9$fR0j7M{{>LbfObb>Y]s1dzZ L4suCL(F) nMkҞwхR,DC0 QR3v"ew5ջ=%[}F@Aq% uAW!.OϿzPz6I.$Q5,Ԕq=6Q̮m)yj-YR 1ٳKhc{ WU; R ZiC,ۥrf{|)\#A|9v VvU=;u,RL]mb/dp7kZ Rla?nQD vVCY;6'`+A5 T]X1-U6EͽkSԷkW}U-<;wϱkE)l!ٱ* B$_êx}Yː[KHv evj1jl<6` ćޔ$.|HK|褹\>B)9zFQk]tws}:)Gxm{L3ŠlMwέ6Pf)Z8ZDCZ.\8VBg{9mLֹz=Ww2RJ7b bk1`*n7dkؠ`ɵ@Tߟxԡn,>AzDU=NAlΟy}0vW-,ﲖ8M?08wh)7w諟'?b{u>N>?/}M3F+]8!&(vvw|ʾc6wf)͛߾h}3?#Dή/uw[wH# >s bcuB =XtlGvv*ܽ_W3T]Ϙ)+Ejb+Bڌx9hdL1ݹŔ?fsxzΛ.`ke2N\s-ќ5>O'ZGaWƑ#/h0Zes&iij_rzKN&g̝5"KoFٳ؜Lxx#ֻmvƍM%*ˋ svwXLrqv:o j)|>ǧg~w#ԭrcj=;;toyu7t<}t\ٽznF [r!5GO8:`FGw}e#D [uSg[nݺS80%>}3޻dmHk6nqtu ]a$d3nݽI=Go_3لBWgB?p/ϧSV&n2߻fC:媟rtt{d~w>qGh!.kYZֲe-kYZo{WtiL 1.PjZv)pF) DƦ:*V󞒕Ba}&Ry@&6+\nmiްxXjOZAi_l9J@jTj J 媤UZ!;v<7D͞Kj{Ya\՚AN>WW $YSVr):(Db7fu\!:@Me>GMA|M%H Nݰz4)Bx>jJݔ H(QIB m1oީy_/N!b8a&kݫUVJi!оT>|R"t1R{{Gɢ@/kY7_5N$svmb>+x +W~|s||jOH7`}}!uP3kk\f\AL{QUVָ<7erUems+9׳LsZ؜lգ'lqLJte[kos|QڸMke+B<}ts+crlLָ +otLi4fE6S>Npu:OpcoIuVb7fl#|rʽ|w>$h\qur|a y6gc}QGcq*s~_0υˋsE:A9:ʌv;F\LOI5r}г};'pΧbe^ gg#2^޳לӍ1:a7 $QS Ȉ8s^1FŠ,VS*4$j;;s5(ZG~5t ˬi yh,l}}p:׀ ayPSQeU{wTT)R^RyW8]SUkC<1smUEڜEE&,Z"sYۙؔS;qDPcD@,"Gj-eޘ Eg]23a行C~r,ef:!$v"x] UYN0-#l4w4PI[b˪)o3ݗfΌO`XրuMFp18ž0P\w0ŸRhJF3vu0bٝfNZ(_̬VΩPfeCvG ZsOВj0/7i,sZyr!3U;ᠯ(9gRw 0߈ 0; #X]`_``|d<[5sf=FJ6Unhp:g&vig;Urwwzc1 v`'-ck:Zc(Cv\:ՅNN? "ФrQ6# Sh,^5WFT`޳7HKG;\l 7,W-KI9“ks3u\S8HmHsN>DRC欷uͦ&IX;E}Xa:p\ |٭&e_=k}v3Mțq]o]Y~ϴs]2`@ vӡBUkNWNU @3C ljw׮F$oS=5Lp%Uĥ8Ӹ h,y365L}&.ZӳOg1|JM+́:P@.湶j|bo 3͖=?ഴ(K D\[kʔHOwZ1{l¹Fu$sgCIZlB2C7TgAc6@V355[~12 }cB90ƎV3g{:.x{ 577= /7Ն_7_xn+_g_ӿ5?}E ܞhqmJo?y7ǎq>77//>ïhŗT^;u<{)+?!%ι_| [^}NK<|cǃS|xz q?Ov?cg_>@=|D'?@~Jy~O*ngx_#𨣎:ꨣ:ꨣ:wcvr+W7`jd7dJm "s)w\С9 #p{GP̗3B5x{dq#$X-oƦ,.$i[<@Wz߲ME!i!`}խS1c33p͠`&b7ddu͑+Нvƥ*B.D[f10PI2ޯe7{λL(Bxl إ.d(;F5xX*>)6T;渋IJ1Ѝ f@) #A j z5wt?-U߲DPy~G5^#tl5OQV\ ݹ]@9L=-+IJYV1ߤ ,>=9kA t jt=W&7n\͘1O9ML;4U u%ұs,2 :~GXq sZNPny/ejJ{eqSY9oR D!U|f>yeJTԺS]HΏd9L5^E '/r~33ʕZG #P{f 愝2lt7DlڗS9KfPqL9\*0 wR~1|\Nno8ic5c5hp:UUj|hgj4L( ȗf6o0 k,@3*oq枔T3Buc}k!{y[ we("lX4[^kLc\NYnBcfslֳ@9稣7pa׷xUK|4«'c_|5~gx{x<| ? ޿/%#G5^|1tx[\!rg0븹yjG}ϟWןG׏qg~=|SZ=ǹ?Gqܿ~)"s~'w꯿ƍ?W/>Nv{_~x ڵ޽Ϳ[~|3< <y/^_1xQGuQGuQG;QMY)HҷMe<2< bݔ:mm' {[(ZFEyGj@`V<4lYc98IUA#P"7YU(I]ixjVm8>s`d(!\Ϥh#RRR׌JzXVZa<<&TU*Tࡲ7I [C&\ lvoK!Ys{J%(Y8.@h)Jfq2!20w[kN 5ݸ=Xș) b 1p6l cO(JQ} p iJf6(u<\7]㈁OqQ "͌cNJ}׹=aYzri BWX ٌ/\510b_Jҩtv4W O-5`e+4҈A|T\p7hvش ψ@N.ٸQjcॆb!g*Yg4K WV%a*Oy] OE#l5Eld/ ׃SM;pE&K83EftO*LNzZNx9_kF}1X9QtL٢s>VlYMqSlzbNC!Sh(D>sE1n|fˈ'Zۦi7z]&O.-9Uhjd sFpCֺFfƾs߄ 뺺\sfm7*VV QmzO)W*6ppZfwZͷ|6ƩX! : nM#<B>r@_Yi`Xq"jWUG66&6J;ݪc 9/>N}R ZB@}$߰g_fc>TB=y־('5uQkV#xw/?>~^>M\+<~ ;1r_`;%ez>ުx?z3ܽ9z޽Wb?/ϿgϿ[?^_<5 >}u/ ?x3$pv??__|-^~>NWo]?ӧ/W?<ꨣ:ꨣ:ꨣ(!\D0ڨ(6̌?@5JAvTqD3kܻ:ީBC;tXZ_j![No`s!wY5Se;@woo)8U2U;UA};Ơ, (MCW_G鸽C0Bc$-R1hMuj9SS\SG bܖJKHmo쒊.@&u`NF,[diR T@RJ"f6˔ ɴ.@bFe--W(p}d7yG].[GՐj?*8{k:m!=W/0J{Ī8̵n-Y[sxQ5]8ј8&8|Ro,CeY8;c3!hHYvÔĽ렿oDF Dznt6w\̔qn1/輇63PW+f&hH@l)$2b8'd촴5mKV  Kj+5#pO_ K@V @eN̜g+-C={w}7rZW[(AaR=/(Θ jq<^:iB;2p64Xwp[_N´| + 8;\\ӊyU+#tӦ]6 K@f*l^IP *AuBy9.nneoܤڷuo6\̖Ό\(R12~]C0m X،Ͼb. К.Lyzh?/5 ]b^DM7SjɈJ1~J.B.޴6U2r~Trj9_0/@WxZ0(Q{}MpƢ|s5Axi>F=O@ۚz@98ml,A{W41?W>DŽ`\Ef X.D c*yutm=V_y( p+4PɵPǀY\W>ꇬm6W05M Zѝ# pFz5|ct_aM&lڶƘy[c, /Wn*:~+|W88oloyً3>z>}<|OqO3l}'?Żo ?yّb3}B5^ܰ{cdz;xuهx_~;|k~|Q'mCusOWݘ ,sTb3Z25!QAU.{L@uHR1 I)I]\w5xQUtYJLGיc_@ נhlyor bcpRfͬS{ FUFAֳ nTj"v##Pd) .5a՟r,kZ&<jj&;@5hS:A^yO3kmuU0SKy Y{ߨ4Gkeq@XwFKCK׼ jp+6UmJ3.%?I9fDz_@VALlYH֢d IDATKkA_aRb&̌rZ{s`9(Ԕ{Yje&w[M#] 4G7]YxRF[=]6TOkiAnokӽsq=mxc)KDc P=KEl<^6Su5l{Zkc w9&h|{5WYkmp`3Y/K\n#][Þ);]ؼ zY7մ,ujM"Np&44f^n̦ 5̱0ܗ)k9}#(W; 58DŽG}{݌tYka~x5/X{QG}[o~-o~OsQGuQGuQGuo[ w 1Ч7x6 y{CnqNGa8tob:<Ӆ}& C+pAZBޭmTa,u*I.*+1΁Do<}6eKJYsgo i2^`d*+ UL;JJ.B 49jRіtFӽ*v ;:?#W {&Fno'oVΌג146#!;gYue۩-9,%]сfڦ꺄7]TQz8i@CֻX 0@Hײ:R($:PKi&p$Q%|k_1PcP3YKfzwNWkU%Jn۩ȯl&ԋThx?3WS!s7#@*9'\811C[okl^9ڜ#a<"|̀Yeړ+sv*s1?|]A%QsmQ K6ja)!5&KrP ȺkT͜9ι)w6 a'pJM*"wi%@T3+\Jw ,O> @ l ۲(t@%Zqm!%Qd,ŠZѮc񷨥׋iNCPJv8Z KfS7 l)A) ;Ճ1'/F꼗(iaab*g 6Nd9; E7}*YrBT/,dSűIi|;[*4f EMv]v ߡۦɘ _RaBU>pB^Zٲgg"s7TNPRWmj}ywf]4ÒkBYfԗݴ;,n6\؜aUdIMnpQ",nkUۆDv`mGuU>ꨣ:ꨣ:ꨣ:ߊ* Y*ǎP0T@֜T(ޚT[v*P)ЭYRarvdniȓhc`wZ(\Y.v%06!;H$Uj%C$cRìT3* #cyK7 cHȃn ̰!C3Yy߆SַU`>p 5 /h]J‚I!Uv5R ̛ԅ̌Hޗ0Ӆ2ý{3V P imUK8?,9A 0&9S1eI> 6e?!2kѦk1yyS /Eu!oko$z\+MSuA‚j@i?6j"Jf/*;9`U.yӹŒw|~>\L T-KBn:*`ihnipp_1!?=-i&9+=;464XWI![=NPr@T:|QGuQGuQGuQi{ZqV.g\ʄ7B"I@ 38Ns# :+ o($Hpj'S51T,pƒnkTȀ7Z~Ae(WZse(B<ԍ %bC0 f9" p`U_Q~<++xͼ=*ϦpWYhfjCkW:CBUer ¦ƙjRY͠e'_ҲٔPm>9F[N9ZWV*W ."aʃ%h@[#;7rNCbN#X1珪6*t+x({ĂDl!\SK}Ʉs# 5UǴnոm5{lhd9OY21"ve  ;^SS ʂ- s1Fs{ ؝6'2sMk0 $uWfYـޢftp! ש'26cIe%NKꢵrq,7&4 0֤٨ۦf@ fSv,ZP%Ne_)gp!oN8!}>Yjz5i>e0umkbEsfm7*Scގ}?.GTbtQG}u࣎:ꨣ:ꨣ:ꨣMmC;R6;̊R9B+_A{SQye:ʰaK eݙMrJ1o eF<T!`Щ HBV ^`Qr y{ H)f9h_1N V#[q9wPp5Cp`V+5.v5ж;Fm!xlVY1b }8bF d&FeTYNePlj(~v }nܑ;Yʽ3XÒ&=&;:IéR*dcE]˙!+TZFCPa&W*8jaOfT'Ό󾣷1X /C Y9hQjtH8U!z\&p6/ǕȐqT`>)5aK3̀r p0/g~,xRb"[֕ũL3ZW}ݚ@h ~ۮx&7c3KZ{auj)o!7F* e(yʗ,i_s4?w±^j)=̆lrK`9X1{@rO;hhU AiƦ$?s36ev:-o;\Dh~aP T"u5UŹ0݊͝`3Ѩ$1U}5Ppp ں9)N AieJo]Y0{Tk(s-risopރ*(PA'D-j2 s6/RmM0ߐ,e!f-ƪ]A xnhqO-qiLEoS9ג:F z |; [o=wT8b魵r5 5>ԈϜ>3 9\9me;Ν}jڐuSZrjaCy+h0Yp[W~"WP. GuW>ꨣ:ꨣ:ꨣ:(`YnlCzkSG PUeu'ɠ򏿖D0VT܌ @a(km`ֆAkLP3C58#S=fx^|Qgh 3" T8T @e(`J,hR[G`V] xƙYMMmÛcv*ȱְӭz\ۢR-5eC|iPSJҨ'1\ x\P&ˌ9PRgkT%rTޔLhiRE=``fpﴸ8Ú+@njwJةz YKmfh%-q1 AXFMPA(~thM`mQ<oi\}ߗuda3JP-(`yN;2]*X5ZH=u>9_yR#.9T{.TTzuwL\zwq:UhȚVꠂNrg$}UTGk} Qo( &L(kIn]M9Ba9!"/MdT~JK F) כ9JEa7DkKɖ_дQɳTzj&jJT2vO'DT˂rZ)hRmK6T/qx ; "DȤ̀f6/0tueǒ/VV@)o~QK&TvL(/+kp4bZ%w6Y` t_\rg h0*YV%lVQE{]@HJ畐mMZvi05$Qe,岲vTzN5Ar|MRVcSu戱/( sK{53ʩrKN]mAs;1z*-B+2=՟DRFǃ;+QjgGIU>U5-/ړR[y <s{1,*Wvɦ\j]ﶾWj0g&cв֮\S6En^.57)is>svwB#w920ԜdrMsu-BT6zk:&~ؙlɦ 3%ۍ3[M15muC;M] 3vKSkb)_ZW*ͺز}1έiTK<ѨѦ ')Lcd){=MllOTfr K&Ttƚlbi, eSӖ;̋޶/Ki8L YtՂ0^k尠,sVjZG'0݁}cqE[Zק™59DHj:{yCJT0d9/7aT.1u`h>םY|.8⣎:uQGuQGuQGu o:G>vBw,uc[GEZdNpw>˲sk0GFu(.hGLu, HՐg~TAv"U(YV 3cNXڲM0o%EjL%ů!btM[9E(P{=i~+a}Od^ `X)+*3fSHI 10&˙M&ZX N]&¦142YO-k¸DtpȢ|F+"^]7p7*"qgjB ⢀}Uϋ}vp>݅l QخO7Xʛ&[f3*r#yy<^ʰ 9K+fTu hu \C= 9TNrBU"K,2u^B֘9AUiVgk`!" AUT¦ 8Gs+67tAu]a.e[_pVb)tR'cd|ƩF= N w"T/Sv#MlJsG {̜ 97B\pft+𩢣b,q;MJV}u츌='@DjyΌ4oڪTR=2Ai\SiSߵc1\ Icf ڷ`hV>*a*? ))wbJQ$GPR74̩VU<}]vqMj.%% ߰7@=YjSXlɋ L^0wBP8PĖ1SiFI.֌JN V6IHe c3 &fJ\^T`}MlX;Z뗌xk6J*Y CW.Րqyv(# l$As)/M2jYؘ18vp}9?{U(NZUO#T %)QI{p ` fNg h0`RKM4iW3L V إO] ˊE@{/y ?3ѽH^`Cp[M+n*b6a0LMr~\ oFf!ZcdJFx.jwgѡEkϜ۳l=ٸ04/O}QIlK:0z> Z0 V@;$G䨣:ꨣ:ꨣ:ꨣCv*^ӰQ%+ѝw7\Q4sM m_y$9\e\( YDҚKo6sc%x)Q%렵0c"o3 ^ppk$0[C:qFNiI BѽɊа+NRMAZ)crF9FŎ}HlnI+ݬ(Qhgǎȩrc)+cn 8cԀ9UCc)Fl2 *a:pM\ } -0U (Ț&X^C̺̘p|zsy= et1$nK^6X[.U"աR0*?5X8 ~C'J9=cuF$b~`9WTq}Y{|KX4rE-ce:ՓOm6XlNoAeW G!/JbHXlY5(Ÿ\TO+SM,#&Tnfλ^'QSEF{]F M5&jp,5QԭR)*~)9 l* >Wjxim#--f:0v5EXO)*Q%#UZxaY7)ڹ?˺Ty TF%59SUX]gSׂ6ʶ} gfr0M}SX{B`6]1d jZTKu5%lFM\, c/s[u7y$Uy η9G+w Jssܓ ٸ_5#LP],TP~Z/lbc3I%0=Wr,ߘ Z![@-Wzn!;`tǀyJ#.Ѯa>1[tvåԏ<%5eLkb5p1-AC JUh~չ|wc5CXe(uIuQ[ ࣎:ꨣ:ꨣ:ꨣmrT@U:Z"NNf%UPt \<<^.AS.]*W7)B>]u ٸըZ" IEGD?525]֩!jR* {x[owfFfa[X<ށ$hʑ뭭l8fapy >dKeX Ӟ8зBcZPZ[ ޜy6&"@2j}=LC+ :qRH~^Ǵk|NhoM7ƐT %96rd<K͝RAVe2h} r41ߤךAC ? QQހSUHp|BӤFI˘Xj$/6zP3?F [oI(xkc-y/(V|^sߞMUyhE0Wr*{FrnƝ<7Dh_m<[-;o'{WU);3$N &H/"T;"?we-ZXdQ" ֵ+*^ HSs~|>NbB2L }| 3;{ν@bP˒vtᔟEtPjrg| 90]2V/B60viD %=-hwaxWA 4hРA <'RZT̈́jHBu?f%AՊvJb/^QqGjdlZ/.*F 1a+QP-+DgIQmW%W6()GBJUF {sfSU=vM)QUF:;I ʪ¦hd\LN|lPVbc+ To! ,$lB:PRx'I֨ R H*Xʖ":z-qRzeh[ZHjh"8oj;p a!s*i#X/AjrPk$1qZQ&Yc2gFp#DsdsJ Y)YS{PkYʘ0g8+b9+Y!V)犪V #ja&VJxi@ PVlWq:FeʲVD͛F2Vk _uTVNhsͶj,m-0Q6VʪvE )y58 n2ve<)[L%@ᤑ)24.HޯoD0ə*8JuTKN`鲎I'c'?o ,FuOd̸Ɍ#yhgg^ >p@2Av EU] &iE^U//k?g){Jl3EVݠA 4hРAAA BjDWixSx/V)][1%lPYu77: v)] ,1J)VU$hyB,Uy!dJ%5TZ&b5UlHb1덫&E@?3g?P2L$S)8W)l@?R'D*P6mUjD|[ٵ; мs,RmZ$F>jO-j'9R5qj*޲- 6-$Y& ձAm\;fdq ZZcH6QwL&,rLv3`wS^`tbĐjYkRdu%)綪M\\qƑLJO] !B) fK9.hF2Q0D]S1 2Ӓw3!4ͪjeQ%kI,\iZ!3)T>It!ŵ8ҢfE!J_D2"Z^*D@*ֹzJSS{ڄQ{| !eqIEZRtn HցM‹ :B)8`1BKNn{\mFJY}56Msm xTuU$My6^ccFZ@4>Gv{!MWtV+!6 I#I9^dʊؤV֩RL$-z ͕뚛+DvُUs/7,ؑ!a0AQiq☁ssӘ5C3^ RalsL*D!)R>7v "U1bڃTes>WQHkh j,+Wy%COF4QTu1 <>Mݨ"G\;W1Qee]LXoE!TU%Dľ/R JUI.0[gm Ue2ZOI`ӷhy^D-CLt՜A*i:^ ufu[m9Q-rXn?fT3 oHCUg%kTMQV,k,Fa|*oѹ3F[gP)t ."O 6cԩub_%~",|*%Mn\R;;hk2m.죷XJǨqё&ĒL{(:aq=S7w&3xCgX|Q0Ю0Dz{{3Qt!T}%wx} OfϙK_+}}BD&Q?R %y1H1Xye;+*{|n Tt!iV alg'}qF롿o=͘1]UP%2i,-KߠA 4hРA GB" JcIu4{:x jWU%1iQ,a~1V8$2nJEe)JUŢ4D0 Y 3b,miNZ#-(%Qш/V"4މL )F-I8bClT15Yf%H :D)gZA'ԌmT q% jAºv)N(J6yBL.-YMd|8JxQܵ.uP'Et2V:TǢoI ,gJְb}hY)'H}[ml0rBxEm*s*Y풎EJ[PQFp&4_PcM[_ԄKVE6 x<)j.(Mb&$ IDATs)F D+VFs)1FUVIAQɋݨIZňiՊɘeQI硤qe4׫20%ĦQBGƆy ENWM] ͯN%t|=ئKsE )Rn}!fl x!l%׷QոQE(L, sCRgt"jflr΁|Q, |c:5蘱P^liʫ2ZȬLmUQM筪JB<sw5BHnȣYry͠,b+l#[G%* m5Z븪MwpV(ʁvw_}-$#I$8;6ZGJD'dZ(йZlJv uyeUtDLm2 gQ`DQ"GcF'_jK/#Q6JCR$HD$WGC41.WB%xٯO2'FDϫRW\l9NUgEzȸB悐:UQDToFe*Au[G^!jx(N\9F2HFuPC$jSJ!$vXIss]އ!) Q2ja,8(3$0B6'Fxi8F#,-LJ9Ҭi-Jָzb8s3HvR;om!i\G n5^]RR+hi lL ;s=% fSH18kRUVRO¢J!Nn PRH;qj'V)D^HU[Ƈ Qa権l]FU'Q%JĀIHDZǐ+^:23sʈy+jOQm:}),FC@YiqRuvѠv!M V(v`(| D5Y42J>1T!aQiܼU5h1R(M]+IX*rΑDNƋxĒ9VVJYYq%IN(}9U L'b(!!M^m3iiB{.d7hф,5iZZQ Ww9+ɠ6G͍4jE/$DrhTT+ Xf5[ʽJ9QGmkTU[ɤAAFjIn.QI(7:D**-JVE uΪu+\siql&. qv4ԙIU92FɕW*qHP+k%Vl/LsBmme4kΠǔ3YrVQ ybY)::_HyYǀ5]U;|Iݦ?2x*&a^P&z'Q/rRrѱFAjK $VJ!C  ZYK@ҹ(WܑR1dkq7$Qedj017I슣 wQ.sdKΰ/wuCfp:!&[|W>I3F;iږ?&?hk[e!sGvÐ*!u9Iמ$V&9Q<0ZG@#kHƑXShnu֨tRLj*S1L6dn0JYS&wڻM6Yr^EIP V5+d i^*ɭYƈ{ 2gz2 4ؘw 3f::[~\{~` [O ہ֘1(:Fcbc`,ǘ)?Gj|>z-eтj*3n<]cI!)xG2eT˚.j]аܑI`M<]1&i'LGnt^;ߔmCd(CWZ#3gi(,l:|AGW7vVcfZ@Oy reʄ~4hРA 4hР*aO,buڊRV )dW1#Ċ2Tt.GDYAFImԬBT 4XD!9㌣,Vx*Eoź3+ZB$p^3gBt":! Z7bQ15XA%EQˊJ厱2ƈbդs i$oSlsa=*Fy1a]! ! esK,6Fr~+U^Ģ$Sg 1&Ѫ-%CӷZB|;3]1$͉.b@hDCR6B 'hNyBL`;RD[T,وB |<1-Q+$V]TuZ+* ơU:PIsh\rAsBDlu1UB f8 +Tab(kU-03 1dދ:\7=TW %,[iFVb7ĸ-LU[H cЅPbg9]M+ZX-: XCG%毆$dDjJBbQfePR1Jb,j *'DMU#)!vyL'EΊ@CR5XgYO&0%ըtIFmʅĉAQCRdN22?&URttjiR2 +g4!82XJ"dLVYnNsTcM`151='Q!S&Q9Ca#f;ijI6[k'y4N|=IOT.Bs*tmue0P54zL`b{5FOX\@ǒFt-A9PkiiV+jq 6.EOvz-f_8_N1-K2~HQLpǒS0g:gvvlųt2ϟM 1oZkقUR$.ARSe,8oLI#Rv [eZN̈IvxH1ݴeMhPCmSRU(lGQ&Q%cET`Zԟ୨1JH- `lT'Dg![R%s257^3B;/ ᧄU9PQJ::Ebdh Q*%W9Tv99b٭LJ!~Kl|Q\y&H(8Ro4( v)KR]l ,5 ,)I|$D^PuauF!Y58"EiȪֵhSm $n̆5&RΐzV ~!LM8% L b'NWiԈ& HVTDDh*D0輨vʹҦ&i2 "OHCSy( 6ݿ80@Qxڕ1 % 1_HphQUb[*PVy'%A\xm sc% Z $z&D3X_{rJKLJPSZo-LFڔ_LX!ȵu~0#9hTjd6PbYMޙ\)Q QLrX+k]): :zϪWH;%9gtmR϶:>C)p<$T+D#5nrVԿksn+2$N HJӇvPRu!M&<-R϶+B2@4By^ 1 fI $ʀǮEj<' kG&yDHF ת^krvpB dL`u0iY$2XBZQ*j4e:YRUDM˽gg$ 6XCPnwH*@s^d0 Am)"XDd &j5]b/HQ0$X֧[hIU)!٢yg#ε_TFǸ̯V&Yr@ 6E?;{cl7&nFUCYR#}f0PQVQv{[ Z61oU⬸ZM VώsŘAٔJEKXIB2Q)G,1E* -%-Zj)@ A%8Oˑ ! UكQ+a ZٴZA7$WTb2 9'Y͉tvSS;f<꺪 x\'Qs.\v6g(#/Axq*F!vU(yS8UubҥB>$i)J^%,Uk֭w{OUob &R+hGR)*8SxFqRDUm*d [Fi7~JBD"<# dv{92(:>Jaki =IJbJJ[V K-Q<&%R*aϩ^TƍGi4$g2HFU 'QR@UC[F,pbk67URpf¸,:%URTJnm-X&%%ϗ!A-swUE!jF${ PQ5L%V䮅%&n+KB{sr;KU RXB5hH;lҮ*QNGUjpHI⋍sF8U?ꦍl}\Ek^("m93Hڤ͢QzU}-PT _2:Ql لsuٙ;H!$6=scG 6,ޅ#bˍ 4hРACӶ 4hРAOXk7y =&h<̝# 4XObx 4h`k6 >Ix{aʔ۷ l5 c K-ػҠA 4h`=bUGE^wA 8]\+7ޝ 4Ǝ'_u =&0ob@bΟ]_ǟc.Z4=׈o`k{L(9n>^U:o]rYC^<:}o'b0~㎽u;T#Uau| Ol=4{?*Z[vt8uߎpA0s5"f1G Ӷ؜f[oK&3—wncV!~[eg~y[EnRצ#KZI82~\ޕ#`ϧƆ׈Mo`Sz.]r<.< }brWXk\k8s17,O4y&:n6o۷NܮIzJb}#-6F861>;y$yqtT U5o︅?Z?~>zt7{{wq/~r*vrɽ7{o m>uǷ.*8<^9g_{{W^ { FUWOo3V̒%\rK!wb؇ GLg`U*#׹>-z : M B_~9컯lw՟;w }a/o_ 8~{ſ~x_N\|=|?ն897RFpVzۯ-8mV̸o:v#Vkm~}۹ )6lu5ݵEb 뼝c^yd}O{'s/Z|*ɯ; XݣFqipUWcp_'5'ʙg:BXq:8ͧ?Anox[O~Iǯg8sg3g?ǫ4v^T}H ]S&Mz?[M6mjxso ج+гy^]}cC[}`6.*W:/{5p{nps9r_ˑ~-fY]lnkz߾{#F}ӟNYZ-3˯~cw-55jϮ?Mg}3|Aqmާc|䊯u>buC}_y{Ɖsn IDAT+cO~Ǐeێ^`ͶȽv-;7v a<ޚֈ˭Ώq z?#꟯;Ƽ4xya}o|].2{ }&Y^{vs,MoGkT'|x.w};l-3}vϼ3g=fܹvA|CYed2'Nq)y':;;{R8$ʲ\/Ǻk%q뿎sB(_pʟ&|iocx/_ƷsDgg'GRoj$g';oӟ9 x{f֬~G3f|/ׯrۇ}<m9<\yIoPP@{|i;94,\I87˾)o7z ;W3{\`xcy7t2/V!/9ϟ2;WPڿ˾r>=cOͬٳ9Wr%K?^7}9/2RJHMyܯ,Kj?z5x~[r27ͼ``SzO.l:3OC% M< 6q=7>3伋.^rZxO>gK<5 #Yg鳯yo}}_8|az-޳ԩ+ե ߺBfx<# {YWa oLh65؝FЧ|gϟ3m <>?zodќw1fhO>5BE+a(.? FٖK{i1װ6; 8mdS35vraya\zy|s>zam{AM?:Jjsf}[Q_L]9swiq5j86oʹ_˾wNKup|#s|1)ZÏ v'Q/?zx>ײ{rmwnfGD)zs4-] | v}]{-̛({A]wn]pǏw𵯭=HnjcI'gDUwZ'|g;WB8gWR5X˲vYdtUx痿<$6b .?>/9E&w7|z&s'fp7{pѢ<ùS9KVѱ3v,!Ě\[ u\_ggh%9 M7sqnuySs/~q~]{ϤkxN;r%ض?Gq>1~9L8j~޶R^)+ohD˭(ccq #Y9p)o}^6z{Gt*xLIJe;Hcpwuȧ ׳F8㴷>{N⩧%?G7vCy30jTwL}ڳ{=ϸu~Xd)y%|S˾ >:go{sXkYt o8՜׆`0b(w.%t?˿0~p:ceN|Y?^̇Y7yyFԺ|Lⅴ^6q z}Oض_;<'O}O|xopyq'rWZ/=ΎN~ӟv{UU`lيmk-Q@{mcqAH]/)jN[b9_fn{1Hfllθq#;>~knj׽N,W^ o/|uzxd?G}O, }k'$Λ~tJ?[2`-6/CwM%9 vڑn#?_z ;n?wwmM%?tvѸ}uzzxѾǿn#?lHjy^]:<ԿkN?︅~>YqJ[zo]^n/~ e:W_mފ˾|>~7Wtq+m?/>`>gqU#2^6˵yV|ygxԧy\9d/Oy?aV?zw tϏT=?iEf?=z|KqPwv΃E7︬>1G 6~8% x<晜{Ђ!c(-;c["y}7^sx)~~?cmޏzƎ+nïy[wx*)^%矻;tό:6;yȯzÏ}5&k9lՏϯ1cFy&~߿ߌ歰oTWZn{/@;lq:)3#18r& wC`o9s>;/ǟKz"զ׳ `xFoe5ꕛak)fս()Z`:x*n,vk?v}ng1'pRQ* \{ /a\vor~=VPֈ~1i„Gݐ|G( pA t<̱6>?{9h:"Emxr/|k^5C%Jaz9Kwrq]SDܲC6}f;d{v/Mg캴ܲJ픾g%oŌky9~۾?"޳&w}⭻,eb 8ք^c6>S}iጿČZg{/~?sɧqų?=1cF-o^w~xי+ۄE3m{yYμ-gƍ|ild9Ӝկ8%Krٗgku,;/7ٔɫ&N{~tn֕~><Կ,88/ weAßW=xn6: /Zi{Å1McSO6W(xNo~v{3z*s''p=w=w򻟬ѯ{yͱp;\a{C]/'?7>3H? ?\r)VN8>*Bأ9>I,Y 6ʴ-6;Zg[ocIL|*{xY0{saɒC^뼭5a8k~ۖǟxwy_ee`:nt%,N淸M3p& v} )l?_?Cq}|liwL_MSf~k7fFey0Hn,-<΋anͶ}Mi&-CKw/XcM}VY4`i\K7' p߆Iz7/X;\yxq'2gM'$>Onܗ{TpٝB[ld!{o2D! Q@A72d/Q6Wٴ &дiS&srG}yORǷhҘ5xyR5mXSgϒ+WY\꟡lj(r.]g#3}2YJ;KY7}?YsLodfJ} 5!!$myYO磭78+]oZfƌAM۶s1Z7ohҾ ʾy}˖tɒL57j]0y״m4DzWRko~W{yfGKܤٵ3'm9>|}=Mg1vmSZk7oQH= [Z|d;qs/hŪÒI~Xrrg\rkӦE3ouz>9Řo}7/?),YҿҏtמÏl1ʨeo0T$ >>jB{˦oGr ܅K$$&booL&338,,W#ƥ6Ȫm6iP{p' x4ͫT3Se\181֙g208`py1jBpeVa3y`ZE01[~xQ"PG,NQx)p}0\ӡH6tr@.ۯR.und2_ "*:M۶h ԫ:mdQ05QCKB pԣg*r)\MH-瞨9j<?Mm7sQ j\ ]_l-j޲dSdDieDi$PZ_WܷÙ ĥgŒO>M?]Ttbړq;JEV2gsU=bةQ`w0ڳ+Ȏ%ӞڡR* OE0rx=DVYrY=+[Ǝw~9ɉoSYnmMvmyT>r33l6z|1~=|H97ajX z03K3--kfvPpV 2GѺ_0Iɒpy8))㶽Z5:u}{(_>ITBb9H& [2{ױGF_ysS6Y$B.uE)b02?\ޭ;wQfذjn@YJQlz@"(U"ɫT*ש̟,ZLȨ("x7I^nZf :rsuI9r ܻg[0rwC/۰7: Kܤ^2n2K11/[VT$%HNN\a KW ~? oߺZiy]?,n]ustaZJt8G%^_~[5ULJ?^%7оp $.]_;c_&';jC^8 ;hȖ{ՈC>}ɬ Y#/ qu< c7?HRkGRLm )[L=sOFaޔ{}c;qVs9OQ:RGm[6(WWWWr9̷M2GRY:hR\yeˠp BBHLLQ([ytWNC~/wM:`|%!1SgQBy篣LjG.cogG\qz4)u.~`0΃|i?Lx߻r"gKj72ƶ7=`%YglPG!qyFҡE= _7L$ay[4cKf>mr]tOc(`kcPk%{[vbWڴҡz|d>8meQq܍VҳD)2Tru n8/Rh͋4̀E7%ؤЯt,(= IDAT4:Wfxh.G;cVBl\7n68G+X0CgQ<ƭr~?)))PڹȨhH6ZG}Q0t`tRσqs~Е^^̘;>>8Rәh|pkˮ&")id[APEd! Tcmu!#RIF;^^U( yU5 AeNt/͞v4ىHW>ez(GbuHTov<,|S 09OKqt-OydVfǚ_^Lw3akcKBB+#; 5kpfigݼř][.Z4| >ж1B[~1<~uo5j!‹nhD덌2ji,qp ƍǎQHrO##6U.3',Y\`4GHJi'  DgԼ5}^{]OF߇Yj GC<}/uGEGےd9y1}x:\xu+&.RE~or:eM $oo903QZo 03?~FId.wKFk嬸iLhIu17΃R`O|lC]$jKHAƴ`vɋRd-];4|n"UF+)Q=2`ߟm8qt; Ŋd_Њߞc'O݌Y2H^T9+DӋ~`LK4:=ufs`)]$[w0|NJy*N='{8?O#@)aw/Z]jmM"x7m[6gcw7{[;f {r6ËdQrHa,J,-cp!᠀;x#*.77Jʗ̧467{]59qn.]tTNc^V &(KP0IgѦx@%|`{N]2Ͽ{Z.&=7>SQ€Ҁ_6@p+Tr |$9a[C˘Qxئp4Ćg\Цf//:}b 7>9FRUPVKPyEF?Y9BNxj֯Bp&O#({Zi1OѺo#/Ը1GH77m 'N%1__7Y}Kl6Xy+SK}bm̪2S$C2OC!__h/ ϟgf*U3^,ɉx;Ӥa}6]{q: Ì#kg=yبB$<'X}/jӼx ؖ%q2F+`O]^[sr?U $MmtIR;z?:MM o!A*?'=M ѷDj͏ѬoX{`l^i;ѓ'RVc0Kyv^/}~?ȭG!vMw󌩔yծɊ\A"O,? 9Br 7nݞ5qU|ơwݒ{lwZ"fL'W s/5eqP |5[iVnWlz;U9ZJL-J`᲌Y jgK{70 [1f0>ލ2JҴa"~23Ԭ^ PDqthg,3q ukդgwqv6M?2d З6Xe`6-QTIԨN^\嬵}=vfafb,?7o߁μ5aϫ󽻛+˾,gꍛHHLSb֪MU>zT [[- wW7^-~P>lY͛NW&!I8w=p  au+;7lYa攉+[6-1e/'UVKs\ˬO>؆sPA>da$>Q٣Dϧsxit))uUxjY!?ޏKB!p#ntוӮshN:q& ӲIcʔ*#Ѹ~=yym*'zi̋ c_ZAKVrr2'Ϝ~p0mhqtp`#BÞX$9TJ%+B)U8-4ZJf|srHK ~ϏXcXG (L>n=8d%'_x#$xӜx gj+Ѡn=ؑg5H&Ø4% ͖R*} 1]A4CI#юG}$6欄;UзL@"s 1l (Z6SD> Zgj̯[:@^sw֐΍"BCy)oNF )CIxɹb5'?0@ZsKUZjVbڟ1EV9g/`|~T(9xNG|NWA2y?NGȽ~lVo0un>ӛEqe|ԗ$cIIPk 0&NL{}6>錁9s09N8PrZE|*Ǜ&.c}S͘FW1`;i-6Tƀ' r`X..u|%ke8/ <ܸaZT7,]jI>._I >ӛFTpf2ߔmq9['N'; f~7 g''ndLoJ뉓b <{sUBdmٱOۛKWXrYiڰ>Cøx%=4[v2~d x>vt_O`}ԋ_",<;vH5ۇсpLԉgذe[E޽vi޸!~ ZC]:L] /]rھ;V&t\Xz=4z{5eϋ}VKxDC~RZ瘘X2oź% ILJL>lz{0ob6)ߌsu8#}!xvƭP/ aOx*c^<0>͘ńdʸ1\y1c9Z^^-# f\磿f뚕/+VxRYk{HY(NGwߌa_tk߽͜ $I_$y sb3բ ?EYh)ȑEeIDw2mj@~Q͗h"G Ȣ-YղsRz [)BiƳX\!G&wgpSt)X|z)/=gQt/>&ݝX^H:m&+_! 7< 6T9 558ȑi-uԫgMb:3}g8FV׮e ͜F5,+vm\s4}AQ(.Ugr?G~ݛ$Qi)8SH~J@!ޥbVӘgq^?!6tޗqV%ۇ"nN+'zt+Iֺ^^>gF{AI\\<=iJЁiײMڿ|QbE}R~]d2_7| Y5Y^ߺNjW _Ȕ}B;]Cs,(d8e{7<6\##yA^Q_S@ ҭ1T; ׂƠn9Z1_fݿ?S `J=d{S?Y3h me>׮s-L:ljM7:T2+G AHm1stY?@^>ov:L&jYeWcs>),oڵL?Aym6*c"dZQHvYލLгV [ tIQ$? 6;D›y8A|#JASj5:e]NVrI<阷p AP*l I<ޞ*ҮU .ycArC[zu $Vi^i?U#V;<[qc[^Ƨ2k)))|=a2tYɫeI?狩rծOӿVm0dׄGDx}RYR߳.&k|MiW~`0pp_8KQR5$wNj?G)Kc^)A0> dG~xSpa<%@Ax<=*˹'jf]pꕭM.Tj_Xu@7# "Ep׽i a DX `AAA~"       ѻ@1PA^5OgAAAAA;bA-v{3rRE_Dh?AAAAA!yЂT$Fy bZmeEӐܳr3U=*6|}K&6׼2wuΑ'طxIdrjwLz:sRe&g 2z]2YWj=,S"; $_2rBqjB8kVdTn 7o?T6vD=yȍS{8-F(KQb}g:\\1`2|JT覹NӏPlXMg"ޕ>؎fՋKGOY\KӢa5q;Xj#&Q}]Z͇* Td)h9|<#JeǁSlEN+;bٶ]7g/ ~K>_gG~}{!Oر$?xk~MW     UNN'>:i/kGvuРd&Tn~CѦ/wԶo;'o_ׯ3b](U9#]`IJ TlA둝BeE*SR\`jt5MWnD?S<*; ))y)\-]5ƧxJ6v.Wc[\Bl$T(x\^jˏptūYgZdXiᖗV=NӨ=u_~}gOFEO&}(Qć& K7?oHcgg˞5߳h.~^Q)rĻh{66Gl<79pi.ɳV稾 v>T8u 2*NcO:w{!:&rwe( k.NԪR;3O]/e?Ȏ72*'/n;QЭ]}lykgo9?N}'~@cL_+&sn0-uqly-_~ʪ] Ff麽4%gRx+uRoRYcAAAAATٽ+9w%ؕmdm"[g !<~>B˯*{*5@6NÛטJ">:;'7d2$ D< q=ӨJn߂@A(Y u;N)V|C_4#1>w",][[N\A#d]g~;k:5n`8_мdiKJۻҵ[!W(s4XZŃ-61G$Kl*2Cś|=:6_~.=ZH!Ӊ2wGĎ9nR1-kK ܹ]h^%'r[ե|颔o40z>ǖ^r=ueUaOOL"1)9ރP P#!QݠRR |1# :T%2:m_ZncыjhѰ*Idu&mH!X~on~@ZhRYpw&\hz@J+qb~SXj;Sv6'?߀%^ZEJ=*Ő>f2hضD+u&+AAAAAAvJCM0{Wh塁xt` B Ѳ(U6\?6Iqx /]{ovu%g|PB]2Wm+BF-drݿY3}\oz1~jI$'pP9M0drX (Q I9%?mJvfm'$ܕ끦SG٨RשV+M_KDQ\ pΘ&9%@r3\&'=VV@)@TzxzH]}3GLOKgUNC\mp5sLctn>lM>jP(|d,JEbdj5 /"(8e=&+.nm}L}7`kRFh۟K<^MzcM?Okm!l0pf1]Q$/^zhX*w]4hN\q-3vZ<ݍ)w~*śz\     o/\]b[̤Lڼ4}Ӥ/}/!:-HώK )c\BR)S-e˯4u:a1)!.cڼvN.ӕI)ͪ\.c.|;q{g(ruqidl&bn޳֬YRkۺG}Hrqw`02{a&u"c͟4*c{4mg/X4s8GN]aWsYΑQ^ 6ߝ_9/u`.R!ճ/`~&     ˪s[ӓ0 d2WnգPt5oga8zqFRL^ŐBd"Âq*@yz?qtL֜۷Θ5_2҈K6ri! ;[',ݹh۬&n.v$mr2IC. ף%M|'OxCLl|r9mG#Xq?Tp|\L.R*9x>T(2ycU,ZjnJAAAAA[sau99~m1pAdѲ⣟r! iVLYZϷӳP)r!5ZJ^ ldo0ku;zlh?'jˀY1>}8;-.Yˉ磍.y^58TП(U srw/{p)ߎዹ,C9Kכ];ܴvN8{2/wa]fsj$$$rhjV.M:Y4s8.f/oΣܼ-K&Тa5ʕ.BX(lm9SWx滴nR2%չ1nNo@1?oya|_~yqvjy#pwP;9315E} Km?{[S}m*L?kjRkStύB^r^٪P( O7g4N'G Mت.eKQ݊ԙaOu7vZTSܧ(Hf5r|1~ؚrкI 6.}r{'>     B*w^cVeklN_/!EOu'kpp1JPZSkmr,׸Rzsl4D>@ecozޕl'hbP9$sn*8YQ|*w k xz\8Hwbu<ʉmS9;Ggowl-R o|W !6Glf,]ovVn-nE&s,IMkR7sl2ҋUqL׏#11ewHI1d$t 1Ƒw0oh>I=K!_/~7'G{ <}fوqBv|N]ʜE[-G*eعj #uf44nCjT[vcҗޗ~Vdq˔MTj:,x*Ŵypw!d2=ACmPT/a aƯ0CfM``s O`M)\ ??|w7 !O9{}$>!vZTw=by֝qz􈳗nTfy̐O3~>ymnįn~Ra      9z2bYB-m|׽)oG^ڳ\)2B!\"e .ߦd=o[iB2WAAAAA dBUml5G *Wg'El\ޜ#/}ܭtoN?g~}ERR2ѿÞQtaʗ.¢5{η]dRoY6     S;eW?p|,%S#gu׽I9~8 x<ގZUK3ˏx+Sf~!IAAAAA2`?I})=P<]6/kUCtL

SChtao}^z^xl/Loae4v[%7nr[TU+pI>Nn-ܺq_fs0k+lyo{Kߣ[O`ow^Pi'ﴉ1Уqجubp@&CӦ!ix(B,XY]0u"͎b7MJW,.-Ʊ'ę\ ]ιf8gEEJDC!2 ȇ}7(k(h[Ln_g=;o_Ͼ UYG78AVhvﱽZ)E?E;3Cf Fd<ϳq0!H>oQyFEǐs5m뇏2MOv`ssc>3m\Q0 ש˒ ԙ!|+]A޺sOOɹU5N!C|a n6\W21N|$g$ϒ\CsZ݄ (Fxj<Ĉb1 d +DT_T(.~Fi-C;wDgV㏑ev[YMt<Ɍ!a<3C=o_ǟxk vAYUܸ Xv\!))r9[vX[[9OECyT%ZT96ϰ=LCǎMrE_#?{zE6W\Fi*lf@Ќݽ!+-ɄnoƓvpPu \|__%(Ï`oggy7 ?Q>oz׾vwS bT࣏h\ŗ^icwۧ yQ89LVs{QN8A<ԿQvшL[:k,b_"P)Ls^\~^xxWi=0!:(btK4G7FA_vwsO1Txx'o19|xUV*Ϝ;}vv_5Ǎ7斢.Dp ++K,,h02R75DzMbai u]/-RtZL9겑LS`ħׯq]__W9wo޽{ ,-v0JS6.G26ȑnkXvbCiZyi;ƑnMwiᤔfppd2^Op̄Wn1jν{[{\||^fRTw1NM'ҦG&M%Q#eՐgYT#up)/G+ H5ȿ% sIYBq&bnS6$1] 9Z+dJ,{AdBV+ag5x^/MHhRFޣUs*W IDAT,4 $|B ϡ4S{ꬖH8o2cR5߈ :ϩY?|5Ԕʦ XTam4dBS;'Kavb0p1B ܸbIJ߼G9|(1vrڧ?X^)QiwvP .UU% D/C(MkʦDܤاih2'DZER:O孟L>0LXgye{,--Qe2<рu1;{TUa{Y[]_blrNX˸,ZҞmnݺ{^O?HU5]LXu( ɝm5ݮŕ%;ˣOs AS  ˬm{Lj ZS9sg&e5gLʒh_U`\V:k1 6=݇ߋJ%t^2m>|NpK~k`Ç7 Y[_gQ5x!ßOrܾscn[p6x<ֽ-x En޸{祗^@j2Of`O?ɡG\p24B*0F`;{(N^~nC pܹu$zUL3neS}@/k>~5}>#`Tnx / EKxN9spmvx_i)Kt;mjSd݃}:6XW1(w1,k8yR|ԍ9r(Zp47ࠩ{(ٹ[?}h"./BxPÉc'$x=Ca<Z1yAcem=f@[ 7wni(pu,qI4!h`YʲyE ?bMr`x@(3zR`Z.y׼|D@˄wm4Wn,7`P8icOt]Flμ5B G[S1ɮ7mƕe$4tjX0RYKSS->⣟ F!g>\#TiBKw@&wCwe&f,mne8NcRՕyk^׼5yk^/RK֣֊L:7cZ6 +?jbnS.Y*#-C ӒcDNEq bZ&͘gaJĜRfKh 5 裸53ҠE6Jԅ+M}m3p } ыZ8qMP<KkE"GrOZ"ࠝš!)҉d(qslIJl}Y "v=EFAJ3BܿC%Us (5!j @y-ׁsAAQUY|l Y&ی :RܾMۢ(rơCa-#CĪ/$yG!_ZKQXDWcU]!&GV ߩpdB+-&j "EQ2o*N1J{ERY?ITzAӟ:& җiHC9G_#޺R__k9s\+K,{8zlD2:=#Gs&?[ׯL4Q[lݛ`{={YZdꚺⳌcM6dJ4s;tm{}~mz(c'k1в=jZEdR!odyouUȊ,q ݯ1R7 YQ6%Y&U%حV'~W*:(N/L>e\ GCRB\>`_9Wu!iP|㰹PM3(x^x'O`KK43j*֒&szOTOdEM{2@{֭;z&2A%[uՠr8GSP.^u;B2g)|FYn?,/w.^;wnqOpǽ;up`w#ǎ&cg`$ADx훷9~F+BXp_7 =V]cC t=N>C{;;|v6NgeuOnܦ_bsc/_ǏoG\.hRr1nihmƣIy9'5y_J5a8r B #9 \tV"*ˤtP{R/.!yN&hu#D2]PAϦS h;u/k- }O5u,)_0Y+]>9(k4F)4IUEQD,!o:?w>Iܙ׼5yk^׼5yG+xc2mp)֨.5DAOy6Ad>H"y* *x/j (T%#v$ʯM׈c+\p ۩2SWDŒ JL/F<S7"T60߈]:Md.7(!v7V Q"&тkn\#.YPWkĵeB\sh3; '3N}IOl*"4$c;4VmNZ+:]Ij{$9*b̦N<5dԝlR^nQNJU &cJWcNsg,4ymBrhGA&yNCՔ&:D8shc\Gӌd9i@Ј΅$0̏AkWE`0$R$Z }7)1p׼;ܼ)?ǎ4uwrt@SW, ,jC:Y^{oKֲ_bgo#2ZǏS7 ZätцhHYyf yC~c&2 `4[N;KSІow.5G:˱QO> E@,uL;\xF2p^=8?n(5GB b 0F?+++)3.&T/> miQ٥\mʱVGWxgqΥ};kFv;'wm6|  8w?{G77Vi huZ̐9{ܽSO>?>쌼Y={NMNщ> D0Ȭ2Dfb9L{iF+t!W_Oyg F=B֛?r=COC" \G{>z HwaeSI_K Vh 8נA,ir//?+ FB=?ZnߺEjq47ork:{c/jmW_}B_MܽsڝUyV[  5_:{5b0֛oӔcy]؅#y`meKマQN'%tȲ`4pM1e3g׿A"6L&,x0EY]31cey8i<~{?5wnG:HS*HȆ@{1͵-nݢ׼+/'O:M*Y L|Ɛi>e(-VodFO@iͤlAcO4.Zf"0kt~a*9yhʠUmȫRH"\rN\>g{߳F6Fz =+_֜>dyF~Վ,4c<v$8V6\oPtrNyyk^׼5yk^W,UDJ v/bheZѤ~X1&!LS ,QBI ,)*А>Bf24ΉX}WBZDĶY.cggiC+qo9fXӴMA$ ÌvUyZƹd& (M'B Oא)5-ackK=u$G$4"2+n*y25Ex4",_ ^׮gSY5"n;2CZ0ync]܍e|)~;h&"KpO99`1륜L*uuZˆV3@-8ba4喼hsp0J7WccşmAD7;O^nutѸ"ϭF9<.Uиr2КUej&puQ+:%>vbU5 }?lɳ.*u2;"ͩ(gK>񄲪LJƣ!-.!k1"N1e;amm OJˊ@I Vr F!r\OiuhY|Sxɧwv˗c2.Ӹ \vK;)kU%H]Vԉ"68ykt4yJ'D)QqQ=2̸5gÛ" YXiQ7*$6.(1/|uqoͿ;"MSqg|W\BR@S3~rܹqϿ<.~Ǘ?䉣,..[.+<#|tVy :~UFRLLZhjTΑ?@o'. ! . J@YaX[]a}uMH 'tɪM{TSJ-CS>D\ɍAkVHO.1Cpd`-4i U5kPk JYi&Qqla2;;.zᛜ.}dM!9gQ(g9/h#7C'+ANqQG6ŭu@҅hWa ; Nl7%r|$'9"5!hB7AbXB3uVEOרiA)h7Q4!K4UUφ˦!vp4F)E[t_{V}kS5Եfh#%Do|Rp7zsXk{N)\jBHqqPnCT#VIUqFi,5c +Z Ʊ+M)SzZ>\r'?wߡ.K}Q^+E U]! U\|]~={`?"bj"·lPZS.+/B^|GQɱ^yd,AKMӀWj2Xq`]P-}#Ԛ/Y=twn u%Bhkq۪=U*MSjQJ*4dcg=U]ሔUC_ڌx"V ia`<&Y@-ni-~!b,W]=tJIfr&|GzGwR7%Ft?v{%>9bĝ1_tQS/;kN¦R%fU/Dn32+i7"ti^/%2kh_yI@po{W>ؑC|z#[WrSΝ;Y\^˿59v'|/eFAw Lcʪbaqiꙓzftֳ#,bt:!'~J㽋Shy=&eR⺈ IDAT=X<Ĺp)<.(?'ꉔaY 2X\+c˯~"/<ɏ N9Dž#C7qu`wwK>t6[!Gez cFaOG1"JG[0㚊d{(㺑|%7V9})6Z9n"ilI2Edi:&4;MJuL/He)ӵXs )9'yOF,JM) v->r?˭OK_8GQ,-/'512Z 4kwą:׼5yk^׼5pi ID *@+)S~d2/4ġ*{dBYV=|tr3O!= z;3{ﰷE~^WܕN-1X;o /pM޹nE$#;jDp ." h1XZ^%s*XX]FC*'Mjyspu04Y"Lglmo1OvzsmQH+z hcG y8Ud4` }&!++kn2_c5;{,-Ag,E dqy=vȌ!ZA&#& 6oSZc%6ȰJBlHDB%?B0ƎMdo{T4eI*Gr:{|r*<|7o=8?W1'4/~ >Zi6W_G?!_y/8D@Lx}1~,* M1P.feFSp7eC_ 3g@Q{;Xc9vդ$t nqGOʥ.|; GOakk̙Q59zxBU7!,qα 18\Ӑyä_Wq$ڄ\*a2 \~Y0}5g+<ܳ,.S6 >g7Eyg+>})w%Z|r$ $ٽ,WZwbjЖMȨ Nbһ1ufOi;:}#Mӈ<TUw`wc(^bo{9s,۬3N3'pdY+W>?xU^ClX[֍NNa5VF䴊s6/^r̳OGt;Y{‹YZG]ۡ,'kvZܽuÇ ,+hɓ'woN 1P)$rm޾6y+ǻ͍C\pA":KyP+|wn(Z[hFq5ZK 6fXt45ڕ`^bT-6xN:᳦ y9e$O3 ŵ@6'ݦtv{/++2 bH;ȴuOY`2`?}W,#2K,#` eG2l^׼5yk^׼Sf*ň^QAGJ*6MpiOiC?'( Γip"XLLh<6FҍRMŽi~^ lF2| KLq E" "lI\}-1U@cҧN3"k#TLLŴU9iOKTy.ߌ>&891 =jDXR4,5׭Ap DKߓ(yF, Sw&E? ?W9 +eES7dyFeLJ976UjxdY\҄Z%4jh -C\@&$D^,ߗJk!{@ lp<5moc%?s*De9**k dC+Kj9~JQ_!T6X5B-3KZV;Gkh %4ִ\Sd2ass;wOJ2g['@z?Vʌ1M#A~^f _dt0+~>β7@KKKUE]V)sY>zӧNSF|eپw,9ukkTM{>|t|_dq-C>}%1YF3)5`bLjjuE%@YZ~5USQ;G^Y9KKri|޳ХJ1h2FCvȊ.Xd,B`xxtG"5J(ZEnPDVTH%ce7`,Ҧ&0_ZOPn5{(%߯e4^Ya}[1wod967(k'1yIn8rP 1Ր~o,o8{=vw~'v7 ܄  @4֌5xj4*WٯS) .W\*i$IHQL%@\:^OC5#L~8\tؽYMeE]Y#G~?9v8F)qHޘ\yuGI7`BL v&n}F3@bյyI}DthBc4[[|Ͼ:䓏QFCյ wyg o-V,sZE۷RZtچ ,~.%666( =%2sM^uYcb<sUN>k~r VIYC 8KsgcL'p5ak5t:e}eGیIYB yNxSgN|KTXanȓ{IZ4RFyH6#iRxRUKnvq7f 7ؿw=\t l&t:_ڕl ":DYUm)Q,{m4ٔRY%y<L~KWXX3J(eo8PW'J(xUzM69TTduy'^ڃ6VƘ*ǙORب  թ+`srfD-ŐB>IUCdF=ךL @mTR}Ťre4jA:ys>\"b2 TTTJޭR`55H,JcR&b9vքl:0b6)#WNG@y_iz1:9o!sg  T61ŪC:7.e+ѥKDZsC* e9<'O3uŽgf%ԯk1{wד=fg8*mA(=%!ggvmGJ6W+#9-UY8YXla]yd<36Ƙɤb߾Un\wt`Cvcu'N_ΟkX`XZZt:vbWlonO+W:{lߥSY%r2|󟧪Ӫbc ~2e4u;;tzYAU 'W._恃p$jKoG;iuژ,NENSt8/1N)Z-"ګoP_0Jo|>sIYR1o(&TH&˜yTuM&dZ⃀C-dm&R *͓ LY+KXC2LD뙐J[m<UzNxYe%d|ܸv,S i8SN'2ǀ9zBK*>PmEjzN%;5``!ɪe BLv)'LZH$1/sdR4(;Xk!e餟M1!W~-UCn6:Mњ<6\bquت!-" "!ԋbTזs'?t#λe,+Gmd^׼5yk^׼*x,#6XFkH"9ޡ 3eHλ$Ie%HMPV41$RrRFRJ&dKW.Hb眨'[JdYT,}Rb -gBӛ\`Lu-yb+/tLRGdBgiIU\';Mk !6|+PQl?ҤodgMtKF%~*!0 -BY"uh#R-kXVI䘬E4؝w]43twLJ`DPң..&EkN(myj0 ` \<γ`0bR;lbJ嚛4 x-O(*:\#ڊUv #[})7Yޛ"Vƣ1E'%c}y[6JZ߸U8qY~Mw?p8I3X쳵|D⫊ϿEu?/]dqa@y66Ȭ۸:$Nu}x{rNr2%DOFkȲ O|Bݡ=ھ5x7Żx%>1h4,x79IKIېHl=1k?J*P0?i`Yk lX:'y>{O]{~/x z.ٳG?#ܾssxI+Ӻmr^MUM̤RW`X;{1լؒDpK<@NqsQ#\r9^&[,,rõ7uZ2Gԕc:<`2Cf3edRQg8|8|L]EY3'Yn^/Ξeu~[7obNn wwv[T.P{HēgVļ9v(ǎʕkD'c.~[nRW.\ޤPxkQJ,CfYGVN#9"쇈;. ׼-~g%֘0e,YCqhjLb&#u)2iNP#/a1=)Socu%h,w1 Ubf]\e7`\kZ4#'wٹNiuXw@&@IŒל_CSJq;ʳ׼:5yk^߳x} ;KG3d5&mi$5Nv>H>2:)lfRq@Y 6,nZ_cԉDi ZU&)cj,9C4gRs1x=m5;drVٓLBm 1`n)jg Ec4cRc#Tkf6ZKOA+#qS8fZ՜uw DX}N{QJ)",En+JƽvCQK;O P-,UUdV /Vi2)+#jtF+67kGߥߗM_Jrҵ Q&@fA"SO&Qv5hMϠ+w4v#uU߄ *xW c ;ܹsL#X X[]{;XkC8tvYXR%?RM9|pJcsk,Y΁}),N{75d<;}Yq-ν?Ϥ>k !!0%3$ d<]>EٜM!8bRgYx:jdgH5^C9)N'b[١dyAlf:i ^"TfhuL&%[,,SW^MZKDM]Wye #`иjLwqz4NqU,T~qڵB bYWΘhh{|^|LL+GY9ENKᔟh"8۷8v~ܹ}J[VVW(2\]CKHEf(5y'"(7=^{- _5G꠳%GOɇ9sq~?t BTUB`R{bb4Rϼ:Ewϱwr>ٻDQGzE?Q?8z8dxC+եEwt|VW>uUYۼ!0+7cZ-C~;ܸ~k5+1 *PbU,V k,Ƥ|HjQz\znpJ%ǀU|뺢I8ȣlmmo~s2 j7f|? b1  snݼ2-QZ*26Ɛ&(5Z.qu8@S!h-1PFcRQDXs+Y(js.]L#G3N1:ϱc׮2ަtN+VdO=M57o>ƍk׸rD-SorM=!ш[p:gΜ>NAiG iĹZPBx_ e.o}I, Zo"fʥgΜa2IAiQ@'Ҏ2wu6WwisΜ9ͅsɍfk>ǟ9ߔՐУX_g4PW5N,IN>ý-:E1YK7x㭳LEiejwJbrG,+ }6779|n{=|[o2J1̐Jkx@Yα@~Σ=*L&#:>q5]T"r 1X{rw;8 GW_c}EXVQN' z]2m۴zmbTTƨ];m܋ϳD׼կ{O@ht,jrTLLdd! 5%9JKno G 0!m4~Bxe5ݎ%bM:GqĔqtl>3!Z4@#FGkK&aֆͭ-Q#ɰKsQڵ:]ʪ-ػwk$Vu=Ϝ[t-Z(\[ ׼kh<[ Va탢*8loC-`tm.2MXkQ&Gk"j#q{ ߹׿jqAq Y[?]N]MȌFGQ&fPUt4s,b)YBSiXX賾NP奁"lmnrmze1r|ȴ2LսĪD6^n`2R&` 崢cv>l kUiZO'W r60]Ǎ[u % kpÔ^B#aiKLK$ NdV9Dyc@!*h|[-*mprXl#̍3tI)}OX#|ǞxLf:Q%$!\TCibNs ڑי;\|VVKcqyEzݻΟ?IG^xqmdڰDg~%oww v V7|w˟CyNyK/_CpҜʠtcj9j۷n"eQ[CdEΡi,.kkOx 24J+S!A\zjn)Q?7k@DSyqPAuH:#9()yX#Onia%"sp JFEUʄNpox'iTuM*|4.=FTeYbt+zO>Wߑ[Þ=+; Mۥ,G lm97Q dk!K`PɋK++| _51jn\pk=IwÇQycO?IW9z+W1釸vD+ˋܽ5ŇxRe2*FV E_|_Wcr2*V"E|׮^*+n޼M 6\x?p{!PkXͱr) b\gp4+d6K B.X]]U*|^?DJ҆4 zPbNF6ɒʼnJ(Z(tcEHVFj@1 Ĵm,bt:t9"I,]5PC:]vf396+*6*_$H;Tva\#M¶&67qRkP!P{}gkˋ,Z<0QCyk^׼5yk^Ri5(D|JJw_l*) 15Q@T D3Zn*D[1J&o"(딧ۨ~D|TAJ%eoRz6M[ik&JeLZFlUc<ܿ~*cJ&ԪfDBYe%R,>C`E1Ypd%kZܻcfMC'&|ص0.Lj yș__;|6ERJEQCPhC딿ybLR<  VQ O! T$d"hP^嚵6EkEIiv㬀#`e>c-6k{%v*Enp ! 4"/ MdyϼLk-v"w ՔDJeҾOv#DRQGɰV#*ix)ySfLhQA%V1NիKUw;w1XX`'F!<n Ob^NK|5yqVg8r(K+{Ċ5!!?xׯsMF[ԓ1~:jv(QYRց@D4Қ,Vo~{i&Q+t"$Qn Z(3C׎g}Zƣ#LJHKt`<ަٻr}G|}ܼq{w1Xqȃhw{3u}3WSρ)Zm޻K67!t:->v .;d3y|o@p %d=pOٺ=x<*jV?$WqG5~IVaggwr]B; :-HLDv,KLJ`q9[]lGEQl_9Ot:I\ƍ7L3%޼5_SJp" 3(0d) [7βiD(\mbƒVPbqvv`8'֯iJ΍ᦍpɍA.i&ݘ4_VaH݅@X_AL@fmlF5$ (> qC{-2S%{^׼5yk^׼PQ%[^:C'b줭)$gm ;货TJKs>)-*]J&e=ZPHuN#Q^D Lr緜sj+})(jKrcg8I7&4ff0-{40fz![,۲Dm)q%V-yN]U4%֭[ue4RnM(-SJkΫXs6Ӟ Szi(5,+)eI [ &g4nX1LM̅fF!&Rnދ2;OuZd1&OW1ߨYZHF#2%v:X[&vnABZKR(`!VY:>YCJiUd2$HU8aMbͻIyF< . kǠ\zb_}M*ibݷgabCk7wr{[ԓ1x:.y8/?+ٻ+ :ޔ` 찺z~:w/1o- 0BssCMrl2Uԩ9yv)(\+wmmP7cL b3bc}G͑'?9d[=b[[T]E`RXk*OK_#GVəI0, 7H\vpt(*ߺ¼yW_ZѸh/_dGxﹻ.AU5*!ߤ*y1E!d- t^!!D,^ `߿ċ+<أQ{;BtyX'cBsbMu^&ān X9I kڽu~lҭ 1,yɧxZ|]_ͣsև1yGRU%_\F/٭!+_mxX\^S.M+&à[*]$Y Ɠ1O?Ƞ?yLbtR9ɲ9p ĭ[7Yo+>ࢠ:&,JvJF,jl<9F;c6>~v&kcPO C m) ̙)b8^XˤEݮc]wrvj?CqǙ !u0S1t;9p N;}>eay?*ݲ #do2?z>r]qڗ^p 6EY1'Μ矧uI1%gY//\xL~'ӧy{wbقx/ wzWsN. wJ^vn;zb|'_YB;u <,I'#|Qq=}`+W3ՔeI&Rb"n"X‥yu`5ĉ:'kvb) GeYRUb=̪jlbھ~vmGUhZ4uehX㝥3?ǽ 8T$)%"X+chk`n 1Rƞ~_Qg"~̍+(㡓iw}w|=pnm/]=Μ>SqEŃe$=@TaE= g// ш_|NUB! X0MVZvWٻw/C_z҄$ VȢQ%u]ˀ [8}%ֿ-(Vkx~)5T^r_}%;Lo~"dakNNQO&ܼ}+e9U4OƷq^(Z$D/@aʙS2$Πi"ealn{ˋs\~M/`ԈÑ? +|׿ʫ/]}˟}?2l,./J^U2Rv^:q'P)9(kkd,~nhB8%b&']eč؄0OHAZZ_]AFH1AC'9rt?{FF}J =_\O?a07ŏ?)9InߺGٵ?5J_PI&pw.>;,/wFZKbl#&txS\x($)687~w8!~8t0s ʒ/R,15YZXbs=qZ0ES0?`nǙϱ{w7⧒SOI]Kj-뱹k{;v|t"׮\9K3R`"k 7_^gkmߥ4 xNt:*tK9bܼsty(xVe/ǜF& k I`OV6Fr$кƀNai3jsw|e)5HDvL-00PPNXOY7U j&xyMF2ӃYJ,zlVqwmŅ/rf5YjVլf5YjV&R<m8/*Y+U1er BB4Qek0 !`ln>v ĥSUQYgՂ8ySMhе1%qSm PilD'9Rh&VSeh,XKЬ_ 2ʪ(3N/ 4MMl!fVOIQw a JrVM1&Slp˵fvksR"Z綀ZR3cZLuD,}Hbo@H>5dBk_!'ӝ.9益IƓ,1̈́Zְ:U@Lud#ibsrSƨ5mDJn10wVrߝ߱:~SwٲhL Z|UЄ@n@[2 [ u6fئbMMSl EX/W c^G\ oG\+sll^O!Rx MhMt]^r`R$YukR,-)biS6<1f[ʲYKU޻"`>ȵW?aRhH vGQxkwq9spaN?epͻ/p8Wa[I)T?ڽuFݻ}W(.d0 uX`5C܄] ,1NԎnwlpEE#B0Ǹ0,t l8|Ǐ=`0yw˟ (}}RcXTIƈu^hM!`Nˏ^yu:XAn0Mxg?WMyƵi`gxix9t>߃fy^j%XS71V.&D3M痉!pa15S7c=liֳ1믽IN3b0$[ó_ =!jr4CEͽ50/}%VVvsᇘK@'x<$ŕ߿`@L_\wԙs߿s,š#1ֱ=f}}NC2xB? f}EB@t+3N[os9| JOY^eߡp`~b3Dqƣ11z>EQ7b|~k3멛Df5YS+KlnZPNiyNm~E)MԀvmԞ俵L:qv$ږLIꔌbC,;ɚ,Fv[!uI~Cf)HKnFF Al6 )3&Fnn٬f5YjVլf5YGeў&%:녴dSH Ϣ1-lEft{|y:5WA:g)QL;J^:oET)Imչƣ),%DQ[E=,`%:FjQ0P~\Őc'`w\ɹ AѬBUgZ|HTmUQ3Y5k3S}%t 9F>*[%[+?sR5V$DV:tJd'j2ΐ"FN=MZJ-d48*3OlN P/ /( G])H%cӭ劺Ic|moJDer+K&Rn*ʪc6YoZh{_m܈M/HBm`s7YZꚀzn&D/I2jaسg;' މgߕՉ}* |T63s/MO/@"%U9%vG(<3r't3ؽx/<9'|`߶뚃GͻF2E&Hh+TqCY3^ddwG"BÏ?fTOx`s M *lEaaGkYX`ۖϾ{,f4SV% Id29cJJSvAG#k=xqih8ge|X"!šDhj2cBr<|ŗ<3,.,s*wnf2v2it u&4:ٵ{:Uz\lI[ǞxOS΄1rgEyK,-3Obb͏_}3E)5f]e.oG?MγI!n޽guC˗//.EU !Ȟ(=?Lș3{WVy_s7- լ~e6ʆ6ʼv *FءL5fF!LMQ֑isTCNU01 !)J>N,ҤD7dNFQZ9HbcM5a$>FK˪>;6V;;R=Q< 2UjVլf5YjVլi˴ JzPۤdTlb9%,b2&[iÀIw}3DB`YA*xTQPv; x b4`SJJ31GNu* m{YJn3QjNmxiSNPm ζك(Z, F嘺s֊oJZւULfc0FcSPcf mHZkVCHO)D cKR@IjIr&}!ʜ2dj_-1W$w yG+h![?$MKS;g#׶_nK$30\$,Jo Ŕ`a3NɎ\щjδ6b QDFqs :XѴD]njC`40nSu{X֘Tfݵ-]ڵ[bjIǎԙٳoݻDɧ/p Ƭ޾ˍƸN8q*pK9wePr;C6Z ;O=̍)Bu%Y[}G8Fc2AQ0YTR65XX '"6Mdȡ'oŸYa,MSfW٢ ٝEozgR&CAXa*;=!!l%1V7zmrk/XٴRrHb$0JV-`>YjVլf5YjVFU dI4V5HIhK}LIb4(iUz&ճo{byokTřg-rfTp:Zu6-WYjaIUYHfHɌE!vQK$qfneSSsxNlpM-(q0L wV9L11),uDgNRA`ØVv\jcH8isUS07U!e$oBD3VcfL6ʖ2)AUәfv\3;Qo,75bS"Fz;:4u9^ZɾR(J Hǵn]Cx`<㜡-O-})@V/`֘0$1U]Ee QdU2Mz]R0dO:冔E}][۔acH9W=D403Ld|soegKnݹ՛_"WpkCVXbH|aanMsׯ}_{IV77)]q|!^y,PO˪"ixnnN<@g2ƍeg8K)GH6=Px艥]po|1rY!Āz/"u:?|%ҚM1`N\Su8T nR Lk_JkՊ]pJΜ`yע(IzL. I>yyor+Os^wyGy Y׾f ZXg& 9MLUEY؜)4k7e(ӄ.~ի7X޳ā}:]t?V۾GJ/5jAT!Kr!8kHM5&k,Ys[ى1g^x0 OWBʙO?O>HYx {ϼ6Y_./T:G[4!akVoWx |K0@:z+unb<YƃdcZEw l OosfD3غ}`g|*C^6T /~c^;O(*&cms!6xkt,!X{yQ}QʲbjK\ &i-"lޥTs} !|icL9q?w77G&/?k>xͽ7j~|X\9*tH<]Q*JmCC]BlCCUVd# M %=j)g6 TUwof~=8aLL _}Vo(X9S_~7}&˚ ~K3cS*MO>] NƐ)܂s}LR1  :915^]%:An'],̳8zd[ +a~~9x d'܊;>H ۣ -GcٯN7N:dQ㐈s{ԙ3n,)NŹW}y-'#zO]xHWrf5_jBp#EQ9ˡY9P:#Lv1la~rVvSfEju֩tFrܱX,(LjBPWgerc-aCHwkcȚMmN\͆nR&[48 +L,9aVbeլf5YjVլf5Jeu2S%-Fr8#!E,hQ(%C9ԄS[ fL&ʪi"EQ07?`4=U$jjkcα5"ĸlv/w'OV?ywi&5~#wW1 !P:sgYٷOzS*(JKQZ,V) Yw~>[k8:3ܤ,XXEonCGxy&uMDeh7g~ʢ"L|_҇SĹ!z"gnMXQͤ:/ &1s &rclx~ʭ7ٷWAD+jpu]v+6(i&SrhH)sou]KFf!64"d{36yop sKz=\7x_`S[&!FFFԂZ![J9uZ7DJy"L"9ir3VlY;?,īvO,*YsDd"S khNonU"ȇ~@Y,,_ů>/`F$k-S‘˨jL Rw~s ӏ9+dPc G=IM db{{7^o~㟱~C~K,ZCĘre%2n-RJ|gE4 IDAT3E/8|uMQsY jky7v3La=[,+&uĹ4aN'⋫ܸz8[3?2) ˓<#͆͒ :9g¤f{!=q$c( KS1n&ԓfL277`R׌u;xkh&/&D2N 6rf8r=|_99eb}Μ;CH_}7o{8|:#*}S$GzPBx"I\k]Dnd.bL^7uJ!‹/Є`0s_y?\::s/<^ :GgЙ۷gt4̈́(5ٻGOc̜yǎ/T41]ASP)dMK>gkےR*.*Z,#('4u]S8N#>@eGI*u[Wm]A4"\ V#r4͏G5uM_cUGh ss}u67u8O;] NI,r^ݭU'xJ2Y`Żo>&9yóGИh4᳏.z[[k:=˸0ufhC ϶MsgPcwo'w|f2d{k=x!e]k䳋ӄ2wT]MM,. (e]>GO ,~V[kD0#c$KiJ&i7(eHܷ+/#yHs=>e" .)"W/zV:Ib$,z旨ʮ)v4b,,ΓskهԓFٛv{}|;#7c"_?zW}^͇b^f5Yr&S֨nj.!c,BdU.!BQ猰mLXgecR1EU08ek1 vФpYb[Z{L%rId5]x5l䖵 kw+V$^$j#3YjVլf5YjVoQ)˹OY!g3+bŬJkQ;9MbQ5zF#)ZNόV,՝ڞ^KBNSSKJUfQ0ш*sݞc49ޭbM='UQ{{v\ IךoiŞU'eƌ5S$&{b,wZu5`|BL gT͙5*eHt&1g݋u*Db,:׼C,fmAn퉄)+f>IPeh|iXŊ\N/FK9N`]9F͙lrtlH҈7N0*b[Tt6K~mA`ʹOsp [cʲ* wo]2ۆu ǞeC$`4s2&!M nvjqS 8}][1L*Eh䔘oJ'2X:PU%XB"u,_lo?uay g1cM&<9.S{JU%6$A4COCCClOw1~3DclG؎iҀءA!! T*g{Y;7u7гdjYKyjDDTxkKl9#f,tx1t ?_>wgq-u5d2[\}mP2yIe$:s?[;|s—?)k%^}~1 E/'??<͓|/{))E~&Yù i}dΊB)zCvGOQ hڎhoCp}}3w՗_:E%sK:sL&{opݧ_|R{F4ڨVD&N>nXB'5X%i`'k83|a{{xubw?6`R%wnߦ05}!6m^d!.wdI >A|JTtA~Q!PzzVsj~M^dryJd=5ٮ]Uy-e9L& >EFր(1.9r7yڌe%n ;١@j%m,>&op=&ۈdg0nCy2m2 c)NuCllL7p7p6Mr<?~W$c'OЃ0N3UZn)X}v ژ㥊~2R\Ox{UF#Q'g#!u-DJ׍U GHíI +JernɜM(41a!(k'E-jQZԢE-jQr0U:ϠNO)!eY)1M nU9*I)SA0sE!EUfԘ1(xb )#˻|f(&c1%s>yF,}@5e*B)f2uVšgyEoK)@%WL ʦŴH% L X-~U#D# dMIͼ.igq6jE \VѫjY}]l6Cz)mu%Aˢ|UBzpMjEQiRs!9cJc 6€- DCg:!)謤Aʲm,ٛzHI\bj]a, Lj`e:( shc!]۱7i<^Ŗ-\(R٘Qͳ\mG4MÝ d40k={E)Ċ!ɵx)1hfS.{?+k|{?vSo㜣Y;Cvwh;| wX +l6U]:ofM#JxWp= | BK7_LUM(T|uuG:sVrȒ5RLǟɌ8έk{qӚ=?(!Sg@<fu\m60/IY޾I Lq0j|O115-(a4,-PwH& s)hDo߳udpPXñ?HY 9~W޸•+oO=NI\aO :CQ]ǣe2Egu}pxE֦ aM~1ZZ#8O|enoS9ǙsgyOb˒_(Dlw2E$m׻K\ϱjN UR': [5^VfzYl>Bc3Fp"xZϳR:cmV!gCb@w fN;t݉c|:! ϱzn$,7!tmҀ,if3f)o)k KX"7]W1vo *?$X3!PS v%CHeɵ+y$"є]GQL`2 Q͠l*+>vnW!szYKo_2?ׯsUUc6tmLf4,%{{?y79{,AALv\g:|*>'OdyuQwR -d| Y^FEqNl۶-:[-v6oĄSB跷l橆3aoo;[[\y ZԢ~Y@#Vglލpt5CcOya2*F l^?UV",l;3l@EdC.V#))\Cu KuD2se Üe,Ȗ 򳺉-]ϰJʀѰƇȰ ).x^MA3[7؛3`:u )p85*+v0ǬQ jֳz/;.?s#索*fmKQJo|򛔮?!p:w;jD3{]O'}A^c`.녨"Bu11yDks*tBR0cmm_*O383 HX 9׾+C433fl j͕7MQH}(SL]2-FcR&75,yCI57+#nzDYP"ro7gQRURMhH5γgOIdBs1rR3?tUzq p4ﰳ}trQLQo^eFqי3<'pUt6u-އpaR1ۖ c LD[oshsa(K:r;`,PHM 1'E(Z)9|8`yF¸B$"-1{n䖌Fu$HcRۼs|,׮]cTRH [7h'3jHM!xv';|s‹z fuuc|)b fc DΜ**)vM?OKp?a`FN:]w!fBQ<[wvxgJMNu 麖 Aߵ2'hC>wn2cQZ~;0y;x!]0ĜeM)IXÙ@HI'3瞘2,|Ie7QmWt!Z8&ӆ *]t C!<̿.IXմ]?qKR~! gM/jQZԢE-jQZԢ~ebե]& U4'fpeFgef PuVdD c`1:o-disr\z1PVۘ^:V>IJّXuHc;Hw] *$9;og`Jrb$X܍<8%ڬ"n f;9l%J4u+o8 9@&۟E&6Y9( ԃDBMX)GՈIv\fnTՖ2=來(B Wl怍rJ㤧musI(uSBŜƺ>7=:+@GȹcVgMCQfﰶ`PIs Ua H>w=@ˈ;]j12g 5錐*ġiVW UX QZve=`N9wş|+VZZ*W4@*nIͯ}y5?ǙOyŗ\z|[ c{LwweN8.g/]ws<ګXkho]Sga GLs?)E$ڶ#Mvid!?Wgk d:̡,1:;dĹt6F]$=[NCĔInFl$v0_B03kTc'w)<%ϬHluyXׯo50aOlB73P()-؃zr)NDL4πQ7cE/\!uR|=CVr}=\ǫڀ8y=BcbiTQ Wih`gw8{n}Y~_E-[9&Mbc]!!)&@Nlٔ\`d#Qy#) 8DFSϓ$Icdu5MZ@&srC|>XF/͕p$bHV{3&̓a`6)l 6U!! aQZԢE-jQZԢPV$m4|j{DH1'AT-0VzOc,lo+tPĐs&fQFrffD؇Q zFQ'qTOu n_>ImWE[lXu2 )JB̧蹅oC"Ԇ|͌M0/nV8gծ[g] Fu:?fkSQuD$#ɟ4C8vhDD½kDAbK\&] Ogb $V=$VSURHbpdU  pdfP+癢d0#؍5Ѩ[$c9AwTB$^3@Cgmp밶f8, 3[f@9AU2ˎ 3g~J>9ÚUlBL(c?e4^+n޸ű'8E?N۴rgsܹޝ;U/]{>7nfgx.vw0Ip0mG IDATノ7Og=a2mZs/Z/=7\ԩS5>Pѓ)ʒ((m&"X:U;4Uu ߴnq{wJђ2eLSoP?8A:e%c%I{8s9fm+(pRoi.=[׮qϽYZ^2f7: e`9Ga ϭUAT`cI6҅DpVi Pȑ)j>|nڐB`eerPڋ/rיs:w7m,O%<@Ձ9R}Uϻ@ ^L~Bf}zU:p&}Ƴ5r9%{Xk8|ƒžDPbɱչP ۻs{N21s[Vtװ FbbiK+|S੿]{&߼t6ae4NC$1m-v$ƹi !P"!t9]Au\A'bbL\0Uv #&Ѷ9@p-YK h|`wop/^dkC|۹šC+8biiĨ. E W0M%Z:b ;~TLȤ#zITƕ`đ̯PXLu"E\ٓ,\rk{9gDKGco±GG>ÌvsfQZ~+f&52ZrBA, Y=tIfΉ]/@d|Pt BY:$M&d1Ŧ2_2l20ɽurĠ J1Cr3Y_mzƱDXhE-jQZԢE-꟣|  Me]7Ycߌ& &JVBaQb:JuҰClgh,Xq2 Ue49mY~$ɪJ4hs 0nЃκz(R͹iV|rYC%o$e![W0%U4Jma@Q:ʢ`t,E<*8g=4BHWQUU)baJ -A1V98m^Yhރ 1>%u$cc5)|BCYJm2PeכLԜ`? jY <TuM4wg b[ufuv~뺎Q1d6i2FF!m;.ʽg0`b 7{SXɭjOv- mZ^;KQɳX}&q3ݥNqX*gMTGH2Mqe}k׮_JT>O~X[]cy&1+nߺ)#\ɗ_7/Wy_qi!Z˹ sbp)b])j8)ǤgÏͬJ)Ռs%IT%!8t:ckgnkgC?_RCt*tXDneyNwy|tz1J*>PzK bCJ^mP ҈Kq;,/rβ*!x8sQ{ WV6/g<N(FAӼF[]87?osJgDE1+kF?[zxN0,mjOA|c#g~H!-V"BPWlIs:ҷ%:hm up?؇SغM̸tmtڰy8ٛH^뽭hf i3ᰦm=]גJDZu6_7)]2g,A3k8t6( !ɌgyGoZ7:nѴ{{y8v[o矧*,++ٺB]W,/oR8hFC)wYU"0u${^EHt~mVOt|4ۀǔ3YK9Қώpְ>8D"MJf7y(ۄX/aLºɄ?X]c7յ= J%V,jQo˦WrvmQR㝔H! 8]VbÑy+~3Ja1mOo:dQ0ev(;v8kOOL2J`P6|tA-uԮ 7Q[ey0̌|x7' }WŢE-jQZԢE-wV֑56}s_p k*I}vci[R +r&Rl[0" !3ID#d)&Ɋ 1 ,X&|Jp|u1{{y/CP0EVi5V^?F%mA/0 !jz 铉Qt>2( J@@sEoakyJlҌ6&%BTnV'%Gr.*#q,6`AIB6Wuu8W R✣m;d#6HQ)t`}OYb Ve)VVBZ\*pE)dr Զ(c2QV%UU2^?⧹W$)e'0{mzKd@.8<^.1xư4 `4bx/ cMgGy4 z8$x\JLkk ʂdJ=⊒q9~qv3ʺ |s-f{L'2k:+Nܺڎky/>u $c%j1X먫ٌ!m+Wpg`,l:.(jMn~qg8ʪ"&dwRghq;`8 w2'Wej:! !˳"Ogv~ٳgq<4];CLe%:WWXp%jP`˂'NR0xۜ>uJV*H?{\ ! 211@Q($<мe$q+2'$k{gW^y:Au+u% MVWWtm ŢKVpHrMCY*5F=XQ sqfR|NTI҇س"iZG08%S VICx08n;6'ȬSb6 /OEb?(m{!T=2jAB[#,8R$0QE-jQZԢE-jQZ圱 @JQKVt7)#IL {B b5durEѫmYu,-0wPT:m>'RT,L;ϟ1ފ2mhUtR>UIJ +XRvE!p#hVkkiԫ Q|BmN@sDB+tEyUu)p'yˀ aZYYxﵡ?jmd\d\HHVX@Y.Ǯ=HnX$pNlINAsZrm`C{$ @4qr X2YޫX2$N@SPJlHSFKTM& XJ=01*FTp6?'?j0t]d0 XID +=>A{ۦLaE7NIؿ$5bt,R4Y!nuMۊ^sVӶ J<2:zOգ *iyw}7ɟᘳ!$BlcLBz;we;Rق.r.%h4`g{Wy$mg/3>Yz$ xϟSL1 ؒǹ6ŭ b,s;is485ng"ә#'/|o}.UYRU:B ,- YZqx~XHɘޝW7QZԢ~[rȔdp dBgbFG"i в9$e3WkNmJbH̺V[D&d&|U%CfMk$ 蟄SM6V0`RY321k1j&ZE-jQZԢE-jQZw9poe0BhY%غ lیImEc6df[lnJb7s PEF9,Q2E}G&'܎*6ƭٲ9wTMQ}$ƀjw-$)ajk IX+Vi6IjaKF[ٚKv2.kQ5+1)<ʕ4j-4F;Y X(+k4'f %Ei:eQZQdDiRP#@zY$勵$V+b$hH^s JV!1u g63b1UXS rZXUSRjpW6&IŅu?8%^|%rq)\!fSy&G =c8`i|)+~6V` ;mnJ>r0gHiKyѱhrSĔ}{YM?Z>򕿹\:A:Z-[ayx7nmIf2 Wj|43-sg`hږE&Q!${$@~y2B$Ct^FNgYuΟVGxGH@qr ސKV:~=H@F2ى2oa{[8[q,q Q6V! 5wX`uQu<पX͟ .F&aX#jIm3)W߃VW"Uu~޵N3C$ĠS29̿O՞7` ~/Wcq47FVX0觪mTZB19zׯ4٬e:rg{K!09ҋ>ruU]  m0)9N `7_ϽD +kcWE֝7n41>.qa 6JHF֠}vk y&/<<{ۻ.us$ksg$25Sʺ!c|L|CO?OrBHl ?EYIl9OҺ5ǴX8%H:M^z8_2 *!@WF Q #ݏ(E.gN/fo`+,Q?#?. AMkR&^#ޞ)DS:+ ܫ[c ) OߘEsCЦUo}N_mV( KI 'QsbI!`6 :;U#SAdPE@ߦiq&5d1{c%?6- !P>BMCQrodCWUE4 g >5֖)YPRpkI]Ɉ uIqxzIڈgyy~C#2[[J qh)!ߓAz hLP">ןtG3N.S0ƱIE=, ,4^&DHAD PF FWW)UQr1v iã?FA[_mps-V7Y/Ӷlˠ5Aeȳe4w=y@2~cYZ[}o}o-ܼqǏ|֍8D;?q5v)VV뒻ϝ4]FԵu=JRבM8fd輟 LY+$Dc<-0䐂BؠNV,ٕd'zu5?ȓeEQUJ>jh""ƙ^}. z.|ߦs ?OU`Ĭˎ 0V5QWf;P(V2 ~dD3f|-) ^A?J^&FG\3a6?8}3x|Ad:̝kBd)˒h6O=cG{9Bq2Yغ/jQow(UCdIg 3]t̠L8 4k~Ò"8”sq f>q%YAFjŽ2#M3U /r9ɶ j!Y rͣ~bﭴPAAogE-jQZԢEsT2z=9[*&5@v)s0UreFkul)GO' J_+MH&j$&ᜀ.Am R/ɤ*P+ts$1V :87?F\$;Wrj0z `$)Q*+Bܽ2(JQ\mYkRE=HkDb%ZJ&5h7䟐SVK(<:I@gg|Qb+ h7a'UIS!D9aÙc Qj^H"uU 2!]MsL$LQ+!+bKR8Db ﱮAFQʢU1Fv& A5(91AIվQǴg ڒ .OK!)i1(@3Xe[EBDZg3f}c .#VԬưxYفi ϟcphg UYptMKHx_{I`lo) Jy%Q6G^ku];~]㥞HLio^7nX& :xz<>/v=aΜȳ?W|+Wߦv,u (qS "1.SquΔ> ~* IL{̭dHy;WP, 2/ि#S遱d1de4[2O k{;aL ﭚuAHQ hEltʺصž"dw*+ՠ"N0;)rȈ_="oqf6iZ?uf e轐u!D5lI=pQv'2xy&uI0fS M9z뛐 lq<➝*}MHr/RPg5eR z ׈X(YSf,& Gt&C2Xc6N?hmpL*4,jQZԢE-jQZԢ~wR *7~sϹؓEHˮ˖,\prF{$ }RP Rؒ-[e(Q7b眽|s}$ᦘT({ޫk9ÊY@ TVy{S}$\PK:U (Q)~BJORؔP1*ĔȋMnʈ1Ew[Δ!rDc뢾q=WAjpR.tpW`6`)\P;E-RUW:FeyHRLh nSs3/?!Ş& mYpZI&pGq3;+5x׊P$7f8I!Чh n57G"äm>`6LcyuC^E>56\$]h2ԙE_ZH1Fecm ZùL'<\׺! XȤ/f-q;[CCӾ[o.E$qs}+:ɴcii|1H^q_Uzc痄ws8qsck_/?C6o^%#~>j_t:I3lb;|7ƍ2;}&+%~?O=w=֖+@iFo̾ZI45ׯs5n6b7Y⋿| oC~Y8z79q$llmqu滻c{WY~bu) $r)KE >(s)1B0fPUG-IsgX.ƚ*ظ}9}(bq7h/ʲ&!n)^qE5Kwuϝ9qiWrw"j:\M8 ,6PDNm50JM< 8*40X$LluU/dmN9_ֱrm 8QׁgjV5ș|zmLwΘԴohCNZ`Qz9UbZ|^MeDYk.RH;:l,yk+ 6o$gsO!;wgXZ";DD]a:ظ6 ԧtidO}<X^^%fL3gn9 9w"/><]#<*}SYN{OgkwG&?G\<w|<[&r҂MKP:3Y~p%r >V=w{ʶ"/lރR6z_ӁF\'摜3K  ,O>`> ?7~sg6R)=c}o?}8 l`9s }Idi̡ڰm.WEHlq@3u-hMm3Ԓ Klw$Rr`Ʌb!N?7LyrA{'E=9b!eϰz`?_/2.K=0ru>9Ô*Z݊0Y-FG ?#*\E^0[1gsB[SXM]Ji58Upe`w$!3֬ĉI@,?K׹6L鈩pfрiXkعdVxxwGC6锧]'Y;p3}LT"JWeK'e&ФO:ƋϿ|6u;,/Kfs߇}fuuT5eӋK҃uz9sqN7ŋ>=ލXYS4IyQ6!Wo?6>+b{ ^t=vL//޻;o+. _<˓i_F#1Ȧxf7^{l-۞\V!{A`s`Q|  J5e( 9FROl0ങT51ヌ?|{E`cgk7c1c1c1MȘCYR[W`SJHsh,@]!*`꼂u6&Cs֦h2)24R6&Y"E1&!:A绀$t]xcw 7(pԌyX:͋@KމExTr;|3QJ\1*3lRYٵVAZs}1@ 8Z}r*,R*;3ik(,ɲVvR6fL[LʒxPQxD$eLZh73L!t^i`(؛JdPL {lRL5DճX9:8bNS5칫ކ,ʓ4u1@5y2ceun,>t*KN+cQ̍LAmlKq(3R060v\z^ee徛Rd#Z0% .\2>1[$Vshl/".(kL&]iuYzW/';;Cq#4~!^}zv6E?y橧9~֛9v*[[,/-3g%450\\ LǞ3=r*ak p{&'?6(D rBei+*/  zfImMr:8U9H6f$h琮hl PuP(xw>WUz =^k96.*s;_b 1ϕ a զd"LbzUPf2x̻.[5홒+QI|_eqYn&%kӎTY,gJ(Y!]_L P{qvӞ?y!osꋮJl@pT2w^|{;9{SOkc{kK/}nn.VVWq>Pr!^+Xky}iitiBѼ||Or~n޸B^xǸ3]q&oTD]wvUqu^}5:-'O /r >r}|ᏲoM-j#{ N͹yyWy[{)cL\7˥WY,fvK8{ۭ;v̬CE4:kj a._=/q!=*Zj=_ƛe@}^n]׹:3SAІDk\qGmD˽XCFc:\c1c1c1cp)ЧEELRwV0iԌ2DO]Xh^$PZ_8]o{ Tβq H~7c7 PYZb:l$,b^Y604bHoTZveiJ{R\|#om.ttI󹮖gՅ('?Í+ɩӷpq:)$q9YKr*jf5O-27%9~>csmL:7O)?8GO&wK=Wo+VVYy{.p!vs&r|!ī/ĵW8r~:AbiBp),$5G8ν.wqgO}Crf5֊j)DRh^s{v2rֳʯ>GHU; Af`,76cl_勺,S*$6X]Ywet ӎO>>ԶFکjks YDX,v9tgΜa2cO ^?ȖĔZ\UXuf5 9O?^~%=̙3v ֡gc1#<c1c1c1h c+EZu`Ֆ+CCZ5@e%+2FA2W ]\٥SfYcVJC#UK9A! (;D&!{z~㜘y^"Ce`e&t*sU9 Ɛ AWc‡`δZ*Sj,*/ꜱSEDzT泫M -J"@Q@$ VoH}]N*\} ,yezf 567Yhx36D VY^Ycg{ P8‰c B+ UT|/< Gb{w".L,w#ǏqLVWwW^{б#=t@᫄1UJ5+ Hz}k׸q"g[=5ok"*b IDATݘY'ηẼlF.CX^]?|3'L:r1>u;#U}E3fʶskڑ`[pQHxO>d:1F0#91q>wVVhciY{SdG|jVi|Nʞɤcyevw瀣HOEj%ZǢXB m(ۺRRυ˗ٝ9u8Ӡ)Lؼ7\</Ģ@U6no[DJa}m/o_~|g:9bJLʨTP RZpT-;s׽S?Y~KCX[~u !֘S.LWVǞKWopq.\d )9|+R3O>2Փ7RYtj|BX X,d^CW|̻?;D\kiEu*ܘ*o.6\x4Sa$du@L͖תm- {8cۇRFNg\=*vRkQm g_hm`Q 쮫RUȴjE9ʼnqb RTj}x1OxW! (E}@˗RR V +jc WRkC{.蹢n@>~>9ۖ?䈄 a2Ὃ8N8|is{3g "LK3"ƤP`kk"Crv?y JJL9Jm|X[_CDH9'r[ l=S6b+/ƕW`ks0Yw^#H[f1PDT \<]a<|}س's6Ţssosט0NL4 jmv ;q#J=#{]>=Gu篪]tc1~?^ӏ>Rc1:c1c`|'?_6G+4@BK}h> +zU0fEZ`@iߍa/dd>ƛ?ͧ9eP2~]oTW]uSP$ؠdi*ޥ 7]o`M +]8^AONk䨔R nU&he zHY NTL`+Jtަټ} (ΫWƸ397F i\I)5O8cŔ)Kz*gA>%[Oʴ`\m%7Dú+?am֨s̃4fw3Yפ_u9u&-/OhfM¸ לM&,ERuZY|U杂U`2RbswN&LkJK/sRI)1_(!rZЅ&/ׯ^λR0ϩ禎'[ηc8}2y[o!p٘wyɽ+88sY4s/zMwu<?gs~^ktX[Y˪3~){~*b:zo N*9#lNNs`2}1Xlz7ϖ"9"(g+p'3* UyGoRA !Zv=1Me ܱ皤~hh8Tc@R5c]{םDs*{ENu[*~&S9ij, j9ZY84͐NW`7ccͫx6a09X~W phj Â[=)t#GxͷSbii&a2N| lls-|#3LX^^#FbFl,};S x)>0r x'gs/wmޛW {ρ'z=w+Wr ISZ(m..Kjbs,,umsχϺ^|m.ZL~:[ۛuwS'q>rt]`b1c"%f;OdM7o^o;fHNxv=?y[o0۝3(c4E6oDJ  h5TO>ξʗ~1te}}tN R:y63?._|n2t]& kkl"݄׮r)R:&Ç\WHI9mHt59ELxʢ2+8T-tM^T!"Xwisj6Lj/ΩvɅ _8g0M/ BWakFJ[~?\ @bY28 0Fv͗1jBSV0 ڤY 蘖ۇ^Li+2I:j͖JBm&}%^ژj^ [fekWAK) gM-z/+Dxś:F\ xQ9(QNˋ{rvY^~+loq1bN sMǼ_pGgmmUbԳ.&fyOLUM)L Y\3&{߲Iuyb G4#/="o:nlbޫ@J~A(z:)|ʚ[Id#уxcD>u  %/_ayҔ{~~^;fu(oJhI6u=Rk(h^+/uZA[Ki jWdC6c1>p?%c1c1c1co~48eG*9Y"ΩUv+Ĕ-XsY >RA cW_bc 0J~ja:[aS$ Vi|&E𢲐δ,^Me5Wi -+Ҁ^ X4@TRJScvϫ]筐֕fV:@VYZځON*TdZhީU2%(nL/+ؓ4^w9S&4*3DU ?_w})^y5._JU>b묘G-oQ?6w|^~W~n)<'yטOS˗._+W/W 9-ط0.1)EcυEo YYΘR4@n*[C%)gM}\4;rb$X|N,ί@i4b0&LmJdܛ6(6{7R,ůsdm_S߿y{V4Hy?{]ʔsKp{iu,-T<{44%ޱ.vR.=mM :U0)UAl,]kpkjy7*Zi7#ژQ /i_y3|[.$*^R k«>rLΉu(qcIױSO^kkʐ5krw_@;]y د(SWkk6֖[ yM&ɖצ o?wop&694gmuJDpԪyUsf2ќ} $t8`}>>8v$U0 ڼ꘨|J,r ^z%b\wΡÇǚq:~B&Q~yf믳<[ϲ؝ae O>}ǎ+?1.~c1c1c1߈_{[@m64]0಺ l^Y V|vhq*xRME*(^!I-8Uh[R)]Z4KLƚ]cA+ .TDZ5]a Hg)אVVo|(ͫWŮ1oԻդWZ GОR&T+ʶi,X!TknnȨQ.ΊYbb̟ʈ6R$˰l~$@cW Ů&Rhڵԟ1@`:~\eрgX`Os̼H1sP{BPF<1E[O]wˏ +KJNe6&z]9fp.E5RIVF+!,Ĩ@3'Eس`k}uyeiBL=}ی1g14wu'QڸCy3ݹ曫v#ᜫG3ȤJ^>ܚgk{w_7Ϸɥ'% 8b䤬 ־9&SA%6M&VPb|p^ת%gje$K{T :L2yI *əJn޴)gd@RW Oeb>sUۤ/cANTz-TQk-C:=OZr5^gI\.VulmJzPRAm( Em1*͙BJu׵T31&KT9d].,&6 Nt'K!HB0y_ң:' bl%X  pv>cf>=h=@7{^b@NZڌ}ss2M9b9! bhc̕[l^uT}/JfNlmmR ǎR $!p}sN>D9e9ASO츠uJIvg0];g~?~^{^<%t,raH&]м ]lno;0'Nr}c>ANګ|_ags AٙȣrQb.y3nc'Oх%._E|lKԣ*\D֘lܸ֖\dcmU%5ozb6,31iBeWPAACNƔLA>4H1Wu@Fs= NBp;&Q gWh3{{6]aGYk[j%1hi嘴G8{=Z4IaX ;0 'ߺ~DZB111&"uy|=d+ CoI3fu dBqy\@8.=}ENqE%ru)c1c1c1ƏsZH}h~G4 EBƼ;HEg** ĉ4 5AeB+%'R"/ ^Н)xǕ`isJUf,Z@*#YTb̳UK`b2֢%`VOV5me-D³~dF٧޶&J1PbjbI*U:٘l[`woҩpnd2d[suF,@yؘ[p ;xފ:kMUZQF\\jܘe"-MM2ĘStO t09l#/ Ě$R]&^ʺ8P CeŋJ@2gw!IYJ^T׋C ~_,N(|ѽ PJ yC1]1*@O*wPyH3}fSbee&]ǂx6 d)s_Xqr).2)9)6~YA`uekׯ1]б$ls17-oop;ݯ~*ȇ9uf^/#r|嫿Wix>vI`@L* cbYYYʕ\rk+a{ܢtp C|`ym_;/ >G7]'X^GLb γÇt~჎eNqilu~q;/? ;3yԧp^ 9|6ck z>_2$m9%Y&{m ͛6?Ǜ麎>D7xRNϫQ!ܶe{G-S&{OV0 zX_H^o9A=]A+[ nVrT15B"uڬ齪*[#E7R;+Eͭ5ؽ鿲bccjSGN6ZU p*-19z7АF\' &'"(9Zr6)z cMj#p osp^<y |%)/f?ݻ6)9tlr=emiӵThE=5ۚ{֚sZpTvݛ}G};0[իWxK/o|@c(),/O8p +ko8EgcG>(ۿ۾]+S?ĩ>seweeaOTM:5*t2၏~y|比2)f|ʕ 9#Ǐp띷s=yx1`%=3,h"IXy4{ЩT8~d]༾)mRo{a+IrT^jt싋X%ڭ%;Rbr'OR>.yM.H/CMvc%eo/1c1c1c_T_i.nR}6'Vtwrɀ"1<-[Ty@پzrN CU E'xog7a4/AgP *[Rmt&v<| ق>BO\pIױ׮^λ|{}z<>sgŸt6'PyrcJP $Z!keƣB߅%itO 2 1g7@ԁmߥ~K8@>Gwo̱,z]VCruhL`]Z9f%pTz n;RA}պoɥIGuKߍ=侷y=u(j΍!2ԡřvuJuiiy`jx\A%TT ȡFj5(s:8>j#O=2;\z .IJgN=9 N9å+sp8}-^&ociME6R2B-g#brphPhmh Uj /z'rxK- _H //qV׸Ey)=tS7݄ʌ'O#T!FksZ/bwy6!TVVx&6JvCq&m/0S~0@beǺi0sAǬ>0~z_VxTT^!l-WɹGe4P+cT˛8z+8pkHPK\Rb6Q|rINZqow_5/*|RƤżTPV) ɔt :@H9+I,KlchR+OR=(Y!oc&:V>3N;t)^!Ξ;;o+Ơ3.^r{:@y|GQg$e 5Mzkr e8(3M.JL&SN8[o||9ؽǽc7͹NN/K>sv*S6UR9<sS4R/I%bmlǟa}k ̫Z%t0NTre4/7i:mШŤAg`tBJc燨!8;(;xI/4x5/t "Hkpdf9+lm5ViΥ?@(?w)7Kuiiy -(k4HLT"Zgi+|+qIלRLpc'c{UZF =x|}ݾeqy߼ղ7VBϦRF٠aI.UϜ=7v:[AyD8~O|)ɔ39~Sl>~>s70of`_KmFÝ hm16^ګ?-vwIR M;RxW.kN=ٔUJLf3;wI"DHc}m__񧟶cP* F?3]yGopw6Ϝsu/ᾫe.{F'H䭷yŗH?4G )U-%[RNx1tFKܼqxxᅯ1c|t~:RT4!!TM>G]!vIL0i]p]\!jm4 oLVZ*\H%ݸN2e0!k@P`Y:R9JT2):qAS{AH{n 1c1c1c??37~0@dJjϩ2B1.1)Vu?CrUJO f 1PQIXLjÄjǥWUΙqq;UY62dtl%J$vŞl Zt>b;AW_% @[6&Cbh40DpqEěl{',KcQ6S1k'e) n\n`>l4Ŝ %(*ټ4 E{L}CcfbAdz~#Z3IQPMeFRl(EL-2LPl`N0Q!&yTvqM)6"_g NkդʪQ%cԦu:sΦCvxcQ A70-Eid]|)0?b`ϥj}N >00%t)q8Wj^9)*JKe1Wb&FiK/{_#|~H-=7w菐98/2.Ҕ,"'/"VSPxY~Ǽ łOg=jܺy'ȱ֫3gؾ{O~<cGXYqǟ|JFp<(mCds=GAAXkHIm%z kT{\47xSoqȹZSA2+U,Y3.ƭ1 M6ZVL>uhekC3 / ڻһi<'ow woj,y+k_7JEDt<ݿ\0rl6F xӤٳ,|#!k\t>YR7֑>XyI=$Yڧ}=^ L:ቧt@=eX}A#VWWxW8zb=uַYuxC_yVW׸}.S^{5qԚyo3c|td2!$Qmt+n2t'  hn1nJ Ym! KĀĨx!* mrIA TB)Dj޽Ҥ|IT؁؃(x$xrLSһ']N=c<1ZnLNJLgK!~/>c1c1c1=]?j J鉱TƑK6ue d_PƘKQ *=Z,<\g5xuJ{՘OiP*4 Β2) 16bvBD_4 LA, A/`cz;38XQsvlv?.a%Q  T;XH *Pizid`.nC\b)@K78lNcZ$1ղVM_MɎTP DUV1c 6.ŤQbJ/& TWLgQyle@51.e 565](*̦@7^U7 ~'ϵ1e#$4iV18`=1VfSjL Tba5F?Eu2epx8WfhRvʱ vo|>YjLB$HocSA- jY#<npw6X9qB`pk\y"yS{ܽϞE;jcǫvL:l2G!.ud)2?ѣmSֈν][>b~Q|_acm©S9{17G/^{_75vvy0c|t=̇F$MM+!i0bC Q6]Jf:TH$R4{IԨzä7gӺOǮR DjwIՎ?Lz(6 ZttYB Dj,nT-J,EB"LΙILAB!tBJ 1c1c1cWłٌ\AJ09qx0`+Pŀ8cHm2)K[.WeT>kEx-|(8젋c\Gj%'+^bLc&UsnRLJw`-soV­8@kA=\3R|R@*]//m{WZwqe %S%?M@ٮ)(Znl J b 5X<"䮅r _C\ɡBHkA^js0l~:c0FQpZ 9[H|nEW55+B9EBU~A:?'86_ј] sds5%Dcug0|ѓ.%!ʔlJv ţs1/ *@V9l|z$f3vwRlŢgum mR F,{꼯̶70$&uyagwZ3̙.M:feX_k?x^|n%:'pww:}3Y]M:Bk/}G<[N3侨ATKk AL+K9TJ1F~ \]zp\ T\IAt8\bdZQ5AZ߄|6~agUoٚzj %]<9YDmu,? 1 p@sV&`׾4Ejb̲lŨ S͞7A<)&٤1l(RKna7{8g.Z3Z{7hJ] <%y/~ĵkxŋ<>u/^y~9$1:677r}$5v z/Ci'\z rg=SbX@叿]^yeB iر-cҷy"6n"Xz̋s yD*dv?|~AЬk70v}egwas4,&תw?c ZGRBTj `EBpA"Kֹ[i ,l`IM[~Y`6QsՕUV5BjEV` c接ZJZTnuuJʼ_ bAo[GX]_g.wnҋ?y^|{yN8`wZ\ce:֝[t݄]eM+xfv>xl߽;I2LI%8:[HV|$G{W}kkkVMϥD;nЃY][1VV!{;f r ELݚd=&cuecGH]{wH7pȢϤ.hT?-UH, +\gw`4Dk@i^TE $g+( vLAۢXyʴ|mU.iC%T/K(k*Ñxvr3>d=}Ť] r*Ҝ{4 - nuz5M0L-&#zMݐ~٪"aoS:w){;;܇Z} l9\b1sqkPJf:P_d/˒\;c l$.ٻ˗?LNȥ'%auuoƯS3ڜ Q6_l]y'-8yVVטf 1ZbWp0btz<˹g27ilop{6㏿ǜ8q~ U'ƹ/9}'|l]^1ڼ)d*/G1]|$Л;jDasL'ڙgw)B%ݬ M)&(d6l2v#IEH-v&IayQd"I#<c1c1cOԴr)d, dFK´ɹ3T1%~*I',}3we&{VԷyZK K 8.⒈}w=ޜ]:V{ +Ƣ \6M'7 a\.5WuS&Xi]թQ 0kgKP?eOY$m̡SQmM lNcdAkQAc^Z5XAm5 AmܷcaP}qHW^A~o p8;Y0U:%d`Bt)jR##ѤQJoVga !PsiesVWW1M_dvjKq浓0RH1:c"D679؟sx8B1cYe A+4&Qa7\@_ k+k韼׿ vv;p'ԓOpyx1zQ~OD!2޷6ե}H>4x?l "$ރ(@XAP)1KW7=<w_o>*dZ’=g.=s>4hS̠ /6h&'=X1菃1pm֥9؁|snsH{y`bk{9v(GlqeRJ֠T ka]1=c}kn2'x's×Aa9ؕں [|+ǚ6ǥ"f3bg- `sŘeڼf{l">vgN[P{yMgZ6wRa1Y]Y̕G*Tm(bw~ʫ/-+3|!Ctg8NFx1>Hc}bj7!djZIkS"뀳S7KW ԵR;*Ib3+OѼ|c d dRĮUn0/ 4bh1馝5֭hglY7ix6{r@"fBl(PR`c1c1c1 g~ǼYP[1:Ϛ^\XXqYi`ɢGV4K2Prz_P@[S U(UM T[3Ū^YV$UYgV䤇j>ifjk50`^VlK6v7}Y1p&`eJ ODKYa&?x*+8`e`XBR KǰtTƙt TOY0,k[x)ƔU`6Q8ؿCBf%oT;e1xh) S*! LZP`R*+ դs_Ƚrk2K39bPiТWzX0_lm^M)ܙvXjѩϜUJ}gZWq/= 6;+ \xӧNrQ& 6=m&Gu \{w(tN]O=L6K`q'g. x'u.׮^e>TYY]_,}[ǎs Yʃllrxr >7pM׹.֘b[y []Lg?qo}y:ܺy;wnsdc;bJP+)Y"*9,1vP<0 mnh@=}I1IQI+M<g7 !MR97zmѾ~.,b QCM@aʦ}g56]?JUYhQ`ׇ50wZdGVkN2x]k6ys6=^\ơ-/{M~mm/7H-5ΧvƶHCO&m/=_Ҏ5n'J{կqw(p n3 ?[8}})ִ{G 5:ɔ{q&Й'` m!J (&jRBY׿Mz Ituƕ+_|OrEJ_6Ec|REwERKET*LfS66s6/Ae?7kb J߿{LKy⩧{{Sz1]|4_"uS?':/CRKsCDJ |puX+IL]K@nɤLJ5;dY (b&w%Rn~ 4wF(K1Y'(mLZZu?f+&!ݴkHlR7}Z9c1c1c16~V7;.i p9RgB!F:ʥf*H(" nUT Ux^nOV EQj0&Hg^ScV$$1PvCY唝L'2lUhG ,C?⇁_mC݀ZP($#R]e:w$>cj74aZU{W(E?hr^pOU?`@z#?܋@X[OJ/bh* A mt  /<] N8E.\'ɋJ?g6bb{++Uy vw#CX7 { ȥs􋅁®x.XZ-/2V>OrI>v=k(3?TH]G2MyG9gww]_D,blд5::p28yׯ_]cu6?}^\}==ʵW9~X <uBHGrҥ&1`_m_0%Ds]m-9ɸr&YrRTG°֫ÑG8 uЩ.p^B% l4Wmh'}Hiι1E\ ">gRZƹF"蹯kٚ,g(:@I6ՁavU$Y^[<`W5UUӲuR ='ܺy]%%0Neww=O.k3 6l/ -11LK;ƪ!#n õU>x3G/3[ѻmE0)w(Q|=A >'l^BS5 L1,IV\ы93dSP+D}Ϫ ~v#!1[[iKzj., Vf\yr~aMqpxcOǞSc.>'5AeݴwD;L'H:]1 Ġ7#]n9!F&&!`ulDAWm~Xu/KT1{ z `xJ)vDjڜ%fS:_ lbh&acR8w4qrV͎k}_fc1c1c1_Y_ot(ܕ Z3*+l3ͧ7JlVjS.YQA@ }&.K`D0d@E/I#ZLD-I@AA'a`b_5yY/ZAC& RlζZXZ+C)bAeeY\V̂i]ǰv*^Z)5Vq":8h`_ВDtn|/<{U%!n_ZmuhZ6@+ql&5)YAr^fd)xWYV]KO@췪3LI6dXmD3߃2vw̘L;bKO іC[B@m_Ә:qJfۜB @fخJc՞i7A$rppd|O3OKa{{{b3Y]o/s0پ'Bcem~osfZX Ǐ|b"fcyafU`|3x٧92Txl9F_UjW dMbyG/_9$W3)4XM_U,)ht5伳eX^>{o0GsJ0/*J6?㓺:&엁 5,S!; l~Yg1 ( lߧPfT9/H!)fRtt]dc}Ї7N M=Μ#c1Go|j$ӄҥTU7B$Db7@ R0&: z͕b0I`+JH]A_ !&*%)"b(}?.:+pk@EU+R:ŅľpjSX(;턮 Lr)4Ri` c1c1c1ϋ*Tz&\T6@DcN (C3 4F5Y%=BLF 8 vY2bfT~kb3r-6^iR0x:CW}ԟNEq}%W}g}eFeT.(y j蒘$S| J0!]<92U9{4RL*Y׀ + GuybtyY:(AA&+nac ȵ˒]O (R2kMYA:}JZA\a=ŢXE@ jck`[kIח1fTz[tDkTH|N+hKR sٔ>Ec.(`o H2֨QKHk,/ܓMF5Z_XJ !v,C9v(pU/=l0\r?Ws%~?O9s,"cnܸCL|W^t!e* 7:?Օүr8+Ád͑:QTZWk]n^˗soկ~_}NΨM5EsbN/ll(`iޭ3KQDbK,W:& n$ 99ٔ@!uw_Ϝ0UQJz":gB"TSJ$5B^,)|* ~R4 XnvRȥ"@_ļܫuuYDcJilZE˄:[L{ )h'5PIc",Z\>9s t%1d%1F>>/5Tm>j3}hB=`bT;͞  8MՉD6|Ν2PG{\'yxx]n߼~헙i"WhM/RM䪪]K\ʂ%vZ)9Jx\sPqWgii74TmZahsȏƿC@o1e`Ԣ:Qh7hm,!{R68q☮>s666LbJtV)҅Dћ^zS1mmb&n IL5**R5A;b`Qg\>BYbf+lMaube*gaDJ (6|}N/ )&3d Qu@"pDL;(3I4HC3>}y+8}d+d2M&)(;NHR& 5/ݱ>c1c1c5~f /L P4' Ca Tv8 >pIRk3H A]LYQ'J0:c3ĉ2#p%Wb-1 2=C_ ^LUl5`~ZV+,d CQ;]VD?..W ɫ:׫yEMeUJV`>HR=_G eAe9l .X*M s74>:H m[PsBØj!8(7ccEMciyZ֗&4CBÁmhKK3[3ec1]%gp@G|>GKl=otIf& +ӎ!ׯGU5)QѠ>1i_y] =M8䍗X=o:A4\b$wz:k~6k^khTu6x˹.1ȗ]|f M&_nۮhɹ畗ϭ7})s{Ĕx:t:S\{riUOּ` UlojDxC'Gbf6=5{dn7`J;[DŹe֭5Y]6Wt-zHf%˼VW ]{;i}soOM3kkNEb1#>߱h7@ B 1%hR؍E@a+\9qj-P#)"\NmNVN.})2'I>#f]JZWQ`u ֹe]Hs>;Ϡ )ibHJ Q=Q,3:#E{`20s!Dz`c1c1c1 I:e:+/bO ;Ν*M^2@I~ ܴo26qZHl歹Yze5@R-us`mſ\cX4 kтt0ࣺwo-  ){): dsp}Ƭ1ƦɌ6TWD\9rw K͇P%q_F>V u I-J5dl/5)6fi  ՠLP ԏ9ȖK8YRZXzp@{ߚ\rT#i ؙ^ϥ@pjY_ZBǣ){};Š vRk+صbL͒Ϥ1{K!S4>_׿ϭ7H]t,z~VWWsQ_9*igT+'fwsU>Qi'jT*8\@u"cGw+y*[+|ngE7IXQ@,z|p HE1c1c1c1Ch Z-֥D r)d ĨRd0Vh)M:1w5>y{6gf؞~;BbH * EEm"ZQ ?+Jm J bbr3g<~sk'Qd3@{Ys>{}Ĉi[k HZVGϔzف#g4*C^{aIPPg0UD 8XQ܈1; T۵i!^ĥT?qLA}JK$TjQ`Kn Q)uˇC= 3#2hLBiv [1M `F^[}Wk IfM.!AZ&-IzCE6i Lΐ #1-`_nJv5YyS&**YQx/b@XSR ˽.yY@Y!Z[oܼ֭y{O_WxMn|;oo\R .9;m&ژr7`oow.)F֗КO|zg3o-·}ob}qi9Q(S^5%f뵶F flJj$O˴Z)6(^qϥt@]á繩N c"TeȚ r<ګu@GieJwtL 3 15VV:WzBb"W[[5=L*ZN rJ,D Og.p \RHͤl;[o%¾dM݁;$ v}12Qvt`wХC2,빱6D )+clpjߓ<APRA=}e/%̌d΀RPDyfSu}$&gR4eCM Y)|r)31c}c}1TR$ iR@'.ƨTFT(MAv/ConlXo6Zk*lTRV!(m3OS/@0&zag^0b ڔQz[1o1` IDAT .p%yX. 1xSR ) uࠕ+\r||s>劃Y?1yʘbjjUVU \dgCy͆IX iԬ赺xmƄ&y R8|esaƆ0*-O r A;8{1ׯ½-]@RJ!)La@RT(Lxn~=?։3[+jDȹr%%=[IB~ *U_ís&8D.]ݘL1U=;ku]Z0DaFSW0T뛭V1R Yd]R d3\:\L>5aM:y 3K` +:w߮e'=/6{hy[K :vYYִ8:j\.Y,UVFD_3Ygb@q$c)I*b.v]b.b=mTDehQLEJQg- l a & l^IF!IgjZ dh!O:Z"i %:Z׊Jl 8634!Ђt/@gJpvr⾐qOhL@ee^(*,3TMXe[Vex[)M`A3t[/ cLmF dXd(?DC br6l 09|] (UH06F(TD54vU 8H]ۛh"qt=Hb.z;U2k!>1_Si * jXɘjU"䷁G@a-Y21 0IѼ]uY&eتt@ @26zVd!_ݪۇXDfȹr)ov3^}-^y67o`Z*GG5w3p4RnR[cGW7 ᯯ]zПL<f% O>$d{Q)%ϧUJk{Sͻ.)\,RdLՐX8DC`9FV)@ -h{ayDtSd*&.[7Hh .k..LSHA8:X0&=Ȧ\l2DAr`LGזrsp iH,#8")#2X*r-N\ u>yXy1<_-X Ř[2ЈAB  p7*G..+)E}0`׷ r.v]b.v]-\,BdZ͵* ep5gL2m%ԣ6չ YU 85!^f5܋)UrJ1xCLZUQP !u6Hl\H>J%Ҭ`=;#<%.ZtMIy֯e>21cU2TJVV]3fPq z&e .Dv 1pY k2ZP+\'Z+TF@6iC@=SKu֓2R}JL ACD}0̀y5$]?%DzuY=0_S }n*hIISCQ5V%{Շf18/t0W>Kc+3./.w)i"_>yD嗾=^—xs_V'6ݽϧ~N!Ɲ{?'O??jO`6$5̓ӽQ&bđ..l6ŸdZALIAIk(u) )@{ !jnW|-ج7||[Η^G>r^xŸwKdy-E얻lyw e xF!03 M3nNmvf2sVdۺom/:~ڜQ6SVŴ)LSfGa}Nr q KH+[]=4m 9L. e{Kpxͷr2\_Pn{}6`Te7.v͉8؜>u_{_Db&쑦9IV!~M4C,h+JِM8DK^*߲2)i3iwuhhMQYq`o8 ,D)ڝqv^zO2,Ce*|3AC=aoC`9&{S^4MA%<ԡ-5p9dRU5><5~?]b.v]bxx5t"\:X8T0TU^$6f[!%.HtJPn7wuuTEUHmuOe+O>$ܘ%7#UFcc6[VY*_Z̤jzՋ.k"3;Ae zkC}!tÊ!ֱ%~,e?ichl:{ feKa26Jz $uvcDj) y5RbԹC}O$c!lJ_QǥiG !%cj5*uڇ(:r = #]M8D2]bgQKy~l1ն =RsZq ͘lyϞzi\-rf1*O<o:oݽor1|1>K?w|wRZޝ7/o\vs>xOq5\=G?Cx77i@a7W`t",6^Z#[8J\3ej;3`޴"JR~ `{X-ܻ}{{88Xg?/%W4pp|J=}/G3󨭍 @"Z0gjk="tuqۣ][濳mKumg:k^0(yɵ֚27& lPq:lW3ҾLmF._w6Z>+O̿: \1e mgKPv5uu>Kg@F "[6}}9A^&#}wI`tVx!fP{rzz0qvrkp8?|' ;?'_:;D*#əJAQ4d\Jß7lvjG3" sCvӁm7(4+ ~o%y~]1 $z؇#TŻOh'-QITx@B oX$ +iDQY%OڱJ-݂Tb\\nR!4<ُAG.@3V*-h~-a^J@]5C RhPĐuZWcN@24D LD恲]b.v]bHTMe|T/O|T^R9W$@džPIFHwffeCY)${n0 1?j~~m -ww@;fc*vz+ьoiZW.5-){SY Dgޠh T˛UjqjB坕nZ}X֨J:gU*)%GW?ài1X.sTf}x~q7}L#ޤ V9"6R&=# L;Aq+)x+5g;57ylL\:pg/O={T_y 9kBĀal:]toTOo֜KjWxeb{>я\eH&6}޸cT)o{RTAg;cf~3qP_A;S<&!}Ũ!0De1iHWaf4sX"Bfd (Z,Ja)]do*BnGU%!3!ld EaK"#SnܝYAۋs!TRJ%fz5I?C҇ r PEwx.v]b.~m| ?@ZNQ=Jɤ8( [ާMTsigX  óu0G:蘌i耎& K|F{2a~-"\·OfJ/+`W6bEEM K.Z:0`U`  y!z1YQUg\f%X_8~DUSHz9\\J[m.ڜ0bgྼccg/KD[g{J7*ʪZ֠Dg 6ZLغVZ+A0TBfݓtڪ= |X}ucJl[L66.>Zי Bd c%{ qnsVV0&eXπY ˘LꬻF7-hhGJL \.U6QRY{H7HI nU%W +_{oȏOƫ_U2m&vZdmʃ{'g[-xb+9kr/^塵 `kg#*Bv 8y<|f[ۘzq)oW2H_68[r\_hS Rffbg5T nD(eB{q'g'{w>f0Ʌ^}z}kמd0^okboo-z\BBP(ny^RMdSXW8^D YUmC>Z61Ӑ>iںZpH2(r+ B#UlGO(dv qf>c"+a4nrVk10>40D)X7xʒ9%V~|b.v]b.i(B R1Іl޽Vc7TjHUj.VtX# Uܺ! ) v͊tFe "XQVكNق^y:Z3W/&z1c*107`ά舸! FBq${qT-PJ-gZrv~V@YحtGWR t*] 6pl6ld7ܚPkx7p4cK*`ZsV_ jeБƍ!Dj jþ3HŤ.ߺhP`:v9RȌd.i lQdљƵJi C"hՇA"&hY߻ EAH)(sB-!llIYVk* }jd{Q[Kk"q%SH1zJ! \`hCV'fb\.q oݾ9刲r|mqA+g` wT^Ehà) ,~fdJްyX. W][wz5lr;՟4(lͷ3q)_~\v9 ޺e0kz*z_*5&i\bka"Xfe:!ǚz#YC|G}/ă88'P/"}RLJ_*\- 5_dLLϱvVluizVJ s.ߪXtIdSg1-`V6,XmHl3ZLXYXCU[\b>t1j^us3 Bɖj]56}̚3n5;2/+b3'D$2Y򚣈HO7lqU)h\o+q "!&j)3K^+L$)ptx{4/| y'y/^}?9;= Ǟx{ba"ZOHzק?1tP+7';;xůܸy?\o(j#d[SVA}c}h٬Y7Tb=oRBh 4Q$ E5A?T\oc`?xh7WEp,b0FMC.&!B(k8V(y"C !1) !B`@WrHIS |\0^g6)_pmQ8;_+ƘX.G1X ZI1\,#c ,zC8?`ʅKY΅R+q`4y.v]b.v]({hѽTzZ );*@EX BǴ򝽲u8[T!+@ڂâzh5 k*Ѣ_`.<\k@o ͊hn X2A;`ŢW4ת0VŬ^gY1Sk~bWQ`RUcӽ:R)Be`$#řliӵyuL^S}Ƥj (ԂF$uڋ\ŕU쫄9E;veB> lbIihXۼE0`Y:x9 @(iy掠Lz1*xfuH#K+hVT$hjƀ RX,F0 E!<:uݗfx[vlk5i"ggVӔPvU Q:4b֌3) \޽n޼!??K/׵blw~'h4ăSGBJ &Ҳ6ypr[շg_ϼZŒ@t3+`.lҤC" \}yz  46Lj~|c节1iK/ȭ'n2)\%unrVs4k*boUy\ynۗ_O{ξd#@lo4 8-UTْK6%IC}xٖwO\g@YL]Ϙ`Xn\ X?m湾΍%yC'Ⱥr ^aAg[ kdž[<#Sd2MګPJf^z0}6ibӂiZsqᩧuTJR}//[x]6|)O~WӪv}&5Pml| mw?U+m @3xw碞X-)>W61 AoS?)nݺ+W9;?8[)nҦ럟ZP-z9dXqchZa{Vj8 Րw~nn5Ĩ>еg.v>>\qzɗkyq+1j2.u%lJC`ǣl6neV{=.'7Pi%BZ2 M@d+JREa/EN6DJanpU>`h-XD/H&}쒀\ S)9MC'z,ƁÃ=7Lw]b.v] +>FeJT{RLҶ(> H li.FWhRM~p3Fe 1iNȓZMVÞ1D+bk._kk2[jk:hCl J쳊1&RqT0Oj\E ׂH Ps@pcQTDK :bQ5uSrŢi-hVeEWg>}Wa>8HzaY?X4@ e[ ܳki2Vn ⵠV}L8k~MGHJZ&hJ1p]kLv\*)DrtbLalU-^ͼ jC{@@҂ʴK"`f1U:!JΕaIZ&ݣ)\{( P?1y'֭0#Ð$ i2yeT )!9QIwit8ZͼuHt&]Z64vڮ]1@ZØ{3)WO}8/ZS ќI 7uV&r?x7>͚rkx}RKK}cS&}ڐ($afuDz۞DÀZhdJ+0,2Q3Uy%$EBӆ&VኦJUI_>2p W٬7+r~~o2ϷT JLSVf|5z݌9W%bh#iGn0.F'z}SQ "Bl{Zn.v͈SDΒHlGorrr8_8z,bLY;ɼ'LhDCja)T&n%~pBܬYƣȲf{{gg3CxXjKG+a_ y8ʍrs%,(Da(0-$8"1C҃?bq̦Lޢ7Mf"g'h0Mq`H)iGzr=AHӟoz.v]b.vafR4XdskMy %lk,'=I^۽Q06RHT”86'tڙqʦsTa 8j\n|s%:uP7aV@ܪee"m0EA !V׀+q͊\W+:.>Zf.َy!5ctgbpֱ&]+Qjl i>ha 7FqS`PyF{AkqH9W1v/HuRyҁ_RE6FTg+5wcG!^k\ )qRUfF(@eRU==}@S4_OsvqI S0_ƍ<-XjՒ\M=-EqYyWiX_e<'8_W~o0 S^Z 1n+lwA{y#Jצ,Z/j]݇ԙ!Fqr3ǓOBܹ}R|# F@lW'=1&TZ@-۳ 8ևwoVh3ka+b3h݁T4歝+.AbTdQ82 ;|7ug{_ִa\ٛ|9AҎ9ju~ho[2*}>|s%]\@%n '{92Pc{?g> ĀjK_|SLeSs{!rD泘"OI{Vo,g_d\69-c.KGϗ36'/& ݦ,UUiE>avepqip=~+ btG ĭ9L=\Je)ܻsE>˟/=ɽ{=`b\ڌ%s?< | Ҹ~Z/h`fʷib %+Wϱ]?3<^bVWG\^kJu~̐ MrQeU:S= CH)r`1D r/V#7, 28\ 4kggKƚH26Sf q``5F)s$0L*qD"b`b}jB=%1 $ȥpS9cb]yʔH)jmz3qtEn7&v]b.v]_"M+^b?c4%gQGbsB"4vu`,\"^d&[.PLu*굫tp"E&L[ZLfgm4jfĠELTU Âݛ[y)EpU؇D~A,jγrnt`^DUD.,V Me]; nQ&2kf 5 ͬ6VQ Y:pЦ)3y%0o5 b qIo=EOescB]"zt==ZL:h]@V׊ʙGc{mZAW_yƁ_/}Z*>?в煾'|xLM}C_VtokԜZŬUfj<ԇ{ <ffJ+߯sKomjF@ f``;|~ov/,= sWV>?;^sĵcs55~f.ܬ U1{-{̺Z!T%ص/\-9DLwnv*T9>>AFm VgWtЁ-Ast9K9pnP0cG9/Ӟg^0[6[n{=իWr-,琾՗vb=?OO}%..Ψpr +>~'xGS~WO?5Q ~byW_@aiLLəkbo ߿ū_~F+//Z}7qv;< Xb@Zp)Ej1@(ӄ)shr {@ j)EޛZ\}̻^ں֮nn-.3̈3<3,Æ!_Z`d3,!cyfp4Cqmꭺk몮}y[yD-J6a큄h4bw\3B+~̰ܠb++Ph΄I&9<㽧kbT C<5s6PEeIQ!LoĖL`f0}(&O]7"c 5 u\7Ƴ5fu&.p IDAT71袋.袋.袋.~8~RZ$YGW FYRLEEP1Lq2Pټ EsG7i=R1=M%?54vM]8+WIEbZ\@HT0-d&rSb J7tTشrT!>31Sq*T$8p\>%O ,4{Z@MJsd@*-0'v,,y?|:!4j^'!ZKof8IÇsG{en޼sGagg~^5$p(MۨB=)^~{OeIaE8AӠhL+ϚdoXX\⩧$;wx# E6V h}T޳0RfgF~О+f1ҞWɳ[,3>{j#W07ɪ%g4E{NO;ztO5(KlwSRIg=[: QκAgQdħn[5sLgatY%o=*Ob] NMɚ MpH%IAY?tӐHP<6XZCا( FuM ,{yQ,-/[ UтΑ.~)_z6u554Mhł7ykM㥒!)~ubZg Y3@ߧgj^1Ereԋw,/o~aDd=q=d4w G׫oz5ÒN @\7G:[#XMC3/&]to&rݳ׮pKl,ˋyo2?0Zs*\˥kxvo|>=>.+-C g|i&{?$516DŽwovF5G/p*3Lja 23(K =+U2:cei$3ӌkF;#7wܞ=bg4L`4f#,f}fP4el F5΄ggmٝKd.袋.袋.O  ӂoQv+1JUh䋣,+U&5*1"gQ +J$HҽL_' a!*%F`B*.|q%YBوiY +rA%q3G6Tz e6U!3xm|fqUf22ׂ&VZ[&e)P$ۙ$! %emXjUzIyAL/Dgb8i!;4RWP'SВPL ^`ì+x8WēGV f(o<_I>&_&]h#L̯uU){`𪾬g(~ 9‰)}ʪp0(0RH gHoF(rF1 ќO؜SIYviBpF}l%K=E^ Ѝ!1[`94CwyF19S[ ` &&{TePb2Ԧ9 3d\| ϾiOقӍ"iMv T>GfZm\tzO@q6iiU ID#ҾS%.^oPHz![U=y g|fzT;1;7SPB#Nx)>|=c-I)֙CS!ĬX@ε3#7|X4 mhuCޱڽMל'0QYZ0Y5R7 >DBi M<8~#)._@WPV?" NAɸ,Ky#&?y2;K]tŏq.ӏr;z|+Wgi>:o-;t뚵I,Q|m9ke<ٷ45ﲰ4˟ܻurws͍,k7/]dwRhy Zf2~|*@377ǑbdksC=! x{fZLΦQ :/kaK[B1^NpcO'H礄댁FA\C.f[FIb33QjxZ}Rv&I e0nDiK ‘$vc=>D .7Qx_cM0RV)Ac]]/^i0EO!ʲEz$U=ׄYlY8fe :A< QY]Ozzۣ0b56 h b,X}t"ΊF<r1B~0Ro^6Ec| LL>^rcrH0!fd/!@VZ1: >R} 6o& 'U)"Y%Im 1$p}Ucy(&g|vpmL5T*;w{{ШxŗUۻYVɓ&ٜX/0ga؝xi"_]tEc9' {\q{G?0 7qAŰ*(Jyv@p|!7or}>LFTEɛ\G}+s&p5<έ5]1ۍ gz|/5j2,-α(g-u3dMP8*2c| B㲈\l! >{&\Y 5[Tۍɬ@T9kXac>fՔ|_>zJm}[I@ GA@ g$WmaAMF_ JUIDF/c U$wcE{6R"N7GEM綹+8Ń/SgP*k{ŗԁ".ՠ1Nd+5X^ݪ3ыmI41Zluh1x ;[F8$bEt6 VxJj}:W@T h Y5(C9G4) hOU\Wj SGuCQXkhH^J#@%4v4㌀e.JH0@cvfMU^UQU>P!P V=Vf8Fh*N9˜'KWB^`b0l8y8Ku,s'=({=cmmYz>>kO;dl;o{L1?~<诱K3*Fz#RKc4ǂG״ ֤N߫ΰ;Fݰ6Ț ṓ=Qn<A_Xs^H!' : QY|it)Lb&I|ѢݓޯS@ثǹSXI i/jIJGfx'( h_ 攬3y?v̱5Ho6$O{LRH[U^ u1ޟ؟ӑ?]CҐ/!ql}^n .2YyPibfWݪ ҂lnV&dYXwoܢ(Kqqʢu,RV_7.-7KE-# FS;|ajj:dOA#$i` F `^_{"xIj2ӵZ )C3x+@uL~:gI2Dǿ+L}0-?5{ܐ$f0P>Q zX'#d,3Ms^b=it"_b*ÁakkkxIRHG._kX\Z|N1||YXE]ƏeE4g~~O!.\,.ͻ<|q&#?,X)7;MO?3q1o'Y9P|/ʫx駹r2akcVػ (ϭ N1G ks«H-^dOQM4b44(ָe&#T#XCa-Xw^#F䲰 (k #`T@`gF0d`cWY,p dl IՐ^ Xoև͠uCfEAbZQYI$A8b4M-Lu"KZ.,Cr# hL44vﶵV 7Ar(tJa/@jfΩ3nJ:'6@`Nu!3qځri Jاmhne24DZp9 pbg x)d_Xdai ۻ{ [x._Wٞp Q s'/D8OtoW湕Ӛd:VɞC>UǰʚH:zOrHgҋ[3K;-!.2-0}ayw :(nݡ`bmt8)1}, qs}9x)ɵ&RvG\}}N~y3n_3qU]^u3\xͭmƓA^Uβ 8{'5bسhw{[ g߼̋h`2ܾ}m3H`NdeecRAe> ;FB3S0-D YV7e."P. +Ukrؘ5:J4[eب*dψ1! o̠䘲c?5E"I*,&v 1Fڢ3F}(,ٔ 犩{J0,> Zo$->PN/6ex?&hej1=tY) L LDrٗ1ŅmHj|Y<OdZR7SLqo=ʜ'nZه3CWcݭ+{qӵr/}ɍ&{|X^ً^=hgLh"O)sVo  V@VU7y~.K`~{N=]߷G6q_EZٶ@_bԉ>` IDATf,ُ]U_ki4Z?wM ڸ οs 7ofsk* njG=,)\EWD>Ogo{N6 ;a61i]i}"7Ism ȬD6r=ў''# V<&|HߡeMg/=k<]r~{}M݌m5+yeէ=S>emZY^be6XZ^ɓ"?2:/E]|px`(akBcY[osIϾŒ9~?`Їy|yN?qm.̝ػogz\| |17M>1[5=x<4/_x_cv!l8t {e[oos\afd\~no~? 9ȃۼs&nf#5<O:7oރh9{s+s^{Woa+V7j7 oy\c+'5K L\l)ʒxLY]tE]tE]tE?"ƻ# c@gMM/E9<"ZXQlEo¨±+D6I z/l1kyM8e!tƴ`u~<8i}n4Ƃ5@C-f d"p$:|h$ؠgRL`1(XCT;7 3(3'pf pN}[W0WD/%9ek+e)Ki5XeMqE}mL%ra"dX#*)&|Ob4M ˝ T=u0[r(_6&P⽛ֿ5OIfK٫p __a{g0iVP&T4n"h/ |ä5Zm)Λ}mY!D1_z4QOjH## qSR.%(ΗP*IltUaTgѳGt(ZK=PTe7P!F!*xjIA[fZ[Q5=u,ҸIM"$k3O1gADDaD Whi&f*!bL'4*%Y~ L4=q=+ܾ} IY:3ꦦ*=~D.,ep` 0FU$dl"*9#C-.g^~G,r>!.=^1+M;{Nq 7:ruOޑ$uγ|~?W쌸#PEl" =URU{l3g8hx3>x}C3 y6lTŽ{袋.lct8~Y.{#aC~\z|k8x Oaf+͘kk<8`PÓ|7FkPxj.Ye%6SSqF~o'k&Fd̲Ir31qLV)UT9UY" a&i|Se1ah|bCRVѥA2L 9PpչB Y÷ @ os. ( ^A8`P6ʅ*+T^q tYUH 䬪 >۲ rQ8k>Y7EYޗQ=EQoO1dckLYI=M(k/)\5lmR*̎VS2+y޹k7WNVYXX9Lk*kgwk 'ʭg3&7v=*O. 68X벷^yy8+{Fй1⣀(xM 5]wT'|Atrޝ$[on4 2p"si/zr* 3ut&"[cT`ٻO<97b83JVV}k-ۛ;C3CGYZ&UzW Y,ݽO^͍ Ndym4gVLJcV p:ѳJ[5w`1\x]Wys81LrWp8Yƣ w[o͡{;ݗa=ڵ۬>`~qWϞg}c˗swxU.\k{뜿|+7o2Ê!m޽/~Oؾ{pK7h&#>߻qΜ>o7袋.袋.袋./B2^$V?_Y-k8'Q^H5JӅzc)#/L#+lt ,Q.@4X[ 4 p,c)a zRlB#oPKU-:cs#lFsR`GJd0SsҒ &(E".N剶cT&(B?|=F 13MyT`ES FEZ$W -L,1ViHma0%J,0iL ?LIm=*51s-G_ R1=e8ki[peD:AS8Z-OGnrP@%q!7i毗D4Q@根a0&{9uhS.S!Lx[gL5uD6;)\!m/$1#{OUUu-{T~x$(uʒ^uͤlm4 ;z=]KhsE p'KScc}():ikK,?7իyw/~9OWӠCb85>$6e*l`Wf@)Ku1]+ќ|tmd\i M 1 a$1S IHϬS}1c2B!~Orr!U A9ZiidJʦFoFX{uV׼jO5S\e䮝?vIM#L]cj_L7ӟctN׈&5;=9{t^d}خ,pPk8yց Jgd)gHvڃSُ8:F/(20E{y2;3zYܾ'qģ=vp͛7333TUi"qޞ3A"l#c8Ʃs7e5]_ ug73ui΅9uH|OGڨIa} L&ȑc9IlJ U|X޷iƶ{gh>3P؂_| 0ΰAN>?.Uz>A_zҥtzsanqW^gy2޽+2{{k ]8_'8:~g"gxO<αS2Mػ{<8b]ﱳ3gYpa8FY|^}9s9-e;:;5pܻܾ>Wp>=vo~,/y7sy4 `k_f;fG=BLx{op2Ks^.'P7]vGYuo/?QRtE]tE]tE]?)/Eʲ~mh]H& aHq O'6x5N. R=mtuJQ[< RY*Z@Npdח-2޴*`|)EZ|$ X43Q '֥C%@\ax1Q 7jJ ڢ봧,VH>f& {Q`z!ɓ\|*T4  2\4 :N+%-@eB"3 Pj~sjayv7 O,SM`D*9Φi$}"-ve1-;E,7p*kMTof-O1mkPצI) daeAxᓿw뇚\עLR@r#W8Qg2 R|"d۩'ol5U7ki Ɣ3Pz"mL( +ҬWe43jԼ s@d\ <WUDtڗʵ3samnlر+|Oxw{&P'@S{RLC[dU ]ѼI[Е=Z'S7o2(53*(Li/r!⛐[KZ˔VZ :FNCmß47*Cj FX|@ʀ|d0/&]3ZPܪCVhFy2[b#6Tz֣ۖ[}U h =(\ʭs|u0.mҵt zmO_*Vj 5MJrXX鑪8t0{`sN8My)νs4bvvgӟ4?$˂De(\)j?yi1w6R,߷[X]e~_CK]t/z>eYm_>&1N3ݷ(_bniW޻Wys\|f2avaD#\kSNKx4_l<乏|ٿ74uMU,-/4Rz  ǁh"y =7lG rM >/~zÒ<$Up,7{CphO=q'ͳpk޽g/Ks\{og?QRtE]tE]tE]?/},p̀LLlWA-Eڵ,2HYqXS(1J"X!F)Z'5 IF 9o+ZTF: Ê/)o!N >XRwx<-TƘHb(0Z0¢H&p!}f7أN%.eŘ$[$h 9|Ɛ}3a2_ߎKjr~#uX5Dk|.#.zOyjB*;l3P@oA(Ed@ZezO`n[Z&K`'v޿)׌WL>Mi-1 $-IR7_gH0ueq Q0 i, &Lu"m)H y4 EQa uQ9eH;@eaLuQ`Y4R'>H^U$bP"dX8a+GPڰ^E= *KOyR7]^d2iZe]㊲Š;;#[n^U=| ylnn1k~[/`#f>ln0 Ɉ MJϠe7yoo|3?#GiԪtQaJl@JC@ Y))"i^a(1fFou Sw,c$xOh| jÄUviRo`Sa)' sV`L5xX׮ A4Y6M: bW=dN,GɁ)D )B~3:ٳ0Jv {Oa۫lm0e>ُѳ3g{Ǚ.袋.袋.袋.~D4-ʊ,<g1xxRKF<&tk)3dCCE!?KQ=kYn2Qp0`?;k &` eQR%>xa+bRR&2 1Pc$1\#Mg ׈(2:bmR8+,xa/SS&,}qj3CS# bcQ9םB|TB0}R6FR6Ѝ lOZ[L>ojDe) [ 8;+~dUk E@! jfdi*CAn m IDATi БLB aḓcB60-è<PP!3zi\he5{|G2+eltoْ: (~S $,2fsj 3k|L&Y[cfF%Mt>Q{b TUO)&^4H^8ixL[ۻތ ʢkBMM<nݥ LM|Sv-r(F=K8X_[b8cc{O,UUQd͇K|s=v~k`_w&;;zZQF!en$zDe[*#kLeSʾA^* mf Tb*e%S5tFse<kOMznh0i0ʲd:{n=/D4$@NG4oH:4Zڠ* ܀A4bT9,$qVȺ>U'@0 MS3  slI[{cln0-> \NGjTVRKBDKGsD(:< `>Є#b<7݇O;un+0!ƱHQʻ!4@gnn=ۛ;L{GB8pVt)G]>cDnKkT%[Tߨg[QrmKMzM?!MLV<9ϒ1C S6%dD)(kK=Vι)f4444~SX+./7ϤQ 065vi>Nȣ'СCy-B]tg?oͿM,iBMQT<|{OEi^U1ӯXwFuoo+/~%1N>`ge"iXd8*KffX]]⹷xW rVWxk|٧F3cGq=޿~so3O\h^kƼqQ%ܹs7N,/'F3K{3oSBӜ !FVMfy]}s6ľ(jv˒(YɛINTWkʩ)'d$='^FxI6-Y)JE\Dw<<{Nw_[QT"޻st}~/`L?+deJ3NMM1X3Am{sL5ybb$4dD[t:!q*WGQ0cZ)93 yU0sԕk0FzZ*E[em;[~I@HDNjAj@`ԇs҉9@ۅXVUS^:QvT&,,y{t]@Xs'yU)Jz5WLfk1fDzի` Y*N9R &2ɈCV1ւ&Ku'4;tR{-LZСגI((ғPwh:8HP1FڛkϠ M1adD힟mj~Ӟhօk LyU<îL*IJ043MoP#9+5d-zHm'7d nQ]6%:u2E)6CpxɆA* ~¹F*=59Ps9}>r ӇHG*+L5a ju] kq&(RME32y癴_ZVѣi) H.CK"60:ne ֵbgШ?>rzPa9 7͆MַUEXmf.?}o^=2V\UW#_صge͠8q/cll(Y;HY^zeqgWs%kx /̳jb W_;7l%=z/>{ƽT59s#GaN<˲哯j"b(F1Qb(([m vʢU3j*6^\RUQb}jkJh_j*s_S:Q{ b W8ZD "^u#X[8墱@(M&iˀ(Ě6 `%tU pⴟb\{Q@ fǨV+ H,BfeYJG*& !*UXk=w% 7$yec!ШQdl>bYc"YE$tc ?0ĹB,Pq7Kl%hj9%$5q UPTRFE޽:*~:qsFj_gQ %&5)1?N֬1=XEoV!|!&"@4 )UV)^^&5qSYu.%rSעWc(& 7?XkiKZcuC$g*/dh%u-C4|Z:Dj=95,+@3mv-=1}ʢdlK]{z>5R.HX\Zja(n k",$ >N k|,_kѧ9_.uѲ9ZN1c S3;wgNrjVIeR0X6b+|N~.{U{@5 `4iG\CP /_'XR [f K" ԵgbbL4R`1{ ha|V7+0mC]XQuR$a) 0I% BΑ W( E%HQCRsHP6 N-o}ln}j+*:lYFߓkq5[P2A<cLK7Xj|B32v BA>=:l;Zb2u& nB X&)Eܯ{tMjg:J ZhkJ>c[ni*[(_=BLqqn3gϱ/O?6c\3'O`b\ַ׾u z=)-ٸnxa۰^ T޲0Ȟ+v(F1Qb(F1 @TUΩmfUB`#e,(pNDbEG YM9gT>R *LZA$Uêbk5ah-u-jB' pT+0_y$SK#4蚄f}MIk%ii F*J'Zb!!1`͐^UFtVTY(Mָ4뽀c(N{fRb\@] l9VDUN+Nx"c*ۙj  T6Q a4!N&)rZ Ӟb1*dA!XFCV;RWLT56&2I5qˡǮ%t "F&7k/{Iv\~=oz--"$"7TNbl$*I1Jw޵ 1!uhr9T1e+($MF}&!쬧;eۥ(]@FoU>cL{>` qJ4 T:Ͱzu3)pu5`ӾY}!Rl&aT*}E\|=V`1i<8D &tT5U.s2۴ڣXl*X.Ɩfjɞ:݋q T@Nnstu5 s>GTASx#DcHs^E Z_Xg) t;g[mg TCGHѩ.Z}. ⡴X?g$@-WJy󘏑@bIɋ>[-ɡBmI:u- zb.$b7b[HᕁhyQ΃t?ۦ5)3c-pq$QbxU?񑟣lt; '[*¬i391FY83l`(nJ&&)"˗-cgWPp Wp3sǸ憛9w0c۸j}n^|)Uw=| 3/,r蹧M cb0ˊkc`bT affK֮b4:yؽ˯tX|{\bjJv캌+˩G0}S\}^xGSǎev'W03=C<}~5F1Qb(F1Qm|?C?E""&*U9ʎEBT "fTud1U2&,E)oS D j+6)3.1[0oKV|sSYIB7 n\+dmm5QI5UEEur*DIl1:9I(UEc ;zp0#)_U(f@P+([۪IdUIB4:UiQ\ۨWuLJB*QbRʵ 59r1 Cبdd*DaTYzW'$MJ+04ǨIׁs.c$&d0pN5%`(J0 9N&_U 1\ k!V(hZB(4'#ǰqV'jmC,S]Y/[D@YKU׀.* ~KK`HUWs3c(JGU,U2==à'gxXt:~}<H9;˙Yf&\?CL|61B,a˒3`ۖnvEy2$ۀi6&BR&ȟXZ:_dKTu->gzo_!%2Q' <~Od%{k=bI{h> FZw[iȫJh+8NThF3&5-7v&P{!ↀY(PFaϥD{ʳH59m`LNPĤp@Bd<u{DMLg9TiL|%fmI5H43߯ԟגqޟK!l׹w+%{clav6ыj497DXK)̒>V-`0(\'RԎ"({5kW?S†ظd$p5א>W4O1m@Qiamҵ~ae]k_:l0ìfOW_a%F#hBy D|v-P"đ;vW <5F1Q|b=>1.>jen/HHur5Nwz!DAɉ.pL_X`RSm[`m]w|yVw76Ο9Écǹk)n}#ccZ~~) c8r']Yi+N,;>-莏q)V^M=秹87ϙSYf5U5`Ŋ8c}-J ٴy_>.^cߗpq^w=K,.-g鍷>Gm_bڵ\Ԝ>yGz}_7nf5\8s;.9hZ斍b(F1Qb(ZEXwŅW }#`'HY lw DM@ߔQ^ZI:Kjx˂+:bMmb(W *̢$m<*Q">7=Qd%Ԣu2Q9Q:Š8DMkO=$1C[U;'U6BV㛒Έ9Q$i,HCE݅m>FL${fiUhjLՄfRZn`CR@_P5gFzw*'jd)_|b0C4ji7CAښ1̤&g(R=j)@*C`-{GRuEMpԸBTIyմT{-S=l0n 熱U6R(M,oxU\˻\ƈe_UU 1yYP Wp'^1FAM*'o#FkB7)2K Q<Y&D:x/(Z-f0i>}_Q*YX65Iݡ_U:gp-b3hpN|1}>{~nAcnaWp1zq1t\{Uv> i1Y/ϑtԊYWy}38DUx V S5I?b(j@d07GVv-vEAU/eԲU5 GWdկu )xUnR&SvNf)hv^;Kk%/*+]iAg1q IDATZ"+~)F`1e#5v4M>o-rCv?dʕ*F1Q|oUBJe3 PU%?4R/ [H,`g֝αr6:-o >756-3?/-PzlݶP?_}gbj:y~.rQnVL#O}+֮c lݾW311`Gl=OlQS2}v宿,k֯5 g{ؾc'C ֬^ Z{۶11y'zj+.'8w ef ww(F1Qb(F{:ut%KZ~v(cM{])][y gZ<5$mX| [e<1zAէU PZ|V$Δh!)[T Gk-q\V ȹJ\s6ʿ䬱$c-Ԩ},h*G1nb,>6 @8IM*g5*ͬs+#V>HZ(,iԑһX>L-'tҮr=|P=c'YL@UXwFlngoRa ن< =*e54jl$w/Bjk'z&huQJ*>f&pgȪb|՛7+A{?yr] ;@@•Ծ0%+X1*- v2wm4N!dBJ mzp3 bHJCb[N 7B)˂~hJe)y*;yjK1X&   W1\]d%ˈ j:.1xX~_M>/pM{Yceob;M,+l%ԳQ5s<ןWXL`J^}H& MKϺ!o&45\%er-{(L>p^=yF1Q|oUY@?ԕh EوMyئu( :풱N98|(?WcYi3wբ(3g zlؾr|]W_I]W9 đ#>GEYw|%z~S^~m۶Id Uӏ?R|GǺ *φصrN?Fwlvdaaq^xy:]~%'frr~oz0`Yb-}WYg>sMwzJ~] (F1Qb(F9S  B^+` AU/JqXg $\aA6dF@)%aV #E3M`*jRZ( u]U1*&H\O(*nl5Ib䬵h}VS( $sNiķS G2QK]Q'&6F-Q`lpETVo`_jjԯփr0 A#Uqk-ڋVF>eSFת(SWz?MeP @VQS{CMMKcS=95i ˜l@KJuDPP߀ng\7t.HJ?juɞHj/HZQ'Sf>Þ=FҫP9u2D]2Kehj?. HYŃ@biOJ)CA!51brU's]Kޯ+YUU*[]5k{w.J^η_l}mT@GΠL.,00 8tǗ`,3m9vv 0))l`rj9ƔkڳMr-Y'Zt3kբ=%Ш.qch [w^|9Y8q(~Çr%\Cc٬G̐ݽ9o ZL՞kRsSGljoKlj܌MָfNEHi.WQIQ\Mu3V\+{jԽ;zl:sjqnvd+VyH}m:M9!^o>X|'0EDi,R<]:A0r@>'g"KVCOlpscvi;ᤏ}U3j|]9r~ugp1lJg|y&XJ"kM!?3226[<7G-HsC?xh1 g&aj`A2CO*u璃J4F!DIs!}#4tAU˸j:({]&c??͋[qV6[ !z*a;d%JZ x\$=<-LLMRZ{KZͱm|wpmછtcj?~la糟sワ}g0豸У0D]2{c47:Z8kpְuLN[a5OٟCzf&&8s4vzc9w,qvM0l9z0S+.ѣXv5G~Ul(F1Qb(F7ŋZSU%7nuŪњ3lUEjW`j)U.̆VQ}28U{ND uPjJ#6 u A,h*2D8W0 u cVAU{y 8>PբdW T+f-dWp4omJ-,ڇfu$"Ϥ^~t(ӾDc'c#&x*ǎ8+"ldEy-p$ $1H"+D0alQ@j1,D柨QcB-BS[ V b^#9thkZZb(biׯoX)O&u)B[UdLX'c`E== &#"yk$0< b3d]7%`,-~XI,R0Ezp{s?AYz~D 7s,}ݜ=}QOUp8u7ݘ;Fc]7DIk=ha3^f%FmuILcz.$ŧ9 DYȮ[K}A !* vu]\.N9bC,k돦{:^z(Fwj5]Tg|b@ UVʇ U4>F+xXy;W`;1E1?O5[vsOgTuMVYJ1L?˩Y~#^]_E=s9={^x6lƊ zbΰYf toz32Gd%'AB<~8W_xO< 7'ؽ2_ąײzZghOr=LN=JջȩNk ۰^C {³Oʒ-/e,oȩ'zb9ڴU~{}*(F1Qb.G~Y-UﻨB-%zQH,Vj(JGpCUŝ D/8KuII9i}Rׂܪ$98k>xZEIalVƨ*$(( |la`xT_ue L0u1I]k_SRh2)d.zМ[ ԈM21+lV@&u}B]ڑ=DM#'r96?59$,!a4oղ5p%h_SM&9TBbwDM4V5kc.PlO:.u zRd~a/h0crs4" l0(P{&ϺEe {Up* GǶY =+ZD BtcAY(dcTDyϲ,hJu7La(0.)<1 ߓHn9-|E!{0 ҵ+/annKqݍt;n>X +Jz/П|/qcJw&0%}rQ~Y';h@iJ^TS'gnL)B>H@AZBFmI0VK-?w=AQ.o^ ٤4M|iw<'"vBqkO]W9d+ݟ8˖1|Z0Ex,Z@usH{1MoeQY76q5 j\I^cBD' [mT p0W> ҞQP;ku66D<󆎝ö:pUֲeF֬^,> -GY0M6I}^ (HTVJ>ˍe3V w4>.X#9]rfhaH}E9Q^"Ȯ2cBc#5QA(Ɩmz!?T X΃>Q{ 6~鎷@l1193Xn=!VT9hu^fnq {?->?6sI<,WN98|0=}q|?X[Į˯75k7nbeyc ^/%~ї?KO>ӌ]355Ś[ضR6lJ5`˂stc,9?=C/=LLMqǧ'wr-ױq:"0+&iw8v(O?ơ#y7Ob*G_>DlR<,[r5|7}o7O?CsL^?p}yO"?-nxOqogj:.煇:~]7rfN|gF {_O]x?u5xUK>ys~;_kw3{+~'?O8cMo;k};[s'؝ ƺW|o~_^wk0{|C uxÇ?sO1(F1Qb?(JMuM[XB,(5` @YD9Itع) QeAUx_\+'tP%-$lBPЇ~]V*dZdj_cu޼r(͠*hjUBzR}Sˊ6c&Ǩ}DM\$H'&TuS^MTl'V c{2hZgp%q3VGz *TelCU%E$ l:ыr+$TEeU@LN}:c)Sa"ۊ&pBN瓗CD[LW"O@AD]  lڢBQQOM$]c Ƈ3B)ymX [!DS+;3|KׄƘY} V /,qt-k d}q!)MR7vs^V6L9͘!rQ3 XPՑ`".㐊M. 3¼%h@z[Ua׺Lc!t^eG*>=Sg%)~.C5&kؤKmѶVU+Yn}o\sݵhiAR/,I`DJ3pE7ަ3dUk~, 3umzKOZBՒ֠ke_PK - Ԫ.͢įS(?:Đn;}hDAu]ÄhX~˗MRb?Y'\U*x Yyn lGk˔u/DEk|k]Kq1VZɎ];Yor'[lڶ5źe+ww=tVY˅%NGy^w 33\~^6oJ+Uc\{MHٲ2mo~d|͇0;ߧݝO7pgqϟ=2ݝ6؛WQW}Z:k?/~kO:玾-y gx m IDATOq;/e׍o]eЛyk|o_(;|}[~s9ey+o9W~xn_s_񽍻?K?Vw ./޿caۮ~0}{{G=y_(~ϛ_?Ϝ|IVoxݷ2|'^xOhyu+7G1Qb(F'ĂV}> jmT#%)[RUcBZe FUA jwj:g'jH"Δ*lbk\HE%$1bI}Pd\Trz*FthPQ[cZΩ , %Qx& ȶ:Qi%(\X> @ШI ^*\d''K?SX z3Mhd xZ,,CVZ~@Ъb-!XCJ5M'EQsQX ѱ2 p''N@oty^ʵE4(< E9h\_5ИWU^6[kO!)L~Īf3zP^n j_ H@A&:Ml +ԉQ `PbB.C? 2꺦*ѵAcU0R~%`7Jon&)@ؓ! Q.t;muTԾ(Z ݌sYkU]c,QW'sAPj"b]Z"ťgGN25>셋;, B])N>O~/Hm-O 6b{P`]Wrut:mz?}>{uC=)14dThgkTԘivR!G\ u>+5Z]R!T$2LYʜu%<#ʧغm+Οa,d jq0/쇀6 ļ71 ݟV.r1::7U(vt~1ٱ@:b* j}z`uSZb_|{YYi-W]}٭paIdq Ca^n_X|f/56v*R-8WNЀX#}ݧ/PI*W!PPG^Ǧ $Q -v@>7~nTXq3 #c}QVk)x6 = WXciulܴ-[Hk_ٰi=֯*1Q-uLeLR\0iɋI?K5y^&8w8-Pt1OQr?IV?kc+v{`in`+p9z\u5-pڕll;33s|Wݸf):wɋ<ή+#3w7W%f&h[_0āYsoKg/߻fk_=w܅oG -p lfW͗ĆˮÜ> X\y/+sӧ(%<~^ey ݉e|w~)+S߱cr8LͬcwqF1Qb(F*0Q ,(ch$iqH6DIe.UI+HkGU<YE!J5mb)9!t8Mյػ*'KBQ3ΊJ*5Ip`@Y; bnE4}j] zYg.Th41LD|\=FDd6b#]X c0d7^Gl6hB^DBD: hY0V,(RoJ;9(xS_"1HA=8+r"P h0z6}>Or9/k 9QjA}] d{D+#E6Lіhp$HbڨBg-sGI:^ E516e(=l9OyXiV@UK_Xv(^f)P|ԗ:"Q_ e>QZCߧ,˜ k϶V!)D919=A-jɋ66YKL*J։-,Q *-zJ.sk7ÄfnUVїطti|(p11mh۶lL, !Z-$8QՂԂt}[@2VI*E+C ֨1&*{5oT٨$k\@h`FgcDxM_|OfZ~'~ [(Ldbt?0'jRrP-㵧z@N{ԫ<ĬlysHgiyĭ ^s+yQ+mwƒa8 ǕWblڶyzNG;Q<˳)6}9٣kMڟ"CU 1x U]{:6Ʌ1!V#^<~UU ϖ3B hLEb0W=hm7̽8y7=CL5=O5Q]zL( ŋNSeS]2OݟBd'u.u#|џ"47)fN:/ i"}~9$t~Q[8r^#hѧwst(F}*\GNt{,_5pQl]ew95O+@&B"P(mҭ( 6 2P@HyJ͕9g?w} ~>==qySvLo&,9qZ] p \X<7ԍg|_cٲx<<%35x~ﺛkQt`lIn~-o3a8ev]Ƨ'rFУ+`rf?_j쉙~3Zԓ!d_qK/P7 b?ǝzMUQ"uSevnvkxʩܵOXZ<x(;ccx 쟐Ϻ,[{0^w wO:u${>{{̬Xgp󻶱;8sC,]{0{]u'JnUzoϻ{쾛۷'n{3+cIz^;7I>`)΢({/|U;ojl!㽉g8oIG(qkXVy>Ͻ9X<ְv׵rv(F1Qb?06(4f ( &KDi+eE5,r]78k)ʒ* c$;GZlrZ$}v0aHFecD)DCYt\ ^}8 (DvL1sTDe'fIqU jexV$*ٺP)cAihTj7 S)J93raUIj8bH5` }o:Fac:'E>x>K7qZI$"d'DS&̝qDlMe%\)5Q&iL qPpR8WXmV$ErSY@=# fj sMCyCUȖ1\IʺԈ1KLfƥMjCF1Nm WXio ,F &*fI B.;GUW{hfWjPPVH]5xk(K-(*/BkA0 n) 4>Qiejj> >1Fƺ]lG|QE!ud~aeblaS 6z%!no o^IS7y|s__ac?]ffM K;L/[;{|sկ|pZz/,Ks1p$ת$)qDUз$ KYԢ" ga~b긄 aT<sx#r V;{cm\05:1(N Uԥ.e ֵ]d"IL-eEH>ϯ$-"WU^w<6ȕ AtL$\tf~U1113=k-sMT12d$? -ΓȪ^@49t >R*Dڦ2f yjEX &9kp`MDDEǪi 0T[ϤMPy~5&#d`J<\b8 )i˖-sˮݻ ੴ45C;yU7ہ'sThhBHZZt|' <^ۮ$x*e= 47Vۢn$OF س̚>QG/Y>Mobh˗SNgTUb"WrnSz[`rjB^pXbC۵%kbyGXf?=xK/KW4A5 4bUsa's#w<_a]|4MK~͏)Ͼyt|/0¯V!w25>eQA oQAz(a[PU-\&6R,TSekc|U(>45nʔvX/_i!2&ع A;#ɷ4- *zUXO𪺦SD !GסuS-:s?f?U+_s.~w~̅o}vt-qc(D( az)\)lfƛK"猲##&zRbF#AW^Ř&Ϣ@Aؠ@I`W!\aLIek*taëbAh,߃q6-FWL{pA,bA i{gA$mP9Yj%V 1BPqۭqayvy'eع~>}M3tpVML5䉐Q0Vm,)0WQT"L0omZ;1@ǾR  I#*7maJF1QhcYff`gDzzvN) XRKZXze CzNӟ_GGEUkǟE ]9xLXq}uq?TR uq.kN{M;fgů~N5}_4_oaGMx$ɮ"/MTRŋJ5l5+9g0ߧ? }ǟ|Gs +WN8H^WS+vfu{cI=m>Vp8W4ÅY=9ں=H5\;>p_C~;WW?zoħs޽9޾D縳_e8O'?e7puOyoPvt-|h^Aob^f?S^Z.ovOs|oyvf>7~>E®>{lF$- ʩlb_+MhE1ZC*'M HZLH EhUm I.V BP @[HfG6y+/E9] C1̪5sG3l)*]j8ē8ٽ{>gd^ۂ+IPehQDBT9cUeZa4'E6L+ѻgmb 2: %F"-WĠbTT l"MH2[mM] jvM16AY]+ ,Z2H1OR$MLs$U0';*,j]E IDATQl7K\"<l57m@ӡ /Zl34% ^.yAH-k1;XDtIFYtz |?cnu,[bP#rj1QBZАV:/I-X$@7kZ L6 M &`&,Q<ёg6bldz<#+踒s\e6;p }ΉgQI+{\h іӚ⣑kqxdLE rn9ٴol!2F#Nch!&a2xD^,j4qZ#JƊ€ATf;yb?'ܗK.9OO(my}ܫul  H#}J'`,W̌(+{QhM $3lS>d4 CDj3N" I~V(h)cǛCN LBk\AMxIL^g-kpټZs6Ye%AHFA!(KjH8F[){ }EtJe#QnN ^gKUy|,K@ ݍiQP+&&pc0.'Cdn~8B(r~ݮ+i3hc-g1kiDbd0tʲ`vt-<,N=TĒ^qEN ;?ymN~ ]vұ :uZ5*q"FAǒQ-,yL2` y]il ZjS^T8IX[oTֹ<Aex/),Ɣ%n+Vp܉'2?;YمyjHۡJň^dPHő9c1M=*!{ER(C8/{OrpԼp~(zGw$UhaAݤt*:2$iTaz;quI=+"1Fl -A%x1"h%.AiCkC4aݮP;cqv? 41]h_eXD6Z#2/K^*n &nXTդ ڟz.k-ђ`({E evzW(kZiBu2;}-&5Lۮ)v?2IsMcUk2T,]nNAVq1r3ku*VEə; /D^8me'O1X'x!_Kmz 黨좜fm޾k uSbi4aE]*0l ROSEaG051A TX+nznzo¡gl|{yP*Bsc!EbW6s=n"=olrIۏw{1w]\5qk+݉i?wY:q>Lfw{9twy`GyoqTz_ϻ{a 'Jn34s̽lx/w|\{|~. [o;YVrLN5g2 PؽmKV ms^z|͆_;x lN~~X]O. 3y&+X}܇#b(F1Q?: וt;Xu &AINchn*M)+,I2YKQ8 L+$i2$0C->tu\^dp+2yĭ{pa(;V#F$$\ !@fI2r_%%DKVBr1: xB ƔH'jKaD1x ~ \ ք}((&mgc.rqhKBXV0)&K p`lI4 I2EV\*%""Y6W`kZ,&% 2$EkP)TeuB~J I@sT @+قrL2Tg 4ޫoI2&xu#{,J+#:jaC4TuMb돌a&9(DVǍ( CCTPq`lENXMd!8 ',nd4uDzN!&T,] wc0}!fz,_eߺsT 4Pp]w{ogfx?39Xʘi2f(JеƊk[LR]D`m=i;'~NN@$#IF:1Ў!"-~0@UW [.|Ksv`<ê%Xu GYttvWbxU,ZaωJB)RQC=)"[^=fS }gIk"@1s]zȠI_4?-6Sؿ>xҁ&$nOIz$jн@o}!Eb1I*>T WtClֲnLM4 P(r@e3"< bJ Y˃DeӬZ1ÒgYzT/OS,b$c,([grbV7_K.&kx_~S 6ƒQLL2>N!x1DTgxn߰?`wi>&F֊aUqͷ2t?q͌355&2c6Νfɪc{㉭02t-L.YGNz{s:>Wlp{}ھ#g\OS![y+I>~m(F1Qb~lcl|LkSv0FqW (.1Ԡ̗a ~|UCDpTI[Rһ($]Y8$ija&tJ0*Tv+%3@k)Nb3i#t;i" Nj2J;Ή',nT($`YKC \Z`(I\m>c,֖xVH2ޘP'BT8c.ѷ,5\a$2( P6F+#<CF# d&*c[d$Qd䃕i=&'%&pm(3O&oc馵*ͪ c M s0-[)J0} 9AcӲr]O6,F !n S7Mlx-ӲkNCTME46iGA,^5T1xʲ/pBjE iBA\Y6|:tǎu crrn+0J*Xo 0t:z&&{c4tucf/,0==$u05=x^,~0%|#>p1Vq"ay^#Pe8x!s2V5aCUKq݊ukؼcFƛ0sE:-)pZn?8uH9dPUǀnilW$mR{$oy18Ю*Z BkFlL>GУŏ:I:F- voaŪ },]˖ˢ7 /BL#5 _CeRH^6(pM="c 1G^^f}#H=>>ƎX6mt;+^3 B ^΁>pb ,[ŎYb%MsxZrA^0-N_ UZ;wB;06j0`jz[tO:K]{Czc%=s \茍TCb W~=s7;mNykٹa6= Oz~>ghXW熧}޽=ӍgӉ%+۵uzصeER4> ]76}_FaveO-_æovbjY+cf~v(F1QbGU{o(Kp7Ԙ >{!_~($|#RE,.Mb41Rlh@{2Wb eCC-5iv T/F2#ʶ| p0PMC,18MRʆ( DG@h$f{\b8i(8q7*|njOJW&PDZ,젒&cv4Yc =K.UjMpgKMkR>3D/ UMb$Y@0KG(W9h/-Vcebѱd3&)GZyL0ݿU Mbj#9AeLۨFT̶ˬ? i\ԕبשUg&&cubbYgE~#}TIReA.fa&ck7R'_N@Te"7pNX &By$0u1%@8d\eMYQ/cG7*&XN#Fðp1KGQ7Spbin CzLNN0ܾdX K.a,YibEU ^2֬]~,zn[n/y?|#>+0˦g84 %&"9q0šVYLǏ@%-61anT1@dM1\8+LZ ~Lˆ\.)>dY;kF֠Dl|3+zn;HƘ"ٴ1n|T'=DZM4Hchh=ϓr2Ztp 4tI-ʆhn!H:,(1) m\CPybbFY;E<F%QTH#ߑ5/Zu?#ݫ-ykS$d5pL!AmASbY㕭jb"[ǴO(PdC+M7$͝d|F:mU->5$6NsZQXG,_iOCl>|nh:._ >cݎ#([qdGɷ:͕oXd$9zbŲi@vb8rV?Xƈ}L*.t'ʨF1Qhc`o~aff*XAj*C0YsDk) S7^eQ1b\#`fh9MN2تLHulx!6l~Vsao-[W f&zx1l5y|놢(!Ҿdi%XEݎ8u p\6=V9NĎ] &&,SSUE=9tifoSvǘY'n7ΒU{&o&-c@$L}rY-?wtAwt}'Jbz>rǟvo{lU^y@{{Nk1`ʁǜƧ9{y+mǍ_GsOQ {J^ N|\bǦX_[`{6y-ws'ލoj~xʇ9nKWqÿ|I}ĩ/Ȍ:olT_블Kq8.Bv۸-~IJJֱ9@(F1QbO*XE~uO`c) ZHuάU&"ጀwDѪ$‘&$uY"1x;T5e!kJ~Fl &’3 _D‹FYP%1 \Nk(I=Xʢ̌9LFeNfJ=%]! EI0Of¸]/ԨԵ WJUjfS I^%wٕ!zLN4 @M`9RrAmOc̬cצ'0†N$ԇd]%Lfk ŒO H VY]9l+KI/f$]. u{,i;Z6 IDAT%Ue&w y,$^'锴N``N5eP4LTbNI]{g0!DD  ĻZn!R[Ga)Mu#yeπs4 9DBy|,Rý^o +Qwk,KN3==EUnKUU ɉ ȶ(qvNYLMM0;;$uVq c[xɅ/b_&߾[}b0}X1Ν}=GEUդBҶxmeʢSт+*l]o!mYg0QdUM{hJ>gN7|jHM$?ԇA4CRآࠃdc _#SOezbG7<՗}q?1|uCl:Nemr@+,6ȼ $=U0F(1VF5oiݴ!&!&PW:qVz-´V 8/u-b1+u9BFk\&z4gX[, JP8VYK7螚$E h7{! =!FiVkRalZ'a~ 3hD-6,_ktڿԷ4on3kKwF\m@нSe⛆AEg((Y8(*yWlI(X-1Q$sDVI)bͦ" +{Gۯ$YQv",."(~Opi ssWRv +'gP$(կ}#sV@$T)*վ-W},UyEA_`!7_w Xr9kgx~ۮw6>4tY{׃?0\^,?ް dw2>5 oIdWES8u`u 9&ʂ` &Pv;yul޴#9BdkʌHS\/29=5k[ut\^azs۷]ĎMS U&zvf_ ҟPvǘ۵Gn[OO?"\yLr 3k1:?eo o/͏پ;~wEϾs{|voW~/CU+}9o _̋o#ڼ>nnO3>g_L-[mu՗xҹ?_繯M.wa]7]ƿ}zٽk;!xN9g8킟#M' ly'ϻ++|ԢXtGq.J^r;8MjǜǶYRU. h" }~#FUyTlpi'|A /cd PDnU5+R&SNkňSⵛ($!mȞ䍏8AA,Y)"YE st&S }fva GS7VkuzXcXX賤n%ӓlۺ,tJ W\!0;;Kcb;Xt veUXc=;%/u<8d/}ڽ>|‰'We>;nc?p-Fq/(*.<@U8 kD!mQy$@&M!$I+C,*I,M(Q \Fc;w1ʄP6 6* : iu<cY:.w#tqYb庵Z0ţuD'e}  U(Nc:>}^WU8JR'iJG [Gaѐ؍ ɼ( QB |WʶP* Q(Il1!Šɳت1›1條!Z@f(~Ec]OIER Z%}n!~A/g HG纡vѩcn.kبԷocYSv @]Q%b, !cbۨqfsuVY-f:I;Q,u-BG61/=_WG=E 26ʾیŏ<ܦO#2۷O*\g2,2t:.uUɃs)'嶺J z Ú&K*;#B3Hop!*+U8I6 *KQ@kEpRmQ DB7@Jk"2Q[l&BR̵7x !NuQ֜8ESB4yFЈebgbX>x%1^ܟ5_Ĕ7 |:n q.XEI4K;S'@P^TJ|2IT: v6ZJ<4m5,h_)+ lҦa,uжMJMB\BSd0^=y[*֥BƳ-Ģ,gua!+:/˒@*Jp uSJ]37Fڱ))e< 0FUPET5@3i Yg  g3@S,ba0ćF6t,cc]&''h:X]v4XW}mkRh5+`hL#%57e^dMWTWck(a8k):e{Hr0u'떙*뿚!jEBLI3tI-/`fx'KX-,H gMV#V U05X!GދsV'$\2 @+4Oiɒ|{N ZBk\&XNZ:ka#I5Z543X3X36mI:ǀ<eh/!t.HiRˆ|ĀA=GiYW ,LUDė+.u]qQ(FËO 3غ%3BzY!@zyPiE;XA "Etplfz%4: CXl}]LJ6> z뙘aa~|4|h+RtƘbL`|b+ְ{nfٶynRg, 'eQPWt :k5Z*4TMCw.0l3c%>A^H!:yֲz f, k˂nC( H]Yj_l!1YbQD PIj}C,I _99]؂J:(\UF&ʢc^rLB< X3s dg1@ WPZ%c|bMd3]C*L'k,>49$mJv&1: ¶L (.1wiWZeF=5Qѭgb+sHVA&].n PԄhO$chQF;7r\&Y>!% mG 䙬6uPg#mdMrP"*9 +v%{8{6+6ZqZI@+A`mcCXӒE(F7k$G_>%C@e/@x"tʂ- H]7zbw{.~cι~߹s%$a|cB(_$ IrSp R1!`c, &6FXeɲ^sΑ1\3e;ՒQCՒ=wysy|*yVvZorLJ41W3Kd6e+e2͜:XѶVA8?;#NO{.ׯ_萋 +i*l7[V5G$;wIrJY/.xib%Wrrrεkܽw޻G>Q^/w~Gosos?xqqvN)+_2rsȥYYq 0 WS ?Dd \&F 6ItDTԗ$1u }; 4toefHZg>OS癛7~>9rwz^z^y7A-dFp|se5pE6p u5;f?nus/u~NfPtv/C٩Ӳ尢< $#g M9$,+.x l d1  )8n`ij&};ĥ}l7*=`x'0ELHf"keڈZ+f  PQi*skg`pWvw47Ğkëǰl#hmԮif#8;X=zZU)Bxۮ87qt0N0(ΌOntK|MghXD078?#I*o8*nAgiD3>o_| EunoEqcݦtIJƒfI݄0~*|Υ(YyeM8q֩ yv嗾fOprzFÓϽw}ݟŗ~}l6O+>}c7]ڻif#g ce9UaVf29W[ʉ"~ r А,^ G#d~c^ LU16 TJ.!8 s'v6 e*OLX3Mk^{%c _Nz`r2vU6UqhI֔iJ뇜˯Պm[6YRCP|]6#Iir1= ?fg 8lՆb`xwk(PM6oޠwM 64O\uvƛ]֐R[!{O5 }gm05ϼV!-l\=0U^ `Z9|YeHwS0; ^cE!EݛyJ2%kH͞ dF^&6ۙy:3 h-%@`9ɢ\R`%l] rHˆZH7PXp3lݡL:M^p=].#p]55C6KZkL+\\^^9::kȅ s`Z f͛l.7\zo|-;GXpvq{zQNNXtmi# V"q$aqoNIjO /hlJϻ&.d)&߻@mTEn?.gG#$g ߟinP{gl^n|//|kq;|yET}fGCjugFN /qF]˘9%g`Vs47%_צkoߵ%%DʣIruZ?Snah)yk6/gWH櫃^!o)AExPo^oc vnI2ػ-6EaxLj~@kR5W&,&0e %f1m+{_|\MDl+$g_\/+"{ב' lspxo.UۏڙL@|4 AN; sE\`[P" ;&qBxu?mb}|;?>=Ľ{EG;ô\4r-Hv5;y}%ݤ!Jh]裏@*ܻwQe2O~ ?_}g}Mo977y7y Wog?e&>v?OxLuMKiًf5'UڼE[lq^ ڹ kgZq;׮]aZSy4[K׾eܺ/īy96 9< m+W{ L}|؏7o&o*:sXo?Ώ#?ʏr1\.WCy/}c>}|뢵NdQF Vx˩iwKF+%+Vٞѓ{u h.W1eU[|υV%\5KRD/#ԄPA{!G,+? Йgo.ـ֝ GA}H,@!?Sto&-&ဋ1Mߧ?FAՊɁIe=bxWc'2ߜktي*՘![wyL+<'΄ue\yUJ6&/0K$Sc3ᠷl֜0D8Љ{~{Q6 E"prfHb"yHfq6F"S6)OIgm,?bLgw| d)u6ڙ-qcqv-Uchh;2oE)ɼ[ U)eMNڢ$`ڤ]북( XqC5dhŁ2Ikإ>o6Εc4%SǛ&.jׯR'Lj`=ׯRJAR)v0A;jMԹyCa+;pvv4j,2 IDAT δ'd?8?/~q~gqUN7ܺym}kլۂ`9A/˝^,|ϟ/ YN`w8@!_ld@%ݿ}Ȗ?ZEݙvӜ`3I/ތQ[a~B23?r]K_❷;w2wg]GѯW;02h iK%=$ə! Y :5UZ)Cv;rakv^>pKi @-i'e哣 AvȅX> `xǮj!c-<s$\fHN29 7R50tpB:z0{G3V5ݟb>d]DWw IރY {7kׯ*ib*WDX4Ahi^l._n+(Lx7ՆOTv3C-i6䦢[ wdv_3S=ʫW>ҦG;g|GUy%_ZZm*vE09pvz̕C1g;5.kZ_G.wK_Ezyk<ē]؃AOT .Ev:Vgڷ2~~8Vq[qi>}cm+uvvRŰD8x**|zh\6cmQ/M)B/M{TJ@ nWNmu/`7ixNFB) z6VTnjL̲*3ڕb ;(LoJή"'Ͻ+9OcJMۂ[ƊzwAgۍm2V ! ãqp4h3QTQrm}x&ыV`_"0y.mWM7 €aֺ09@H;W!Ɔ2˰ Z'da0" cJwfw@ 83.^vPO89.ںҾq-j1^I:LפK|Sb=uc߫j#dK+ [L3LSvxV3C^<ټA9Xh3Mٮ b|&Oˍ3׎r~zg'gוC.6[޿{+W>8`s\966g^pQr^!.BIV2 <Ͽny㍯q~zƴ^"Gl4Dz|,Dl/N?g _\MuzFD5`/]1 "[nn{:`6wgSv? w\ו4rPx܁e5rν}+4W/)&;hxf0q 11)Arp^]$Fr~[q_eT({oix>]44iWeO'8x6K|\^n "0jeu%BR7cn=zC-3q{6:#RJ 6dcٹYiHϿq88X MMƾssO AZ}c>>XDc:) $@Φn%Ix+`Hc5%..7\O`+Z)S{JzR|™/|`VۭC2)꒭pSBa+egie2s߷'t޷2h虿opZ1VVVe2$} Қib&#ZdZ[P$4IiW+Ůf)fko%S5o!DKkF=1ݼ& .jR lJu\}HvfksֱYm `-E WUr 7ȕVޟ*ݽq7CHvi!-L\C6Yupq^ TO%w4c Ui5 漝Y')ΤΕ*]]mlz2Q=.^%tj,!@gmnσHf$EФw0j/GfDCF[|\XX83jDN+]g_Rc//U"ce3e|}Β@}_]1u?ϣ1bśƉ KSC⠻P[U><#\l.yb*S~ _Uz ''6y;S))^ثRL"'uob w>6VO:0HLF~R O>j\X^q cږiE>^(6^*ir+zcəVVdr6l/)1I}c>}[ e֚IKVcP MQNV)h@\Lj1dl(cEgMBscb2쌍TM)Uh8$: 9I:ozvig+uv: I)+u E֫@к"0 `L5G;Vj a_\N;C.>$kQ`_'٥Ot+'X\3BBkE<(Ԕ @Qo6:37ςc$pQe:F~G5yVRr0{hlMڥUx. ,\PZCWXeY;)؀*.4E&־$vL1q]>]q6`fu)00:bZ!\(7\eX)Ieblj*̪I)lh!gֺ˜gb{:Ŗ SJfupr-4;d3}NC$.e[z:j%R\n7hiJA\*6.KL%g^աiքׯs vyE}.[RJnyfi 9Tm8oQ$˲6`Xg>ѵxx^K=EcCAs6vKe0;;г s&zV+؟؞p-+_ǟڵ!M}vPm ʅ_m|mxe#~FsM95tMA7/!ܠ6ϓ`/YSOBxc9 !rϬi1^`R6_\WE(GmXo^񿔗Ƙr`A{D]o4v.ܴ:\6E91$;|vѼ֗ӱ8kSUJa7{@{>=~+ǜ}'#D4Q톇8K;S,G'7557TCe?!-9AiKv5D;{r+s$Ƈx17}GȊ=E.JBd8v;>og| 9ue1Z+!#Uq@63Mv(% 9Δʙ\\R^঩؋U1Xm bۡKyk*; d/~NO/ɇkT,ژrr/{޸v</~c]:^R7g_ʿTOO{?̕8;;pbޘRz'=Ι}c>}c툒!ٔU —/QBZT{];T;Ô!0NLٹ5ZkA!LVW5)o]^RP[bEjd][%9/D+ЗB]kNړdսDz""ȱ!2'e[bψuxqk ! U77ߓ^^DQDVa Rgq 12B7fa l.Ic7:V~MÛ(tdsqG]mj9vV ըM⒰;bv&{=K.mpCJ:ǝ=Z]ߪ>W8 BRq/vfums;5cڮRkg*j gJfHId{qRHrpfsqINzb}f:Y)MWUuLdYwZuavFPs$!NXfKʉuRdb5Mut9{'x/8{oT fywa3_ҵя>3o6yYJ2(D3F.L\&&C޿ H&=.@<'aB'A/?=/~]쀝W''cnwBiZ\$޸m̝·Y3?|7o?Ƭ6j^lM}D=2Gd5ll= 6K#bR?)-7ؘ.jj OT1x\.>ce5P_DAv`?T4)LG;rsrp8Phda~f_*%[`V&T&ژG3-aH2ka} ;+"/ U#&k"އRb@D GXB6$ڙ{cpy׿NGovNON9zmYO+/~ᗸ8?Crg7ܻ{;w=ܺu.k۞2'{̄3cQa}@~YC^5Jt4%_d0w؈1~ͮ0n%DÆ,>mWWlOS_S?*+ib]SI {do;7uuRFĔGz=Y'm=IKJā&ipH5&ƻEJbu*̛- _n6MC/4$ZZ;$ZWv6/.YW%y@{o씫oēO['c}}c#buoOQ83mqjvHNO|@4vCI7u֭:kY¤]$Rg+per_M4r (t;&0k^{uʤhz1֬s&B$dM &n$5`#%QH40LU= kjAUuG5wzlHqo`j qHD"N)0lZA3! +/Erхea90.3wkTpIs/Y7!sPDV)X 81;ݿ7Á(B/1 s_IuRF?D]vp{;2#ΜksVYRO"@X'"TܣdM x_9[F)S1Vy/scCo&M %f ȭY_ƺH[Be븮 79D#K w0)q<YKrszUhjM3ڒ7$O9FhG!/0G\&5fEby%/'fpЇ"|%_!n^4жPG ]v k_d8:c3c}킑1:cDN<^<1BI!Eb|\89;;w`l7[Zk[ƴZsu޽\^\p]xk\sMVϙ 8.w}6/w`BhXR:n4je ƺOIb9#dgn" y>s%>~z@ `{Զ.$)%Z=:N&䄒ӤBU ѰlzU^`qYRҭt _Gљ)GTr銄uj,ԝZ-Z텫U4Mc|d=" >[s/ȍY%s %V .)>}c>}[hJk i va}EWH~-I:D xpg'Tt.Vɥ6e }uن1oMb\8W+^j)1&6-s`yޚ'mՄׇ 8Ogk0|ZZ ttdiy&c)(9gLF]%F 뽓kuaNNp!kdNJJk\<ުԙ7_99֍c֫3;.ruBHPXm2.d)'[l[H4t˭*ξcK~NDHw 3yҺYtJ# IDATmjC^lwb<ą*/~3*UN\hλosq~8̇tGzW5Rv`@ly;[@WψdXwpL.e}cNb-mR֘sX)VӊT$QJ6{5 nMUcr 9c Q8KAJ.jix3 kԐ%V2vܜGb ^F--rLFw :v`\ay{"4Y{7I<ŬV{0ӊh`ZwATibE6rln;!u^OH%@Owu$ փ-wa%v@ҟ^&Dr,^o) `o0w=wX(x15и{|ߕjൺWkjr֟^Mȗߝ0@iYtPqȊb^ԹHNu9gpZ[Gy^[}u#b"I?fFj5Qpڼ/&.k5sʀ3D>l:aEm'j]֘Oq$m[}Ob]:2vrcZt Ȼ.uƚ6&dw0}R`jL 5-1{ .$}wQ'sJCW|0Mzdk,#666Ø~0ƽ`h65$?*gJJ )-gyi p<֐ICڼNc3?;sW*ͭ$s\+%}=;/c)-qKCbdyb.K/O<Om}C"5 u3x,J7gykM#͛y:u<8嫿D;i]Ğr|?T&3>Ӽ}=[T&yI|j40s1>gǜD ļTY9j(<5@PF>Ui65DϔXLo>#vZGҭs#=uD:C%-K&)N%SJN:zmKb[|+!=cr5zR'c:ODIVYZ\ (+ 98\3יܶ,yrqLg'\bLmXgu1wr>}c>V(SqLO*1u46>){! Sx RRDX'/6fk# -@)\ɋlź hEXS.(;IHZJd이OCm37msV^1mV{>熤=mw&1[S j0]k:Tm#koW*)$/21T^K7@FX"j4d#"1K0t~\S -m'δ$ٝ:0ݛE0VՕ ֪y])%rl2J:3ϕT8\l t; g8[eN)6hT/w@$INu`+:׹yB&ޭ'_re$4wVCO.ލ -,)eU^)8}p} \rjouIBzY/,q\7W Fj봶!$lRQXHO] MtWz&;LmJ* lN|=Vܭۜ4х[b;?$Mgj3`U1ϝC޿˗ ߼ɇ^|2 2(~Da'"u}"H2@*|EsJ0`Lr_:Ri^7} F/&&#X)a >]Vwvg[ClMEшMMϼ-LJ̳x 9օ4QCiXMXǯj517|NTI&r;! "䕽A70E$&55û_lr}4gQc"'ER!Rb㔅Ze`f!'Xʅ3ivA;^[6-YGx)>(w]޹W{fVu:o8/>}c>o"4|Ud( 'T;9<:d[Mɘ٦45)]c}1%{\MdX)Sjvbe)^Pvܼ;1\IȒQ)J!m$Rj5n+Jm3Bw`eLuv ,tF!>H. M/k7AUupĬy3u%c8ldrpYU Og JӻUE`+VO~7g.48>Mu؂eR{~Q6%.3_8W8`9FNqTV|v@%3%̧ XCVs3%-쳎}v:9 6#BN9RRvMb.KAqC>.ufm/`mkdiZd J*B70w6jڐRJa;Ϩ }_kG5D2 3kCC[`sADmzI@2FXj 1>&-l`\k̭$?"2Y$M>&M[@!e o\a\Lf삃|#oid;QB!z'4a]jLM :¨>I/aAy]ۿ<Ό=m #rʔ]yuBW4ވ.V43ݷh(Y_/'Xyގ֝R6V @v˚A4}[^Ld$5kj lhSpkkRct9i67 y?5t3j @Oy4t?d4,?Hn vwP7$:Fs)5X:)HFߕh<1 +P]T8,$8֏7"i&Sy|ק2_/rښ~W/k7Lz__җiWz*nk֛oquG_{us<ԳH$<c*uFDp` Y.3ݞINjuM9~^PoܺS>ᚄyti* :7$nc>}c>hJI&}SQra[+VP kއ9:XYz-%.KlXg"3UD&k` U/[uhʼt&+3[ng5?Zoc*u&9]}_WYdNsՊ!k,<{ )Ip1 xc˜"7hݙ؎9[ʇ``)ґLm/.[d J_IlS(y=$k Tt]"Bbd)(k _cϹxK ۊݽ;s#0@C?>0R 8Q%XP FV@d암v$Zw5,`"٘Onu ,;,;oGxzcD @]ؖ8oJZJ J)Ɗ&AgLloϦ kF0kHJv@bJvq0ogB<;S1AI+`^ߑJxPZ&}un)ɁT8Xj4O6NN/=dL!rG5PYl|obyE6TU,_17˿@˺5oZrʬr"kr``ʽp t &iB6̫HYS­ۏq|g?KovOVcj B*iZD) {xC7p @.ֆ-W|4noPK{6~mn·Xbl*ސs5{-![i-W[ {vK@1S [9!9(hY岀*JC\]gW(*_&yX <_oK\Vs|qoM 69אl඄,ww`TFF3ŗ?w}8<:cnܼ :5޽j5K/S޽ܻg>4=8>u}c|U TlNºd+H-ڶKTzmL0&+V%g&L٤Df31 $s &) Ŷ.H8O{"ﰯ d|n8$zJ~I׭gdvagU)ly!wMw>f&4^M} jf)D.; `wCR\n\=4z="e&$bݫqmjxKת1@GxӍ8?`6@en¾\]|"Q~ ,˨g_w@uCǜ+MB[>l7o ;qX&Мq p3t1>7yB fχҔc$1_9O#BJq=ݸɳ=zbllf>4 P]} ]^:j|58 q [͎.8*Id41XDC<}|[I@ڍ/JVL%sZW4!y d'N)y֤Udfcr8{okKv\3ϹVETkDjR5Qj66<?lOhݖZQCTTQ,M̽#aEXWvQыF i*&źnYho\7CQgfQ(p Z e\}ڪEzG-T>aѥ+LSNh}<|^{=c=c=cR]`T ̞DA2ALu|Dv|D$ 7SQbs23U(',zQXtN>l'Q m%+fVXh)njJU݁I5|dϲu&Z',OІ(d{"GMԺ(^$q Z$vQZQ8ϭZ7R\5Oo}-:6[L3~X>ːPkD@hP' MLnJՆkPb{ d&*"8&";bMv0Wy|7o?ƃpV[x嗱F]7=t\{E |2խF5@<uTs9fk5!ËTKx=$y,҉/;kelL2 I!P  _q 0w ǒryLeW9/!$/?7dfd,[N4w zc%#B2\ }b IDAT&w \C,pOB`j|#g$1ͥẕ̇7Kͩ b?Ɵ}OQ^~G ? ?Q` >O(Oa&X34ᕟ8SZTdV)>Uh[+ I8cY}Q+;֐2 F#>*zJQKܞq<bkhv|M%; GobYܹ>0;$,(eqK%/-% ҇OG>rUK0(9L&h``w% R. (:HRKAoC07pY-!/*x P^P<زQxR*zB~[U2 d4$!eJгe 8 !Dd\2rx&:ߐyVsyH bZf6F@`+HFh @lS,ΧmJZ\Vf_oR^3wz E.1E &uJ׍!ITIQ'ֵ֔Ų6sֲJ8} R-eCnk`yQ.=k̵bmi:@59T)hv ! Ak 0D !erpOg<}rso&fb'IC2^ٟ㉹ycG]8xOq ) N:FJuY4}wCs+"pǯ}k_2S)h%-! ڲB8uc;;B#c6_ƨRk%89/<_}!@xx;  BFu{w=dM6k00"D[<GC} Zk3[qu36@ e>׋}7?#$#6Y5cr"ॎH/|hOݑӱaJGm as~W>ٟGO?#|_W~xX3q<1<6E,BnmL=#$ z[Fd7R yւiXZHULSxwDz6gNhݰ, =Çxӹ=m :AɇaF)ܹxuDf=k&FUQtC|);HB 0BcD֐+r b>y}pJ6&9@#J%x_K[Y1j-lM/MJ?$8KA)7Cв*; tHas~DM hzG.r"sm\øH܂rb\ n0;AG|^Ip :cgjPEȳKJ&k \qTp)=SS!C;؛*̹D^szs^j:LxGC%0}t// " Ɣna"d$ r o0OI gO&J_ȁchµ3q뛀AE =wʦX }.:p<ΨUޢɱPlD|}/A8 [P^kzf~pZ;N+PdXwlZUEZ K$u#Ɵ2Fz -3Rقr9ubIvBM6HaJN¦.q#a7p1os9alJ #Dkhbs͚8".>iynsslG+kfצC3 -)xEo_+GcYos{7x^4W|ӟopus༜1OT%Pj2Fwi )Ìh`9.c}BzS!/കFԹdefĆ[A̝=㣍wxD=: PrLS4M!B9NOCHп&)*EpzJ٣~k`J K,N@H6mQ'uVqEu+ FFFᑌ%C7|웦 9ogrDR{l^@hHo ݶ"y B s;A8R"-(DҔX@@A:MD͆FwfǐV:=))+,m}*d &H!cL7v[z1F-yɺ5t&@?P3(QG$iC9k-M @Gxc8  `ޭ8 8(R2'iCfw]E }y;PPb+ڡJ|LI\mѐCZ T2%d,#Kcw-rK:n`[kCqu=k5Yhf~[|y[Ԃ)tǾ]KG_#s[2OR9nxǜsz|l3`pfEehHVkf|7~ ?_o3|p0CuC|z|"@i/uɚW<[]?hĹzP"=qj?<|Hߓ!s]K0Q~.$ Xjas !5dS ə: `?+&>hΔ2w,ȆQͿ'~0z^ *yr77W:V|>^],3fF vM f=덡`{x0 R ;ElQvte=j)K5}`iZ(qM f =w;Rń ( PL)6MbzYg]Hx÷4Y8 `e]%KމyUE %~5@h C k<ՐЖ*Y|d8uK㳄/a󠻱I>3 1ѱ b.DQ ݠ([ 7- 9 ׬V^!;=׃aTh2 $;j(%P6lH%n:jCjT`YiZEdaT RX Cp.z&'` c`eh*KRVhv>n}\֙%/$c=<2'q- PJ  Ó'\Al#mN XC aJ"o$@l JJِJIAvyd3."::5{x/{6^wɟ\]_xR%CVjFItHApҔZ BUhtE/π(~^SRۆm`\#5/W["#!݀N`QgJCyIHKx^21q{|=@[3So~MY;x6!6>O%7y4H]\>yorR$Gd #P.>o ̯`C_DA4d5E7@^c{c.>\ uq捫jEZȊ<4OU+uQj6 P&eG`̇ S.AZ0 y aIr8$ QM!-J4GQ>M5o6$V4dt8Y mm57{1N9n_K SX y=c=c=c=7>g DqeWdۧXz7L /<<=~'ORO j'jTT2bוU``ER%7G5ft:>( ᮮh>dgR᫡ {\QXpoєM֗C³ x*Z[dPAe0W&i}Xn!RfٽS؃ܰ\XH5tHFsc+sTL,'CMx[RXb{ d `I9yhvٚ{OH[T'6<SU{-!#}D!J9Z7i _dYh261h Z8~ B K H%)$l/J+ {ޥc0,J-(d}*NS_xKhֆR^|yλu+:1^\H ,&s p2@5%Թ>Z"TcR&l+r놩N(޸LA~F2SB6j,w8c= s@B>LS#lx0M*ྙq6PI!=c?WMWOx+_ǹ7wA_WTxX+yn^8K%|fx]\]l> `= bTqnM%cu]89;b o$uu?z r mߥiK{|@uFsi|<0 ŅD@_ݹ)hHW]`ݶNOq*5C-2$hOh#aPuTG*P*W@v("uXW[Af0nk 7޿`}fC=Ox'xsxp<^|=c=c=c;B~6D6 Y$yabO> yLs+;hq=a]W _Qf7T z,KoT 56/WPϣe I\pA:ڰ, Y4[Ou' 8=ѱ:S?.}p=$wEJBoɂlE& ThPjGy7WB>Xz;D21fp(JwԔº7`u&:O! !je>`({~K!p!b* teELE \ DTOP#=,8/6j&r1Yg1Rrd=C)h)wF~.p%dmܲm}HL֫>EzawPڵMAxHkeٮZPTCZ G s" %H_R F%{=x/\S4@!mIɦ:Y $ M*M3ŕMݠ8!X3h6+<. 6iDr[Ǻtpuu§ sh| @R!jMr`bs lb")(:r|H{sokHRb#67_16s}P*/~ ]pu}$s8A)T }cyL VV!k vz4'9CB \+`k|k*ubSj! D ~Ά(`$W :-NWOF*ʥ"wZKm_/mӃy|0 hUfX׎y>`)[CZr;QLsACOhrv.xKYpqfw𭧏KPfd|'gv`ςI("8+ںbiN;rdKt,)on!7M^c]NxrZB)J`tzܼeǝ ݻW{{{q (h,l ZjyP"ˎr*TucYN>sS IDATp?3-南eU %4U{p$nS*\ ZZ*׀yBX{ @1Uua69ւT֑bA5Y! w>#Yk T&i @\C@YkV9@YV-ZJ @X!Ⱥ Ѹ-6q,o])IuoJP}L؂C v_:do QF܇_(LGџ>Xpz$ RKTk첁H~ J *"]VEpJ0{5ȂuC%@?GT) lcseǵ x@YDwO$ΞǓmP,o-lX1U1r]}Js 5XwwG}i X?EW HP;sME 阈!C/-%~+Z8k_kAaj8f\]|z/7[o__;טkA?8Ί|U\]_h h-,-Ux-{0؅4mq8 𳑂Jd?l$26XUkq_S{Qup<2Gv/JP idaem SW CHk>M@`*ju,kG_ m #{p<8yBIָ;7"dSAxic 7gsb@=ٕX Zg@[;;C<8-gX %Q*eE9o{{{! @h㰙VUF AeO#HQeqiӄeY`0S8 iuªwzH] Z8os#p 8 m,ddJ|wx>=a.exBfa0$,gAJ0LvY`$3J Y10`4du)"(bP@00Y@&h/W7C)ns:}C`pT+eϱ ҥC1ƕȂZZ>MOVa8O@#\%EͩV H{7Efbp`陵!klh(BXl#>q默 H6+H0FzV'ȬaHP3Ydj[EKD1aCCW[ <nJRZ I]>RȦN2`UK(q뺢N;;ǫB30 K_,A{ƤLtXVm7:HʞvlLCW@-F RX0b2墑AQ[F.K4Jq`>znx L9,7ǬG&tFfhrdp"Xk_/ xIٯ3y@OyXJfEpi꬝Nkbi+pxͷQDS8R}0BI3y `]a?Ga]V@7rB29Za#v&R[uJ#\;i GnslIbOP5vJ2>*.y$hؠO[ǃXrِe8_ =Lc'#ykVڊ Esg_3C\3<}|sA!x(ǛNy携cJ(^X쉒N)cumH9.|W*ra#e(Ab̰np>/Ȟ&k ]Cx8`-{Gx೿hQW_ݹp{{{τ`AٷXk?4QjB Ҕ|.57LI4Uy~VI.@z` `jh#Bގi)l sQp`WZ[ XJZGAW`9/8!̆o|Ng=<)%(` v5$dlѷTo@ސ;s2K+ XGpH h$!,(w2 .Gcɀjd)\](6k1t=Air,ԓ ~>攌1(gݟQu *YƊ$(=fo #-;_̷2 BM[.Dhr>2%2wz1FSVZdfsBTb`Xh`Jd]v8`إmU v&{T_/b>L 0۝+WW7oBpgGm'^B 4FxgXfZ gS (K!g{lfnM2<:ֵ὇tܞWjA<3#j za)UM x؃ /!'\"b_Y?5,r09zO7>.G&Fd:& y*ddsK!=:WTi7ˊ/OMWWhHHLvPrnQ-^ +."i`*1:aqh!;|Xs+֨B<xy Ŋ|Ƹ LEx;@N 6{\q\0k|y/83FJ޻S-ښp.!/Lb.˜1ݱeFׂh&С sc{G%7VvFiFV'ts'S>Bux*,nh0Oa͌r%:Z IeԦI13Z#lmA(zJM;ZQ %J-HR*Kp(poYWRp+N V#=nEpZV\y#5 h|^x.zO>?Ϋssi8=}a^=c=c=c="TYtn9V@YȞ*I/,9Y􈭂u>5EaCXBvVe}@o-hf'Քe4$ ֚a9-B0M)u>YNVx %(',O3'(`]>B7w;N ϨҋD+?_ɘ+>*`yZq6XTaX:d~N@2*bB|ux!לm OoaBd?dCUPdY V;`z+ߣQŃc'FW 0^㜂F |w, u1(7H`HƸ /RP4Z b[cCkcXDu6OQSܹs#~ꕗ;1oy% Drod9F>-0olǯ9Z`"Q5%) OdFeZ2gĔwuE5) #z0e?d$rqF=`7ķ(Ycb@BP!QP?~N\Ls]kϰx<s>N(n-v73A$JR T+ڐG( UD4>p˺ybkk"SUZ( SKE:w?g?''-=;{{{aJR`6 5HʹN*^)hmѴLo^3lX ILQ+ K(z&}Kf&X^.hmԂylf_E0ͅnX M@Q2WXe< (ol,y'.ֱIIB@:VlX$s`&K3} Q*@Fo R*C,{3wSŲm,pǓ'jT nn1f㷙{ȼ'F]xJtw DAƇGZd V6;|b5\7uTjτ:|xfN;*j- E0>\pS{W!Q$ #@4u)Eny9C7pzGJ_J L2 1DP:ݎ?~;wop}5cf ʹ TSQ1 wn-y|~_W|gz]*ൢѰ^ä)}k>ݶ9.h56"k[1i2rW{#b͌8@آ@\{lv, ;4 OsMuJc򇵄;1#rDy.oJz~'LPٚRM@4]kmd6;:o?U/ qgÉ;nnaquuo+⿏_o?ݙp{<0ཇ^pzr 2{ {4=]QudkXsᜰ|KȦsm %|Gf;c4\Lc=>bFk<֢82u`1js:Ε݁q fhz?Of)6u7T JR6j]e0Oxuw7bS +΋aN:#A҇ȁY;,:ZtB- ty{jσݷ㧰p5c|믾i0{{{?,zȝRgL sò$fXs4@O1 +J ã8$%HE Z)hbV~N7#VC)KB2 s3] ETjEVh(aiȜ BZP)͂%l3jf"Hyې!xfm`"{.x(SE˂rTuDQBu_)mF0k L$n-uiN,Sx> `jj-aeeyAJ%2Rs8BVs_e'4ܒr݃& PhȚuld M5,9hX{CY9"8x籮 M_% U>aW%YcNw8 9f[r2 f5`(vi#ưa5C>kxN\pui8f<ź"@ɋ\|̡17<cPa},gsR8mr-2gq<1.JcRG&XKx "x?xm4m2T* u;wW^?ym7^\RT9*YD %d5)wְ,|Ʋ,Xg֕=VkcH'5O+8O`$:fHDJУ9XI7R3GwCm[Ob]!dd#fmh })LSA懔_2 `;΅IG׎l>=צ/e J2= B3d:X9)=ƫPQ[oyu@ e<ϸm9vYD`Z9U5ֵC2^ָR~YV.6Z Nfq lvթ>XZ,PMsDqE|iit ed*Q£G/Q[4+xfF_!ss?|6 p-ۊk;{9~Əq=9?ztc=c=c==-%BN>@Fu3ȦZi-'"*H?A&lHЬS~f1M 3u-DfVLgJ^`T̩uXS R9j7m2(3kI0\ʱFL@LR$}cf`>ސj} =Æ)aiahmE(Y>F?Z*=s Axǔ5PH4W`&^Ģ&AH&!{qf hݝ~Sap&(4guOc %ْޗY f&):15L*,R܆Da*!u<Hƪ|M3[}Bdȉ~s IDATX7ϙC@0z< i07s59N)aZTк]ȃ;s:$g N9Nvb]W83^z{)EƵ"rY4d`_@=:֠Z>*(&P'RhlA &r-=9qm   TdE{ãdžy. sq8`eo9 nrgd[4:0O&h2FHr sHPB=ۍr~KH3sWXx'ixw0O3iM55#\g*@O/k[)~}\߬xS/<~?q؅ R874:ҌM7Iւb>8B'w;${B[^(4p٬ ^6sXψ"h!? ;L%5C #ؠ۞sH[֤'):xr F##M7}[ #|c9}ƚ(ks1RzD5RH+D~)=Ŀ?v>]/=wf׾}<|!}=|^0`YnVEZpgd9G_i:xŏ!%awMLL0l \qss#ٶ|Ə{9{|`-b?`:X+d$PrIJyDQŦIB2C5v;GH,[FRQjSJ< 1Q;X{yY%i 蔄.ju^Zkqq::̘ONXQ88 XS [O{_?;W眺dVe~߷XkNƜ+vو@r. N @4As!c@ɗ:?eNyRu  yFR=”re!ZAΞ0č>@=B5d@ͳ4%ًpdK^\'t8: @X_Qt.\ >5y9D/y3(ٸ"1gjw,Ĩc D&ր!o]QbYu }^#g(AINk"sޮpq?Zk\/xRn *s}#™h90cLM`cP݂eG הOF"Ub9o0mzZq>bɒ. W?#^xr8&XX[Ǜ Kx0BPX0;}{xz|4Oxz|?4}icnm)! 3 Ah|.`Yxre_X$]ϝa6'QK BU`ƽ=T' &ȖsMBߣ^;wƐIۣox1Xt`wc!E[@n&CAq7#Wko{OSq:Z1SC2OTǃ|8ܮ>чwf ~ÿƫO>4Ztwxz/^+]uxYH+:Rem?}]qYmͷnacځY!p#B({Ǘ_FT?:=;2$ _?]3rۮ5*%p J5SEò=ym;J :._ϣ!VVM-kGױ,w=Hw [w^KpTez[QbS--+ 3 ̊ (__//)nׇP{{{q= Ҵ#fp/#:3YafxpO%<(!dևԩLZ:UE GAn L! &RU PC0O3F[G{(Z!Z\j!%Ѱ &!hl* ;>KEeYɆ,Ec _;xkn[)\(NX7(VMk)d¼+QS1 `͂]9!,\$䱙X`l $I[pQTxAT;Ղi!UfI)VXJ:,JlZ˸&׎`.G\"~@`K -d1a;$R0 %q0dAUG"9N\ _zu" "N7-%"޷y.1D$RPVz J7MG{G5OvFxUxt1MQ\n+CźHD”Eu0&܀\ˬJn`sKC\b1s>@)7:jE1 `\=꾓 p_Є2?SATK$2e o|:խw&Zp?r(p7o/XRr= cSabc Tr|ӊɑJp]ZO<{+93N=?xzigx(}S?jD9.V|tqr5|T宐E5&&pʸѿa[؍ XPTHQh1=Aِ,n{?.3D|{ dЋ Lb /bp_64s}J?;eO+r4ő t83pl~w׻{w9a݈B(=wXK9緇4gAj`{=}}gݿ>8׎w_N8=;5w^ 8Oo>@ex@ TWd* 2R@8Q+B8q 1f$d!l?Ns/q2{==,8z.Jى<`-'7̇Y`-&8mncX!ݻäT+}C0emA'֕/uCxe6kY+&/&a*U@wwa\ZokM;`̜'>G[z nuihz[ \ \p׿ >{o8f\_I{{{wk[()`(ez Ꭷ T15Zpxtt$kOF@D$:TQ%3F0##*m\c̓IOxlD&WcQ*4dږL(erDY֎:Г3YLH4]4\6<2ڔ;p|v =nAB* gzz%/;`VtY^V$˩2ӂNT1nwW+2/2tP@{,Zum8dq_72Џ3|*TyH(dM >|s6'r"2 鼻;p:( |y6x{0%p70'$1Opg#z:Ip;/ #?Sq<=g|_|7aBт0c<><`x'S_Hǥyd;{Xbא}Oe[G%' +XJG8E[X uCߟ@{ώ(0ڱP[KAb |`(K嶠uTXᔜBT̓U05VF( K_e( —`:>kH_Ju pʦx5b\yxId ;T6;7w;s%Sxyy]'mup$o7dd~;$k`1 丠$3o/Ig;yeʖw-G-s k})EɺsdZKsb.S)X{kLR+j zӄϏ\VZWށ/_>l, `+y'<=^88f V3|xܽjJdzB͠QRqu-su +N1 RM v xlq|L:7Ux!!x15 @Bke`uxHf}Od \~ȹ-Pϣ&i 5}|7$Lq>/3?! @ gLSE G ɂR9Q*.'3OgOzuG(ȵ1AJl , eP J}I w\Ws<(4muCιX$S_G{3c](?_?h+J53D)ZQrc.Gۥ|.譡Θs \_RUEmwA=JhJo-µ{(>,r}G7WxGK/[kһh2'_@0+pOt`XYKAU>>ݰT T*PYFuhQ$yƺEp }rpkxxY5p<(譏n mYY=u6MY먵(2^֕C2~":M 03n8~?yij~'{{{{|>($+LY7ԉvqJD`bZ$Iza h͇E ne0<>Xb}q<~\1 (`P #QRɬC70un&9T:RלTqszֺ܃BK^'~C)ۥN荊[`@Kp)Lx_1D(UC[ʜT:$S@UkT )L,/#B VU$(*0fqcfbg}jVг2$dc*;H?2zwtU! -Mnҽ'%i1W{󑕒$-`!=?LRx[A?PуLI#t0p 8gn2cJ ȴlv IDATRYf%ƺIz7n 7a]WZ'8 pWʶBpH+|zAf(N0Xkcxl~)}]LPA@7̵B:6Z |(!SΔJ7X\xEpn0O(p<qnC]pp>y߸rmxo|팏? [4W@ yuw &NpszSo1gD$.:ga?knTrGOoq8PjTrpdv+D7V[^O^}O>o~ %'tNhMfkH9U#{K ޾yx1O-˵7PT%jtNE\xtc(2u+3A`*E*>(\ T8ZZH8孭ftt +6xD`Yl6]0/"qQxzӇxb_\vN~寎B6}=v>{vlE[ۈ&+g)mMJ{;)')sG}G_/?oowvZ*.|+xgx9i Gp]ipx5ucàU!nmQf C_(@ YúNՀ8i!1v = O74[ZTCY`&LVv3OEy ]k-^ZEWcs7^E nS-h>8sx~%- F(VJXdVrZ&̇u+e-t̀mA[КRWZ eaQĿ/^N'=c=c=cLA L{XSYg5X@$'2.X*ְ@Ū:>Wk/#}I}a5䭓Y62{DJ`xJJAɅ Jo2Sڸw9RŚrb׫eQz,,7 R( "ueBk+0  @To -%%;D߱{&O͠(a?%/ !:O@m 7@/KÁ)=O3:Vimm(ZwNm[ Ͷ il>j( :I// ɀL$4[Gwywx'ɀ"',JQceiE>@w nQk*r' V\)?ޢd\0*-U̱ͿTv}t9W7dCgswXk -|* c.,1坵X[q^+ra]T1duP+dDX 3_2wPBܠ5j8tER$ ,M9\%}(qxpQ!JXJ>B cE.*ALPPGn},cو VgY  )EbQH{"`LH{8`uJj-&'_wлtfSP=U5BθBdmٔVd@(ʶ7Ӧ~g!x-BQ*f^op/7,p>γcߡQg< Vз=Ow Fn%]cɊ"֓ds2 E)3qߡhjAWZB@{|pJ8uF 5ߢineV̇4,+_,Ym 5| SUpn"S)6`;74 aFk \AF`:!u-aXKFy*X*u]fX tJ6V}/!PT0OW=:O3g?{{{_4`}J$,{eJ Y-3iL;4/!oXUtի5o!7xUQ&2Sl'zB"K0t0R֙vdLz "A z2LBw5# xwe$]4<: ymb :Av&^}a`QD)wE4eR7UrE'1l2JERY S~<6AlX.0t$~P3@Ƥ FDzV2 w|=Jo?,X+BsHdndȏ$;V H^CZ nc|%6@m`~;uҦF@񭈏_,e@ixĊ+<~LjD@ 9`QΙ~%-<ŷ{![̹r|8x8傶3ybݤ Ɖ?WK4Orv 1nώmq}M j6n<@U(Qރc8C2kG$lEvLOq|eLm? Bxޱ.Ԃn@_W@<b bvs@wUb|1l/P RU^w}ϟ=x||,譡e3/B p~6>x?k<<^޻/P%T,|(`o O'<;q[nx->-y0\c;A7Ў5!4 Cc34Jʢ HLx̱Hϟ uϥ8 87&d9.lܣ X#O%E eE;3g|$b?EPDQCY(_I؞o J~Zb\cĈl&uch(1 %=o7Fo-d`ё'e<9~9>x 8z]q]y6gN S x NeHZYḮ=E*湢.޽Zۨèo=*Nu.jaVܖma04uK jNpkhkGɜSɭ4c-VڭX!W˲ip>tx:`-(*ݟ9W{ЪP|qI{{{w^zЭ(}W6 W*[n- - eLLе` rI̩% ńz$5&  M&j}?dS$w2`ӲuD,A=Lu~8u&da 1MGTbk<zm!pzԹ#PĻ6zN ;4@K8G}e$og;Pk%#n!IMt"v&#Ck@]ё>%#1?@2ēS X V)w#t暣ky$Tىv6,lp$6[ Y hcd֦\rp72Is L 9|H/6' (pt62P*8%Ӵ&jOJ= WՖۂe][4UnK+t2@ ۊi:ً3.OWn+ wMhFXg<><CхF |H֎)t{:؟)e<ؠ9zBݬ9esq7`4)( Bm%+ϰh+w>S>b -A|=Q#H@S)j fixij@ p/PݓW넏>z~jQϧ(QLSY'h CU=s\;>'^^sU[=$T-$lDz70;Gvofv9&3G{xwAzb}51(V}LE|61>T0E\_s1x~1 <\ڢB#UƳ}÷ZxtM{$,y)nc~-,B2O>~JQ\Eo/G<{~061yƺ6hG. o;EJH P:$fۛ9R]bܓY `q+@By`^s`= F"i^D 7\A놶t˅5<4א6eO%)LT0M4Xyp<q8xu]~/jʅ׵iG-|jX:[qv[X]=o}AQ2|.UQ`] ˲`]VLUf%.?n{kxS {{{{`9%g(^ Dm)H$\;tLJ֠R`[JS7Rj\̽*ɺ [**Quh&k 2#d`i ^Ed',dHz&kL UHfT4#k "$~d @SFCV@Wʴy&;N_b( 8x(5ΔqB)Lr&2drW_Y :ؿAn I4#2FV%E-JX`NK|q"i* &U`|.Ao齇u|}HpY.q@ٲu!#X'L9=Q*y(@A9-}w@;TF5aOܪPTK;ӔmqAdt - BB[L*۸n-Temh+|/`$/Xݷp HZ$2(*ZV7.J@lh1)[bmg}H!x] Hp8Pnxa,H[2JS7w&@x6 񜏱/Ԁ(_CZ;0Ma'<<]G\n+<%-0AcmE1Re#הmvrhS"w" d]Oʳ3\m>5s-e:8-M+RF?oY!c-E$@FJS* ewˀ`dq٘'Z ၽZ5s8܄U`wu!?QHwր[(u p8ׯ^w1Neي:۲b 9ן~PiCӱZnxByv+6 hR|(<["΄m3Aalt&{Ǘ_j5nZWDuLE1qRfƥ@JN46ZJJ JQ:CDRCdֹe SAb!&X^~q`-/޼WEUŤ_=gTD8|(V7&}LtZT$XKLojyohwgy{H={k#`ahj5MlǶF|*@6{'ߎZ+J 0@ d 6f*@[tbwS~CBFl2aD%LS(M\zS08 J(@pChcnpJ,' r췁P؊`FzL wrdF@VGB< PxQ$!m<`P&_ٯ'K?=yFFR5ߤiI IZ 7L)n^.5d+(eFxX*:=cp<PKA})6DaFQAO8RY\Pk (ُQ"p >pZk+}eH{Hhnz.dƇ,!J'm~\W r?6l|QȇKYdz=+UrE[Y`!uɇ߇j,2|- HeO;&k RPP2RFD0a üs T}"ݍ=ˉ/#L \1UAoո1O|ah쎾'5(TK ihw0@ `?AC6:_<AkuR̥(ih-w4sur5Q(UЗi*x(n(^JiNov{{{WVdDzSlce#fY 6z@`$wȐe[!>|Q;X;wӒICg!խų]; 8Du鑠uMy_)3' ZpBaw&$Ux\TE$XL $ڠ}aH NЯy%YGIJ 칔ڌ7:Q H0JǰB$!@Z^b ô"+vw [T%F"2'#n햟`en߅kR p4s-`!C. UE 15$]5!mޭ6$؀kZs<_x(x!7BJ3u,e/ي;rlq I~zI-D`;#5'QP=z3*,VIDٗ-(@?N% xaw/TH|)vxQeRlXG@ _W~W4~o}o}'o>_yn  8Jh#װPfZ^Zh5zI@~1- @qc(V #P*@<_N|!UT$:P ZCgRj*f-Ұ6DpYxZNÌmDjJCP7d|{fnRmK"%*ʗ,V+ ǂe1<=ݢ2qn,>+>Biw^L8O<]/@ }YBd{|+P{{{q-Y `R:j518$X0^2ѪIpe.31  TUH` Äۺ Ъ(_z̄h 5-uI8Lߗ -iאujp'8U'x|5ܔ#5,`EYMP:|k"6m-Z3Ԫ$`id yP fcIueVTkx5됺.LS{2BbVɲFA倫 LpX6U$= llh盟7-ʜAF!< ,l&}d+lj eyH"MN!IɢK$J_e>`1 <)<.~ORj8mQ%@뱔ea{uLZBb}S)?N5A`x[rxt@)Vmɔ'aǵ70oHZ:jEnМ6qnF$T,)7$_X/ s>R? D=T>ܷ )Hlg5T[%*$b 2̉197@5d}g!??)r'^Ñ~ÿW0M N'ck.xzN}m|8zg?3hK`ߏmQlqguQK0{_l1v?RJ8xGZX{|y`s'P{my>TVTA}1<]n\fܮ\t1XWmixq<נ/~kc=c=c=~Q˄ K(J R]i nT @XPˁI @l)Wpb:>4f,5 u_,T"rU9j9ߥ}-f;h`V윓mWv@"Nֿt΅}*cqkl?]gc b9J`ꈌq~uj}Cb) ߳ec pStK 4~5\._۵b0匲,x J!KF(I=\zN:"#/yruEϻ@(yHW٣:ۨo'1 w\6{q1lGk7 AK=MS#erI b؄Nl mд:RJZW<=o%`tZL\6lj6ժjȉ8ȌۺBP p{nV*P8-6pș%)myP.2r S=D s)\V~A?ꨣ:ꨣ:ꨣ:LI xR;JxueNz7V(BQ[C)NpE[19UL޻e<'^B8'Nja`+Ha} t7*];=RsP5F/nlldڳ]٭IŎWوݱ[cruXΕbʟQJP'UA HטȔ|_֜.ŎQbJnQˏT)IM5{oHu`qSfӫ6]/"V 49jqrFUR9t#րWkDNf`F.x3P[,p!` t+fv(lEm`4}(c ˌY#G8@P24թ,5kUWZn;*E2UEv`  =86þ5f6%h1QŕǮ꽣ֆ2sΘJ2'?':J5Sݺ}t%9p Qpf`? oR%x\?ò;!,Ê)`̊ 0`Dl"nn{]5?=m+z X'x'#\r.w_}w0O t)XdW6WKnoo+0 8%2t dܜ2jF4qwwJ>%Q6{~J)ola.'*od0y2e|<]-I?ݟwctl 4T<ֱuQn}3_`6|sA<Ns1V b/.lxNwH MLu+h32fQа K{!o' k*RaeJ8 u8Xx0zLST`{Yg{#PɈ|2{6p6`:Wu2H+sp3 Z.( ޿O?~+:ꨣ:ꨣ:ꨣDdu4 e,o#ׯtZN/Lk®Q."'2_7o^#qB^fpJX m)gsJxucnjwx4aI.>`ik A!3R<8-3. .xz`}X1yJƟq!a+U\:0/uFAڏ56_˶ \1& '?hb$Aj\%=v|gʰ! N W 9Ãķ''OOxadݏ +̠r|t %rIG9 NK\@|yqe]A9!zf4z:x|֘ Pb XGuԷV*Z82#JFʄu +`74ej֊>z"&q 0O,/KvHIA1$1&nVc!zftAތMsssrA[jt9:-`̭/ms)Z"i邒)XPr%[0Iƒ~QGuQGuQGu"ߗ`Pٲiw%vnuXC%0'$ϗ3 ŀ\,S<7E&039BKᙵ JUpqdv4Ţ4F;i5I{n+s2v Mi( `) 6U L`Hj׺usNvFNA q8MQJ11Y?ގAq6×ԑTI~ֻuh7ĂSDi^p=Xޓ:5夁x{Fc&֔w7)a{PEW'3_9:@z1/\fH6@.9 =$lF.[4TL6d v2 nb)Ɓ 1G#ñĈm$' }>HC#kasuQu^2H9.v2@bw6G; aH2OW}yJ VH` ry͜ mOOko+C]o軛n)N~m5cb=]!FJ6ҏY[GD ʁuQn}3?O2`8r.]]d]BLn l`NlC"fa lj,CxuFV< ZZTyPJALbo„ 9i>iY[e]tAJim%39AQ놺P?oukRHdl,WerVPpY LS|i.x|zՍQGuQGuQGuQLi@dXv#" hI\If5K 4k6Kir3ˋej$})99p9lt_a*SGڔٛxtRvp`ޠ=Y.2{r{n LJ\`CP7U}@q*@ R{A_jUԕ/aGXJ5.=[B엡4͢9c6 H`&vpa9n8!gW;:I ҔFoJd5ޠ ڽn@z 2@p: ۾=,{}߇2t6ܖ##l.'!u`PKlT|(섯@8%rZEjV@(îY m}-b ّb% h|oJ.ԤuXq$/X )zތ3ā8JG[ƌ:du|Ko8o ];iFk M72sU|csnWxa^]H ͉VC+n HL ׻B@)aĦC|ߔb')=T\gVQJUS2)}-]U]h@m۠8׆iQ u\+Og,7 ﱭ+-ayE8O_~Lؔ gK+/_:@Fs//j[C]7R^ibY2)"h_[ IDAT;]5p\Qtw=lwc}6<5+cE'l;ni9]$"`TAq\vm@3l- W}OW"wn?OusݝLLnbw>c D۬: tݟq5^w9yO/9 $>#{}Ln@yz*˟/_͛d&Ϙ(怆rZc#wg8 w_R۷oܭ"殍sJ ތ$zrg:ۭoCA!ؤ#d/%3PTٲq QO.`S1֩?4m/L \KF75¶n2fECa5`Ҍ)xsw tAfGAuI!5/vLsAN OWAa* 9.kE&pJ[JZ+nW::ꨣ:ꨣ:ꨣʥ9 .1vGU fI0lN fAu%QN)jyN I!h^k #ܭ/#Ծn@b Y9m혧Dn!Q9lYoz64eh3F(Phx3X AFL]L* 1}Y`w,F01׌ qvrpNMqZ^AKNp0>" ݳpvQv(v_tZu<>_glu2`:e燛ӌtBr|ݻO߹G\!C cps3yö`YO@խujn7)cݚ?:ꨣ:ꨣ:ꨣFSFˀ5k0'#FEÊFNg[0v H8={/MtsLJ 5^CYҭ02'6Y޵]6W2mdh+4ےۀĺf{3&+% Z":=ޙ)Yk\v-S׺ڬwH5)=`mrDڐSF.ev+Sa wjڬ/Prʣ6|4=К宊BO頁"#t6 6["C]h)=CYM rL%:V)\q@)Vj*(.nxa'| ^b}X99CݰpqgU0I)HtdiNy>+<+v{VNɉ(SqFvz'PM;'Ȯ:|)2#GjB>ұ+R:߳s'2=< `^,3ǎg:r10{w|Vkղ!fd:"hi\vNDh>ݔ'>RS "gYur7xn\֌RSI(bؚpw{@ fY͹3zm)޽{e,3ӂ/K>9/@/ߡՆ??gx>oPNSBk /oFv})"%QJF c2|ĸ9sQGuQGuQG/țHiA6(3RIa)~v)03Aǖͷif1/ FfMy H_k#Cz6f'cGeJJ_wsef&(SV&xA,]s83;6l9Wdjh"FJ*Zx> rJxq`^ jOPx7@ٳ)1e2H)nxxzĻq>䓏^77g/,ug~_|~_` xywSPQbQ} eQd|w'N7'ޱՆuݰ^690̡@xH *FB(~"`C_y{:?aa0+ߏC |J{$@AkC6 ȍG@K߳Uve hg+laqY``#!ݨ?4k*weٽ;Π_?r~]M×8- (3mEoHHNI9QCT*Xo|Am7̐P2>":TCkє78;u^O=дX@9Ci gGuԷU>Cݪ)$͘ӔA <=XIP_ؙIy(9,WLNW+^VԭB>XR0)e\6ȉIX:')Azö5Li.PA oDX/+dJ 뺡o+FIfS ʨ[ekWlZc.) +3c[Wc)q+=ꨣ:ꨣ:ꨣ:W[䊼>+;\,'n+iTƲ 儅&T&^zWdv3K"2\P\F eku]45W|ydl20a*ڪTCOylmA{5Um6e4gFV! P'&iyCQ57o{+PGѻ %"l?nՁrdUC k`kYĐa8s վM% uM]2z.Fk2xG,ٕ>P|r 2`+fxg$j~a7ŬK P!On)]mDr(D))N[%/ɉDь&tMna=2HTHU PFNٱ:S((SX*MˀE'_/ @Vsb4R|6$J6߆o%dCŕgĹ*za^ex<Z׮] N Uj|J綄 uP{vtG+7 <quQ77#? w%>o뿅ɧlD K4AT=hm eqd B:53sM=KN*!r 'n$nM@A{5Y|IyGuԷY,ڑ"/j/%Prr3aQLgdfCvξ"9dpk3F 1LV-d@rZ(,˷#eR!632 .=uVa ak7EKBWH{ؽ+z}RB. hfha]7䔱e0eZ2a)a _οpQGuQGuQGukNRux譃\Ue*j*NhsՔ(:ZV0T2ҥZwHMs]a[}LH* 2KRLb$`&χ$VMnvtpg*+MIH22i;Cnëa<^NHoycQ-RLHNhi&P(9KP2kZzԠ* ljf hk{c DfZSUTpP4" 74mX*I>EP!_2T9u5;ANAl%%SjhS~|rapbA\h5LSgj& u.ﱛޘOl`RF7#Ҿ0kvWDZg:Pu =;4+>]Ƞ7AjV8-X)T}> Z5Q#Ol,{ N2dY瓩ΤGf7{lw42/^Wl~3DqZf(^ ҷ}0P,}m6pHs#ۀf;vlFp;I|f``² < MڞVb9ieDKO3q\۷/Pt"AkPrHwq*O3erGH%ã)_F*,9/~짘O^ǔZO(2۷CPJe0лy*ƙ@ưj}"_ 8+uюyb`0DS=.Wn.bNm3uWrcYf,++P[fv밽~kZLu f?xHLA|Xڄbn!{Thqk~nBo$ ݵ{\P8mbN`\ : B _43 YaOcWv  ~Og?? +t(3eBwy?O[ߟb'f zˇ=]gNm9<&w۫*P{1LjuQR}#8^rPy;i.l3T0MٙOP=bضf/(՘U h%m фKB a- uJH 3y¶n "LVLK/Zm(n![Q;$ g U @ɕͶ)OSAY{~{W1GuQGuQGuQGR\¦QkR6 %4VqcV`bW2#'PtBMiic  777B5UYXVn0OfC$%l!lU%#YDl`- L]=Bj X`X3K"ST9 WݚPp)t&kR|G& f`hz^iPeS*W #l4+{ *( qJG6?bkJ p0,֯u0.1!5!esv;dɳIU4ybv1%;+M6宪eNwwC#og ZOCP9@ћ/֒v7υ 0הRKz8l}ow3.'X7v5nf]I(R J)hm A>MfMn]ɸgu"n&dm׭u\K9۰]tah@ 0\tA*@>rVM hY9v6 P A9c^^.vd?WH ?(AIC0b݋ƴ R{F5\<Ɗ[Y+vd_-^f?=/w3\"Bm Y ғd#Ŕ`*IHQD1kmO?/X^ށX 9yL _~%>-޼}Ӽ/|C8ew> 4a^f0. ~zG]7l uPc SΘJ¼9><\kG{xw.4459H$`\hg{qovZ IDAT=R`ܢVpX5o ͊?A<_\<AXEQk}"'dPP[ ~< R|=`dC@a U@w֐eD*Ɯd'@@duѰvĐs>xrd ftCqNQWہAodjNAcfG_x>(rg ||)޼~_/q{3c]/x^ ]<"ea ;伯`88aHUsVGg;Hdm&bDHnNlsEKv[gh̨ɏvؠ8"|xu4g1^| #|G\.'eqQ?9%N'Lbԉm?3%\CqfqW`I+;v ٨}ˈ&åc_D+>>Jv2?>><!>~u?sd ۆi zG3]6 \p=  {><ꨣF>`olopRl*xl8WN3NlpR0 ?l^ +)#[V;lx=3a'0 J)0Ą\a34toa`PD.T0&\ΫT%8\ꨣ:ꨣ:ꨣ:W_M7iljT䊓;{W0䀔K4$-jrsukJJ3) ɐ[%~Z`IuZ3Tp FE5Jf#yYe%,Ov5% srT =6e(*Vccj.@.+t[7S@7tÂ{a b#stj.mm>tu/wbFPP[P1(1ؕa \89T:Nz;yo29@j q>3&ȆlLɁc{FD+;D@h T Raj˰Q @|EHNր! p1ݒ\h@7]@`"ܪ[ղ<b|o0VˮYPIٚ&DnU>iD'un9%@wŬ2 < \Ęʄy[g4lNк{35 wdmۣaLWd3S@{~ +^ig, M-*w+Âz3-'bf7R t L^6_T 4 A^ʊG!hux#)(i* Zq\~4{׷FQuY's)57v+nr򄁊G? ?a)ٯv<ɮ j+ډ9"> g)c0Uzݭvl 2#yT!yJNp<+lWIwL =)؍Y԰l_E5Q'6}? (^a?t)6ǀJ^REVؼ}LeFrndP#qHs:򏐴w_~x|xQlcl~?xA8`f3Kş?/o )9.!g8#3 Nikj{s M0)GuԷV^nn@MCuMIAضR N7gxiGcAng[>:Л`>ȓ$5gOew{HLHX &S| -9$XJ y2Fv07@udVPEXNbVv>6ӹQ[z@L9#gcكYz󨣎:ꨣ:ꨣ:_JQD&0y3;hֺ5 |N NSF^_4$5FKPfjNHcq{P+픻54|^U*ծ鈤?i&[ 5{08,N[]MKFGu\EB.]Ն&A4!% h2LBR;? M1٧z3lLE,;U7&0nxi*\ H3g7ۉwAղѣPo[S6l|>)yv+Mk &50-ՕP P2@ؕ6EL lvWT*(H-/,v ީR- FCq{CŅq2ַUrՏPDj}HW_`@˵ͺ'`ch{VkAI2U7*y v+l|GI# i]= |mpS1tW'fBn~l \Ѯ@X2R!V OO7xqy #T@cĂptU~X]39Dfx+A1_vBm_ce}ѥ;wD@؇55\w#JNG2"vKpuBEJ6Ӆ^7 lY`/k2ݣՆZKޠBn8?XnEJu\lMp>qOOOQ9htAΌ>~Kݝ]e‡g6"1@Y"Jm"Hh51 [^lfG,, f+a7_1ꪫꪫꪫ5$.D\tl{"`h-c6љ(JQLVV{aГ TȚI2s暈I83O!<4"SsA[CV*rƩ1lPjP+]EBF AXU> DFeDz kSa%rΚ6ReٖK[b@͔E{ָ-s|o}I`m➲8s.gSsq*k^}Tdj2h &f!gLs ||ՇHtsvc4?l[g3'|{?ě78cL1L _0ցI Qضˁ/3ܛnO;>~~5i#Y!@wL2$`/`v=% B~ ^`+&`OM]D\:37b1^ߺmxĜrǁ/> o߾ތQ`(w6fZr AZx' WNr/9c k9S{D,-umƝxЀ2 \|~ Z;F={}i^{Ee5~k^?}tLkҷO)GܶGxz?Q` ~IΉYk""Ѷ q ? x-ZoT?HMn@H Gr}\Zz=D =f1CajqNfb^uU_}'!0b8ێu)`aխx~6D,6pKFgN7w݀c7hNwZ4@ &{+1G>Řǃ h sYA#Ћ8h;w =<ǯ_uUW]uUW]uUW]ʗp,0Ôijf0 αoʥZ a 6$:|6p |wtR&;Wӗ-q d8w̘pq@L=]A =rgR1_0aBPJ 6c'1/톲KA5Yf[Z-.幐~O@JMAZئR͡a26m|H(+dkZAbjڃBؕY]7D1r“fÎLܶqo_}b? 1''|7}|qg|x?ٟZ8g3(1۷쯎c\ nSݦ1L}2I9=]V\cY*g J"ZTZkgv4-~iE*02EBHZι ڗM4cp nMj voO~q sH xdo'纟 C$T9P<( GVs ֞}¯- \vڪ8Sp GO,\Ț FgW=qu, ,!p2].k1_#o sۯ0ӱ==cE@zǛw8ڧG$74l[ke[<`!b8([#H |} A h-xiL* ~O~,ԿW]n$6'nF 6TZ|]{! db0Rcvڴ1p1uێc 1F|kdU ٬;c f@ #&#,&vΘ]J8PLed4խЍUW]uUW]uUW]uU$HKŭ^xusc8N7 & T}S.0 ߌo7hW j8 ۚlц5NWNz0m蜘&`I*PҦrlYBRg&Ж|Fbv&ǦAXtޜ=o9/7*{ ;x7ΚC)H[njme.^J5 5 4LehSÿeR;UVMbiCT?.uT+%Tskc2eaZz{%Ʋz=N@Tڍ@mƾH < 6O C]1S&,WsyeL,>7\c%6ˏJ[1DĨ>ZdYeM Xpef ի,KcTn1r_G|]eF`6ARԼun[1Nĝo2 //ZC3ö'j91dw(`ܗ=jȵŜs׺.G= 8?c",vCnOx/?)~~s>p >wߡ[ϥ$^- X+NW髫gض=_= &К1WqRGXr)Sܐ8Mn H5$xERҽѶ wk/7Çӿ6~_o>8x=c81[g:SP7$`;" i-> tSaI[2&Ij}4{vȫke& _Ξn 3h=_>?syN|yqam%8sɁ//S y*c<ɰRy"-t۱o1pu:fo!6UU-ۿ\uUW]uUW]uUW]TYG} Sk Uhc wFt mb$,# Aym-[ B( iݑTI1g9)RٌVzTf? ϦT$`jhbN@ʡDŽu|G [Ly *ekDsZGf+V*g%02L[sWR]?wDqi$93άdچfq5j3@@QJԹTͬfpHY\{,d?<(80sV&Z9S@4H 4YB ]K@k_K&%\*>׀gd.%G5P#C%H RN#SY *?8tr8kGWq<4qD;\l$sR˖45 0rm9y -fWم,ވ5o*|)e:u]Aqӝ!HƵ㺖 $A /0R>|wXk_+[G}xѻa7<=$n>?1O77|#bPezC:}Ûϟdޜ{I$1хf",2%xr;A|2 b[pq,vm/,)\P~ aQ<hؖR : *:˹z8/tx 9ǯ>,z?ΧE{njȥH{9VS\K o"OLh'ЫBf_Ǚ*ew3# R; 9}xS#~xCHf'1]"D\1m3dw ǁ$7zFנr@mTBݫjp /UW}f|Ar?f؛c9>t0~G ZU䜀Fx w<}%Ǐ@:)ˆ˝*> @:1&;|z c` ˃7h[í;1︙ہC S0ܝ} lO)z#'_= zw}L[ceQvZxɯꪫꪫꪫUs_- cC2F$$B[5Ꝺm).L!0c$6fGP)[WGs*OK6`v3,I&O֑F1dRsiJ Qd 9_;6Tm_T `!W)RQF@e7lz9:U XwEpyjƔbY=jWN`oIO#3Z39f jNERT vQՠO'5$E3Y5ՠw_@zj顮iqأLSDsZVoD_myϠ5(柣A ,E4N@t TRO:? OeW^#\=KWc|d&{_W39b]m=Ŗ}|ydZO1XbeVƷ%#~_w緩2/G3thyZjO l͗%5۩x4X{L@9ho$5L}AfkY~{ʖN2E j@z3599_S: 'WКÇ84s}'p~;Sw?j?L_NUT:"8ZQqc>("EI:;-CAE9EY{%^8gk߳v0_UZ7Cjs:[[礇"$`E"CYz`___ LHڳfwEt:Ief޶RB芎S#0`"&kLγ},LAR_uUK} `>o1f|ŁozɍŤJ=m}<=з$.JZn'm-Pw>V^=<7f8d;2~Ȯr'06Qu4 1Gcz FY9#8d$d*@6ꪫꪫꪫ*,fմ`ñ3njMUMH(ۆ¢mj*8g ҘN` 0n$RGY!eY/pg>`7fZ$UjYJKf)[3qA`5P!`eΉ[.T6_&3DFRe5; Zy 5JH@6qmRFVսoTNɄԫwuq5w{FX@wSn33 }5ػ!x39I0dfVsI*qRw(*yRJpȆYSVԸ1˹Xc2a2y~o޼x<&޽3l[ ?kx<xs|787x<yt~8"O+.a9Isö5d%`;pL{ǁ]0`<+`2NUY>?Q悮8e),B7 .9^K ]nHY |dk0>[51aRT^SEdž6h7 (c$sG~_FGV*OQ 5'IB{7p[죓eP t(L9skO%]Y*ٺeS_@ƹH_k|J>=$8z[:Nc{~axO{1@xt|6Lv1 mk})}|>ȆGrpg8xsn8;'[,{D"?x¾fx(y?42״;ZɼyyL|$mt;/rrea|A AmPlF?ܺ=8d@m8܊5L8 f"6w>4T@NGvUW]uUW]uUW]uU߽xHo$AhLMB1ZC}l@;vl=F尧{[h qܗ$OyU#:uF`d`CSd) <7X1=zιtS Yz9mZ-3H,p*F} Qv5 8:~3j.;a{jVVոu꼩jxw4k8TDc#[e i nK*g,opPٛAaL'}7okt]o;1}ckSv|jXNĠ9RQeIR}12 I􍶓PƢ,e5-9 p,ьu[@4/0PRL+R@:)nyIn֜ўBO`v*z^gKʯmX쫙T0G('|cҕ]I4o}?f̭^, # R+ 7o>D(|mgzs9AT_oT)7]@&qjEXxBy_بSzc )*h/P`~ISJF$F(pu*ɗǀaq>?=s\i/n#Ɂob~^ x,05Z?9Zy{G(;/x .}1@jMuM֤EuG4g2 <@O;|i︿<_/_b;j_8B:F`I>%R(j9}]#]uTk$v3^Z̰r+n t4R SHO$*h_1`x{nꪫꪫꪫJzI-Tf 3NE"0GR˺ZiZkcvΘN8Оз }ṕeβ,F]\Y ,l'):& zLΧ*dS[T*ՙzPGB`P6] R m@pu D23*VvuS~l[w' e }#`?; PAF: DRWcE b#leOmz0Tr b0@fy[ά3IQ\rC| ÈtfcLS0K*JVTJ S_)&Odͤ-37Au=_i۞kZYE|ei NӣlkpB^vl١i]i["L(ufh<mjiy\sQ=*B1 C'c?P?gt%XHX_arNe)qP{`VukvǺs-%"Y}B UTǛ) D8<dNeMk{FsNkl &f2. _מ ,@wB. o c3d3iݶ$EsoZAO_0w|x۶#3 Ϸ6Ā3 +F _><~ܘgJ0 VO@rPez(-Z?"p{+%uOM/l8P6E"  T\ƭnDֺ ]l._ Ow7D\/_^_|{J(΂E3 \P=;y{,21uݢ;yoW gD4 MCݷ,s_wy;.9#+Hb:hlδ`MaHkzsnfc"IEcX ۾%U/3 }e1&МX3G#xSͥxꪫNht5WM s<ۡ[l&J}Uc2i[@V0GbycY7G_h68e7adio[7GV i4FyxyI$h5Dlj$ [kƇ92?FASb kꪫꪫ_o0c8Amm?TRuPBD2r&Dyp"&̓a=yxnܺqUg xۤr[I$!r2 i8U2KA̱25x^m;V~lRH0<\AL6'.jV P.LH5IRo /h)W= l`2 3,]G'A}~Dgw\l&p,S?k޷WȠmv&WJ5T&ΌR&3CvF U#eCSfs-s3k:H1G%yťs  p1Tf=HY߲,+Jss_h RAe\ymG^}q4llR$}% C)^[̞M1r̉7-5̙itCBsZ9GҀ1ǩzSr-̉8ӎ _|ܷmj44@`kZjHR7 ̗J[f [K9rGz <&뵖Ǖ+ 7*U9l×H`^ks-u8f~q غ`kcてhV {Z|ˊ_|C[ޱ}+ 1q F, xyLs?߽/1ޚvBklxx\@Z*veAw\i9Ú $LvA",[| F=mO\cu RX/en5Ju#c?O_#տ|?tnZ1k^yg\dH8Ɓ=Q׼h {ʂp}Rc/s e}&YI9su="I9H|[ 0Ehs}Gt s2 $~ maܘ4 LcƍvG˪guZvEϬ^h4&ro }@3w;ůaؚ7tc`v|x?~Di8ʚ, IDATꪫꪫ_{02B\U9 19[᝘ʨ"o@{ֵcLҎDkF/|'oa>&Aؤy*eWM;%Rт5gH5q2{F0JY V&A$덃:ƀ' HIRfTQzW47e-R3/V?ZQ IdzqKIîy&M㑫Lu5DfW/>ћKEv0cռT"LP%0G{.ʛRޘ-Z6FP*.'Ĕܼq~qMۀ^^ۆRm7+N{HJI^Y^`@~忛#cb R֞a'ЂCr)5i)b3隊 &Ż;͔CtB$  T>GN^ D:jUsti<^r!Nsr`vz9{~L!S$޿{ wǘxxz4N_ˁӎ//w}; +s_jT*ôDGڷU®kZ@=D{PYGX2gZRo uԖ; Mha"\xTzmRҺ+ѷmy~›7OO~?}/lwާSu3~cŷ~ k R8sb^=x{Hh|Z l 뽕t5+V╥VIJ$KPBJ#t=}uן2\ bNR#}КaFZh7b-KgK̿CXUW]w#=uuC/{. =82~L6ضlÙ N*sI< l{ x4XĊ>}cĶ?\Y0 OXٌ컣g|aMvn?}~(~GF y}zwn;{H܏LJDwhy}zw48>}[ٟݲ#nHHW}%I$!!κLH ^lZYy,թ?[^0c..N2 (57^}oj$ɠ Go%Ԟֵg"}^V1&#HkTe_{JSn^3>1 g]8(E9= $){AS>XtL/5(24Nc 't, IRW>;Tƴ9SIpzo mV@tED 1fb+I9ˆbHQMt@mtAe 3BTHo6c1h H IYFq' FEd汃`@, ،39y,9Dp,^52eLQ dY1Jd<5kjS|qh81XI1T^e$*~o@jᑩdkX|ȆM ✨c 0OummedyR.Y4wRERymm5}:a\ǸWKQ2>r j .r&kKZHRv igù@ekLw Dv1)^DԺ Ouu` 䕻8_^pn;LmSiR|EZ+ <@vJ9`äkme ױ27:FT L S$9 % F๒cb뾮AK1I9F%q$Iu٣8g[R;b`!kvM`p9` dAȝ"rh_@@5'vp 1x<<pwH/ޯ%Ikëm@@ZG^[HH)Oݫl= `,fe@œt9eXWÐ괋\u"rAüZVw@N"T#&fxHG4Fa0 =tqOV7yIf$mD$ZO}G #LfIViRJRd霱AD1 ˪֌+b j%h꼛ge=c `,KM:,7`KW ū5)VFwToSe]q`-0l"cNHԝM3TFPf*Sj2Ü+TBa@΃ mqX;f&q4L+E'ymT~ ڶy`KUaf5":43mǘ*JnM7+׀)AȄvle+0Twn x(BaNh}@ hMJg }m-]e]e[We_Z▼:_/estu.2G$AcԱGi)ƨM@11u" aʴM,SٝZs 8`?liZ[N-1_iX\`Py g7l5y 2jct׺ > m5$gs`I003Yuľ7g fo$XO`C=q֚~yݚ%5K0Xsw@am<hIaݴgLXs{Gd☁96OE%>~ߐp w֐s,Ps#Cq*1z`̠{LF K nm]#ܸfУ"4'j,koUA(,Ѳ@x0*R#\w%KaHdmFWE#E ws4Ps7">tcJ ˞O9њaHd.{oǠ#L[bZ.ꪫꪫꪫP Ywܘ&Y[Z-KH[] lh.6'qjn\ 0FNJLHmcL1a/DO!Ki@R@g1cʿ ޔ+d@Ck'[_ms7RS1 63R a.`Sƨ#HqLdkKՙa]&uQ@O Li]:R˸9&|f6 M z_ Tdnpkkf!'&`s 9ȕYjjc3ogM$Yqp4u:6| 7G1Zd}i;푓@Ԛ%^ 6@J,'Aᦀ!JRT_<5έ5|jMCys '~H)e-nÌtB,=5:>u3#a9Pַd.pjcZ KS9&;2W9' vX&P:Ts>Po\sS@ү↡}%BEԓڲ&E+S׍.55Yy]PN@=*0r]ho. 9KM‰Ե5 XYlY (;ub{ PhAcް#0crolf -vEP+ET?!5p7νe"sM@۸41̹qX) :* Ukm{$/ mVf*L`ElGB[Nܭi.m a._TPw-~wqwqwqr3kxĭl3~d2qUaA20>:4yr,XX! 6o)2D9  "׈aeL̹pj$}m騂eµ&"IhX2!W1p g⫟~hn 3+~_!AyuBc+Tո՞3&qiV"ek0ϯކ8Y]m;pY &z~U.̃ª1.{hr= 3x@#GxÜ>浐9riC>91'kʟ^gܲ=/[ ׼d^c6pk_7'ڽ;"t5Dj֘Zg@Tz3Bnq fP|j\c[?w}fߦ{YiؒG*o"&qswqwqwqwLj'Ȱ x DdmU 03a L0#X5t;f+=wB; gBCEetcxl`cu5/{gDp3Q28Ly6&{Y lG9f%v)\T92Ն>x)/2$bV|t6z&`l qlVl4.`ݿ-%x <!k P R$ иI2=t_b unp#5d^ IDAT/b3Y&Qroa3`b«_uoa6Vql\pq li0xi=c[PKg=|7( 챖q -I+"B8|*,MNx`ƞ{S w1CACTxJ%0 N% fQ"xXx~2M/֮Hl=ѲU~i.|-6(2_KDsɳ[3ͯªU7o={Ֆ.̶J}M힨ZdCss l\B bĤ8!0~x);٠8 Gގ_ |oMĜO9PB*+UT`.sn}vѨJ^_õ~Hbbi v g\~=V^9Z=HoZ{wwq.zNq;RK/.8z-$|~x;wwDA3QIA5<1g/pw|qK@qwqwqwq.2 HUҍ+6:⻜ f(4ZJ+1QNoK\DaFp,G6q'pLd?;.$6HPƗ(/v+35Ÿqej&/FaK|"}&5fގun'#$//!,Fpok4c2 `kP΃\N4O) ̄;z;!0#S敹ʑip,Iv}Qޠ{]`a-(ö́p쾣Dl{+dͤx^ |ңWr=@:h268 sQ`T񃙋UZq s+pLvl&5p`Ja#6T7V4AHo `d DP%nlh?+@}, F6$y ~wzv l&,fΓPg&,#P`~ιphO^kYW^DsCU5 \l?W{!] Vʻ@@Gn^*!9Ti\S! [mȱ Hqcd|t/だZQͬvKCq~.d3 =Q@&g4ܓ̾|xf{etZ\Ƃ +,"{+u>'9`O ` Тe4 URYꢌןCR{}k{>wa|{ Ke% H^E/WWEjmGy,W M {tɋ%w7DvLsaXP|0  9 JۆXڏAz*#M&f_^wn sXcĜa-2֬,UEec}ߪEsw^‚KU98: 皰vs8oNtJArJǫs]wq7;;;; \,Rܜa|ce"(%* 7d5S^kp }"#=٘&0e"BRfc0s+3cX)J9PsSҶ8 HD'un8G-[HVqjg3RGLV0(#LubH vK%/̋B.U?fRQx{8щfo*f1})LDJB@o -AV8Oy tmJ#!pf=6|6R/EjωWE6q ǃ ,+Q jFP9ڠn0^}[9re4# VZ슐WF}Y(l@@U _@,'J^l; CvyVYc[ۛ/K l1۫j"A*IA5O57bok{(DQslt:,lA54]`[`5 wz5`\Evj9E rBkV}!D5*0jVmݏ 9a1+ŸhƟbzjŠ BQ1ü@얻V'S\J# )aqAH19w<hSɂPIk] ]LkOBqnxכI[T_DҀВ\wj]녂ygBm{u0p_wϖt(@E @%*2),1 G%E:YB/oR`P&[>|NT!%?oڶ`Ỉ){`s;~fqwqwqwq )y?=\SDzȠ$+&ɢ+Yl+\fc#p"dX̏Bu6Cǭ0ܰ&\}R֜Xf3}3Zz6+q<<֢$/O&)k֡;Ay o?`"CN I]ԙn5'"`%:{yz`@;A܌8&ϙ'(lNw(rO..P,kqU+N~͖gjE&p=t3P9'%79A("1֤;5&E3aGWF XqhXeZdbLlM fMPG"`ebl2.V+!.&pn_uJXU7r]{Az f(Յ_/04s\l=aH0Ȯij |aPR`a `16(ιDboo<qd 0\WXСB?ɽ XLaԼB6ٗf׾Pw{X-niH_5.͵?Asp{O(>úLrҼ%3ΆS-{$ sbNjy]B3\!|R(7`IҾ.\G<[w?$@U5LTH#ZVCKQdwap/Yⷝ+Q+|b_|77sa/?uH{rjoy®:3Ֆ( {a<խ "@,L_T)}2l߻V>'|iA5C" ,y6ec f1Sbq_wqwqwqwc7C:aqclEC9d}.M™`{O!Xsn1䓖cp T'[VW 2aϪEFx-,dk)LbHVyp $o<'FSe. k&@@l6o kaDBRF`!l\d #J1-kyb(Ɲ)Dl-@KD;bp,,d%Md,44.)6/v7#H.\#"03MYb+nvi-ʛ7&?Y֨-;$6LA;QIaKyf ELih{@PYMa1YLw]i6@.kMI DmRP7n|cr{@|1jPVa!& 2fln {sGwT$9_AjyXÜ8A@ҿkx>%,xq89-? 6}y9(Srd+kLeBVoMGx(dѾ2v7y{koJL2a fr;1zr @X '>xpgXaqtǺe6C΅Ҿ50KUCcp%b8NUY~#~WFI}c1Qf^P5}GsO-_@ p D53Wuvl@4lz6gBZyv5݀tẎfne6ѥwV6T-%&@_%5]L@Қjyt0\,X0HΉ_W ;PNd.R\"L[]l`؄tB6:n(@{j m ]fpT006o:uNd T[SYLtw|L< ʯU\&eXŊ]M[Kr8\xuy2TKP6`!SenzIk U!q̂y ^>?P! cLmҦ(15aA) [B>ɯԃkKqwqwqwϊo)]fbej-PDK#CizVQ VJYmɷzddA&bc3_ m7vjLϢJ:$j遐pZLFP׃Iu.,K,~]k[ WR!0Lv@٪9j2u@Bk3!<0A̫ &Ae@@rLKClJ#2 r2eSLfoy 11Ϗ Cm˱sD]Tm@)/>1F6m3ppw|7/sIhlSs*UFXXN?J! " cmq -6oWMn{0y897׽?C~/ih,>^E JGWO[;;;@gK`d$XXGPx UUN"7Ls_t~7ǚd1Qn` )GB/%3q li\Ϋ,x9G}x<6ՅԪm]bk(]{؍[{Z9-,-Yf<6y.<~1c Iٙ,IS[\FL7UN53Yg>&X5w ~ʼ \ K֠s.`c%,/^#7ʷ1I".ɻ&>dBj,ҔϜ<19Uȵ1X"3s͢{u4r^2JL^p{. XgGyƠ/=B/jKJ^-Kk,`-弊Z{h[>gK>1ISb]জ@\V'.yNHϫ,5`·,ZS`Rs 9lDmPV"M%~!k//qh%*\W֪UQ޶ vz}c豊.20۞#_|:p~0ۧP~ҟ8_W\{׮X^k}jG>[`׷.h@7sxne}շP=G}|jlr]C@ܭpA4Poܓ)n~1Q3_l\󻯬UĠU`(d9_^?DpO5`h\8'˿_.V~w~5|_z83H,)~:q+l))SP>]-[y Zj2xЙPj"i5zNZePX 1c w3I -#PF;4,塽% eX+,`"Ao6i4pFVa~ ZxbYDZ!_[Xr a&ϣ`ז5&@DVG2f/]Rb  l2'Y78u78cDa ߂n3au%QWR$k@Y6s] #C-2K M PZdR6/p9[L¯l|"#x²f7Pjn*:B%Z2G3Ɓ9P5jbmz; o3 X2x thFbs @n*v!{b7@#0,u S RȾ:/.@XǼ%_ָF/]}ǷO_}+ pj<2D8b5Y^Oz4tOͮ C|lk-S֖\۟8f_kaE_= -޾}b4:<\hg;pǷ,9o!y%Ơw ׊RhFknsD΅D#&,K`m`΅y.ޟ ϙxݤ>مymZqa\N5=k#ݱګp |v ipa]pآ/V< 7 @r\e]sG tܜ}ḱkQIĤڡ"3\ÌL?ɏ_1!~!M)QtΒ. IDAT"BT/0)R=^6=r&z5^xQǥޱEz=Z*A^AJ0QKm '&([Omtw| r8Жۖo,Kh6кq`Ws̺R(Ҁo遗 k#Po)}2"#QÔ[ L4Tuk4]Y=ZqItU};;;نrS 6 \3!.;@3@dl3Axam`p b"J&YNY'>}a&R|~V^k]U s9`Z a2GH1syvn8q1IVG1$ÌI̤iJټ1az I9Z%hp L|S`dKxP;~%ݘ?Z]\m3c03PR]HmB 8s.v,ߦ n y\ #+tPǒO1%4 VK<~5fQe2W$ʢ 5gzb$x !Ѱ Xŏ#c,nf? ~I^k){N6CL ]&6ssDY1VjvŪ㥷x"T(`ͤg_2d/qX*Q$5T3זW ߾70w؀/s0Nz$^Ynci\_R\bM{;o/ʠTe eL`l.cAl?}=+ ^8'CrZ,WmYh0§0QbpNҁ>5(BnWр@%%#Cf?Ml`1։W_/4FӾM /$_Qu_jHo|˶WoB*@Y?bn k':s1TM>W9^N[A_!s :,AH}7_[k̴&(8yKD@N Z.dǿc_''୽yxc;[I_Y,Skoi=_[4(t?^56ho]o 4i^N,TcYGbZPŃZR8ww h80C#!|oAacoZSQ#TI *VF&72"Ȏ62]Y:dzwqwqwq63O>fjԻ"ôeU`F.֚R@ѓJd5twds+dB  uϒsR9m5 <c=?O L7{:s &YpmV p%)r Vc,J q/Tȵd\KRz=\`l%3Ŷv^Н &Oc @Cr ѫ@@ -6Z+,kPO V<>y1 82f ʩ@ڒW8T'n{x\#Ax(8 X&P)Xr.؉p*d' 3O<@3(kdZE`ʅ-.U25$'`DFMl")ζmM_XؐBLc[_`xؔ* 3v>5 l-W /ad{UЬm䁭"Ԃp mx~cox{o߿%y3q8O3Gc`͹8^_2`^+a;ת _KVr%9tC1,0*(Yc+24V{zWY ܇'N'Zw4@y>wNxc}a8Po91:wM9qC]b`#7߾#{ӸWT` 5排ͻp%Xs14.T s1=U_%{ဨ=/8L@/U%uJbVi-+{׬ACK!%3N o?"#,Y# :j-/M~IV}QqY*D߅#X⿆5'~D 8NT8z.)|B]Z{jbظFKSib m -8„'wBvT=~0VܢV|!υ8.H[3(!-e )P$h/p>qW?%d,H{ XõɁπWl t2 JqbE"k#q _3y?]2W]4nfm9b-  M`>ClE2S]kTnJ޹RڂD ۹Ax.fL`t ;*=35'j^Bsw' ?։SҐOm_^+5.0 t5I<- @ Ush'޿}Z1.1b&𙋾PTl˧m$zmzp}5{Y~/ N8{Z!gb,3<35= Z{L;;1;%[FF6D8z+qRsk ;C/ l^$$U" 8k3?@5+OWiS-յB BΒWƲ`B!`2|jڵ3M ܕU&R{D7{wqwqw/&L؈Cc LQm>0J= L%R'L220or*7jbg<du,;sa*1x{9 Hx{<(E\1<eH7&6@R 8( ]0Td f('šsjf2KKqXbcx ]elfM]O(uxe8Ev`k&$d.8=Y!V񵘛B GH20Z` ܽrlg.3“(C M^`Ј;\ܲ叱I ༀj$ʩ<&NҵVU.&H~fbXJj ._#;z4f (0&91$.n{ nmir@d_,PH8^t_ä ;}.`ӽ0{~*?d  Z1$۬%3%(lv9߲ p͆K9>" SLXY9X'Cu]\PU4_^U91XSᜋc8~)E*{hz3797{q͋69sӇ>sjn3Hy B`^dӠ^9,b{8ZEK1[F Ay dn|8F|B|nO9XxeN/2H9YbUQ`.Yo&?s?%s5 R`W u% B sr^1ZU&3X?$ wzM5^.H0>6a̙-_- `6:plD1 ')U+ùN`26dep hJ)ʎWN,sDo֒"8Wk!;2n-,V3 %9'50׽@cpc&31FЭY=mC,V(|R͌[&Xl.Dju0/IMAMhʔ?X} ri57)<n .bV!Jf\-wJ`!XR}̫ 8˄es9duVIW߼Ksr w {΅^OB~M,FAYtewP MÒl7si=fz GWO䙶ӳ25(lCpNk^y*[I ;ܩ4=\hK"N"cTqL>kj0מ|hpq8c.5:q VCyd`etT1[I}ë3)ތnͿ T.& v.5.xJ{IAp,ہN3^kwXqcQuӒ>,Q;??_u/"~/k`>=4GxE OU<;h>f,lo s?CNGlhp>OBe UO ^ JCed9an$=KC;\VPEUr,ZbZkn *dI*[t0x>k]J2/^7썒Ί^6"V=a~-uNn4KuShşI|wqwqwq6rh 3c6ax>O LRB@9^͋Z313/M$/%d4މBN<15OqxdCI+Оn5)yQ +b_ {? %ĠdD3:y0$j:&[ 0'SL90 G @薛%Fzb-9#[۬X΅sLb:9=֤Wp bІQ7څ-NЕ`;  ,Uxrq{r|6sl-l휓[ņp.I̾}a3t#f 5`׾f?j_%Tg|.b(϶,at_+1޸>>?U.PW.sñDse0l uDZ6`9)jռ {O kNUvUbA@abwvT]\;Ԭ&9AOn/j_\Y@_U 9cЗDcP kJ`Ή1tY l`iǚW"`/}1[ڝ6.PЇ㽲 :WLS5Zޢ hjVR7& &;F5ً,㯴p'Fs ?mp#qOk?~MO~ ?=O ͕<@L*{epEeAȗj.@]4ak(huQ@Qx\B-S!}?%#]ءmlሚK:E-TƇJk,U´+9WQJUe*]=-}=(B?f[fyJ=WB->|r$W27z067Ԣ{|;\#>08<>;;;gde k={5iGQJؒ5̈́0$+io.$+@%dgѓZں.IN,%sM1xaTDH%1] 眴ꢉ 6%^>BUUQHB$2r\ P $˱Y^w6nаmx2U8\$xޙ͗p97P[K-i`@=DUb_xD\$PsN0H}n[' \T|3sAc JC|s"]$!Y !*U|B] ݗs[yYj!J*h4=}[^=4.(ݘY IDAT+2:4֮{%ܓe),V^!sb/.8d,kf1f/ٽp ysω?wxt$=;{Le=z]\H9c/yyC9kS pxU xjq5$Ӯqӿ=`vo*O{fj(~_{#ݿodYҽ`Յf f^_RkeC,Vp1bOcp!rKgQZQ0F*(X~_d>>NDa*dٶ/`0`0[]Y=jle=jx펱k}_7{|71a: ,bQD45=ay\ݥO<@zZ6zK0KР 쪰4UyaCU:%4% X&Y^>`{j- |  WNV|\^wqwqwqwc8^zV`,$\ Ua L|ϙ; % ;5`l;O㐢/ٴ k9Ml/߾ĜxEYc!3`1IF˖,ǚv [Յ^gXdE8,Ҝebny1SJ{0^nUR!E0]>E~d?tnX1OxAdq `Xs⫯?ƱĤj K{phr].@<3A*/^yC%}M #kƨ$u 2§DH[qce}{͢=}ns,C׻В\@SC=T%cx8{^[8,2zs>H$D3@ S_-g? lр'ϔ'e޻g "ב }5{9{]]Uͷ@%O?AM,Ts|:QA>;/9'qUj.0njLK;Ksb |>'>{ G Sybskeq3'֡f SCϩH8F ^θ]o{]_UZsI '/&n%0 63P`;B7ARA{As^aKXS*4k } Hk %TlhrEW9'Ir)~l{|7Αm8~)"VY̩2٫6홫,)%o̒;$ 9,e =ǃk&<&#b8MLظU qQh/kͩ# h7 hMu}P_ivC$SA]B:5O"`~E{vZ,hOk?_%ag# _  4Szr5{h| xmZ&u+/_w~,SsF{}U؍*0>y⫯큷1)s9 kX -@ w/\ ?ӟo~_@<ƌ~/Lgȹ%&! t=:KKvY,Dss;qw=%ŷU6zs#jL7!& ">)b07O'akoq<@XĆdBUjZYrφeɢLon5K: [ڪɗſi>qy-%;;;G̅1@eBaXs0'<mfpa% [|Uϐ0 t:9TT4 qk‡#5u-EA^X|*1*>8f!91'& C}/h%s%*\ 2) ˺@YmFjnJsmLόl{~_]h77 PcZ[q/i`@LZhusG˫SA5pA-C*Jr[oeɿs->+/ +\(]f,+<AŘ$*vCMINW6rgJڒu])5bRӓk n 5jPNӮY"D_ Ke2 8Nel\,՞BkS Y @J|l@܌sw\l dPlST|n)`05[Z$;ݩ7M5TX,Z7<sVXkNO\%s\BpK \}X|q70klpoF JkuR`BYyM\@S5G峳8!}Hrzd)OWڰ8ptOq~9o"lb. %';(S9Nu!A֩Sޞ;.4`a#/@հ'~%}{2ݬ<| d;'Z_ bB>& zn.O+WK8 8 p.W_c| ~cs°o7ІA@/u4G?<877/xǵs6ywKk0bs913ԗjlYtv7fRX,fXk?Ǘ?%>|;__g<;I}zi.\`xޅβ5Dl7{d JaZnza- nhڤ\(v.usS`8Y*zx̗;cns*2]5 dV* ӂ ,7rapqR0saPHqhiBX\8ӗ;;;;~5i%M ,xC`䀥k.X4qbXSed`@*QĮ*T?Hkwqyn0}y[җ<`~ t!Yms6;SI%cuG ܩ͸*P>jn$˿7|<72~-v^/1c`S*rb_5(]748)В0k8Ɓ9O@9}E}0kʶt.q5e H9qPYjfݭ-<w\qeQGsILOng Rt C~[ZzLUGV{h׹ Z\'7yȆ#G ~6?}nw25wzk?;gse?s|{lW.x Pa^6VWӜDMɍ>S}92\.ި{ݍ/n _{_|NJ Pgk 3I壕Gm v>o-E`?ķ}+Oz6}w=OxP}{geAz_Ywިn9g{nV,Af+q?T|/-nqkJ^.$L$GW]*zHw?Nq:|?`&?IJ,|wTtyՁb9;":ѷJeB#Af0;IC\}5X;;;*ɋ6BלF=o-) KpoxQ ;i ʬỎ!)S'|U8z'Ar8:k€s&1Fݬ6CB$r碟lf"/C sf"r9f 3ʗ&PEnAE`pk~R@٪\'A4ݏ D†+# [6h,^^=@@jrKEo)[Gr5w7XP,RAX@0]usFf"Glk1YK NKzp0`$}@ndl!;o@[6$>4H.y͹kShB]Aܐܛ y41/>]j`T]E*{syǮ0Fp7`^Y0 dSLA`-/d!fu ,E651.p>bq QPubCҽݚX3ƁǠ7v%btPujةg zM`fBHnuJIo2_qp,*ȹ`/&A|k+tQM=0n;H>g}cs4%yJ c:O}x`-ZQQC X_ ^%oy׿@B3-1$9}k7!ZB_5R- jф'%D4.y$ 6 ws 8'yE(֞wpoM'"5?Um3|2? K4&kY}?gDƹ~lOem x׵5Pc SMrA.+H} kQ$oH|Տš[s.ybUa"T 1?f<33zٛ}UzUø/ytX> F %7^CI5jiApD  C ёϮLF/DcvR2;ikl't JFC/wG^~x!YG0=Z+1::exp ;;;_ut3Ҵ%T5Lq\rU k$<ម]P_+چ{c<(.nߔWzh }v5/vA##(k8y&>>?bt g.KZ˫;7ŢyJ@*)wy NsTVa<decͺX|L-K*i pJ^f%纮?t҉]ZHQaMdոؼf`BIks-,rHvXɢRM")yX8k$[?3.1U v }G 'cӪr@ |"!e69 \'A4 V ^dvs9aO@;OaFJMΟyV b|<ʓ7OKO$rBf2Xx]?G{e.oE?CW36aȫO*qzh068g 7FFx[;5^;m 3Ge V|ŒG nFs ]'HT=6I! c`:p>OJ<\|z]sYM`f4Z+u5)ǬRJ(D[q?X|?`kiɋ$!%c`6.!u,`'hƋ4tW^ t}U0HJkZ HGV^i0CYQ5lX'Ēց`a._ xux3Ofqwqwqwل;6pT50Cl`9un6RWO1͚Ub5o7\,k & $ 5SBR65Y8.2V$}Rp儭}^^x8OɶL1"R D b͈;'(1\7l<|~P馳@]s6sjdS$SAK6X"~A,08=c3S꜔YNvy|gKy<Z[kO-X%A3v_LUsX:oHa蹦FS sK&v)f< 1 /liqؖJmaf#_..64/fh{^j`ĚepǪ|al{x$"T[<bX2fĮJT瞜;o]wKkͮzm\mK4Ѡsb|.#ö=J>|xlqU%0*iwJa(8'~?5_nJmHڣq)O|euu7hl:IO-Fm6TCh9+/ }e/淥\sZehI-Q/@ҕ>3pկMw6xO7w|oc^x71tnU@KW>__Y]+ fM|Ih8@4*{aCzɵ&[ c#ڙ[c صohjʫ9 s-I<'/r%mnRMm+`v^VkubzϹ2<|xq[OΙ%"{bn[ќanNUі$yN 5i_ܹC'U^x88\sa'b~ 8lr%XKi*_.;,K;~иa4&%>\1n]wW.-Ls5e<3mɤRsj+cx2w@Q`l|q'hRSknaman&ɴ?v1]I%skzmI>ʐT0 /0UU|? _jB m',DB;zo/Q݉؝Ffʪ\aМ9\ ;؍SФPPNItdL/qEٖ,577*VX V@^y`qwqwqwqwAL2=qq-̭\%#cŮ]K׻Iֵ[ T0D<16 򍛍m2#Tn[vu15=<@8Bq<sN1eU7qQd~"󉕓px8>X@T0 ; R`"ӏL6f'U%aD\|Em0aVlmpлZVX5Mb`t|dDϕXF=6xnIV)K,;Zpy'I=)r%#8>Ǎ^p퓬 H49v{ޒ:xі,)t{IkTo~wj }Qƕ_ ( ]wuFM8 ygFh/-wU,Cs죇rhzɆX|<ׂ2#ܞir䜨w5a}|xq<ЧƤޟ1\x wOyA,dUmD7*Uq?d|/ C\w-cPcuY'66P%zVoXjtro)!g30]oSRƀL v<zRRN prIBI#x>ٽ[Q{Swqwqwq ӈL`;Ysـ *wQ儻8>,-m%ۈy =m$r> Rp^K s |1ў%F3. fk-ٰ #pba6@~w}7xHL5^O o7Z$3PʵP@\,g%ceDn[Bϲ6s΅%zxW^0ƞ7sF 𴉜)G_]b='Ypqi5;*KEcFPAd۠w9OX$ |8g bW!@vH"2OsY1V|3u7nwJ|$5dbu@KsǸmlP>arPj:P5Kbd^D+E5TV <& pȜtw=k{p@mXH"9=Zki*2V"hFj>6\n\ǃco pJn/OLg\K[9^īil@IY,9">>$vd՚(Jy ̹M 6M̹01\$ L|%wK/Ljf*'b8E8pܗï;/x;$/粒6]Ek>Mf4%LlW%s/OrҦ)5X7@ ?oUF;C5`_m@f]9sD3T=/Uj-+ش%{#&yb(Չ ޹)ϷvKjHQd9_AL;\c-o0lphiT :_- nǖ_^s6p00G`U`f'2g.Xx|oU (kxp87q?x|?TR ߢ#B1cF1YB]ƃXR|9g37e1h}BKl_SɢGT^[ƍFVCncH~%"^:0;;;=.*z70lb8 oÑ9!o9VbyYOPks2h%䒕6lSwS\@@?܀<Հ\,@>D&A2^_*眰;h? .N:A3G$e43 Z^5ST;ĺ[ 0/mPVJ&As2 h F A묦ι0^4ͱk x*ۖud!闛]0hݰ;C8'iw~f l``RM"n+rK|&vܨN-[Xۊ Z x3j0D:_+>9׆ \rs@klAl<9kVZ!?/Ѭ $' kb8BKM՚U`u"5^BkG(OJX x] k;X]jvZM{G }ae'ٓmHjbGZ^<1A5wI8ɱԕI$;Y0GQJJ"t$YqcU,*`@/&RʁK-M~<.{VK$-cXW2O5Vd˕$]$pPsGp< =% pO$;洰ѹJ5gښf"R?iip |{}AL~Al,XV ^݉iUoS4weڭ}qr>Qn0Ws ֮.swIŇ}:QZJ]~7-h1xMc#ksvT1_?_7pe*HzV9Zm_ fG$ uݓlzy 9+?}[&?i4y4d%ZV("Κ8g&" ;__}~ӟ&_nxHz[F2㇎'#L}al@ m^:䐒;zzsML-@01}-2Ÿ!1 8*GZ(B.ԍk8ω$۲ PfMd&z IDAT?C;&hTl;;;wGg%֚du1Xh!*aG9kkIi @'&j\syxHN6?bњoL>ÈAE߷!SJO{8޿3Z8Iخ.54瓌BKඔ0N8P\/e6N @)e7iHz5 A6̂$ \lb4X"[䡪تC o&I"d3Y'!B9T?>Z?>Ie1>Q?{;)]B΂?m/)-=Oj*߳ZB=\#HR0T.VR QNn׺J_` J* #!ߕ]7=43`a`+ Y[Z,.IzbM/`H@m珩k-b*z\J`w&m42 ZƤ)UƫQsjȚKM HF<B^̎ J2Kvo}ul Ċ*8A%aYJ"ږ]sE.ڄ% {gB֒4膢a3n&- hInni&%q hY mTR`KXk ^bEj3'y̟p"|H@9M#I_๾ϰ'>ndsYE5n,|Q, ;R:*f.<0k^@^5{z`XYXbnSw2uZЯ~}V0CqDc%U$H@4WnH&UzMyB*w8·JXsŗaVJ*@uB`z^,-;yJ׷zƊ PnWqa^ h[R5l?3֎&uz9"ow*]} /q<|xibhϿ=ǃl.nFKǥV :Vזc/|uL3@*z]{ VA c[ߓAkcb?"pIim؀O<dRՁs4ΗqXM wA|ObR&f>LDJ^R2LLWͱL>+}ؔ;$_CjofvjsGD7`mK/喈a zozJF^&גq3`\ >L96x}yVMriݙ;;;" Xۀw>]6%c`*a~. Qk"ڿ&zZbkMѲsΙ0@MIH}$wCL2> wJ+s<Dǃ,7z* 6 `Spjnf++u=ddqIv"5_, 9al3ݹ*U KE1T̫^T,$qʧ$8 @J:1xU7Pibl\M G lmNJ-LQTnFlRexEykɋ9.)~a%N"j8\ bT~I%67О'F?p%kJ6 KI9F,`5 p {k&4lttww*pι>:z}1|K%|@S~%]ުʁ_~gӌP>%˷Pjq$yqe]$1vPZcB`WmKJ96`Xkns G j-s`<{=-O@@quՏ+ y x7y^Uubl%؍=. VK9f. o?oՍ>kI"%+9wly.0<4TPp^7)FYYd-ψxγg*G 3V2eI Ωh[V݇mY~|)ld#\Db<[?Ơ_0J+w$߽Ubգcw{,[ F3n %N߫}Z_MD{罵O?56۵ %Fmw}1~'O~c T%ߟ1P+[[ϵ؍,%X -=_3ލE8n `˅fcb%^ӮA/s9PF3۠V"j*xvj ${==mF~j1PsԸrFʖg?D.e)' ]O7q?||O=aGccc&; 3:؅c1(aY@\1m<ʔp4FZlPFZ1U"5e>]3A9&]{ԁ\(s0 :8#;;;gPi(Lٖ|G[F a31Bs}FVq fT mc5iA C$G Pa%`31bE* h"{|p'nINqKrӎ4'Ђ_Q-q8vw-\)IiT7sv倞-]&nȋ|`A٤Ub%7Ռf(Spġ-h`+gH^.bauV{Zr|]Eo~?UEص}TVMތ(o8mbW ʅl@Bſ,ޟ |sJ.\uJhidl],|G0d,ghEsyn֘{%ƀ5'32xNe Dr ܲ]BB]zϡfn A>WmE[_UAW)4jp ^M dc>^m+`{ F.Z3sv ]ɧPkp)0$rF-@9<8ט]^J^$Cٕ  [+ KV sj. @W !;{KKvBb|PxTxOӁt?%ᛵz>kkX'9.i[@k%~o}!֔ '1.`9ǫm/Ϩpv9!5 bIB4U˽^O-^:7uvCkף&uwz5t\N =1#sEM^k3YvQD )WV*.lhڼTǣ^6DWDΘඓ2Fb@T#ΗNTm0LRR(:yuy"W\qW\qW\qW3*^ޗLsYs/:|al]R+s.sPlf`Hb0kDXQԀ1(|չs3 (jb2y/G2#8p hIId7aMNޅ)O@s$fFpSSUWEYhq>* v: Y Fsزldoh=1t^ʟMdK`TY,PCLb{(ZC>璢}rhQis`Jq7zIa7ƀ{ˬQ│[Bube{ f3va gfo(1W/\#0V=9nH.FLʽ" B,grQ2{x`G |J͖H>Q5MuM-|.5X^ jȨgЖ_'Gw.@95k)} dp[lc H4yR3a &þ9:u)޾5AʗFt9Ѽ,?[13]&A࣓6+H퐟fT r?,Zs&S>&;搒Wo%Kgn&P-U ቔ5'syNP |ɼG&rF @=9Z&^^x\5snIjB5de:s5A\8%&H|&)놧[>n|77>}uBUjJjh,z{͍() dJ)Ĝʷ7(E]Wγu+T.x;m|Ӈ"a3>Mk+p=Ŧ6EBSsODm1Za]7bbbmC[@$qCA& ]^+LRcOs>eW߹d6E ;^sng$7lvCȳ+z+CɃIF]/%!M1< $ 4o?_ V/I;yKM1PCN]X%Pr\Dfrvm5n4\k2M7&a.4uVA\A~MϷ7u7 t$򸱫<)RoW\qW\qW\qWrsLT( x0## PT ed$4V@EfCDy:Rо%1sJ9]5.@Γ@bL]l>^_ONzC;k LhgL`1U?"I;7IIBk5A2b7J#`8-w\b0&",7~H9%ⳑ֬}X@L2r.y*GR2|k7,9N_4bST% )el)К뜾 M_8t$6CoA7"6ږi IpuέbtU3(î YKg b@f`sQWKzYD^Ac ;Rjlv#(۟yK]9IE׭{8P:w),8';5mJ2(]=g} eg?s}}*+s~69ќo. [_l'8DZ[J8L1YfbDb8@"N4[zՆC7@sܶ]]Fh+5X"i &ur{'!'Ak1'ΓR|S| __ю؂W\q_ nbNPP "q̂cDW_4n q{7~#XqtDhQ_ w'$.g WG.ѵe5vIҧ:zmȓoP =|zkN lӂN)-W#L%.1|W\qW\qW\q/7H889Oyv ˩n,UzJ&ֺ[M`mBS>ndRޣ.oЌDϘ|wJJV9"Q1bQ޶}Ӗp#r? &7CN`9W4hBO"UDʌ7J*v\*b}L݌ 5uP.Dlw33Ig/6|U$)sUFR@8c[#ؼ l,ѩoJiLyzjl*]4 y]Xpv`5 1` XVɄpITh0 W [ǜ9YǑ;dػs8=Rs O1Iysa6]$[ j;M P/ P#3o˟L։VNLB-Tjb@~\a0I3M!!@x%K-mbSMIÙƤr1}92p%e5xݓ5K-f6i,K 727SI03ҭIm [y_J$cRf!m7sx&mB t_q__|%Ѻ)90c&IW~7$?{q׾vTcY)6-d0 q~my^m:\ eX!-h Dl],Aoo.ȀdgrJm7#^<-7^bߵIJލb?-7g;69%f(SsQ6,LK?x]3Nˆd/c9ߣpz~|gF\x"A}~<@`܋#|`kq^' 8}b;w h!TM嬍Rq G5F_X`X56B_cDL5wi7hq&b`1oRȘ@n6m=b&7p亯8kJrzXY_XHC9[Gb2#8'~U@>|c5&")w8ds~W\q_ܜ VIOmXAF-"0DCΉovC{N6m0q 03zhLAk1ҋg'Mo[*i~pٙbY߉6wfk swEeQzRW\qW\qW\qşD 2q`Iլ۽0!Nz'XQl< k *odL+eUEyY UNa3")h(qݚYZ8S0&na9{.P ~{GXC฀ G <ѻ;+]n j !b<qC׳EdNFo>O)`0^c$mwt%\c)YI`q8ezyws3֭tOր j"V-BpEαM r)Us-丁ZB@,0k.V+ m357+W >RfumNYuL2[[̣w@Veۑrߛ1vop ;' v 1Hl w 3=U'{dT&Krty^ m޸df$&&"_X (`S~E %(IOVM' lɹ/`Fuw`jR///8q<=*/=wVJY&S5=Pd6] s+%b ixI/޺IIqجyVőbR3ϵz,1ZLd k@_gaqtJly3 `3OBEV)\,YcX Õr7mDru*]f>֤7Z j 矈(;)lz(3~7yW:wlk8(%f1GsI,u9oy1,u$bhLw;iSP /]yIynL MD=m wxm5@_Q{ja󊞙ʇp52K196ؐlzɺFt/(T@q*a%}_w|%8f5-%6h+uK#tG-| q/W.\IشUǤބk7UcK* _Z۲MVA19sGp{ucfbB&B 8[ώI$Q>2ؾ\Mn6̉ŠO61g(V;Hukʭj1׉|>z&;~"y(-/g]6Tyx3/L$r aklp;$%ݛ]ب7FJzV9=kXJ!"v٪Ɋ̄"g \jCMwrSNB]NZK#N,hy `Lsp vHB/&sOC/͓f x6m++++~1@VS i2~30܊q X71˔ &MͿ#sUXVxP"#(8O~ZIs/ƞ\x`:hb |^ 'c h Rx)ۊf̉ױ^उms {7K9 ,Ι ˋOgþ9YUn z:FBMACf9r]3j`㤯[ea^UVYc޳ !$YMyP^@C 3:jg;'y\5P PB{O܌fR2$؛dftY;Ʊp=1sP/~uM `N |$k!sإ%\z>wtS2BR=8<hkML7Ib2׈YpIGX3d78'Si[4-,(J?wش̳ 'dmwz?[|1j41U=\?\L͉j!SҐ#%[^]Xӵ;3}B &:8 o 4Gl Ԓ tMET5vQf׮w(`HWɪcR0Wz//xh8$fyE ]ӉXo`c3棹pxr_Qw΁j€, {5%LC"ˡv%1oX`REy` sgѢQϮ⊯5| eQ_i>CQ6#.7{owN"j$MWi[|W\qW\qW\q/;"CRSEXgsPF]t7#q*n("leS(,&́~xy}]`F9)Gln11f\&*9Xl]~χ`Ęd# i90Rnc) chz*}cYT%C[aULx巳|c1R>V,dd1"pƹnxax}}H4#N/RB( (yEIݿcXA`o"$v<*9M@ nN<md Ȥ(;/yAf'@LIdln9~ɀ,Y~ΒP ZxCXaN AVqDd^qkY +J \,z)KEjh)Ԝ=jFaY?R~<>'[95V2l<-jtQX2 {8}FGFqʽ%^gP./]7 ''RGx)wIY~yjꗜbaJN֌ <%g=Ns&!Ɖu lT3b%g{*T:gnv,bV [#s_֍Q2%㬧R1Ѽ|9is ,k6pRlyvQ`Ttغp,}:05seR.jIPޅ 6k2J.pd˃V3,g-al_PN;k}@yC4app?1'y-/m+`",5^7%B`e>ݗ{&C\c frb'6NHby  a옘wGs͇ЏXdOgϒ^`_5@~Zb"b 3p7`X}>W%\F'YCYHxXϵ`<._sc6sDgBY'o:p 7&sp1Tړv>RF@9WK25ʘ@Va5nK ynzεOQXbIs Zv.l&bRpb?6zR皨k\Y"W\q_ uS54mbZCSc$_c8_'|~?_'\IǍLJZXx1T0ЋzXr;(V/.p guq D&8Ge [[Zk -su=J%nӮΙ+++?H,T_GrHd|fn LfUeqDN6P[ܑh%YZ]fqf*{GD  ;L1, kM2UM81o<4-S>޳ &};9b$DZe~Y|`d|Cd`%7t7)bmR[$~ZY*bbkǙ#O vuGĐok:/p"'ƭؿ|ݜlª@7F Uu1h,<s@#i,l`ZC ybYK>< y|m%& z1 Zc9zd.0φ`{j*PP@1@b&~bgQ 8}`s=P$Y67y KC@Xsh~uN_RT\^[1?<" iӨ"ٍ́R3K4KH`/rB`4>g~O0CÒʖǬhpbuU[6w95GM8g__q!넷PSX"%uI?Q>qk~ z ;ER߮7'7%G*sCu“N,1T&7}͍'s#Жseqd9ǘ@kk3V9%C+*S`!֚~Ռ_S^d.s%zxwH\-e P2{HUʘ& (LtC^/h˲ >i +k9|9Tc8uO \n+ ]{~’ ќgtSސ1,ł#j|W@c G) ?2r.;~FpO1yǘcÒZY E:рgjj1K; u$ħ 5 "ty|v}qy NO\Msww[]zXZ& c̝Qug~amyVӔQ@PFq3к;q急#Z8m Ɍk֚9W#\_XZݔR(lc$UؤdžBJRk.\Yh$L핪^J)  rkrX*,Lj~7RXk r$`M wI$ۗ:xAadqeOk hT 竧4t= 4&$ :&PHKEAm k ՘D=˚52hmj6+p$RPwv*qlksXɊwo@ տ7~O!^?{?@O :$ Cw-mֲ`O1չ+emKV+)^ΙKv~/OOeDsۑ" /0yws<@XDǁ'x>:^'LW\qW\qW\qşD9X2F%T4drˇX~)'f7JzjedM"9IJryߖWeؒXE^cx;ЏL8n] I6iA&qi+)HkOv98!"TtZ}/D *$So=Oz፾Ė9[SFU%6@4yLyFoc/!96b&9F^s2JRbFbMy0%4m|΁歪%() :o΁:@D2SzH (A2ou! Xm(gDS;$T܎lR lOmz*t; )F;uȜdC&0|Ƅ$mחh.iT: G PXn$> f"Üb椻P'|ɠ/R\pȜ? q!g5dvC>{qOkL.8c Y&9]Ƣ:'pPb^q_k|E hkqU'CBvuԂP%)9O~o|~__o?=|W97ʀJ˨V&5} h@ L<#?w;~0!^qL/&>|8>-4s>ys;'z`vpCο+++KM=xx?$,To*Xd&+5Z P[#K`HNbuoCҪK1;/9)oҝ;eY!@1fƵoSUl#&&VJ"UԲ$yg؃̻},ש5(\5\=5WT_LUG3m9k[X+*{(&[m91`FY⾜\sU!~חW oFVw}nxyGst\@S/DCIo@ 0|$o. g<9$հe%ߍXyץ0P;૚PkAM5n2udCHjn%\:5Ft~[C;;w~?sؗΔuP+r^Qߨ"J.:d꼣@R 1,y}gW#U\k6{]_ )Й 1# հWLpA n.W\q_ NH%4nQ4mBĖ?5?|>oa^;OV% _ w%儕l14TW*Qw4G|Ĝ8zs7(v$}^j  "AOxZ`wW\qW\qW\q`|D3ålFػq <7(- LXcNȄOց[Z@>$(% '`F,{.2#v*ݑ1T^DkByʗ)f2gǖĵ\?$kD_fMLu.p qFmIޢDC @$w`yS~^939R>҆5(ۀ`UoDPVy1KS2SEwjڢԂ98)|UFٖ&:]7CR<0fI~ yGkoMJ?nxukM,X+Ǚ侳d]$B=菈MSCCb uLld,Rh.P KY Su-Tp*- 5R<Ru`w1IftaqSck)Ǘ}7<1Հ/CJjjOcUJ.kV`TAwܭ˻@kM575mPcym}[-<#б3X 9ȭ<*2@nk8V=ږ7y&Ӏ{[?75>#|?9+ydW>T:g)[!rp/=<@ɿkTr[ Z'*<1H|s$ʭ&jH3i|5\q_o|EF`QkYg%2AěnxaxIC -O7|O=9=>ǯ|o(>~ !ɛ_?p4^ӧ>O7{=2< g3|^ u{0OO:/w.蒣2y(#q̙+++?(*Q8l%[( 9'3|ʂ<&FE+B@d*EXy3ɰ`UxF,652T(lI T+ܴ- YHcͣmo9{^z#77.6CABaF\ R Be@h1-'Ykcιyl)QcXK IDAT}Y{f1(&LBhSBiT<  Tld&6nk3g8%& T톳 CDLjV RRTyzxɠ .g"B 5O)+U$lə,|-'#V< SDٷbpM pP5֓4)HD>KSנLPjRQN}1 \}e\ŗTm)zz̪s nP!)0 ;g\] oԱ Ny-p5_NV|-5>De|=bE=8ݤw1)cABum2)[ 7XI%4j] {`,`jA[q И"OϬzS5 =KgzRQ|*&C*b`ACBJTkUѭԚլD-}siTM\DJGӁ@,n<>,sH$ J,$-r.[JВKBT9z`BZ]H \V`.XV?*4_YSSB1U=taJ'$ 6;, npX}AeȃEb Yͺ^M2( zq=>ԎBӀEj\[7ɔQ7Tz M 3HD}˅6|9ƊmZE,v(:KxZ勪?]6׊o(}B. Ɋ`l mWaMLQש80Gtuw`^r$N JRHlL2 =x<7p5Zbd+0sb[vZSܶrsw-`͆K[ }P Jm=P!>A꽢|pEpF,%x1A+<|ӿ)!(Vxl 6yaˍEkp\牮hgNBf~‰CG;ۯs+z h xA0M؂:&9U&PK!!xZ3P猎"!6plPzh`^?Cig1YqA3ϰi;,>(E+&L!1-ZR4mU]5,.# ,\?N@s<_f~C}m.faG>ݛٵc&[c|[| B4s e3LVai _6N|8Zn"ky3E(QsЈ) #ҳUrMieUiRC'UkP$.#yYb,f1Ybp0 BC na jXR Kԩ9x>,^ MTIzRԮ5CMO&@Ӵt3@P5G4jQR dѾ5f̤ <-bXU[હXcMKGr}GxUI֎H-}7! 4ID}}_MVpT@'X( ۍ+sՄ~b@ w ^fH<(Lr\ XyP앚LU`ЛO![5`SxQ- V}FD*Q +Ȱh݀{HWcY1e:{p>7u^6}mǔ O]\潕XO̊ ̆;Oʅ8\sЖ}RcšL'BԵb^ X1Ddi~5,ʓFEl\ZIޛ 5gYjCD$Zfk栈a @J~gzbs 0siPp͞ "-[꽎]Uyouk^N;p\{54mcvZl3Li3ؚUwP*Īwys=4/n}uF kl'yk5E7v>Pܪeh 7=]je硞Ӱ)z>~O,SΝ9)&8pQiLZ_dB8K \r^s!&>sß~;.gyml݀rֽe^":Na!Fd*Vɤ~ Ĥ_~;PI}HS>EGՉ=CVW^Ð^+!j+:~ Tpg ,f1Yb,fy1^6iH$IJ#tm;SƔb˪ ~pĠl@LvܗLH '3aAN%Nj)H,m=&5 ;Bi&ֻVίhET;[F4,wxD3s-4F?Q>34b;z)H1$SC9 m@/~-ϒkY]_J IfusejH c(*\Iɉ%8둩"d*`5j`<@1{J 3}.;bEsp ոp=PЄ.2, z7 {S!+QUw5%+\-ZhFEhHf?r$hrTz^S}(W٫}9bZ {:ap8!@>5E 괳kk}28rвcUDg:Mݗ;,{czm=LQ0Uj OhAM WA| ¬]@]iEAn'olEF,%S Սs_ -iG :^hs!dXesG9S¯'`xJu=1MQvY1Fr®HH/Es Ң]^X}qn lf%By"67q:a<ĦL΃kcRܩ%K>@ m,"yb]h6=,^ܸ |EI1cfƣ9N[m[.ݷvofc9z 6//q{A2+XXgm}sgq:_y[`%?u6F7-'yt;?鷿t2:[wKL' KĖ.$Y+f"Isp>J"VŒl#V>61 H/A,%lV!E C3,f1Yb,f1Y|#H!w401m 6,XEphDART]D+P*Dx (p)qWeOiRA)6&C EU$_09h Ъw% zjh6BЪ;͋.N G T6ȍ滜dbȋe+$Aj( 7wf=X뙞U=t\dOof0K{&{h?BorW`bZ `ָYHI][-qDu:{jR5AɔIؽb,:MSgo "&%H&@jY2&=oUX@uT[#zm 6H+z(!W^j:%N}-sW{xtk\uU"FO&:MqE#ևk W_n6bNcw*l)u {Gk۩؇ѓʝB/f-P=rvĀŠ0OQ+н!jav&ݮ}Q>~:EZEEO=}{ٱ{ާo֗9EEkQqM9. qċw0\?-hԭyЧ>0P-1}.Sg1YqAxR˓qUز~61%c&Ѷc|7=4B[غe KKYYden}Bde r 'M1Ǹ;Soż^%VW}=9ݱ&;KH ۛZY\ĶټF"Ry HhSbi3M P`tӶZ57|·,few3fO/b,f1Y;z^OT?ZAb$MDgC  h* 3U;bjoD!#Ҥ%z .@-Du<*tfF>bVסhE +"ԋa ҙz$BgE*vB|%S:U4ˢ >g][B쎄0X0D?P$Blz/KybAaAR+5뗢Qs}_j_X7ob9z?GJ_jއ6sJ39 "URk}‹lb>ܳs>3O?ɳO?h4K_*.rX1@wp R5wl u$'}D]'?e8@qP'yp1O"^Ub{?C|k{5X~dlsR\X>jkgə'?'([m"{RH`=uuQs;r~lTi?գ87+S~^|3hnk^(T]zo]\<53KS}{kFg1qAxeu3Ϟ`!nbǎ-=A޾Q᧸/{sS7x'9I,D8{]d"׿nύl[y9k+m[Y][ı\yՌRb9ʚ/c4mX__g]L]<{>._5B`8 aiY¼VuErai UE;Ν: 봋K,-ы0J:[]4 K L6:>ƂlC ,/~K[ǟwo'glڶ_wqh=woA>]jFU׼{X9}G? :"zۻe_-zi>?Cz|7/\qk |}7y\r9qӬ:>By;_+o}-̙csw}9o{"ທ s!y߯s_g{OhT;y_Y_.캔3_1Yb,f1YHׯ䮂6$2ap[3MTK, ^i R 7[+Hl DC&컎 !K3Ae)9R2Y*U%sLjq^>C:X b-L6[}{}UhM) W+-ޣRzD†AV*@ODᮺAMdK4$G$R PX_mD41cUE0(v l*U,dWS:X<Ȕha1ݲYZMA(h:.o~puUٜq(q;= %}ajE =t 衂Wv.lzA7XRUj k]1k;-hPJx1qn>'9\hQMI MkϻCdJhZLWPcgC=+1{czOW;aAPBq7D+Tn\pQ IhRD% lH 82EHM$hA L1{?ĤZA 9*T7^$t `kV$iKc_ syX_nk8qYF!pǙ{7.,Th Pի `[USŁ}a?Sحš.k JG+P)l^hh/sT %43 ctZ(8 뜒\軎8Q6̍.;kF7aC2yn#> K[6C{v18=@`{ǂ| ^HtmkJ}X !"ݏ:v$?KVX4]UyvCwg1;._kQyW^'NRvmK[hV.޷;X9{O~!)oM]2ox_euaXXk솰zPB;ĝ8_Vڔx}3\w-%ۍsV9vfcǏ{Y^Z;^Sgg/Ii *~g~w|72.qy% ֺ^"8mLQ/+ȡ(ٴ8(fFHnC#62 =+(-lw!4ZazwyN~_~׏s>k ?\_7_"]uK{`4X_cmSl})cOgw|gO :`nqùGe_W9~%=?/x0q,ן]E_ʛCC爇Szck_ʗ}?}x>7{¿a1.y7~!缶>Rկ{/g/}_x,f1YbG vLVgD)Z&NX,~!U>H{ڶmZn 7. JXhR@z)RMM(!Y5U` ^ !86ԎCfi""WjӨR]6EnQaqf}cyD"1ׄ}lIm`c}&zg^ 26> ,1 \!(p0*4H7E'95 `vmKOJv5=5k7 `jh] IZ)EMæz3 w!%*nb F6+T Rqo tUYCP2س(mU0^{<R!US¹bjX%RNՠ|Q;\%(s{_9R쵏M.=֝gzjJ1%pT)xSW=eLJYgUX 8suTXFxKv[JH9[nM2;6P\X!Z=At Ex Q|Κe7#8;@h*"@U!A.]̢;ϢhEP幪jS_VS!Sf[(m) ,mE\SG;]Lsw vo YBT1Lbs:ډj {TqvX=*{<1uPkØiz?apbd8mȶ HVa}NsHO8r]'oYZ\еI!:\ClX5j&L9L581!+}^\&S:AV^ F~,^ |I.2k'New+"I:ш&%ڶauegW!$n&;,{75}?ч=q1F1>SϜe]t댚ƪt|xOpŕWgVh}\tK%;~w wW\sekro٬Amkߐguc=r}emC`!@%9 +lltL:]7/Ƒ-2յ^+cdui15NyOvnC}WRʛx[\/'V~G|y~}]}+;}~MV౻ٓ>|x׿u/grkxϞOSюx7|c/zؑH,%\}J,f1YbOԛ͖#фZK K_;n}'pܞ\ \KRPb"DwD .lbH!lnjjBbMeE|,IG28+GiچT#_ (j]Bd4.%WB,s$ShBM${~]3M6Ү`-3(' Ȇ&,UA^bY*G Oɀf{+p%nmZ!ysm 󴢨־ Mn fj[1%+,hjzF`AX=g]i6X*2r=>RUJz]Ƞ }[A]B`J v\B\w<ޫtU5YLLYlz +`=_U&}oE =(f>^{j`MGɤ+u;b( :h\:~z/FP/)9w![k} 2~e mj8{8M NTM14j`B S{P`3 #QQ> A*NJTEE3m(2E`%XvZ/Tlh}uO)Xߘ0??Gp5w}o!} 1\%RےRbna}#okW 2מL`ܾYle>|-V mٱk b}28[m뽠"-]*-Б"Srŋm_U} slx7On<ȥW\A\lo07?xxZmG9^O7dvf3˄B$=tk?F_ }c3JVϞ3fMĔ8r<ÿ}qvoﻓ[obFذw̵-s#Z)@߷u=]f"Gϐ9X]Թ58Uo bN]=[KsV!uܼB(.IFM h"^5^/r7}#>:x zcΞx;+ny5+p_ʉgd˹Wr~7}?u}{C}_s.|sqm7;\8so+ny5M;ǃ/z<?CcPs: ygM H dP;d`Y*A`eTK1 Szr5 7yxa )E .-(S.aޫAAX~r)UOO`oPv!|+8y)KxADSP` ,F;ܔ,dhV;FϹPLJ3-H=_3PXD7)[ղ,C ^ykpA$O<ڜϺ¨iɥ }&Bk_}UfK/Z4 8䉶f+&jAπ/gZDfBoC*-0p۶HvE!FM@0&z@K t۶r'9~p?gO^v,Y8w";+,-̱uyϰw&^z>VήqQ}8~4~^8E.ھʤp)-=@.slۼ՗ؼ<'βgsP>M6SclO?7-Ʀ{زN>Ne<{X޾z>[w_Ɖß>g\gO^y3^pu/e?/?߾}>8}smoFxB߷iG\ye_ﻠ{-|).v^f΋8կ q_]wyn9_|=_ϟ򧬰›y3ʷ#+(y'佼7f;o,f1Yb?0i4ѭ,&1ѕBj̊5qHK!gNs;ګT=HmW˙iG&5 -) wb(2-zU鲢vb H1QʼnW4` Stlr1mתK1ҕ\!&%hU)fcRpPnH16]%2c˅T%$T]NMݾS@F&3B6;OZ=~)>m1@SWHV3!(m"t N0(N&uQ7AukPhɤ$t]ĤjggrRYyY =M`}} +BTghkQžܦi)Ao>&ϭơŸb_[jD{~{~P`E gb@<MUm :&=/%WX=)!h0GATM*> <6W\3 Dڬ%E"E'mqs_Mĭg^{h{(uEk k]]\z=RJ)lΕf|NT{ jlA9~GmK7J+LFv΅q˺B3 rv8֞Y0%P&#=II`݋{!XP1PJC4'b ^ E:1@W8]nqJ0F-})B))DzR{5 U+K4Q$!SpWqLcĞgWx+^y~6/뺄QD3U(8dx~zY/`=Ǵ[ISD ?C豲^t29b{XtmpGyDRahYB!d14ʯ xaB@B 5#bJԱ@-wf˖̊ c*RJt}skWڧxmUc| ~ kz6 [{Tq@m!<]|bc5Pwzi]>C {ŋ7oɦUx.Çm[qˡG&W]y '>ç?ΩӧkiGcgcm}{/~9?GϱG#4B #c~WO#@ԩ3<rزu;D}{yǘcnkBR}I!U+drFiF[>zg{UF^A@CyuhAYZfԆR A E'!R"ڃ6Ġ+"~Պ GS!owAGC/ 4O[ F*sv@%ӤNq )[a)2*0`dEً8c {a[,j7tV oJA b2`52vZ'* v⽜i^FKEbv] xĨN[l`YNBs ["̊G!O/7{x"RQ6>OCw KmL*JskyӐKxqKKV`I j_ȥЛ]jD4M)u\F@1S[b)EFw) Y:R9Փ ,6 MiGjq>-H@P;- 4בm QPV%YHE57w9}d&ogkH,fVWϰm+[.ҋiF-=Ŷx_޿]} ծۋ|0ˌgQ`,W z+,&:ޑeZT75B_W_nA3LO/4llLؾ{]u%OE~7~szq_g" s9BWz94_ʾ;9w wlܹ훗)p=p3>5^ODΝ"M"Mؽe+шm{Vzi.Yq=>YY]O_9b4ЫsO[شm7Oa&jݎkR?ei|a|} ^V|A|b'ceo6mY;y#yyۿ}O7TKv WL m"t]_UGj|l"fd?cfY db ohcN0֠}* `.Tr% (<65A65U*YR E!N(n 3&\Wpl|Uh ~Z1T+uj2ײre뿧z)* uHT :($qU8WGgbˡR!`PR:2Tp1Ho. C6*= R 5ԯLEhRSf4`Aiji V*ܤx!~()ڈeSvv16Īx/{onuwZ{{oխyV&M%K*Ƀ,yD 66Cxq!d"8Bp  Kh4TJ%\{^?[$ ;MϏ%{>~fwlJss׿ZY>t-*h EA3uMM)6ц֭I1 jk٣jsLT0T121`T>u`A:V%w`Ē:nL´ކ -N[VE] Pw:R4Wtr[GF&WlH?לM193X5bhu͜N=0oI H vt.j^MJ!Vٚy/.jVih$H{=&Yac8Dl.w׳~~qZhx};v 7R*uW=+ Z*1_냡:Wj 6d6+9+[\yŰ"%|zmJc>GІ*BPa!C,{ë9~ŭ\6p4ֈ vаv֭_ks/hC\A ٛ3>zXsu?cU _k5Z:ys.yn9gbkI8LjR~&.U.[ha˜v^Ǝ[)9sI֮;eK,]k.E>=%SSڗֿt C{Xr91D;xj!3Xgd%3E+ٲzm^baelZasS8ő#iZfgru Iνh'Ån~#G0-z*NfeW_֮[p/~ 6x!7-WN;5`vzk6j=<ofveݏi\yc㎋x{ SKS{S'Z2\Gx扇اg"׿c{o1s׿1w!`8wq{"/p>xbǿ;gdÎ8FSڷ_iN:g?}>௳g;x)/nf%+k.Ԥ&5IMjR7:d)6@u74h@Pm}hM7JͳĨvL?Whu9+)9+ (G3bԾ"BLI]lF 6xIli[b,t#U t |Y,e2B @ո(5Im{uR7Kڦ1/YͲH1Tu"'u6uC؀e㏮-91{h`&U4 3iZԊ2TۍIa 1gW1){1>g!!UKlO,6'K6W2۠ǏʊdRT{iĭmȏ!%Tϝ6% aQ.q)Fţ6[gtub4)B5Z'ZD6 r:WЄbvvካ΋B/ ^IG]Z A}bVm@6[ !12h? 9wĨ99wcf1jl q*]iso6Plc\(P >"*q%"֧n_c vZ;?m|+SDk@3w HI枡>vlޮ'||}f$.Ptfϻ<=NC&aa~JWm*=j1*ִŸ1"N];,.p[[l H[[ zϸ8ۤcW62Vm_k}Rט 僯go9 Z7d[i#if[ϦLmb6kҲe(jY;K싅+HIuA5 ~ދz q:f0FGAl6XÐUe3VfIMjRz^x{16nԠsΡi>9;N)nʟ ytW\Ǐ3?3=հ{c;|_b>v7O3$kNK!B`EKNJC;hHmK;XM {^'F ]1kd05^zً]:C'Xb% Yزn\}?{tg3i{=v\vNB;~Çjr&g p8b~8ѣ{_K.pWBJ9֭ʫ^]/p3m?x KpIJ7b\kY~ <q V{v;G<K9Y傪F=qIo]֟朝bSWo|^ʒ9]t%+o~3mY}gzϵιZV??_u]/Y6/O0r]Og  ouup_Kw.nv{w&5IMjRԤ&+K6:U[Tѩ @Fu#l34U;ͦNpl:JR&FhۤV^"%4UdM;,VtADZ7V`쭚Ctv1$B\S_6=cPa Ih~aBT[IL=i)A tB Wd/hrGWIgMdɜEsgog+Pi@ eԘ7B)ȱ*}(X%`ZAnXPQls6^0+`̾qߐK!@tC|J r5S!ۨgdz=)T)r:ڍ6',;Uo)Kc2%*KoP[J3 jB5,!QsbF X6c~JbÛ  fqG. S~@PonlG[ 1xR ` 9+І&Uacsiµ?fvrNX9z(9z{+)D>0d#Gq9\v, OS`f hgs`=p{405)o / |g00;_N~_<7==8h9;o~ί_y/M[4/]wOO}=׺ѻg?x.5rsǞ9kν܍83}wpƕo{,[.{ٚ}s~2Vmb~|C7)6fKLs5Wk'8iN3;ܞԤ&5IMjRԳqāAS1H۴juiC3ܩz+z6ئo Z1Dw!JLᨘ*<;N0]7%N q7֜.\mZ2I:<6 JWhۖݭ& 7 **hm<,q5t."{ XTo܏nxB[QiHL(]Eɥ8G TVaO 1'\:Uu]Ǖ:,VT{cE_\WdžXVD(`CȢqI; c {녫k?) /`!!Z{ ̇aRwn~+Ve4&5,O.B7<[X~N$5 7oo;g_RiRDS IDATرc 6} ,a銵frN>@Y {/ &?5v\=M{?'j66_p:K_v}3Fs?-zk{Z_N;=ë?Gzίe+8 پ}=B`:>]P cbxS돮|.[/z ,s=~79w~C s/媷$}꿜iywqk͟Gaզ?ǣ|g>\nn{y_#wc}ӏ?Ȟ^ ^~=\ _;-Ԯ1>'I$[81Yfii9c}3Ԥ&5IMjRU2 b,fKl*Z\sPEP!G ӖQkJ ^sm ߂ƒ3]ɶKDUkQc) &)TUg RTbVu:Ɯo ѶmLl3 Ħ%B=N4̖y !È1b ["l#E; t0V 5w4HUN5w9n2t *QȦ a)Eђ k`SZ $K0;2F^ W:*h" ŋ%S B И0JJ B"Ue2L`W$QJg/\Shy ךJTwSo1ccߤ6o Aiv-0;J)j?_ m6ktHv-Br,ڐ{;d=b٦D Ne*ra`S R2xa dpRjnt,!!\A^ceJQ0(YfwOF %Yj2*C7Ev7K)f9BUS2幍#Xc B2E*mUDFtT["{Aݧd婩C5?3)0B1X&vCȂ.,T؈:9A+҇Qa4Kff?=$MEmi :Vzk9!X>YW]`V׹b5躱.|nohN{ix6Q f?.Yr֬fߓO!Ex?=e?V$e<!},t;M,œ)RhN255ͱ#ifY6qF.x?ĩֳaYYױ뉃YiOӶP!;pwX'tO}~y w }3K9iHM-xi9‹_8v\@ pFpq^usյW3^6Uy%'xWgǹʑ1^hx`>y õoY?" sǞo/s>K^VR3~_;~'G_9^Wɹ[>ş䗞}}}ӯ?}4-?/?q~~|c޿D|zl޷?7?5o r^q]Q,_+3,[S͟_zwCSE>~0x.\Ǟey;Sȝ8E%q~vn!V}wݪ8!>gyo୼U!|S|gTԤ&5IMjR+rԳW&*T8ZfƔTE)8,8Vxg(1S`VlC$l갆&*4U5JOpQ[`d()FB)j]Pn<&5 Ik+A*T]45x1oB@B)W%,ު Ce%M`&XlC(*t,ZtoLnq1;OSLU8j.NBl ƹx歂_},5_:46 ,uF,dT` 0PnTd(,f-6ݚ4h::eb?Qh 8xce*V{n=`Br(j*Z+sjm ¸"`熫RVL=X3YtL@ ŬrbJTx?ڽZGE—"[Xi˪XK֦U: `"/EvQ-_kqal5՚ viw6!ѹ,rov jzuns=Vz5w]62442f!m Ο^`j%D7loL[d c 0`ˁظF뾮s`B|%c j49ޒ 16rZǔC֔c n?-VՆ1BWy{S,=JHI\Zi! ?Ykp}/=i&=hcIיSA n ̖sχܱ{y'x"HD-Q,T㔚m+Q-EuXS̞¿5r,s-aBh}jQ@~i#ebw9,us1,`*צ:=d[csӯGL&QcL Hp9ؾ}i`k]1Xn8}CB-$μM(%SA#.^zLZs'JOU_/i@BPL3=4)t8~'azf|Y^j9g8x`][UJq.]ܩl|s'N6٥K1x<&%k Ԥ"a9y;{;#ټ&6({o/aOjRԤ&5If7-+ t~CEM m)L35K}o nYB̗4 ER3cJ EMV215$ۼtĨ6"¢b J'QHMpU5`&RcRj*ݥ$g!Va 7@)̤2 m̤W b`6 "@UcUhhiTine)YD(!ՍvSP6;UW F-Hsi Jgو̖,*RUxX7j:BJQ(vZ?^aʫhd^ү,]z@*sٲz-bԳ٣J~,{1!=񇀃Bjkܑ^[ ,.? !`Qb{l͢% bKW)}_eP7 rgXmY1&BLܲ{T A6-v빫۵)El(/{ۚPa{P}Z홦=$Ћޫ7wR0Cxg`6w̟}re;/15o畯լ?kopEb,zǎu 1OG>BĠm# ׭+n_x׿'xز|~'fhSt㎮W2dSC.,à%}g>ƥcѓ<<[8' ggx֯|+qyz> /8%S-SΝΛoeG8v[Y|9)F>̲e˸暫yGEǬ^;n[ж {v?k֮Ԃfo3!wͬ8unMC];_ߤ"빞1cq)v.2_#Ԥ&5IMjRԷ[dR Y3M;: ङA iRK@Um lR&DCf#QTI꛷-Z\MfnaFYg p3p 'ݘv+ٶI(pl3Uǚ9hP0b++Jjhڵޯm(b?ĔKI)-)=CC1ЈPrts|QHEaU6S8q)#]V58&oPN;6d:Zp9$P@zT^2IzbȯnlsҭA>}J™SlZ7v O*Ľ%1ԲxѶ-w.v vBg5_tz=KKAlS͊rz." b*1{aٚ ܒ\5}.:ڎK0=`@>XUXBM]7&(m΂کN?t`na5X5عuxR󲨒>D \* )ʈ+ ~f1pѭSm`vŠW.:W3^Km3m0*r%B+I R v+5e~w\s!}#Aͬo[R),FtETv֞^Eh0hN:^>wpUvRY}fuW$.+`mTG]Wâ-] q!U6a;B  t ۃ>5gaFe\G~ "Yz{e7R=Ԥ&ݨO07wF7^ KbaY|~s7d'o|{?0Mn7cG|ZpHa05C;XOlYfGygx{?'?Ǽ{_K.weY;v}?ΆMXr%!jÊ峺 zb|O$v)9rU3S!jvN/s]o[pկ@BmgCGKpQF7͹5xΞx ;\ SWDSKfxE),]pj:v ~럾[Lͭ8u>}Kky2[ȍw  Gg9OjRԤ&5IM-hə\.SͲ7MYT!)FYZ)]q$&s`WJGRU2E$iSKesbQ7ԨEHT%QЮPs-AmR7EYJ0`SfĠȔBd0 t]f\2J= IDATA]7&`m.Fe:8R5B1O*#ê }soKx8w R$GTeAێ&kU?,ٷ%0c R^2~Sĺ4w8'zm2lWt4GsTeQb*,ED;%J7Få}Bj#A68gJb05X 檘V"Hq`= EBs\Kٗ[jEŮQ+zSCbV 4U qz&E<;T6!Zݔ1I:%(+:ۦt>E@ѹ8<$ s]6FUf[W(z@(Z2 T*g]7Dd%trhq2*hB *ѲtBgMUgChRijXv8gJtkn&5,`,LOOA  j4t,rU97E<w]-L0մM0ku]{S Xu isB(L {~DƝTH=m.gw6ؠڶ!hdYE?S( ;SΛ:;ztfu?hYv-/?pxv抵k-:9"P#6kDAθ7٤6}f?_t5fR1גQ E.BR/N5kw }Ҏ7#iNo_kx@ufh/[\Mz^!{4Ggs?O]M|,şE]g$SHw+swXhDkjR37g}pX[iG//&xRn?R]'Oq]+c#F#| ֮[ϻwOD 1XXj9Wz9dj@;hEѬiشQ̵׽?GG>ƍװeW3-_ c=~$mh$^'>n_IƼ'.榟 6;4A,cU{ٽ|? ˖/窫_AuXf-7m=M>tm[6qQƣ5+!p嵯GYl/Ɂc<۴S5ESK^ ܍9ra8uWQ߳yw_CԤ&5IMjR_s p>L9t4TEYLH~44?6T&V;`} ?&эqT91Xa!Db SjBdN̝Xͬ|KQ ܍uL&6ٻX ~vfqTy]tM xEӲeA{tTG}.s oزXGW_u>U;hlQ u Xg;C*ؼw5,E#Es Z{S5YU[M$u ??Ɇx?/1/?%&syEr8 h["W:v\{-fG#lܸØ^r1K,嫟$gߣxXj%7m⁻Dcxjqػ>K伋/aY[ظq=΀IMjRԤ&5IMjR`61a0Ո) iR1ZfKfM`jlcȘ65\r Ȧjivi4ǣhdVZ^mSL=hN";¸ HU@dSd|r@ZIA WkF.3$SDN7%[JC73Luut45  uc[iD rrղ5 9wNsz=oW3P! KTUUjql7RuejjWztn Vnv/֦>V=rgZ@qYUK")RYtkjS*vf)ۿƪ");*"5'Y-/] 3=6֒u?֞!` U!B)lSC~jlJ1EfDohG:u*4տoȣW.|\f ZS a3ךt>[Ӛux)6V/!D1 _T*DQAgsA*H0bk cT-/T YjAT]1`PmGk7(%緆45AE}l<ͻXӹU(F$vĘHi|wMP8Y/Lۻ`YW&&XdC/ZȎLUp']GsbTKEsX ɚ JyDXXUaMSSfxIoQ;uu՘ HX!  9h͐ b ki΅qϨXntj =d*GN-[;NL ˡXہ$|Ҡer!XN(fM/p-^]S쓁Եf̚9݋Dן3jn7l| =@F}1sS$ ѲCrmSI]Z\L<9(c朰j x4i"RC^?j_֎!LL U-k57ZUlxKLC@5ɹ"les)o.)Fňut_7z鵉LjRw>Mj9v{SyۭtƷ}vڴ&φ hʈv1O|#_hpDjϳn&NΝf˖ml<,>5W_5<{7)5t9|ԒY yhRp!y:rCq_ɩo_eڻkV]_A#1!X׳0]mpΗjr~'{ 6oʫf~4ֿk LO> O(;z'OR~&bp!v=5.gϑϾRԤ&5IMjRԤ&e%Ej^BD߮RFù8cUF{4E)f tEq)>se wjt6!PIUe<2ut1 ^nk/ԁcRi-2QC$㬫!u 0t6]"*ۤnZ/vc˳-,O`jRƔ cU ڹ9B"]XjƝZF79K4qUu]W0cO@ʪQQ15C3`t+U,9Fb(v|cm 9|9M]ݦֆ^hsXI堮4;J&5$][Hgٛr:ogS}R"JSM"BBd$wϺLh –PTmlM$B#ƦObVD"U@u:z'$t6~>D vfm]YFd!o'#"@iIۦFM^l䬐_bfќqm*|[sunȺ&Exڼ̹ӱR]!vgT!^Xl/ѡ!z5 ,v }*ƣ0@u6f\PXang h'B+]}o}}S@$1$I H8HHˢl˦<ѱXq\JJUq*_J/#1mٲE%LS"%L A=w{|Xks|Cх9 "޽쳇{okځ"nxQHE(!9ULi~x:bb)дzdyvLڟlJր@L[RUN~"nym1-l^׎?GWҁf^~.Ȑ XM`H)S2,ĞpW5+ DTRŊjr6lN=SgXvMnMk.b?Pi*V~ʴ_k+ 0hJ"t`0}>ԏF~+7pqlVZ+oj5;=5(Ie$ă_7!U1yI$,]ez1a=)+]"n@b;oa *_zZ!8F! 6Vd8+Hb0'LN \ThaQ|B&hE%s1+/ ?|uQ^x2G;^o yM?£=eWSWGL=mgC9}ꐸb aȌcu>Ac}MȬkŝF\ ÒsoGjlxᇍcPkBzZ]Z 6SK 1 Pـ6?}#t(1Zlʴl?%Ι~T 흣}BgҴE: و6v⾣Y gdT#S*3k?CغMnĘLxkL!ې{U^j ρq>ru-+c<=8?&VLUA0sk>}n'g3gg~Ϳ s?@7z]Vqna9%tX'{gzaSO;v ?xy{'>׾M~$Z2CV%jVCWy,N7ݿ3Y]BUP]>8|n,\=dnV7 ?`\]|ӟ?&vƏiV+@E!< ;{|s;}yHvs8}/pәW/sdso y 1s1s1s +]+hhTɖL-t@#ɀ@ΦH;,f Y]xb{椞Kdrs7%[Y v֐^X]ߔb`1$eo`] 37Cևk왴f)y푚҆P1Q% ?5lW-Zc9^xYɗ> xǹi.<oy;9}4Ϝ;_z9r$^x׽ws9>y2[;3e\|׿,y [7s/+V}?=>O܇@GRQ ڠBʉ\>>7ţMnؿjŰb\WN:ɩsgy6*py>s:E IDAT~ӯ?] \})*ÐeDž)gg?|rÛ\?\ڢT򥫜v>p}o?Þs1s1s1=I&JH~%dxb/e JWW @jJu] b0o Zr0GX_5(MAk%$M}PXMi{m-E]* 2bs(*,RE05qKI=ud $kuzFe]:(`@RPGHV~'AXN6 |'(cVd6 1V5p}{{垐*ͯ𞙹Y?ђB;Hbx~LV ]Ҵ!lşi1ePy{gFZPOcyٮ@J. PYwFuCbl&M8SnϿ@18luC] To\!V8 kXRC2I-iR7ZM뮔|i\.,ap;{2cAS#W[ @oE 媽$8F]$#8 n $$9I"j삔3䗼j3c5p" AX3C@550]00bv^mN6 ?b|{O61| 0K&H>Nc4c^ŒmjjcIP}HXk?Kzakש}Ι;>'9܋Gϙ?ːY,2uQNJRIiBZ{YՀz,8<8q,'N /P {$U"w(zͥ.׾w~>;d Zp ;;|+_W8sӍun~;Po:Oz9s;lmh\|~~}|ϰ\pM7~M|3rsr5]1.]ckwo~۽?ykWqʕ+WK>wgÏ5`9c9c9cY]!QȮT{ٓꮔUV&EƍI",jAjO%\45)W![E"Yh)DF١gAQšVuUQu 5v*d}cDNj)e6-={l RFWXXbCbVk95aGk*FreAĀ6S9g3 Ud2"OvaȤa>@Yv;8iM V^+srvق}SL(pTgߕb[jfiHzۮٓF침]`D )spk"􄲸4՞*o=V޺tV0 |`Xm{T`D,lkyX05ǰ{LXOyҡ=R7Urz QDb)4WUa_I Æ"njP!yoqGra)\͉LI~>>ϮƠMAPNfenK!ijM}k1{b_6SZ Hf(xBo {8߁9f6kIpؔmb4]_1V]5ہn؅+}3y{YHԦ^VRB&}ҊlX:t;hxJ==0HߗF:pD1CTKE6\<9תhcᙧ3nކjg n;>`fRB7'eWJb`T0+Z"U|m=#ә_mэm#BXoMCd/ ( 95x_}pHj{,Zdf#@{>jn-=Be<;$ؽŊMLrvk&V"*S+~VϿi|B(}Kwo͝PGz=r=M7ŝR ?e|I)/~ׯ]f<=)nײ^)MvmV+/ hͤmwwҧ}|<c9c9c9O\f5bUg[ײ~E%&ܞU!jea\PT4'S;:%lQͫ+6'zEΙA, =M"ga+UWDl>W iή 0&9PI gUzr v%e ڰ,+YjCL/)6r2Gr,gL$&ՙ )e@KfOw6mESz3`{(-JA$#RұKl ##M:K Ub:(V uR|CXy5n 'M꘭wn1Q0!^ lu՛Oyo'Гe[фRt j[r43Ǔy;AD1-,uR]ٔuu}u%gdcS Y@h՟^s!5ݾT_yzxզHsilpПFV֍=LS#༩n' mAhl*x,yH| USbuHN^b5ԟC<.wWC>׉xR>*MSj6/r*{s 0ov4}BHXiӽ8[ Z;r$JWj|nꭒ`X!w˫^*Q9ͪ١H1Nh4*ә{=q) dس5\njf連![#V%>*hľ;W+ҟxiAf'mX C۾{O{ioTnknsBg*yF+(b6H9zᠸ&Н1uVWsJZǏ;jjr۝wCO!|gz9q ș^_+ͷz,loo5\nÕ/pp 00o e=7 n}ͫxXC?3?yւ8"IXnm/=ȑc{XndoobkZckdwg-IY,x񅋾5V/rs;͛R2 Nw W\|C+zY~嫇WWZp[%Gv 47jwG3`9c9c9c8aaI4WXv,QZ(Nզ420@2RhE_N2@BkNHZ( CZ80X(6Г#%Jp4@ضZQtķKWf)H'1pW*I2LXJ-q4Z6n-B,D&Ph L3JH׍ZlR,YO,)ڼsk:݆dSr( c6{Nkdc՞F|ܲ,t-kԡאROT1: 6J բU%UˆnF@O~/VjV2V)Kay9qk/x=3}:Zsb[Fӂ6]gkX\_)&$5mL z]Qmf۝/ %@$@4k&qSqsC&-dlSd6D <,9zkȐ e/ruퟍ:q4Q__'sbz =|(D5Ne}1=jDF>&T_:_D@m7+eKw*p{CSh̀f/3QqZ_/k_!EA+G"Nk:EE}m7xBtڪlDY̶BPN?s2OH>-C|7~!?[,vMw{~⃐;}O~7yqE%ɒǾ=Ξ9>euk_CN;wZ[ bWpU~EK|7%7Y *9c9c9c9'u(jc3QX#duh/N+gbUA'+XBVn*6DDmoo +z~5;T?KpRMs{ie:{+|~N#;%B& gS]L9%- ˦Um|B'UϦs1+/ G%#{\~/<o?ʑc'ƒDž?'x;ŵUvPGc';lorӌ?+^|~O>x>FDJH+pPXtJDkŞHi?VPgRy2(;lcGޟ:!c\*JMdӖw7)In̴Rr 'nv!yTW, 0N0TW>z1Y{X瀍)&0$R2EaQ:Hڊhj{V{S.uήLxj`Vޔ*=eV՟jS19ژD8^}q F,A)}0do9<6Ud>bJF14ϗfP0%ĭT/ 0)֝3chFD8\Ҵ^GLIP^h5}Cl lW‹+ U1gsށV ;D#ulg?#Jc1D*j[bCA ؆D#Yl_\ iC]*"A&s(,Ss?u+^D؆rrL,!;1ܽFfާ礔zAOegAϛ;79~GV$aݚ~kgGL"&0< ;\uBbLj| 攧~"/" ;YtP:,ں♮"Vش"ŋ'$Ӓ9㕏wvwU?{|3 ?Vp=<_g~MW.sor˫nM˗fe\48uyƷs/\pyz;ʯ2__agk..Pruvvr*Sׯ߃R *s/V\x9rș37 Vcux//O 'yN/<͐GVݣH;ogk>; IDAT_s 2淽i#Opxb,#F?,?HBk2: * J2s1s1s1=Z9-f-5 sEEԬE3*frtYd!^A޺[Uf$Lb͡`IĔvr@"Q X3fdnZz/EVKWA[PИ:QA.G”mlCH(s[(}o\w%%EZOX6KX6 Mf{dB1zTaijSe[d4}69E✞hjc2 35Spb$ Tg;X' yR?]a0CAihv@=f{jM PRFz+GPZ&u[U +Bם6ŪFj J:#c>RW`kmw;_ }E6O5Cw!hp'0 zbRѭ$9)Rv`uP iհf]/hɦW#j {F+AuRa6`+s~Hx|c/>֊Ф)"޷<,s``G93CJkE5l7ri 5xo(*Il}ޫ7hXbZ%xBx߀ns"}d9l: 2+k}E3ؐSہXm\&# 8`l&ڗd2Zw#D_ Z&c-Eݐ6(gPoZ|K9$-/{Xs*?;7Edq}oػ]x1BwecKw1u91M gɜQ+񹳹.qM}Qo>6Q3s2WU>>;n~o]?rg/pOp vv)%vi㚕{6\z|=ok{q}ڵkre#|  9x}睼s}-bSG#XS{Uh t7=a_2/|Xל8~cN/?=/f"/vppɧ!vox5o|癇m?;_wBk[A/T9'dAվ4e]c9c9c9OCJjm -pp"5[YN 5)3Z@TYR Awn2 +TâAb Ƴ`xOP֞]bJa@~šb)2 U+'=Y [J84dl4%]r5y5VC6'¨ʰ><[ jra6/%*E?fs;@&ǼaҴno`\U[G`߀R=0:z'rzi6NɩGC6qWz)[ >$_W~~ >{sr:dxGDr/R~ͭ**E^aD ;㼷o<( HB)#amc>~ +G0vpdow0~^"ffgXhS/V̐crJ jnU ^;{sذYjؙc쿓M͋v>ž'PVvCf˱o9gPC'&Ӿ59X}l<]0-${ߴ֍=rc/( WV g};3 P/q gl3~1l,9?c=?y?\B#{^d9l#|,e}HU\~#Grxx 9qΩSi^{i?ԩWqpE>>F/]! K* KeRqE4<ѽ]v^!>XmkZ,v,O<~9.*GwU7rϛ~7Vn93{sx?u`6OOpe덑ڔzmv_u뒜b`9d:hJK)u̒-Hs$2) vi1LS'̑쏞SbU;T5l֙-I/?5s@%nu j (mHР5JX R "0@(Ur5Bl; +Z5^B Ugs;b4Vq;X^[1[ [햽*ȰER~{/ HT:k1/T*NTr9.lP!lv]4IWcRųdܪU@nﻡb}u%n-Z+nmasm=a019yхZNLi WBU{&oFqe4Rvܾz{4s25-a] _͋ZBGWjVee6]-AcP(*uR/b$^ZV1빛M]n=_ qmv)2[\zq\sth<݇ηຍ1O*mMjV&!3Jkխdj k]|v8jkn*}`@[5GFoN")#&.b?"bOٴb8|>]Q/;}^WMAqlՀH8Lsq2AV+@ݢy!5ƳqtvxVooaďDiͧP}ݯ}ƞc9^xY `Pw6᷾xpș7qrwr 8y9GvXRTr>Irr9Ųf}x@Y|A~?>?1xpb[ƍ/ JWrܳտtYX,|kNp֜>c8"+rsgKetY. oo*R< ( ;bA.C1s1s1sCj /ԔiWQfAWʖ4%!Hza*,a2PipCLIʐ扻j58&2P%Y;t$n+=tX{XeRIa,X8TKOKCi(DPGhm3C>66ZrT eɔʡzCʹyH'[O$>jP5z^ }?]䁪dz96j춪2[j&ZUZIya1XBUa6yaŭ}ןl@Mfcb) QV)ҪvX'TޭVOM*9df-hI0)ChZeL@mJKdJ{&lO :l%WU M!NtJkaM>52ݷ'œWWÉ:.FsH?`B{ ЕuՄi#k(x, W+kMe@|^ bDSd0f"!I]uLuDLqY&LAƀQfk 2icT C֬ i6v{c!dUC,6?p . ƱM62,2t if;Xۦ\. 0ZH`l< [Jַ(z;vl, e/!Wз#oxc3$:jQt֤;F!J\o8.:8t(Q5N)C11O{uwRϙ>)i_#ֽF;m?hЀWz3/9彰'@;KHr!)Å^ QcK[`KH_w>y# &Igha_1 !4s1+/ 9}nwp%Do~_*Wv8?_ԙ\~Ñ<4G9~la9 lo-X.R8yO_x!/w{s7]ox "'P]s)^W)yU"Rk|)>8^` ZklK+VRTInP[y[拟ǎ;>>&Y?2׮]aðͯM7P/4AJ6hM6M`= 0xc9c9c9c9~رW.\G %پdmiY%łZ C^j2%~lz]\r&2piR&^k0dym̪]E5çSt>Z Yq0 Vi@kwX1l֙)gZ "pI09c֫R]) P ϫu=K>Χ;2lvj !{D\`=JX9,fi՞`孝-j͸{BkϊfSiqx؁vS L6ӷxi1xsv ˆosHV4Yu&$hŬZ8YU]I,CVih6Bu{by dgJj:X'6Ğ MQB6)9F*rD!>&fRjpaЀ@d ԋM ٖ]"!ĖjZݖFZgbt@V\`yg3z[/90X|tIH)tvwk]뜓+]J5[xO]MrL(ZAErQu$E6m]ZnkB?O=uٮ2Bܦɗ/9WK_. FM+JWPU{. m#>|LDX ""XU^wlu-FaF#$ !O j1ښb%ŔRm*Jo6烹BUeIn]3(v0 ]մzTkkaX4Ļt꠨RZ0L#:Ӓ'm$ɓDݩqv$@%@a T<9w-8;,p[ɮTMa.gJP%ҡ@ 9T@3(R5ziM{4! J9XSf hPu.]YZBWCKrQ_}9d7TU ޳Z+ d7K|֍1ԡ7!z;iC7q*WF[EdY@p,GL>NP&ZL%gFK,=CToECO/^G~6U@{T}{˲:h̵>2.]UK[}Soj-%qE,"x7o0<a,;X,[Bv IZ})u떷;g5'c̵O Q(/{]{9ƀ2‡S^;'}\C`>=ki`m% |dcώT ilXbX+<@Bdׂ>p844?:ẀʵR=Б5Rȑ 1@V5e ߥ>pXp~7G.'oѣha9,0~?)L[yȩb55p[H9u-.*[p"?'=pѮ!\ ,@uhn,bے=Z>#j̱q'T@"E)k\Mhb^O:9 H&[3Aw>fI vke=xo]>zΧw??W_} ?>7^>òx<%=;~m_|',|ӟƗ~w Oy_LVRkʯSB^M}c3׼{{{eRn220Z%HXK$bֆZ 0E*Nc%HRU&Ґ IDATB=_,ȵp>xLPh)A6@62 KckwDh|`/6̝-,$H6X2e! UA]V+ޙjYC*x0>%O`"51f!6X2b d5$bUR+րBLQF#0'ZS#\miwxgy2) V*adrp܇ dg@`d <\X^9d_x d,uOê<"Hj,@YG>96 P:6*6L鈔auy\21@Kдn&sX;*fH`͌ה{VXj2 w'{l8gu"I4pL y$V0)S+p6=SͲ$|Q 1[ɨv Y%Fƾ|+=#kҟBx 8fss&h)jH6MPgolm':M 1x} ~osK1 ݓ |@y̓7WF_V'5*sR ЙML}sg*uIyN+vqUr1;/N2Ac<b/a1B96}!'fpN͓AʁJUKpXy9X#*̫u``&͠rEs$gs DљbFj2&O\2xC p h{tzNhS9Lk/^^bRS`q%Ȍ6tɸ9ޙxgR )H!(|> 6}@6z,eCYtkV8<ߋ2Pټǻ@4y.~7}=GkWx8'`6ndso }=?+?7<^8<_ ^#o&8>}O~WGᅧo~x/_Q7LE7"|?[RqY!U|ۯp]a=[\ {:~z ^|$fW%$e:5bawxv>օ6{{{{lQnGmH2+ x42CC`!TDs Z*4A(:G. k2L PqYKdVd  R{|JH?z#eA)b՘1 PK#(8 ,lѱecJ6c'2ƀCJvtqch<8rlX1`WDžRrKo eAK#vu\Xgy? gZ*:uTy ,'wXUF@5"Rd٪UC>6'l-Nvh> V? vkMƵX= V)I],5:6 3Tn 9xԶp\QΨ`$X|"׬qJJY L) $"0c,|%xƛ  䦗@lb2s}54"@:ke`lS$Xm0̇()]5fɝᒼ} zB9KZ5c^*6ψ=Z7. 72#4m -ejbck֜A27ԥB;3Z5 'CTGΧR_nZp >u2jh MIm3՘-W'+]* cup@::i|Ǿ_~ >N'GO~q穧p}}x.Ru/@kx2l0lAt}2j./T3%9)NS$("} H hIr<6p7Z{11h)M!eJ@<\ւi eޟG4yk=Ԉq—R'P= &go0Oп§jYZ?w5,+/M<}e|䓟GoᓟSxŗgx|s>"ʩx<7_ fx}|Û?˸yr·0@/xdWe܌ ~C'?}w^_o&1? 0֕ݻgo:3X^z ? p~?a?o/~za$ndzKlY@|ac=c=c=Q BUހ3Ze%Z޽*\ɔhXGZлQI Y^8E-Y(X2g Jmc8YiK;wNOOFۘΨd 1:zR LjNLβw6 j2h*,,У*HR̒!P9%!@ݣAKGv]ϳpD\x\b-e6ܞq=!6JXG.9< >dbC3ES|UccB !L9I56#FLq5%)pFdv'wpG}2z lt埇؏,jw&0^/*ek.KϦ.d>J!Qm/bh2>gTZQγ"%Vİ͜jAkT0uQB7 T ($Vw6Dh2QklԤR&|S̹s^G) 6ToK41!^J\kDHx̭.%K23r)kg gG*_jA)uyZ!;l8&nKwZ&}лJp.Y:bOuTJ8{Ja<|ei8^XX7LyzI͖,JTטR,YகEJfX:/9ڬN( < R x/ AY YʵTǀw*†W׸>\ ֕t{:hÜ&#MAb,"S@-%|&ļIOdw u'ҥ\.8Σd8kmxvU qHYu)mhO6}Yd& eV` (`4bc% vR8dj%Y`Cb}r؈1XlטERȜ*L3P~7e[-AŐ`kuJ~N@ U པgm Us^hn%D$MLd) 4%7!Zрd]ZR"5Ad6J5Jm o!ekB:w"9(׏AK<%)[2د97id6ӈU`.ggXcDA5U5 tL൸8]IyvfmasEV>R} >{Y Hm.c!G׎&cdzԯs>uBME0R\;v@Z0<1e!y>R3098dW-^i ڛ)h1d6l+>2NZ< կ|='>)U5~i=a3.:~Z} 9?j15D`$T;{^_,-R1!i->3㒉s]i= P%=/.Dmtb (= `khSp= |g<ϱ1LWc@v3z0p5= po5}9s%{{ Ʒ տĄR+ΧN30a^R\qb<|xOg?߯LJ>~|ja׿<୷o⥗_%('t:oR_<aX%ֵDKR-Vp}xusOÝvvw9!<;K{c=c=c= $L% W+PILIˆՊ/Y^ zYjr`XmRFJbH6V) I#8D e\  a#Fꊀ6ZT菭0; Ò.Bl-w4/,ɋG*  b@g3% NdhLC`b~qM,u]1t~ᰐucHk2i آ1l ,WK0]5Ȁf'Nd}a$Ôքi`!Uf:PF %6٦rH 3*Z5H:JYWIB7q|5Nvf (b>'-W@mF)4!ۂ P4qEG] Kt z6GuE?l {fK{ ^)i. "k y\ާQhq^{ֻ+6J@PҸ?ٌ">^=JLͥ˛*1xϼ~.v'? 6s r S@;<5p lٴј{dk Z ؔ%$aTxTI>cu+RԊ2S) $(εo )f+ڲp,vc vF,`S::p8`KӘ1%3;'=๣N}@llnf^jʭfwxߋ/?oWwx٧}=|{s =<^GpSyʀ빁 |J!+ BgPL5sFfFLgr+rh`lfY6O~ʜH  @Ga[G~$3,zƛ&ɮγ'LBy+[\UdŹ6EL,1QOx{dK)6D^oH:Y )uYc< f_v>7ܹB_W^" Y&uE;/t{³?;>|7V_}dƔuyɈLVe,"o*CɿQG"!Ơoll^Nr[ 0-"Q A+ZhlU=ql6F.XKi`}ȃ[ D5D'+$Mt/ l6}=~_EIKܕ5}ܗ`w?iɼXS:ԗJA^"81o34+ȕBY-@doKI-'}v)nnbF BXw"̏ _˶"a2XȀX6&ǽ `":۴jk@.)XpBֲb?ʴ0豢- >;!)M^'.(< ;?a(3yTZ(pA/j`Y}O/43'@`3ָ9ek1(<ϚB"T&"؞a4_ 6 fQ~cãՆŗ^o?o6WXoO(8W [M|3K}Ѩ6y#wG8A& CSY"Py$DIaHs<7r Ԝ.Q-(s孬SȢsge$]@,7ȑfs-ˡ~lssVjb[)$S% H$LɔN (sy O0&LeMe_*}Z@c"|8zm˂ei^#MC/-{{{{|_{-,WI P8c dlX_S7B`x8+= m 0z:݇]n,Dv`(b݊wU,i,Z-(y~W$|7( ծ&'# ABpu"P*A3S69CxEvc@ZAY |-&pddY)XGLpF9v(yゔ|wa9Ѫ!f88Z0De9`' 1;,Vf '<7W2lµI$K)X B DJbjR4ڊ-%c- rjppu~9.$5 IDATH?6kϊ8Z]ؔ%JVاu@ͻJbåw$L&r[Y% `HC}vL?"$)\|O/0ǔINZj]]H;bbEʗfƔ6O=4dq'݇ӷ?G0' MW,c}E3 m (m#1k"F߹0?ʦ [_װ\_nTȏQv==ٰ1)9m0o;lJ[$7olY.Q3= ebxg#XiM]zɛ;~/ǂN^uqqSJDO`YT̨يO: ` {=xO]>|zg݀尠^z[ޜ=p<%q$)qƺ=" vkdkGk-PGq2~})8݅s| Jz8 zk ONg-CNܲ AZ+IZ/Zy$iZءgq>TfWo?r+ܹsĽ{wmc=c=c=O fA~6Yp,yXyhbJƚt xH@@ >VD7X)JZ~o.&pQt֌72\{DzRȄgibiL.y/`=CbA6JA+ 83Zmdl>d(Bۚ젳$#Tu\€dn*r_R4Z+Tca=q{{r8 L,[y$GX_gJAg``'bPۜ#ǥ4e!+o 4CjC:;ݵ8wfAugG`XCaU^U2ʔŜ"$~ؤac&r? Ħ&A몹侨Mm^} ;Ye6z9#LL8%ۓܔRǬ$»̳CMXPB1Z! @uZɸL@$ۑሼ؀K[U/b n]c& yRyʅ2H7b^L_)0Np0#$ L(C@oұlH c\<&d7Amm(7ط@E T}Oll7^w݇D/˶o_B#WӒf@) qZ 6"# 5U\p株& , R*h?=7 d0 ؜Ae;5U P<:Ykw ,t\Q-eyudsOdE+t;y==-orhA賩t8 yP!lEǻ>G =~\__x\owX::< YՂ>s8,pfә9l8:p<puuy~ O,肵wJA9R',Kqm0>ZGlJAC_^P10]w֊]`Ny(|)0X[o\,w\x|WS{{{?/?|"( EEVɜ 0b5b $CmEl+6%RJsgP,+CLoJG`+'Dh)(ӛRw[@ea1}$7 6i=X,f7fcר)6 A)a GYcjm~31 R 06A1}97Rǐ4/1$ aӭeLЖ}]q:qge)8. `+nnn`pnnnq{>SLR % RZE1(MmcKAYT8J 9=3*y5a&˨5PQʐn) E`X1@`V dW{)˵,\/#Jޯ|᡽sR2 UP#6Tb 2f-Ya1lZuɎGv  K#47HAlPt}zVl<@Kegg Py &|$2&e  {{Y*e$!U'ή1~#Ǜj6ljlulfpx@`؅46V'?P&4ɺbn6M VJ鼇!Yskzk!Hj9˹6Fƞؔ;`6T>$kM)#x XlV |B) u9TIIM8e/u* GgQ4J)0I1;JJ86 phCÇOq\@T˂ZC +O t4!^ "TH1A{ړl`CXת-}?pXWzR+ngx8[o ~9O;#z̛_q=|v=b*sgS%>ЪIUb2K\s[#Q($˳)Cy0})3EOp2<$S^:b(צ]%jVTIym?d.O=D`K0b?)"d3)W y}2a?Rn; c2c<}eg5