diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..144c6d82 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM ubuntu:16.04 + +RUN apt-get update +RUN apt-get install -y build-essential +RUN apt-get install -y emacs + +# Ruby +RUN apt-get install -y ruby + +# Git +RUN apt-get install -y git + +RUN apt-get install -y curl + +COPY . /xiki/ +RUN /xiki/bin/xsh --install + +WORKDIR /root + +CMD ["bash", "/xiki/bin/xsh"] + diff --git a/Gemfile b/Gemfile index fa75df15..3c9aa50a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,15 @@ -source 'https://rubygems.org' -gemspec +# Xiki is temporarily not set up to be used as a gem. Just download it +# and run bin/xsh directly. +# +# Eventually xiki will be installable as a gem, so you can use xiki classes +# from ruby code, including using xiki as a rails plugin. +# +# It's unclear as of yet whether the gem install should put the xsh command +# into the path. + + +# source 'https://rubygems.org' +# +# gemspec + diff --git a/LICENSE b/LICENSE index 026c6c81..aebeeebc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,353 @@ -The MIT License - -Copyright (c) 2013 Craig D Muth - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2015 Craig D Muth + + NOTE! The GPL below is copyrighted by the Free Software Foundation, + but the instance of code that it refers to (the Xiki project) is + copyrighted by me, Craig Muth. + + Also note that the only valid version of the GPL as far as Xiki + is concerned is _this_ particular version of the license (ie v2, not + v2.2 or v3.x or whatever), unless explicitly otherwise stated. + + Craig Muth + +---------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This 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. diff --git a/README.markdown b/README.markdown deleted file mode 100644 index f8544f42..00000000 --- a/README.markdown +++ /dev/null @@ -1,54 +0,0 @@ -## Summary - -This file shows how to install Xiki. Xiki is like a shell console with GUI features. It has a web interface, but rens best from a Xiki-enabled text interface. It has menus for quickly getting started with using various tools and libraries. Also you can easily create your own menus. - -See http://xiki.org - -## Install Ruby 1.9.3 - -On ubuntu, do: $ sudo apt-get install ruby1.9.3. - -On the mac, install rvm (http://google.com/search?q=install+rvm) and the do: $ rvm install ruby-1.9.3 - -## Install Xiki - -Either install as a gem, or install from github. - -### As a gem - - $ gem install xiki --pre - -### Or, from github - - $ git clone git://github.com/trogdoro/xiki.git - $ cd xiki - $ git checkout v1.0 # Only required temporarily, until the branch is merged in - $ sudo gem install bundler # <- no "sudo" if using rvm - $ sudo bundle # <- no "sudo" if using rvm - $ sudo ruby etc/command/copy_xiki_command_to.rb /usr/bin/xiki # or /usr/local/bin/xiki and no "sudo" if using rvm - -## Verify the 'xiki' shell command works - - $ xiki - -It should delay slightly the first time, but be fast subsequent -times. If you run into errors and then fix them, you'll want to -run the "xiki stop" command. See the process log for -trouble-shooting (/tmp/xiki_process.rb.output). - -## Bring up the Xiki web interface - - $ xiki web/start - -Then follow the instructions you see (it tells you to go to http://localhost:8161). - -See the web log for trouble-shooting (/tmp/xiki_web.output). - -## Getting help - -Join the google group for help with installing, or to chat about -whatever or share your ideas: - -http://groups.google.com/group/xiki - -Or tweet to @xiki. diff --git a/README.md b/README.md new file mode 100644 index 00000000..787c9419 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +## Xiki expands your command line + +Xiki makes the command line friendlier and more powerful. Xiki Shell (xsh) lets you use Xiki from command line, in a way that augments your current favorite shell (bash or zsh). + + $ xsh + +## Install + +### One-line installer + +If you want a one-line installer, this should do the trick. Copy and paste it into your shell console. + + curl -L https://xiki.com/install_xsh -o ~/install_xsh; bash ~/install_xsh + +It will walk you through a couple setup steps. + +### Manual install + +You can download Xiki from: https://github.com/trogdoro/xiki/archive/master.tar.gz. Extract it into your home dir, or some other dir where you keep projects or downloaded things. + +Then, to run (and optionally install xsh) just execute the 'xsh' command, located in the 'bin' dir: + + $ ./install + +### Git Install + +Or, if you have git, you can get Xiki from github via "git clone https://github.com/trogdoro/xiki.git". Then run "./install" from inside the dir. + +## Tutorial + +Try typing "xsh --tutorial", or "xsh --help" on the command line. Or, get help from a human: + +## Getting in touch + +Tweet to @xiki: + +* http://twitter.com/xiki + +Join the google group for help with installing: + +* http://groups.google.com/group/xiki + +Or, jump into the xiki chat room to chat about Xiki! Use this link to jump right in, or use your own irc client to join: + +* http://webchat.freenode.net/?channels=xiki + +## Troubleshooting + +If you run into trouble, try running "bin/xsh --d", which may give better error messages. Also, not that there's a cached "xsh forker" process that stays alive to speed up execution. Try "ps aux|grep forker" to find it. During trouble-shooting you may need to kill it. + +## Supported platforms + +Supported platforms: MacOS and Linux. Pair with me if you want to see Windows support, support for your text editor, or just to hack on Xiki! (find me at twitter.com/xiki). + diff --git a/Rakefile b/Rakefile index a28677a1..bd7f1449 100644 --- a/Rakefile +++ b/Rakefile @@ -1,8 +1,25 @@ require 'rake' require 'rspec/core/rake_task' -# Define task +desc "Run tests" +task :default => [:spec] + +desc "About Xiki" +task :about do + + puts "Xiki expands your command line! Xiki makes the command line" + puts "friendlier and more powerful." + puts "" + puts "You use Xiki Shell (xsh) to use Xiki from command line:" + puts "" + puts " $ xsh" + puts "" + puts "See http://xiki.com for info." + puts "" + +end + RSpec::Core::RakeTask.new(:spec) do |t| end -task :default => :spec + diff --git a/bin/.xsh b/bin/.xsh new file mode 100644 index 00000000..5179ba56 --- /dev/null +++ b/bin/.xsh @@ -0,0 +1,237 @@ +#!/usr/bin/env bash + +# +# This file should be included from your bash or zsh config. It +# defines key shortcuts (by default Ctrl+X and Ctrl+O) that let +# you quickly go back and forth between xsh and your existing +# shell. And it defines the 'xsh' command wrapper, which let's xsh +# send commands back to your shell. +# +# Example: +# +# ~/.bashrc +# : xiki_open_key="\C-o" # Ctrl+X to expand in xsh +# : xiki_key="\C-x" # Ctrl+T to show a topic +# : xiki_search_key="\C-s" # Ctrl+S to search shared commands on XikiHub +# : xiki_tasks_key="\C-t" # Ctrl+O to show the options dropdown +# : xiki_go_key="\C-g" # Ctrl+G to grab commands between xsh and your shell +# : xiki_reverse_key="\er" # Ctrl+R to search shell history +# : source ~/xiki_master/.xsh # Enable the key shortcuts +# + + +# Only do bind commands if in interactive shell (to avoid error during linux startup). +if [[ $- =~ i ]]; then + + # Wraps the 'xsh' shell command. Calls it and then runs any commands + # it wrote to ~/.xiki/misc/tmp/grabbed_commands.xiki. + function xsh { + + # Always write history to a temp file, in case it was Ctrl+R, or they'll do a open+recent + + dir="$HOME/.xiki/misc/logs" + mkdir -p $dir + + if [ -n "$ZSH_VERSION" ]; then + fc -W "$dir/shell_external_log.xiki" + else # Assume bash or bash-compatible + history -w "$dir/shell_external_log.xiki" + fi + + # Run the actual xsh command... + + command xsh "$@" + + # Save xsh internal shell commands into our history... + + file="$HOME/.xiki/misc/tmp/recent_history_internal.xiki" + if [ -f $file ]; then + + if [ -n "$ZSH_VERSION" ]; then + fc -R $file + else # Assume bash or bash-compatible + history -r $file + fi + # Be sure to delete it (-f in case user has weird settings for rm) + rm -f $file + fi + + + # Grab any "go to shell" commands, and run it... + + if [ -f "$HOME/.xiki/misc/tmp/go_to_shell_commands.xiki" ]; then + + dir=`pwd` + if [[ ! $dir =~ ^[a-zA-Z/_.-]+$ ]]; then + dir="\"$dir\"" + fi + cd_to_ignore="cd $dir" + + commandz="" + + # Run each line in the file + while read -r p; do + + # Don't do redundant cd (to the same dir) + if [ "$p" != "$cd_to_ignore" ]; then + + # Store in a var and eval later, since eval inside this + # loop doesn't allow async commands like top, etc. + commandz+="$p" + commandz+=$'\n' + + fi + + done <$HOME/.xiki/misc/tmp/go_to_shell_commands.xiki + + # Delete it when we're done (-f in case user has weird settings for rm) + rm -f $HOME/.xiki/misc/tmp/go_to_shell_commands.xiki + + SAVEIFS=$IFS # Store original separator + IFS=$'\n' y=($commandz) + + for i in "${y[@]}"; do + + i="${i%"${i##*[![:space:]]}"}" # remove trailing whitespace characters + + # Add the command to the history... + + if [ -n "$ZSH_VERSION" ]; then + this_command="xsh "$* + if [[ ! $this_command = "xsh -r" ]]; then + print -s $this_command + fi + print -s $i + else # Assume bash or bash-compatible + this_command="xsh "$* + if [[ ! $this_command = "xsh -r" ]]; then + history -s $this_command + fi + history -s $i + fi + + # Show the command... + + + if [[ $i =~ ^\# ]]; then + echo $i + else + echo \$ $i + fi + + # Run the command, it's output will be shown... + + eval $i + + done + + IFS=$SAVEIFS # Restore original separator + + fi + + } + + # + # Define key shortcuts... + # + + # 'xiki open' shortcut (Ctrl+X) var exists, so define key... + + if [ $xiki_open_key ]; then + + # If mac, run line un-blocking C-o + if [ `uname` = 'Darwin' ]; then + stty discard undef # Let C-o be mapped + fi + + # Make vim mode have C-a, since the below keys depend on it + + if [ -n "$ZSH_VERSION" ]; then + bindkey -M viins '^a' beginning-of-line + else # Assume bash or bash-compatible + bind -m vi-insert "\C-a":beginning-of-line + fi + + # Define it... + + if [ -n "$ZSH_VERSION" ]; then + bindkey -s $xiki_open_key '\C-axsh \n' + else # Assume bash or bash-compatible + bind \"$xiki_open_key'" "\C-axsh \n"' + fi + fi + + # 'xiki tasks' shortcut (Ctrl+T) var exists, so define key... + + # "$ xsh -foo" version + if [ $xiki_key ]; then + if [ $xiki_key = "\C-x" ]; then + + # It's Ctrl+X in bash, so undefine it first + + if [ $BASH_VERSION ]; then + bind "'\C-x' end-of-line" # Causes C-x to be bindable + fi + + # It's Ctrl+X in zsh, so unset all the other Ctrl+X keys, so they don't cause a pause when typing Ctrl+X + + if [ $ZSH_VERSION ]; then + bindkey -rp '\C-x' + fi + fi + + if [ -n "$ZSH_VERSION" ]; then + bindkey -s $xiki_key '\C-axsh -\n' + else # Assume bash or bash-compatible + bind \"$xiki_key'" "\C-axsh -\n"' + fi + fi + + # 'xiki shared' shortcut (Ctrl+S) var exists, so define key... + + if [ $xiki_search_key ]; then + stty -ixon # Let C-s be mapped + + if [ -n "$ZSH_VERSION" ]; then + # bindkey -s $xiki_search_key '\C-axsh -s \n' + bindkey -s $xiki_search_key '\C-axsh :\n' + else # Assume bash or bash-compatible + # bind \"$xiki_search_key'" "\C-axsh -s \n"' + bind \"$xiki_search_key'" "\C-axsh :\n"' + fi + fi + + # 'xiki grab menu' shortcut (Ctrl+G) var exists, so define key... + + if [ $xiki_go_key ]; then + if [ -n "$ZSH_VERSION" ]; then + bindkey -s $xiki_go_key '\C-axsh +\n' + + else # Assume bash or bash-compatible + bind \"$xiki_go_key'" "\C-axsh +\n"' + fi + fi + + # 'xiki open' shortcut (Ctrl+O) var exists, so define key... + + if [ $xiki_tasks_key ]; then + + if [ -n "$ZSH_VERSION" ]; then + bindkey -s $xiki_tasks_key '\C-axsh = \n' + else # Assume bash or bash-compatible + bind \"$xiki_tasks_key'" "\C-axsh = \n"' + fi + fi + + + # 'xiki reverse' shortcut (Option+Ctrl+R) var exists, so define key... + + if [ $xiki_reverse_key ]; then + if [ -n "$ZSH_VERSION" ]; then + bindkey -s $xiki_reverse_key '\C-axsh -r \n' + else # Assume bash or bash-compatible + bind \"$xiki_reverse_key'" "\C-axsh -r \n"' + fi + fi + +fi diff --git a/bin/xiki b/bin/xiki index 5aa19535..4a40a47e 100755 --- a/bin/xiki +++ b/bin/xiki @@ -1,33 +1,39 @@ #!/usr/bin/env ruby +puts "The 'xiki' command is temporarily disabled in this version. Use the 'xsh' command instead!" + +## +## > Summary +## This is the "xiki" shell command. It exposes xiki menus. +## +## > Example usage +## $ xiki ip +## 111.1.1.111 +## +## This will be invoked by the "xiki" wrapper gem files create +## when you install xiki as a gem. +## +## Or, if you installed xiki from +## source (github), this file gets copied into /usr/local/bin/ +## or a similar path by the copy_xiki_command_to.rb the project +## README file tells you to run. +## # -# > Summary -# This file is aliased to /usr/local/bin/xiki, so it can be -# called from the command line. It exposes xiki menus. +#require 'rubygems' # -# > Example -# $ xiki ip -# 111.1.1.111 +## Generate with correct path (see below) +#xiki_dir = File.expand_path "#{File.dirname(__FILE__)}/.." # -# To create the alias, see: -# @xiki/setup/install command/ +#$:.unshift "#{xiki_dir}/lib" #.sub(/\/$/, '') # - -require 'rubygems' - -# Generate with correct path (see below) -xiki_dir = File.expand_path "#{File.dirname(__FILE__)}/.." - -$:.unshift "#{xiki_dir}/lib" #.sub(/\/$/, '') - -require "xiki/core/ol" -require "xiki/core/core_ext" - -$:.unshift "#{xiki_dir}/etc/command" - -require "xiki_command.rb" - -result = XikiCommand.run -puts result if result != "" - -exit 0 +#require "xiki/core/ol" +#require "xiki/core/core_ext" +# +#$:.unshift "#{xiki_dir}/misc/command" +# +#require "xiki_command.rb" +# +#result = XikiCommand.run +#puts result if result != "" +# +#exit 0 diff --git a/bin/xikigem b/bin/xikigem new file mode 100755 index 00000000..b56ab44c --- /dev/null +++ b/bin/xikigem @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +mkdir -p ~/.xiki/misc/gems/ +export GEM_HOME=~/.xiki/misc/gems/ +export GEM_PATH= + +# xikigem install foo, so delegate to gem install... + +if [ "$1" = "install" ]; then + echo Temporarily setting \$GEM_HOME to \~/.xiki/misc/gems/... + echo + gem install "$2" + xsh --reload + exit +fi + +# xikigem uninstall foo, so delegate to gem install... + +if [ "$1" = "uninstall" ]; then + echo Temporarily setting \$GEM_HOME to \~/.xiki/misc/gems/... + echo + gem uninstall "$2" + xsh --reload + exit +fi + +# xikigem list, so delegate to gem list... + +if [ "$1" = "list" ]; then + echo Gems installed in \~/.xiki/misc/gems/: + gem list + echo + echo Xiki also has access to the global gems you have installed. + echo Run "$ xsh -gems" to see global gems and the above xiki-local + echo gems together. + echo + exit +fi + + diff --git a/bin/xsh b/bin/xsh new file mode 100755 index 00000000..2b71312e --- /dev/null +++ b/bin/xsh @@ -0,0 +1,448 @@ +#!/usr/bin/env bash + +set -f + + +# Just "xsh --reload" so reload and return + +if [[ $1 = "--reload" ]]; then + # Kill all "xsh forker" processes + for KILLPID in `ps ax | grep 'xsh\ forker' | awk ' { print $1;}'`; do + kill -9 $KILLPID; + : + done + exit +fi + + +# Set gem_home, so xiki gems don't interfere with main ones... + +mkdir -p ~/.xiki/misc/gems/ +export GEM_HOME=~/.xiki/misc/gems/ +export PATH=~/.xiki/misc/gems/bin/:$PATH + +# Resolve the xiki dir even if there's a symlink somewhere in the path... + +SOURCE="$0" +while [ -h "$SOURCE" ]; do + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" +done +export xiki_dir=`dirname "$( cd -P "$( dirname "$SOURCE" )" && pwd )"` + +# Complain if project dir is ~/xiki/... + +if [ "$xiki_dir" = "$HOME" ]; then + cat "$xiki_dir/misc/command/shell_please_move.txt" + exit +fi + +emacs="emacs" +emacsclient="emacsclient" + +# Get emacs version, or blank if emacs not found... + +emacs_version=`$emacs -version 2>&1` +emacsclient_version=`emacsclient -version 2>&1` + +function check_dependencies { + + # Underline formatting + u=`tput smul` + uu=`tput rmul` + + ruby_is_installed=$(type -P ruby) + emacs_is_installed=$(type -P emacs) + + # No emacs or no ruby, so this must be linux > figure out how to install... + + if [[ ! "$ruby_is_installed" =~ ^/ ]] || [[ ! "$emacs_is_installed" =~ ^/ ]]; then + + yum_is_installed=$(type -P yum) + aptget_is_installed=$(type -P apt-get) + pacman_is_installed=$(type -P pacman) + + yum_install="sudo yum install -q -y" + aptget_install="sudo apt-get install -qq -y" + pacman_install="sudo pacman -S" + + echo -e "\nIt looks like you haven't yet installed the dependencies" + echo -e "xsh needs. Install them? (You can optionally install the\ndependencies yourself first.)\n" + + echo -e " ${u}i${uu}nstall the dependencies" + echo -e " ${u}c${uu}ancel" + echo -e " ${u}s${uu}how them first\n" + + read -n1 -p "Type one of the underlined letters: " choice + + if [ "$choice" = "c" ]; then + echo -e "\n\nXsh can't run without it's dependencies :(\n" + exit 1 + fi + + if [[ "$yum_is_installed" =~ ^/ ]] && [[ "$aptget_is_installed" =~ ^/ ]]; then + echo -e "\n\nIt appears you have both apt-get and yum installed. Which should we use?" + echo " a: apt-get" + echo " y: yum" + echo "" + read -n1 -p "(a/y): " choice2 + if [ "$choice2" = "a" ]; then + install="$aptget_install" + elif [ "$choice2" = "y" ]; then + install="$yum_install" + elif [ "$choice2" = "p" ]; then + install="$pacman_install" + else + echo "You didn't type 'a' or 'y'." + exit 1 + fi + + elif [[ "$aptget_is_installed" =~ ^/ ]]; then + install="$aptget_install" + elif [[ "$yum_is_installed" =~ ^/ ]]; then + install="$yum_install" + elif [[ "$pacman_is_installed" =~ ^/ ]]; then + install="$pacman_install" + else + dir="$xiki_dir/misc/install" + install="bash $dir/install_source_dependency.sh" + fi + + + # Check for "show" first > so we can prompt for key, and if they pick "install", its code is still below + + if [[ $choice = "s" ]]; then + + # Show install commands, prompt to install, then just exit if not "i"... + + echo -e "\n\nThis will be run to install xsh's dependencies:\n" + if [[ ! "$ruby_is_installed" =~ ^/ ]]; then + echo " $ $install ruby" + fi + if [[ ! "$emacs_is_installed" =~ ^/ ]]; then + echo " $ $install emacs" + fi + + echo -e "\nXsh's dependencies are largely transparent to the user.\nYou don't need to have any expertise about them to use xsh.\n" + + echo -e " ${u}i${uu}nstall" + echo -e " ${u}c${uu}ancel\n" + + read -n1 -p "Type one of the underlined letters: " choice2 + + if [ "$choice2" != "i" ]; then + echo -e "\n\nXsh can't run without it's dependencies :(\n" + exit 1 + fi + + # They typed 'i', so continue with install... + choice=i + + fi + + + # Install if "i" + + case "$choice" in + i|I) + echo -e "\n\nInstalling the dependencies. Could take a few minutes...\n" + if [[ ! "$ruby_is_installed" =~ ^/ ]]; then + echo -e "$ $install ruby\n" + if ! $install ruby || [[ ! $(type -P ruby) =~ ^/ ]]; then + echo ruby install failed\! + exit 1 + fi + fi + if [[ ! "$emacs_is_installed" =~ ^/ ]]; then + echo -e "$ $install emacs\n" + if ! $install emacs || [[ ! $(type -P emacs) =~ ^/ ]]; then + echo emacs install failed\! + exit 1 + fi + fi + echo -e "\n\n" + # Continue on to run xsh + ;; + + *) + echo -e "\n\nXsh can't run without it's dependencies :(\n" + exit 1 + ;; + esac + + fi + + # Mac and emacs22... + + if [[ "$emacs_version" =~ "Emacs 22" ]] && [ `uname` = "Darwin" ]; then + + # Mac and emacs22 and no homebrew, so require installing homebrew... + + if [[ ! $(type -P brew) =~ ^/ ]]; then + + # Homebrew not installed, so don't make them install emacs + return + + # Old > made people that didn't have homebrew install it + # echo -e "Please install Homebrew, and then run xsh again.\n\nThe homebrew installer can be found here:\n\n http://brew.sh/\n" + # exit 1 + + elif [[ ! -w /usr/local/bin/ ]]; then + + # Homebrew permissions not set up properly, so don't make them install emacs + + return + + # Old > # Tell them to fix homebrew if directories aren't writable + # echo "Your Homebrew permissions seem strange. Try running" + # echo "'brew install emacs' and then run xsh again. If that" + # echo "doesn't work, try 'brew doctor'." + # echo "" + # exit 1 + + elif [ -f /usr/local/bin/emacs ]; then + + # /usr/local/bin/emacs exists (even though /usr/bin/emacs may be first in $PATH), so hard-code to it... + + emacs="/usr/local/bin/emacs" + emacsclient="/usr/local/bin/emacsclient" + emacs_version=`$emacs -version 2>&1` + emacsclient_version=`emacsclient -version 2>&1` + + elif [ -f ~/.xiki/misc/no_brew_install_emacs ]; then + + # They opted not to do the brew emacs upgrade in the past + + # noop + : + + else + + # Mac and emacs22 (and homebrew installed), so require "brew install emacs"... + + echo -e "Xsh would like to upgrade a dependency to a newer version." + echo -e "This should take less than a minute over a fast internet" + echo -e "connection.\n" + + echo -e " ${u}u${uu}pgrade the dependency" + echo -e " ${u}s${uu}how them first" + echo -e " ${u}p${uu}roceed without upgrading\n" + + read -n1 -p "Type one of the underlined letters: " choice + + # Check for "show" first > so we can prompt for key, and if they pick "install", its code is still below + + if [[ $choice = "s" ]]; then + + echo -e "\n\nThis will be run, to upgrade your emacs version:\n" + echo " brew install emacs" + + echo -e "\nXsh's dependencies are largely transparent to the user.\nYou don't need to have any expertise about them to use xsh.\n" + + echo -e " ${u}u${uu}pgrade the dependency" + echo -e " ${u}p${uu}roceed without upgrading\n" + + # Re-read in their choice + read -n1 -p "Type one of the underlined letters: " choice + + fi + + + case "$choice" in + u|U) + echo -e "\n\n" + echo -e "$ brew install emacs\n" + + # Make sure brew command won't break because of license agreement + shopt -s nocasematch + if [[ $(make --version 2>&1) =~ xcode.+license ]]; then + echo -e "\nIt looks like you haven't agreed to the Xcode license.\nThis is blocking the brew command from running. Please\nlaunch Xcode and accept the license and then run the\nXiki installer again.\n\n" + exit 1 + fi + + brew install emacs + ;; + + p|P) + # Let them continue, and remember not to prompt again + touch ~/.xiki/misc/no_brew_install_emacs + echo + ;; + *) + echo -e "\n\nYour choice wasn't understood. Run xsh to see the options again.\n" + exit 1 + ;; + esac + + fi + + + # Older than Emacs23 on any platform... + + elif [[ "$emacs_version" =~ Emacs\ 2[012] ]]; then + echo -e "Please upgrade to Emacs 24 and then run xsh again.\n" + exit 1 + fi + + if [[ "$emacsclient_version" =~ emacsclient\ 2[012] ]]; then + echo -e "Your emacsclient version is:\n$emacsclient_version\n" + echo -e "But your emacs version is:\n$emacs_version\n" + echo -e "Please fix this and then run xsh again.\n" + exit 1 + fi + +} + +function show_setup_steps_maybe { + + # If we probably showed the setup message, but ~/.xsh exists now, assume they set it up, and show steps to reload + if ( [[ $# -eq 0 ]] || [[ "$*" == "-xsh/setup" ]] ) && [[ ! $(type -P xsh) =~ ^/ ]] && [[ ! $XIKI_SUPPRESS_WELCOME ]] && [[ -f ~/.xsh ]]; then + echo -e "\n\n" + echo '#' + echo '# Now follow these steps:' + echo '# 1. Type "bash" to reload (or "zsh" if you use it)' + echo '# 2. Type "xsh --tutorial"' + echo '#' + fi + +} + + +check_dependencies + +# Proceed to run the command... + +function save_args_to_envs { + let i=1 + for var in "$@" + do + export XSH_COMMAND_LINE_ARG_$i="$var" + let "i++" + done +} + +storeargs() { + while [ $# -gt 0 ] + do + printf "%q " "$1" + shift + done +} + +function save_args_to_files { + let i=1 + + # New + dir=~/.xiki/misc/tmp + mkdir -p $dir + + storeargs "$@" > $dir/params.txt + +} + +# Force 256 colors (what if terminal doesn't handle it? > probably only do this recognizable $TERM, $TERM_PROGRAM and $COLORTERM vars)... + +export TERM=xterm-256color + +# Maybe use /Applications/Emacs.app if it exists? +# emacs_mac_app="/Applications/Emacs.app/Contents/MacOS/Emacs" +# if [ -f $emacs_mac_app ]; then +# emacs=$emacs_mac_app +# emacsclient="/Applications/Emacs.app/Contents/MacOS/bin/emacsclient" +# fi + +case $1 in +--help) + cat "$xiki_dir/misc/command/external_shell_help.txt" + + exit + ;; + +--tutorial) + + uname=`uname -a` + if [[ $uname =~ ^Darwin ]]; then + open "http://xiki.com/@xiki/tutorial" + fi + + cat "$xiki_dir/misc/command/external_shell_tutorial.txt" + + exit + ;; + +--samples) + cat "$xiki_dir/misc/command/external_shell_examples.txt" + + exit + ;; + +--install) + ruby "$xiki_dir/misc/install/install_when_flag.rb" + + exit + ;; + +--d) + + # -d, for "isolated", so just run with no deamon... + + # Alternatives considered: unique, unconnected, separate, alone, peerless + + # Load with xiki-no-socket flag, so pull of 1st arg... + + shift # The 1st argument was "--d" + save_args_to_envs "$@" + + exec $emacs -nw -Q \ + -l "$xiki_dir/misc/emacs/start_xiki_no_socket.el" + ;; + +*) + + # Emacs 22 on the Mac with no homebrew + if [[ $emacs_version =~ Emacs\ 2[21] ]]; then + # Emacs 22, so don't use --daemon (a little slower, but won't flicker in Emacs 22)... + save_args_to_envs "$@" + # Start emacs and load xiki... + $emacs --no-splash -nw -Q -l "$xiki_dir/misc/emacs/start_xiki.el" + + show_setup_steps_maybe + exit 1 + fi + + # Emacs 23+, so use --daemon (will make for immediate startup)... + + # Always start emacs --daemon, and then connect to it. This avoids + # emacs flashing weird text and formatting on the screen. + + unique=$RANDOM + # Start up an emacs daemon to load and then connect to + $emacs -Q --daemon=$unique -nw &>/dev/null + + # Now that emacs is invisible, tell it to load the xiki classes (and cache a few things). + + + $emacsclient -s $unique -e "(load \"$xiki_dir/misc/emacs/start_xiki_daemon.el\")" &>/dev/null + + # Now load the parameters into ~/.xiki/misc/params/ + + save_args_to_files "$@" + + # Now run it. The hook will run when it opens and load the params + + # /dev/null thing is meant to hide errors + $emacsclient -s $unique -t 2>/dev/null + + show_setup_steps_maybe + + exit + + ;; + + # Commented double-semicolon line here > will make this error go away? + # "line 313: syntax error near unexpected token `fi'" + # if not, maybe do 'exit' here + + +esac diff --git a/changelog.markdown b/changelog.markdown deleted file mode 100644 index a2626883..00000000 --- a/changelog.markdown +++ /dev/null @@ -1,71 +0,0 @@ -# Changelog - -This changelog likely won't be strictly maintained. But it covers the big changes for the 1.0.0a release. - -## 1.0.0a changes - -### Installer - -- Improved web-based installer - -### Library - -- Xiki can be used as a library - - For example using the Xiki gem in rails app - - For render html or webservice versions of your menus - - And for using Xiki as an app building framework - -### Web interface - -- to try xiki out via the web interface - - (using it kind of like a web framework) - -### Xiki plugins for other editors / IDE's - -- Most paths types will now work from other editors and tools - - (via the 'xiki' shell command) - - Including - - dir and file paths with filtering - - shell commands -- Most text editor dependencies have been removed - -### Menus - -- You can now creating menus by creating... - - text files - - scripts (no longer required to define a class) - - ruby, python, js, coffee - - directories - - directories can contain arbitrarily nested dirs and any of the above files - - so different types of files can work together to back menus - - now menus are no longer limited to a single file - - and can scale to the level of complexity of a sophisticated web app - - notes (heading will expand as menu items) - - misc other file types: .bootstrap, .markdown, .conf - - in addition to the old Xiki file types - - menus, classes -- "Handlers" can be created to let other file types be menu source files -- All menus are lazy-loaded - - for faster startup time and to immediately reflect changes - - a caching layer should probably be implemented at some point - - if the reloading every time causes runtime slowness -- MENU_PATH environment var - - lets you configure dirs to look for menus sources -- Make menus that wrap other menus - - for convenience - - like: - @cheese/2/ -- Navigation to the source files within menu dirs - - that correspond to the item your cursor is on - - analogous to jumping to the rails action from the path - - but with fine granuality, so like jumping to the right model for a nested resource -- Any file path can be expanded as though it's a menu - - menus can be run inline, with the "//" syntax - -### Conf files - -- Conf files can be edited inline in menus - - if menus have default conf files, those will be used as starting points - - for example, the 'mysql' menu will pop up with a default conf file - - (the first time you use it) - diff --git a/etc/logo.png b/etc/logo.png deleted file mode 100644 index 287f0a64..00000000 Binary files a/etc/logo.png and /dev/null differ diff --git a/etc/shark.icns b/etc/shark.icns deleted file mode 100644 index 2f404314..00000000 Binary files a/etc/shark.icns and /dev/null differ diff --git a/etc/shark_script.icns b/etc/shark_script.icns deleted file mode 100644 index 9b865509..00000000 Binary files a/etc/shark_script.icns and /dev/null differ diff --git a/etc/templates/menu_template.rb b/etc/templates/menu_template.rb deleted file mode 100644 index 58c7fafe..00000000 --- a/etc/templates/menu_template.rb +++ /dev/null @@ -1,8 +0,0 @@ -class {{TextUtil.camel_case name}} - def self.menu - " - - foo/ - - new menu/ - " - end -end diff --git a/etc/templates/template.menu b/etc/templates/template.menu deleted file mode 100644 index 6d8bc81d..00000000 --- a/etc/templates/template.menu +++ /dev/null @@ -1,2 +0,0 @@ -- foo/ - - new menu/ diff --git a/etc/templates/template.rb b/etc/templates/template.rb deleted file mode 100644 index 291754f4..00000000 --- a/etc/templates/template.rb +++ /dev/null @@ -1,5 +0,0 @@ -class {{TextUtil.camel_case name}} - def self.hi - "do something with me" - end -end diff --git a/etc/templates/template_spec.rb b/etc/templates/template_spec.rb deleted file mode 100644 index 3aa7ecf4..00000000 --- a/etc/templates/template_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# require "spec_helper" -require "{{TextUtil.camel_case name}}" - -describe {{TextUtil.camel_case name}} do - it "works" do - {{TextUtil.camel_case name}}.hi.should == 1 - end -end diff --git a/etc/themes/Black_BG.notes b/etc/themes/Black_BG.notes deleted file mode 100644 index c73f06e6..00000000 --- a/etc/themes/Black_BG.notes +++ /dev/null @@ -1,11 +0,0 @@ -module Xiki - Styles.define :trailing_whitespace, :bg=>'333' - Styles.define :default, :bg=>'151515', :fg=>'fff' - Styles.define :fringe, :bg=>'151515', :fg=>'111111' - - Cursor.white - - Styles.minibuffer_prompt :size=>"+0", :face=>"Monaco", :fg=>"#bbb" - Styles.minibuffer :size=>"+0", :face=>"Monaco" - Styles.echo_area :size=>"+0", :face=>"Monaco" -end diff --git a/etc/themes/Dark_Metal.notes b/etc/themes/Dark_Metal.notes deleted file mode 100644 index d3341422..00000000 --- a/etc/themes/Dark_Metal.notes +++ /dev/null @@ -1,24 +0,0 @@ -module Xiki - Styles.mode_line( - :bg=>'333', - :border=>['fff', -2, 'released-button'], - :face=>'Lucida Grande', - :size=>'3', - :bold=>false, - :fg=>'fff' - ) - Styles.mode_line_inactive( - :bg=>'aaa', - :border=>['bbbbbb', -1, 'released-button'], - :face=>'Lucida Grande', - :size=>'3', - :bold=>false, - :fg=>'000' - ) - Styles.mode_line_file( - :fg=>"fff", - :size=>"0", - :face=>"arial", - :bold=>false - ) -end diff --git a/etc/themes/Default.notes b/etc/themes/Default.notes deleted file mode 100644 index bb10e091..00000000 --- a/etc/themes/Default.notes +++ /dev/null @@ -1,22 +0,0 @@ -module Xiki - Themes.use "Black BG" - Themes.use "Flat Blue" - Themes.use "Path Mode Line" -end - -$el.el4r_lisp_eval %` - (progn - (tool-bar-mode -1) ; Turn off the toolbar - - (custom-set-variables - '(tabbar-mode nil) ; No tabs - '(cua-rectangle-mark-key (kbd "")) ; This is aquamacs stuff - probably put somewhere else - ) - (set-face-attribute 'region nil :background "#333333" :foreground "#ffffff") ;; More mac-like selection color - - (setq-default - frame-title-format - '("%b")) - (setq frame-title-format '("%b" )) - ) - ` diff --git a/etc/themes/Fall_Fonts.notes b/etc/themes/Fall_Fonts.notes deleted file mode 100644 index 7072f26d..00000000 --- a/etc/themes/Fall_Fonts.notes +++ /dev/null @@ -1,8 +0,0 @@ -module Xiki - Styles.define :font_lock_comment_face, :fg=>'65614E' # gray - Styles.define :font_lock_function_name_face, :fg=>'f32' # red - Styles.define :font_lock_type_face, :fg=>'c21' # red - Styles.define :font_lock_variable_name_face, :fg=>'fd0' # yellow - Styles.define :font_lock_string_face, :fg=>'E6DB74' # red - Styles.define :font_lock_keyword_face, :fg=>'A5A18E' -end diff --git a/etc/themes/Flat_Blue.notes b/etc/themes/Flat_Blue.notes deleted file mode 100644 index c71910df..00000000 --- a/etc/themes/Flat_Blue.notes +++ /dev/null @@ -1,35 +0,0 @@ -# Blue bg, dark blue path -module Xiki - Styles.mode_line( - :bg=>'6483af', - :border=>['6483af', 3], - :face=>"arial", - :bold=>true, - :size=>'1', - :fg=>'001122' - ) - - # Inactive has light gray bg, medium gray path - Styles.mode_line_inactive( - :bg=>'bbbbbb', - :border=>['bbbbbb', 3], - :face=>"arial", - :bold=>true, - :size=>'1', - :fg=>'555555' - ) - - # White file stem - Styles.mode_line_file( - :fg=>"fff", - :face=>"arial", - :bold=>true - ) - - Styles.mode_line_dim( - :fg=>"bce", - :face=>"arial", - :bold=>false - ) -end - diff --git a/etc/themes/Light_Gray_BG.notes b/etc/themes/Light_Gray_BG.notes deleted file mode 100644 index 492e82cc..00000000 --- a/etc/themes/Light_Gray_BG.notes +++ /dev/null @@ -1,9 +0,0 @@ -module Xiki - Styles.define :default, :bg=>'e4e4e4', :fg=>'333' - Styles.define :fringe, :bg=>'e4e4e4', :fg=>'e4e4e4' - - # Adjust light colors - Styles.define :font_lock_variable_name_face, :fg=>'c90' # yellow - Styles.define :font_lock_comment_face, :fg=>'aaa' # gray - Styles.minibuffer_prompt :size=>"+0", :face=>"Monaco", :fg=>"#333" -end diff --git a/etc/themes/Molokai.notes b/etc/themes/Molokai.notes deleted file mode 100644 index 77d4bfc5..00000000 --- a/etc/themes/Molokai.notes +++ /dev/null @@ -1,34 +0,0 @@ -module Xiki - # Black bg, light gray path - Styles.mode_line( - :bg=>'000000', - :border=>['000000', 3], - :face=>"arial", - :bold=>true, - :size=>'1', - :fg=>'999' - ) - - # Inactive has Black bg, dark gray path - Styles.mode_line_inactive( - :bg=>'000000', - :border=>['000000', 3], - :face=>"arial", - :bold=>true, - :size=>'1', - :fg=>'666' - ) - - # White file stem - Styles.mode_line_file( - :fg=>"fff", - :face=>"arial", - :bold=>true - ) - - Styles.mode_line_dim( - :fg=>"bce", - :face=>"arial", - :bold=>false - ) -end diff --git a/etc/themes/Orange_Path.notes b/etc/themes/Orange_Path.notes deleted file mode 100644 index 851193ea..00000000 --- a/etc/themes/Orange_Path.notes +++ /dev/null @@ -1,13 +0,0 @@ -module Xiki - if Styles.dark_bg? # Black bg - Styles.mode_line :bg=>'333', :border=>['666', -1], :face=>'Lucida Grande', :size=>'3', :bold=>false, :fg=>'fff' - Styles.mode_line_inactive :bg=>'666', :border=>['888', -1], :face=>'Lucida Grande', :size=>'3', :bold=>false, :fg=>'fff' - Styles.mode_line_dir :fg=>"d93", :size=>"0", :face=>"arial", :bold=>false # Brighter - Styles.mode_line_file :fg=>"fff", :size=>"0", :face=>"arial", :bold=>false - else # White bg - Styles.mode_line :fg=>'222222', :bg=>'555', :border=>['333', -1], :face=>'Lucida Grande', :size=>'3', :bold=>false - Styles.mode_line_inactive :fg=>'999999', :bg=>'888', :border=>['666', -1], :face=>'Lucida Grande', :size=>'3', :bold=>false - Styles.mode_line_dir :fg=>"ea4", :size=>"0", :face=>"arial", :bold=>false # Brighter - Styles.mode_line_file :fg=>"fff", :size=>"0", :face=>"arial", :bold=>false - end -end diff --git a/etc/themes/Pastel_Fonts.notes b/etc/themes/Pastel_Fonts.notes deleted file mode 100644 index fd5dea2f..00000000 --- a/etc/themes/Pastel_Fonts.notes +++ /dev/null @@ -1,8 +0,0 @@ -module Xiki - Styles.define :font_lock_comment_face, :fg=>'555' # gray - Styles.define :font_lock_function_name_face, :fg=>'f95' # orange - Styles.define :font_lock_type_face, :fg=>'6c7' # green - Styles.define :font_lock_variable_name_face, :fg=>'ff6' # yellow - Styles.define :font_lock_string_face, :fg=>'e76' # red - Styles.define :font_lock_keyword_face, :fg=>'999' -end diff --git a/etc/themes/Path_Mode_Line.notes b/etc/themes/Path_Mode_Line.notes deleted file mode 100644 index e5a8ab45..00000000 --- a/etc/themes/Path_Mode_Line.notes +++ /dev/null @@ -1,37 +0,0 @@ -$el.el4r_lisp_eval %` - (progn - - (setq-default xiki-status1 nil) (setq-default xiki-status2 nil) (setq-default xiki-status3 nil) (setq-default xiki-status4 nil) - (setq-default mode-line-format - (quote - ( - - " " ; Apparently required - - (:eval - (cond - ((buffer-modified-p) - (propertize "* " 'face 'mode-line-file)) - (t " "))) - - mode-line-frame-identification - " " - " " - - (:eval (replace-regexp-in-string "^/projects/memorize/memorize.merb/" "$m/" default-directory) - ) - (:propertize "%b" face mode-line-file) - - ":%l" ;; Line number - " " - - ;; status1, status2, etc. (in mode line) - xiki-status1 - (:propertize xiki-status2 face mode-line-file) - (:propertize xiki-status3 face mode-line-dim) - (:propertize xiki-status4 face dotsies-white) - ))) - ) - ` - - diff --git a/etc/themes/Shiny_Green.notes b/etc/themes/Shiny_Green.notes deleted file mode 100644 index feb9f9e9..00000000 --- a/etc/themes/Shiny_Green.notes +++ /dev/null @@ -1,23 +0,0 @@ -module Xiki - Styles.mode_line( - :bg=>'44aa44', - :border=>['55aa55', -5, 'released-button'], - :face=>'Lucida Grande', - :size=>'3', - :bold=>false, - :fg=>'fff' - ) - Styles.mode_line_inactive( - :bg=>'bbb', - :border=>['bbbbbb', -1], - :face=>'Lucida Grande', - :bold=>false, - :fg=>'000' - ) - Styles.mode_line_file( - :fg=>"fff", - :size=>"0", - :face=>"arial", - :bold=>false - ) -end diff --git a/etc/themes/White_BG.notes b/etc/themes/White_BG.notes deleted file mode 100644 index aa6bd722..00000000 --- a/etc/themes/White_BG.notes +++ /dev/null @@ -1,9 +0,0 @@ -module Xiki - Styles.define :trailing_whitespace, :bg=>'999' - - Cursor.black - - Styles.minibuffer_prompt :size=>"+0", :face=>"Monaco", :fg=>"#333" - Styles.define :default, :bg=>'f8f8f8', :fg=>'000' - Styles.define :fringe, :bg=>'f8f8f8', :fg=>'f8f8f8' -end diff --git a/etc/wrappers/wrapper.js b/etc/wrappers/wrapper.js deleted file mode 100644 index 0ed466e0..00000000 --- a/etc/wrappers/wrapper.js +++ /dev/null @@ -1,17 +0,0 @@ -// Sample js file this can invoke -/* - @/projects/xiki_wrappers/ - - pie.js - | exports.menu = function () { - | return "hey/\nyou/\nthere/\n"; - | }; -*/ - -// Gets shelled out to by xiki to delegate call to a .js file. -// Just gets the args passed in and requires and invokes. -var file = process.argv[2] -var path = process.argv[3] - -var clazz = require(file); -var output = clazz.menu(); -console.log(output); diff --git a/etc/wrappers/wrapper.py b/etc/wrappers/wrapper.py deleted file mode 100644 index 9292b3a3..00000000 --- a/etc/wrappers/wrapper.py +++ /dev/null @@ -1,20 +0,0 @@ -# Gets shelled out to by xiki to delegate call to a .py file. -# Just gets the args passed in and requires and invokes. -import sys, re - -file = sys.argv[1] -path = sys.argv[2] - -clazz = re.sub(".+/", "", file) -clazz = re.sub("\.py$", "", clazz) -clazz = clazz.capitalize() -execfile(file) - -method = ".menu" - -import re - -if re.search("^\.", path): - method = path.replace(r'/', '') - -print(eval(clazz+"()"+method+"()")) diff --git a/etc/wrappers/wrapper.rb b/etc/wrappers/wrapper.rb deleted file mode 100644 index 422d2922..00000000 --- a/etc/wrappers/wrapper.rb +++ /dev/null @@ -1,25 +0,0 @@ -# Gets shelled out to by xiki to delegate call to a no-dependency .rb file. -# Just gets the args passed in and requires and invokes. - -# TODO Don't hard-code path - use __FILE__? -# require "/projects/xiki/lib/xiki/ol.rb" - -file = ARGV.shift -path = ARGV.shift - -load file - -clazz = file[/\w+/].gsub(/_([a-z]+)/) {"#{$1.capitalize}"}.sub(/(.)/) {$1.upcase}.gsub("_", "") -clazz = eval clazz - -method = "menu" -method = path[/^\.(.+?)\/?$/, 1] if path =~ /^\./ -method = method.to_sym - -if clazz.respond_to? method - puts clazz.send method -else - cmethods = clazz.methods - Class.methods - puts cmethods.sort.map{|o| "+ .#{o}/"} -end - diff --git a/etc/www/xiki_web_server.rb b/etc/www/xiki_web_server.rb deleted file mode 100644 index 86e0f23c..00000000 --- a/etc/www/xiki_web_server.rb +++ /dev/null @@ -1,370 +0,0 @@ -require "sinatra/base" - -xiki_directory = `xiki directory`.strip - -$:.unshift "#{xiki_directory}lib" -require 'xiki/core/ol.rb' -require 'xiki/core/core_ext.rb' - -# TODO: Enable basic auth for security - # Add menu to let users create a password -# use Rack::Auth::Basic, "Nope" do |u, p| -# u == 'foo' && p == 'bar' -# end - -class SinatraServer < Sinatra::Base - - set :port, 8161 - set :bind, '127.0.0.1' - - get %r{^/_reload$} do - xiki_directory = File.expand_path "#{File.dirname(__FILE__)}/../.." - load "#{xiki_directory}/etc/www/xiki_web_server.rb" - ".flash - done" - end - - get '/*' do - index env['REQUEST_PATH'].sub(/^\//, '') - end - - def htmlify txt, options={} - - return txt if request.env['HTTP_ACCEPT'] !~ /\Atext\/html/ # Only process if request wants html - - # If starts with "web" - - xiki_directory = File.expand_path "#{File.dirname(__FILE__)}/../.." - - $:.unshift "#{xiki_directory}/lib" - ["xiki/core/ol", "xiki/core/core_ext", "xiki/core/line", "xiki/core/tree", "../../menu/bootstrap"].each{|o| require o} - - txt.slice! /.+\n/ - txt = txt.unindent - - txt = Xiki::Menu::Bootstrap.render txt - - return txt - end - - # Turn menu text into html... - - # TODO: should probably maintain indenting? - txt.gsub! /^ *\| ?/, '' - - txt.gsub! /^( *)[+-]+ /, "\\1" # Get rid of bullets - txt.gsub! /^> (.+)/, "

\\1

" - - # Use relative links if path ends in slash, otherwise use absolute - path = request.env['REQUEST_PATH'] - - path = "#{path}/" if path !~ /\/$/ - - # Make path be "/foo/bar/", if no slash ("/foo/bar") - - txt.gsub!(/^( *)(.+?)(\/?)$/){ - all = $& - indent, item, slash = $1, $2, $3 - next all if all =~ /#{item}" - } # no slash - - txt.gsub!(/^ +/){"  " * $&.length} # Get rid of bullets - - txt.gsub!(/.+/){ - all = $& - next all if all =~ /#{all}

" if all !~ /^ 

" - output = "" - output << %` - - Xiki - - - - ` - - output << %`` - # output << %`` - - if ! options[:no_keys] - output << %` - - ` - end - - "#{output}
#{txt}
" - - end - - # Handles / - def default - txt = `xiki web/docs/` - htmlify txt - end - - # Handles most requests - def index menu - no_keys = false - if ENV['REQUEST_METHOD'] == "POST" - cgi = CGI.new - post_txt = cgi['txt'] - - # Temporary hack - File.open("/tmp/post_tmp", "w") { |f| f << post_txt } - - # What's a better way to do this? - # Pass in pipe, and send to shell command via STDIN - end - - return default if menu == "" - - # Run command... - - menu.sub! /^@/, '' - menu.gsub! /-/, ' ' - command = "xiki -cweb '#{menu}'" - txt = `#{command}` - - # rescue e=>Exception - # puts "
#{e.message}\n#{e.backtrace}
" - # Ol << "If we get the permission problem, provide .notes file that will run command to run xiki command once (message explaining it first)!" - # end - - # TODO: return different string when the service is down - - if txt.empty? - menu = menu.sub /\/$/, '' - - no_keys = true - - if menu =~ /\/./ # If slash that's not at end - puts "
Nothing returned.  Maybe service is down, or maybe menu\njust returned nothing, run xiki command\n\n  $ xiki\n"
-      else
-        puts %`
-          

Menu '#{menu}' doesn't exist yet. Create it?

- -
- - -
- -
- - - - `.gsub(/^ /, '') - end - end - - # Html-ify and print output... - if txt !~ /^\s+<.+>$/ - txt = htmlify txt, :no_keys=>no_keys - end - txt - - rescue Exception=>e - "
#{e.message}\n#{e.backtrace}
" - end - - def camel_case txt - return txt if txt !~ /_/ && txt =~ /[A-Z]+.*/ - txt.split('_').map{|e| e.capitalize}.join - end -end - -SinatraServer.run! diff --git a/install_xsh b/install_xsh new file mode 100644 index 00000000..c5aed36d --- /dev/null +++ b/install_xsh @@ -0,0 +1,27 @@ +# +# This file is called from the one-line xsh installer: +# $ curl -L https://raw.githubusercontent.com/trogdoro/xiki/master/install_xsh -o ~/install_xsh; bash ~/install_xsh +# +# It can also be invoked manually, like this: +# $ bash ~/install_xsh +# + +# Make dir if not there yet +mkdir -p ~/xiki-project + +# Download xiki project into the dir +curl -L https://github.com/trogdoro/xiki/archive/master.tar.gz | tar -xz -C ~/xiki-project --strip-components=1 + +# Reload and start xsh (it will lock the user through the setup) +~/xiki-project/bin/xsh --reload + +# If token was passed, save to file + +if [ "$1" ]; then + echo $1 > /tmp/xiki_temp_token +fi + +~/xiki-project/bin/xsh -xsh/setup + + + diff --git a/lib/xiki.rb b/lib/xiki.rb index f26f7743..41253df3 100644 --- a/lib/xiki.rb +++ b/lib/xiki.rb @@ -1,29 +1,33 @@ -xiki_dir = File.expand_path "#{File.dirname(__FILE__)}/.." -Dir.chdir xiki_dir +XIKI_DIR = File.expand_path "#{File.dirname(__FILE__)}/.." if ! defined?(XIKI_DIR) +XIKI_SERVER_MODE = false if ! defined?(XIKI_SERVER_MODE) + +require 'xiki/core/environment' # Used by a lot of classes module Xiki - @@dir = "#{Dir.pwd}/" # Store current dir when xiki first launches + + @@loaded_already = nil # TODO Just use XIKI_DIR from above? def self.dir - @@dir + "#{XIKI_DIR}/" + end + + if Environment.gui_emacs # Not defined yet + $el.el4r_lisp_eval '(ignore-errors (kill-buffer "Issues Loading Xiki"))' end + end -$el.el4r_lisp_eval '(ignore-errors (kill-buffer "Issues Loading Xiki"))' if $el $el.set_process_query_on_exit_flag($el.get_buffer_process("*el4r:process*"), nil) if $el - -# $LOAD_PATH << "#{xiki_dir}/lib" # Require some of the core files require 'rubygems' -require 'xiki/core/trouble_shooting' require 'xiki/core/ol' require 'xiki/core/requirer' require 'xiki/core/text_util' -Xiki::Requirer.require_classes ['xiki/core/notes'] + require 'xiki/core/launcher' require 'xiki/core/mode' require 'xiki/core/menu' @@ -33,149 +37,51 @@ module Xiki $el.elvar.xiki_loaded_once = nil if $el && ! $el.boundp(:xiki_loaded_once) - def self.install_icon arg - - emacs_dir = "/Applications/Aquamacs Emacs.app" - - return "- Couldn't find #{emacs_dir}!" if ! File.exists?("#{emacs_dir}") - - plist_path = "#{emacs_dir}/Contents/Info.plist" - - plist = File.read "#{emacs_dir}/Contents/Info.plist" - - # TODO - # "Back up plist file - where - xiki root?! - # "Tell them where it was backed up! - # "Show diffs of change that was made! - - return "- This file wasn't in the format we expected: #{plist_path}" if plist !~ %r"^\tCFBundleDocumentTypes\n\t\n" - - # TODO - # .plist - # if change was already made, say so - - # TODO - - # 1. Copy icon into app - # cp "#{Xiki.dir}etc/shark.icns" "/Applications/Aquamacs Emacs.app/Contents/Resources/" - # - /Applications/Aquamacs Emacs.app/ - # - Contents/Resources/ - # + shark.icns - # + emacs-document.icns - - # 2. Update Info.plist - # /Applications/Aquamacs Emacs.app/Contents/ - # - Info.plist - # |+ - # |+ CFBundleTypeExtensions - # |+ - # |+ notes - # |+ menu - # |+ xiki - # |+ - # |+ CFBundleTypeIconFile - # |+ shark.icns - # |+ CFBundleTypeName - # |+ Xiki File - # |+ CFBundleTypeOSTypes - # |+ - # |+ TEXT - # |+ utxt - # |+ - # |+ CFBundleTypeRole - # |+ Editor - # |+ - - # 3. Tell user to drag the .app icon out of the "Applications" folder and back in - # - Or google to find a command that will do the same thing - - - "- TODO: finish implementing!" - end - - def self.insert_menu - line = Line.value - indent = Line.indent line - blank = Line.blank? - - prefix = Keys.prefix - - if prefix == :u # Insert @last to see recent menu names and drill in. - Line << "$#{Keys.input :timed=>1}//" - Launcher.go_unified - return - end - - if prefix == :- # Insert @last to see recent menu names and drill in. - Line << "last/" - Launcher.launch_unified - return - end - - # If line not blank, usually indent after - - Line.<<("\n#{indent} @") if ! blank - - # If at end of line, and line not blank, go to next line - - # Todo: if dash+, do auto-complete even if exact match - how to implement? - - input = Keys.input(:timed=>true, :prompt=>"Start typing a menu that might exist (or type 'all'): ") - - View << input - - Launcher.launch_unified - end - - def self.open_menu - - prefix = Keys.prefix :clear=>1 - - return Launcher.open("- last/") if prefix == :u - - input = Keys.input(:timed=>true, :prompt=>"Start typing a menu that might exist (or type 'all'): ") - View.to_buffer "menu" - Notes.mode - - View.rename_uniquely - - View.kill_all - View << "#{input}\n" - View.to_highest - Launcher.launch_unified - end - def self.menus CodeTree.menu end - def self.dont_search - $xiki_no_search = true - nil - end - def self.quote_spec txt txt. gsub(/^/, '| '). gsub(/ +$/, ''). - gsub(/^\|( )([+-])/) {|o| "|#{$2}#{$1}"} # Make extra be green, missing be red + gsub(/^\|( )([+-])/) {|o| "|#{$2 == '-' ? '+' : '-'}#{$1}"} # Show "expected" correct value as green, and the incorrect "got" value as red end def self.tests clazz=nil, describe=nil, test=nil, quote=nil + options = yield + prefix = Keys.prefix :clear=>1 + # Handle option items + return " + * navigate + " if options[:task] == [] + prefix = "open" if options[:task] == ["navigate"] + return if self.nav_to_line # If on line to navigate to, just navigate # If no class, list all classes... if clazz.nil? - return ["all/"] + Dir["#{Xiki.dir}/spec/*_spec.rb"].entries.map{|o| "#{o[/.+\/(.+)_spec\.rb/, 1]}/"} + files = Dir["#{Xiki.dir}spec/*_spec.rb"].entries + + # Sort, handling error when certain type of file + files = files.sort{|a, b| + a_time = File.mtime(a) rescue nil + b_time = File.mtime(b) rescue nil + next 0 if ! a_time || ! b_time + b_time <=> a_time + } + + txt = ["all/"] + files.map{|o| "#{o[/.+\/(.+)_spec\.rb/, 1]}/"} + return txt.join("\n") end # If /class, list describes... - path = Bookmarks["$x/spec/#{clazz}_spec.rb"] + path = Bookmarks["%xiki/spec/#{clazz}_spec.rb"] sync_options = prefix == :u ? {} : {:sync=>1} @@ -184,7 +90,7 @@ def self.tests clazz=nil, describe=nil, test=nil, quote=nil if clazz == "all" # Run all specs return self.quote_spec( #prefix == :u ? - Console.run("rspec spec", sync_options.merge(:dir=>Xiki.dir)) + Shell.run("rspec spec", sync_options.merge(:dir=>Xiki.dir)) ) end @@ -202,17 +108,17 @@ def self.tests clazz=nil, describe=nil, test=nil, quote=nil if describe == "all" # Run whole test return self.quote_spec( - Console.run("rspec spec/#{clazz}_spec.rb", sync_options.merge(:dir=>Xiki.dir)) + Shell.run("rspec spec/#{clazz}_spec.rb", sync_options.merge(:dir=>Xiki.dir)) ) end txt = File.read path is_match = false - return "- all/\n" + txt.scan(/^ *(describe|it) .*"(.+)"/).map{|o| + return "- all\n" + txt.scan(/^ *(describe|it) .*"(.+)"/).map{|o| next is_match = o[1] == describe if o[0] == "describe" # If describe, set whether it's a match next if ! is_match - "- #{o[1]}/" + "- #{o[1]}" }.select{|o| o.is_a? String}.join("\n") end @@ -223,7 +129,7 @@ def self.tests clazz=nil, describe=nil, test=nil, quote=nil if test == "all" # Run all for describe return self.quote_spec( - Console.run("rspec spec/#{clazz}_spec.rb -e \"#{describe}\"", sync_options.merge(:dir=>Xiki.dir)) + Shell.run("rspec spec/#{clazz}_spec.rb -e \"#{describe}\"", sync_options.merge(:dir=>Xiki.dir)) ) end @@ -234,7 +140,7 @@ def self.tests clazz=nil, describe=nil, test=nil, quote=nil # Run it command = "rspec spec/#{clazz}_spec.rb -e \"#{describe} #{test}\"" - result = Console.run command, :dir=>"$x", :sync=>true + result = Shell.run command, :dir=>"%xiki", :sync=>true if result =~ /^All examples were filtered out$/ TextUtil.title_case! clazz @@ -242,12 +148,12 @@ def self.tests clazz=nil, describe=nil, test=nil, quote=nil return %` > Test doesn't appear to exist. Create it? - @#{path} - | describe #{clazz}, "##{describe}" do - | it "#{test}" do - | #{clazz}.#{describe}.should == "hi" - | end - | end + =#{path} + : describe #{clazz}, "##{describe}" do + : it "#{test}" do + : #{clazz}.#{describe}.should == "hi" + : end + : end ` end @@ -264,7 +170,7 @@ def self.nav_to_line return if ! match file, line = match[1..2] - file.sub! /^\.\//, Bookmarks["$x"] + file.sub! /^\.\//, Bookmarks["%xiki"] View.open file View.to_line line.to_i @@ -276,26 +182,15 @@ def self.nav_to path, *searches View.to_highest searches.each { |s| Search.forward "[\"']#{$el.regexp_quote s}[\"']" } Move.to_axis - # Maybe restore this, but have option to only mark if not yet marked - Color.mark "light", :if_clear=>1 - # Color.mark "light" nil end - # TODO: remove this, since it just delegates to .path. - # Make callers call .path instead. - def self.trunk options={} - self.path options - end - - def self.path options={} - Tree.path options - end - def self.quote txt Tree.quote txt end - # Other .init mode defined below + # TODO: needs updating. + # Run this when opening .xiki files. def self.on_open orig = View.name name = orig[/(.+?)\./, 1] @@ -317,94 +212,134 @@ def self.on_open View.dir = "/tmp/" View.<< "- #{name}/\n", :dont_move=>1 - Launcher.launch_unified + Launcher.launch + + end + # Invoked by Xiki.init after the fork. + def self.reload + $el.el4r_kill_and_restart + "" end - # Invoked by environment when Xiki starts up + def self.upon_reload + + # Do stuff here to load on top of existing fork + + Keys.map_reset + + FileTree.define_styles + Color.define_styles + Notes.define_styles + Notes.init + Notes.keys # Define local keys + Effects.define_styles + + end + + # Invoked by environment when Xiki starts up. + # If already loaded, skips loading and delegates to .upon_reload. # # Xiki.init # Xiki.init :minimal=>1 # Don't do yaml or awesome_print conf that might interfere with some ruby environments (for embedded case). def self.init options={} - # TODO: A lot of this is deprecated after the Unified refactor - # - Stop loading the old menus soon! + # If we're reloading on top of a process after a fork, delegate to .upon_reload... + + return self.upon_reload if @@loaded_already + @@loaded_already = true + + # Not loaded yet, so call .init methods of many classes... # Get rest of files to require - # classes = Dir["./lib/xiki/*.rb"] - classes = Dir["./lib/xiki/{handlers,core}/*.rb"] + classes = Dir["#{Xiki.dir}lib/xiki/{handlers,core}/*.rb"] classes = classes.select{|i| i !~ /\/ol.rb$/ && # Don't load Ol twice i !~ /\/xiki.rb$/ && # Remove self - i !~ /\/key_bindings.rb$/ && # Remove key_bindings + i !~ /\/key_shortcuts.rb$/ && # Remove key_shortcuts i !~ /__/ # Remove __....rb files } - # classes = Dir["**/*.rb"] - # classes = classes.select{|i| - # i !~ /xiki.rb$/ && # Remove self - # i !~ /key_bindings.rb$/ && # Remove key_bindings - # i !~ /\// && # Remove all files in dirs - # i !~ /tests\// && # Remove tests - # i !~ /__/ # Remove __....rb files - # } - classes.map!{|i| i.sub(/\.rb$/, '')}.sort! - # Require classes Requirer.require_classes classes - - # key_bindings has many dependencies, require it last - # Requirer.require_classes ['./lib/xiki/key_bindings.rb'] - Requirer.require_classes ['./lib/xiki/core/key_bindings.rb'] - + # key_shortcuts has many dependencies, require it last + Requirer.require_classes ["#{Xiki.dir}lib/xiki/core/key_shortcuts.rb"] if Xiki.environment != 'web' Launcher.add_class_launchers classes.map{|o| o[/.*\/(.+)/, 1]} - Launcher.reload_menu_dirs - + Launcher.load_tools_dir Launcher.add "xiki" Launcher.add "ol" - # Pull out into .define_mode + self.init_patterns + if ! XIKI_SERVER_MODE + self.copy_over_default_home_xiki_files + end - Mode.define(:xiki, ".xiki") do - Xiki.on_open + if ! options[:minimal] + self.awesome_print_setup end + # If the first time we've loaded, open =welcome (if not xsh)... + if $el - # If the first time we've loaded - if ! $el.elvar.xiki_loaded_once && ! Menu.line_exists?("misc config", /^- don't show welcome$/) && ! View.buffer_visible?("Issues Loading Xiki") - Launcher.open("welcome/", :no_search=>1) - end + if ! $el.getenv("XSH") && + (! $el.boundp(:xiki_loaded_once) || ! $el.elvar.xiki_loaded_once) && + ! Command.line_exists?("misc config", /^- don't show welcome$/) && + ! View.buffer_visible?("Issues Loading Xiki") + Launcher.open("welcome", :no_search=>1) + end $el.elvar.xiki_loaded_once = true end - self.unified_init - if ! options[:minimal] - self.awesome_print_setup - self.yaml_setup - end + self.init_misc end - # Invoked by environment when Xiki starts up. - def self.unified_init + def self.init_misc + Topic.init_cache + end + + + # Invoked by self.init + def self.init_patterns - load "#{Xiki.dir}menu/patterns/core_patterns.rb" + # load "#{Xiki.dir}roots/patterns/core_patterns.rb" + load "#{Xiki.dir}misc/core_patterns.rb" # TODO # - Better name for core_patterns.rb? - # - Load core_patterns.rb in ~/menu as well + # - Load core_patterns.rb in ~/.xiki/roots as well # - Later, pre-load all files in - # @$x/menu/patterns/ - # @~/menu/patterns/ - # | (just grab Xiki.menu_path_custom_dir) + # @:xiki/menu/patterns/ + # @~/.xiki/roots/patterns/ + # | (just grab Xiki.xiki_path_custom_dir) # - be sure to load core_patterns.rb first! end + # Creates notes.notes etc. files from templates, if not there yet. + def self.copy_over_default_home_xiki_files + + dest_dir = File.expand_path("~/xiki") + + # Do quick check for one of the files, and do nothing if not there + return if File.exists? "#{dest_dir}/tutorial.xiki" + + FileUtils.mkdir_p dest_dir + source_dir = "#{Xiki.dir}misc/default_home_xiki/" + + # Copy default items over files... + + Dir.entries(source_dir).select{|o| o !~ /^\.+$/}.each_with_index do |f, i| + source, dest = "#{source_dir}/#{f}", "#{dest_dir}/#{f}" + next if File.exists?(dest) # Don't alter if already there + FileUtils.cp(source, dest) rescue nil + end + end + def self.process action case action @@ -427,7 +362,7 @@ def self.process action end def self.dont_show_welcome - Menu.append_line "misc config", "- don't show welcome" + Command.append_line "misc config", "- don't show welcome" end def self.finished_loading? @@ -447,8 +382,8 @@ def self.finished_loading? # | Xiki.children "/tmp/", "a/b" # /tmp/a... file with path "b" # | Xiki.children "/tmp/", ["a", "b"] # /tmp/a... file with path "b" # | Xiki.children "/tmp//a" # .children "/tmp/", "a" - # | Xiki.children "a" # .children "~/menu/", "a" # (or wherever in MENU_PATH "a" is first found) - # | Xiki.children "a/b" # .children "~/menu/", "a/b" # (or wherever in MENU_PATH "a" is first found) + # | Xiki.children "a" # .children "~/.xiki/roots/", "a" # (or wherever in XIKI_PATH "a" is first found) + # | Xiki.children "a/b" # .children "~/.xiki/roots/", "a/b" # (or wherever in XIKI_PATH "a" is first found) # | Xiki.children "a/\n b/", "a" # "b/" # | Xiki.children "/tmp/" # delegate to file tree # | Xiki.children "/tmp/a.menu//" # recurse to "/tmp//a/" ? @@ -467,7 +402,7 @@ def self.finished_loading? # # > Ancestors vs multiple sources (not implemented yet) # | Xiki.children array # Ancestors (eg ["/tmp/d", "rails"]) - # | Xiki.children array, string # Multiple sources (eg ["~/menus1/", "~/menus2/"], "foo") # could be confused with: Xiki.children ancestors, path, so maybe one has to be an option + # | Xiki.children array, string # Multiple sources (eg ["~/.xiki/rootss1/", "~/.xiki/rootss2/"], "foo") # could be confused with: Xiki.children ancestors, path, so maybe one has to be an option # # > More thought # @/docs/todo/ @@ -480,28 +415,20 @@ def self.children # > TODO: delegate to one of these maybe # - Which one? # Tree.children2 - # Menu.children2 + # Command.children2 # Launcher.children2 # Tree[] # Menu[] # Launcher[] - Menu.children2 + Command.children2 end def self.[] *args Expander.expand *args - # Menu.children2 end def self.expand *args Expander.expand *args - # Menu.children2 - end - - # Make pull in menu, to be accessible as class - # Also define global 'xiki_require' for convenience - def self.require - "TODO" end # Make pull in menu, to be accessible via Xiki[] @@ -512,7 +439,7 @@ def self.require # Xiki.register "/tmp/foo.menu" # Xiki.register "/tmp/foo/" # this dir # - # Xiki.register "/tmp/foo//" # adds dir to MENU_PATH (makes all menus in the dir be exectable) + # Xiki.register "/tmp/foo//" # adds dir to XIKI_PATH (makes all menus in the dir be exectable) def self.register "TODO" end @@ -521,11 +448,6 @@ def self.def *args, &block Expander.def *args, &block end - # def self.defs *args - # Expander.defs *args - # end - - # Just a placeholder for now def self.caching @@ -535,36 +457,47 @@ def self.caching "TODO" end - def self.menu_path_custom_dir - File.expand_path("~/menu") + # This dir is where user xiki puts user-created menus. + # Users can add other menu dirs to the XIKI_PATH env var, but ~/.xiki/roots is always added for now. + # See TODO:... comment below for an improvement. + def self.xiki_path_custom_dir + File.expand_path("~/.xiki/roots") end - def self.menu_path_core_dir - Bookmarks["$x/menu"] + + def self.xiki_path_core_dir + Bookmarks["%s/roots"] end - # Return the MENU_PATH environment var, plus ~/menu/ and $x/menu. - def self.menu_path_dirs - # Worry about this later - # How many times called? - memo-ize this based on MENU_PATH value - list = (ENV['MENU_PATH'] || "").split ":" - list = [self.menu_path_custom_dir, list, self.menu_path_core_dir].flatten + + # Return the XIKI_PATH environment var, plus ~/.xiki/roots/ and :xiki/roots. + def self.xiki_path_dirs + + # How many times called? - memo-ize this based on XIKI_PATH value + # Worry about this later, when it gets slow. + + list = (ENV['XIKI_PATH'] || "").split ":" + list = list.select{|o| o.any?} # Remove blanks + + list.map!{|o| File.expand_path o} + list = [self.xiki_path_custom_dir, list, self.xiki_path_core_dir].flatten list.uniq # TODO: - # - When user hasn't set MENU_PATH - # - auto-add ~/menu to the beginning + # - When user hasn't set XIKI_PATH + # - auto-add ~/xiki/roots to the beginning # - Else - # - assume user has added ~/menu (or the equivalent) to the beginning + # - assume user has added ~/xiki/roots (or the equivalent) to the beginning end def self.menuish_parent options ancestors = options[:ancestors] - ancestors && ancestors[-1][/^([\w ]+)\/$/, 1] + ancestors && ancestors[-1][/\A[*^~?]?([\w ]+)\/$/, 1] end - def self.yaml_setup - Kernel.require 'yaml' - YAML::ENGINE.yamler='syck' - end + # def self.yaml_setup + # Kernel.require 'yaml' + # + # YAML::ENGINE.yamler='syck' if Xiki.environment != 'web' + # end def self.awesome_print_setup begin @@ -579,7 +512,65 @@ def self.awesome_print_setup :indent => -2, # left-align hash keys :index => false, :multiline => true, + :plain => true, } end + # Xiki.web is shortcut for calling Xiki.expand "foo", :client=>"web" + # + # Xiki.web "ip" + # Xiki.web "ip", :no_slash=>1 + def self.web *args + + # Add or merge :client=>"web" into options + if args[-1].is_a? Hash + args[-1][:client] = "web" + else + args << {:client=>"web"} + end + + self.expand *args + end + + def self.kill + + # Get pid... + + txt = Shell.sync "ps -eo pid,args" + txt = txt.split("\n").grep(/xsh forker/) + + return "<* not running" if txt == [] + + pid = txt[0][/\d+/] + + # kill it... + + Shell.sync "kill #{pid}" + + "<* killed!" + end + + def self.init_in_client + Clipboard.init_in_client + Tree.init_in_client + Themes.init_in_client + History.init_in_client + Deck.init_in_client + Ruby.init_in_client + + # Just invoke this, to make sure elisp var is set + Keys.noob_mode + + end + + class << self + attr_accessor :environment + end + +end + + +# Sure we want to do this globally? +def X *args + Xiki.expand *args end diff --git a/lib/xiki/core/block.rb b/lib/xiki/core/block.rb index b91fcd6d..25154eac 100644 --- a/lib/xiki/core/block.rb +++ b/lib/xiki/core/block.rb @@ -1,25 +1,44 @@ module Xiki class Block - # def self.value - # res = [] - # with(:save_excursion) do - # found = re_search_backward "^ *$", nil, 1 - # if found - # end_of_line - # forward_char - # end - # res << point - # re_search_forward "^ *$", nil, 1 - # beginning_of_line - # res << point - # end - # res - # end - def self.do_as_wrap - if Keys.prefix_u? + line = Line.value + + # If |... line, indent consecutive |... lines... + + if quote = line[/^ *([|:#]|\/\/) /, 1] + + orig = View.cursor + + bounds = Tree.sibling_bounds :quotes=>quote + + txt = View.delete bounds[0], bounds[-1] + + indent = Line.indent txt + txt.gsub! /^ *#{Regexp.quote quote} ?/, '' + + # For each paragraph, wrap separately... + + txt = txt.split "\n\n" + txt.map! do |paragraph| + paragraph.gsub!(/ *\n */, ' ') # Remove linebreaks + paragraph = TextUtil.word_wrap(paragraph, 64-indent.length).strip + paragraph + end + txt = txt.join "\n\n" + + txt = Tree.quote txt, :char=>quote + txt.gsub! /^/, indent + + View << txt+"\n" + insert_right = bounds[0] + txt.length + orig = insert_right-1 if orig > insert_right + View.cursor = orig + + return + + elsif Keys.prefix_u? # Grab paragraph and remove linebreaks orig = Location.new @@ -37,7 +56,11 @@ def self.do_as_wrap return end - $el.fill_paragraph nil + cursor = View.cursor + paragraph = View.paragraph :bounds=>1 + $el.fill_region_as_paragraph Line.left, paragraph[1] + View.cursor = cursor + end # diff --git a/lib/xiki/core/bookmarks.rb b/lib/xiki/core/bookmarks.rb index eb7eb504..56882730 100644 --- a/lib/xiki/core/bookmarks.rb +++ b/lib/xiki/core/bookmarks.rb @@ -1,6 +1,7 @@ require 'xiki/core/location' require 'xiki/core/keys' -require 'yaml' + +require 'xiki/core/files' module Xiki class Bookmarks @@ -16,13 +17,13 @@ def self.menu | Some of the common ways to use the Bookmarks class programatically. | | > Get path for a bookmark - @p Bookmarks["$t"] + =p Bookmarks["%n"] | | > Expand bookmark in a path - @p Bookmarks["$tm/hi.txt"] + =p Bookmarks["%tm/hi.txt"] | > Where Emacs stores bookmarks - @~/.emacs.bmk + =~/.emacs.bmk | - .elisp/ | > Save unsaved bookmarks to ~/.emacs.bmk @@ -43,42 +44,41 @@ def self.menu | open+point: jumps to bookmark file and cursor position | > See Also - @files/docs/ + =files/docs/ ` end def self.save arg=nil in_a_file_tree = FileTree.handles? rescue nil - # If we're in a file tree, use file in tree (unless C-u over-rides it) + # Cursor is on a file in a tree, so use it (unless C-u over-rides it)... + if ! Keys.prefix_u? && in_a_file_tree && ! Line[/^ *\|/] + path = Tree.construct_path + keys = Keys.input(:timed=>true, :prompt=>"Name of bookmark for #{path.sub(/.+\/(.)/, "\\1")}: ") || "0" - $el.with(:save_window_excursion) do - $el.find_file path - self.set keys - end - View.flash "- bookmarked as: $#{keys}", :times=>3 + + self.set keys, :file=>path + View.flash "- bookmarked as: ^#{keys}", :times=>3 return end - # If arg is a symbol, use it as the prefix + # arg is a symbol, so use it as the prefix... + prefix = "" if arg && arg.class == Symbol prefix = arg.to_s elsif arg && arg.class == String - self.set(arg.sub(/^\$/, "")) + self.set(arg.sub(/^\:/, "")) return end - # Use input from user or "default" - keys = Keys.input(:timed=>true, :prompt=>"Name of bookmark for #{View.file_name}: ") || "0" + + # Append to bookmark file... + + keys = Keys.input(:prompt=>"Name of bookmark for #{View.file_name}: ") || "0" self.set "#{prefix}#{keys}" - # Append to bookmark file - log_path = "/temp/bookmark_log.notes" - if File.exists?(log_path) - File.open(log_path, "a") { |f| f << "| $#{prefix}#{keys}\n#{View.file}\n\n" } - end end def self.[]= bookmark, path @@ -88,218 +88,234 @@ def self.[]= bookmark, path end end - # Like bookmark-set, but accepts buffers - def self.set name - # Just create normal bookmark if file - return $el.bookmark_set(name) if View.file || $el.elvar.dired_directory + # Lower level method > saves actual bookmark + def self.set name, options={} - # Must be buffer + file = options[:file] || View.file - $el.bookmark_delete name # Delete real bookmark + dir = File.expand_path "~/.xiki/bookmarks/" - # Load bookmarks.yml - bookmarks_yml = File.expand_path("~/bookmarks.yml") - if File.exists?(bookmarks_yml) - bookmarks = YAML::load IO.read(bookmarks_yml) - end - bookmarks = [] unless bookmarks - - # Delete if there - already_here = bookmarks.find {|bm| bm[0] == name} - bookmarks.delete(already_here) if already_here + FileUtils.mkdir_p dir - # Add to end - bookmarks << [name, View.name] + File.open("#{dir}/#{name}.xiki", "w") { |f| f << "#{file}\n" } - # Save file - File.open(bookmarks_yml, "w") { |f| f << bookmarks.to_yaml } + # Handle bookmarks for buffers? + # Format of file? + # | buffers/foo end - # Like bookmark-jump, but accepts buffers - def self.jump name - - # If normal bookmark found, use it - return $el.bookmark_jump(name) if $el.bookmark_get_filename(name) - - buffer = self.buffer_bookmark name - - if buffer == :buffer_not_open - return $el.message "Buffer '#{buffer}' not currently open." - end - return nil if buffer.nil? - - View.to_buffer buffer - return true - end - - def self.buffer_bookmark name - # Load bookmarks.yml - bookmarks_yml = File.expand_path("~/bookmarks.yml") - return nil unless File.exists?(bookmarks_yml) - bookmarks = YAML::load IO.read(bookmarks_yml) - bookmarks = [] unless bookmarks - - # Get buffer name - found = bookmarks.find {|bm| bm[0] == name} - return nil unless found - - unless View.buffer_open?(found[1]) - return :buffer_not_open - end - # Do nothing if already open - - found[1] - end - - - # If string passed, go to file+point of bookmark. # If nothing passed, go to file of user-prompted text # If no args passed, get bookmark from user and jump there def self.go bookmark=nil, options={} - #el4r_lisp_eval "(require 'bookmark)(bookmark-maybe-load-default-file)" + # If arg is a symbol, use it as the prefix prefix_to_bm = "" if bookmark && bookmark.class == Symbol prefix_to_bm = bookmark.to_s elsif bookmark && bookmark.class == String keys = bookmark - # return self.jump(bookmark.sub(/^\$/, "")) end # Use input from user or "default" - keys ||= Keys.input(:timed => true, :prompt => "Enter bookmark to jump to: ") || "0" + keys ||= Keys.input(:timed=>true, :prompt=>"Type ., /, ~, or a bookmark: ") || "0" + + # User typed "-" or ",", so jump to "continue here" or "- implement!" link + if keys == "," || keys == "-" + View.open "%links" + Move.top - # If ".", delegate to FileTree.tree, since it knows how to hilight the current file - return FileTree.tree :bm=>keys if keys =~ /^[.\/]$/ + target = keys == "-" ? "- implement!" : "continue here" + Search.forward target, :beginning=>true - path = Bookmarks["$#{prefix_to_bm}#{keys}"] + Line.next if Line !~ /^ *:/ + Launcher.launch + return "" + elsif keys == "\e" # They typed escape + return - if path.nil? # If not found, try buffer in bookmarks.yml + # Non-word, so delegate to FileTree.tree, since it knows how to hilight the current file - return true if self.jump( "#{prefix_to_bm}#{keys}" ) - View.beep - $el.message("Bookmark not found!") - return :not_found + elsif keys =~ /^[.\/]+$/ + keys.sub! /^\./, ".." # Add another dot, so it shows the dir, not the file + return FileTree.tree :bm=>keys end + # User typed "foo/", so treat as command + + # User input included punctuation (eg "foo/" or ":foo"), so expand it unaltered... + + if keys =~ /[^a-z0-9]/i + View.to_buffer View.unique_name("untitled.xiki") + Notes.mode + View >> "#{keys}\n\n\n" + + Launcher.launch + return "" + end + + path = keys =~ /^\w/ ? + Bookmarks["%#{prefix_to_bm}#{keys}", :raw_path=>1] : + keys + + return View.open "> Error:\n:-Bookmark #{path} doesn't exist" if path =~ /^%/ + prefix = Keys.prefix # If up+, open bookmark as menu if prefix == :u - Launcher.open "$#{keys}//", :unified=>1 + Launcher.open "%#{keys}//" #, :unified=>1 return elsif prefix == :uu # Jump up one level if up+up+ path = File.dirname path end - if prefix==9 # If 9, open in bar - View.bar - self.jump "#{prefix_to_bm}#{keys}" - else - Location.go path, :stay_in_bar => true - end - - if options[:point] or prefix == :- # Go to actual point - return self.jump("#{prefix_to_bm}#{keys}") - end - end - - def self.keys - # TODO: put newest keys here + View.open path, options.merge(:stay_in_bar=>1) end # Expand $foo paths in strings. Expects strings to bee bookmark names, # and replaces with corresponding paths. - def self.[] path - self.expand(path) + def self.[] path, options={} + self.expand(path, options) end - # Expands bookmark to file - def self.expand path, options={} + def self.bookmarks_required bm=nil + return @@bookmarks_required if ! bm + @@bookmarks_required[bm] + end - if options[:just_bookmark] # If only a bookmark, just expand it - return $el.bookmark_get_filename(path) - end + @@bookmarks_required = { + "n"=>"~/xiki/notes.xiki", + "x"=>"~/xiki/", + "home"=>"~/", + "xiki"=>Xiki.dir, + "links"=>"~/xiki/links.xiki", + "source"=>Xiki.dir, + "xs"=>Xiki.dir, + "xn"=>Xiki.dir, + } + + # Bookmarks.bookmarks_optional "t" + # Bookmarks.bookmarks_optional "herring" + def self.bookmarks_optional bm=nil + return @@bookmarks_optional if ! bm + @@bookmarks_optional[bm] + end - # If $xxx found - if path =~ /^\$([._a-zA-Z0-9-]+)([\\\/]?)(.*)/ - bm, slash, rest = $1, $2, $3 + @@bookmarks_optional = { + "l"=>"~/xiki/links.xiki", + "t"=>"~/xiki/", + + "s"=>Files.tilde_for_home(Xiki.dir), + "h"=>"~/", + + "d"=>"~/Desktop/", + "r"=>"/", + "tm"=>"/tmp/", + "et"=>"/etc/", + "dx"=>"~/.xiki/", + "b"=>"~/.xiki/bookmarks/", + "dl"=>"~/Downloads/", + + "br"=>"~/.bashrc", + "de"=>"~/.emacs", + + "fa"=>"~/.xiki/misc/favorite/topics.xiki", + + "i"=>"~/.xiki/interactions/", + "links"=>"~/xiki/links.xiki", + + "ro"=>"~/.xiki/roots/", + "w"=>"~/xiki/", + + "co"=>"~/.xiki/roots/conf/", + "co2"=>"#{Xiki.dir}roots/conf/", + + "q"=>"~/xiki/quick.xiki", + "qq"=>"~/xiki/quick2.xiki", + "ti"=>"~/xiki/timer.xiki", + + "xc"=>"#{Xiki.dir}commands", + "c2"=>"#{Xiki.dir}commands", + + "cp"=>"#{Xiki.dir}misc/core_patterns.rb", + + "ir"=>"#{Xiki.dir}misc/emacs/el4r/init.rb", + "th"=>"#{Xiki.dir}misc/themes/", + "li"=>"#{Xiki.dir}lib/xiki/", + "xs"=>"#{Xiki.dir}spec/", + + "m"=>"~/.xiki/misc/", + "v"=>"~/.xiki/misc/versions/", + "lo"=>"~/.xiki/misc/logs/", + "diff"=>"~/.xiki/misc/logs/difflog.xiki", + "yo"=>"~/.xiki/misc/yours.rb", + "cl"=>"~/.xiki/misc/logs/xiki_commands_log.xiki", + "tt"=>"~/.xiki/misc/topic_top/", + "pi"=>"~/Pictures/", + "k"=>"#{Xiki.dir}lib/xiki/core/key_shortcuts.rb", + "us"=>"/usr/", + "lo"=>"/usr/local/", + "ap"=>"/Applications/", + "vo"=>"/Volumes/", + } + + # Looks up bookmark in ~/.xiki/bookmarks + # Bookmarks.lookup "p" + def self.lookup path + file = File.read(File.expand_path("~/.xiki/bookmarks/#{path}.xiki")).strip + file + rescue Exception=>e + # It looks for a file and falls back to optional bookmarks if not found + nil + end - bm_orig = bm - # Expand bookmark - if ["x", "xiki"].member? bm - bm = Xiki.dir - else - bm = $el.bookmark_get_filename(bm) rescue nil - end - - if bm.nil? - bm = - if bm_orig == "t" - "#{File.expand_path("~")}/todo.notes" - elsif bm_orig == "f" - "#{File.expand_path("~")}/files.notes" - elsif bm_orig == "h" - "#{File.expand_path("~")}/" - elsif bm_orig == "r" - "/" - end - end - return path if bm.nil? - # If a slash, cut off filename if there is one (only dir is wanted) - if options[:file_ok] # Put slash back if there was one - bm << "/" if bm !~ /\/$/ && slash.any? - elsif slash.any? - bm.sub! /[^\\\/]+$/, "" - end - - path = "#{bm}#{rest}" + # Expands bookmark to file + def self.expand path, options={} + # If ~/..., /..., or ./..., no need to look up bookmark... + if path =~ /^~\// || options[:absolute] || path =~ /^\.+(\/|$)/ # Expand ~/ if it has it + return View.expand_path(path, options) + end - path = View.expand_path(path) if path =~ /^~/ + # Just bookmark string passed, so just expand it... - path - - elsif path =~ /^~\// # If home dir, expand - # Expand ~/ if it has it - View.expand_path(path) + if options[:just_bookmark] + bm, slash, rest = path, nil, nil + elsif path =~ /^%([._a-zA-Z0-9-]+)([\\\/]?)(.*)/ # Path starts with %xxx, so pull it out and look it up... + bm, slash, rest = $1, $2, $3 - elsif options[:absolute] || path =~ /^\.+(\/|$)/ # If relative path, expand - View.expand_path(path) - else - path + else # If no $..., just return the literal path... + return path end - end + found = self.bookmarks_required bm + found ||= self.lookup bm + found ||= self.bookmarks_optional bm - # def self.collapse path - - # # Insert $bookmark into to path if it contains a bookmarked path - # Ol["Not used, right - obsolete - was used to shorten window labels!"] - - # if ! @bookmarks_cache - # @bookmarks_cache = [] - # # TODO: pull this list out and make configurable - # %w[a tr p n x 18].each do |name| + if found.nil? + return nil if options[:just_bookmark] + return path + end + # Recursively run if still has $ after lookup... - # bmpath = $el.bookmark_get_filename(name) - # next unless bmpath - # bmpath.sub!(/[^\/]+$/, "") + found = self.expand found if found =~ /^%/ - # @bookmarks_cache << [name, bmpath] - # end - # end + if ! options[:raw_path] && found =~ /^~/ + had_slash = found =~ /\/$/ + found = File.expand_path found + found = "#{found}/" if had_slash + end - # @bookmarks_cache.each do |name, bmpath| - # next unless path =~ /^#{bmpath}/ - # return path.sub(bmpath, "$#{name}/") - # end - # return path - # end + if slash.any? || rest.any? + found.sub! /\/$/, "" + found << "/" + end + "#{found}#{rest}" + end # Remove file from the end (if not dir). # Bookmarks.dir_only "/tmp/foo.txt" @@ -310,8 +326,16 @@ def self.expand path, options={} # Similar to ruby's File.dirname, but which doesn't recognize dir paths: # File.dirname "/tmp/a/" # /tmp - def self.dir_only path - path.sub(/\/[^\/]+\.[^\/]+$/, '') + def self.dir_only path, options={} + path = path.sub(/\/[^\/]+\.[^\/]+$/, '') + # return path if options[:no_slash] + + if options[:with_slash] + path << "/" unless path =~ /\/$/ + end + + path + end # Prompt user for bookmark name, and return bookmark @@ -327,31 +351,10 @@ def self.read name File.read(self.expand(name)) end - def self.list path=nil - - result = "" - if ! path # Print all bookmarks - all = $el.elvar.bookmark_alist.collect { |bm| - item = bm.to_a - - second = item[1] # Either (filename . "/path") or ((filename . "/path") ...) - second = second[1].is_a?(String) ? second[1] : second[0][1] + def self.tree - [item[0], second] - } - all.each do |l| - n, p = l - result << "- #{n}) @#{p.sub(/\/$/,'')}\n" - end - return result - end + raise "This is cool, so maybe do this with new :b dir" - # Open single path - View.open path[/ ([~\/].+)/, 1] - nil - end - - def self.tree paths = $el.elvar.bookmark_alist.collect {|bm| ary = bm.to_a key = ary[0] @@ -373,15 +376,23 @@ def self.open_quick self.go("q#{bookmark}") end - def self.persist - $el.bookmark_save - ".flash - persisted bookmarks!" + # Bookmarks.bookmarkify_path "/Users/craig/.xiki/misc/logs/" + def self.bookmarkify_path path + + # Expand out tilde in path if not there, since paths in bookmarks have tildes + + path = Files.tilde_for_home path + bookmarks = self.bookmarks_required + + self.bookmarks_required.each do |bookmark, dir| + return path.sub(/^#{dir}/, "%#{bookmark}/") if path =~ /^#{dir}/ + end + + path + end end -end -if $el - $el.el4r_lisp_eval("(require 'bookmark)") - $el.bookmark_maybe_load_default_file end + diff --git a/lib/xiki/core/buffers.rb b/lib/xiki/core/buffers.rb index f7aec4d9..b616ba5f 100644 --- a/lib/xiki/core/buffers.rb +++ b/lib/xiki/core/buffers.rb @@ -1,7 +1,7 @@ module Xiki class Buffers - def self.menu# buffer=nil + def self.menu # buffer=nil " - .current/ - .tree/ @@ -20,36 +20,70 @@ def self.menu# buffer=nil # Mapped to open+current and @current # Open list of buffers def self.current *name - prefix = Keys.prefix :clear=>true + + options = yield + + prefix = Keys.prefix :clear=>1 # /, so show list of buffers... if name.empty? - case prefix + + return "* quoted" if options[:task] == [] + + quoted = options[:task] == ["quoted"] # Show all by default - when nil, "all" - return result = Buffers.list.map do |b| + if ! prefix || prefix == :u || prefix == "all" || quoted + + result = self.list.map do |b| + name = $el.buffer_name(b) + next if prefix != :u && name =~ /^\*/ + next if name =~ /^ \*/ + + # Try showing links.notes etc > maybe implement below > move them to the end + next if !prefix && ["views/", "edited/"].member?(name) + + next if quoted && (name =~ /^\*/ || ["edited/", "ol", "links.xiki", "notes.xiki", "difflog.xiki"].member?(name)) # Skip the current buffer modified = $el.buffer_file_name(b) && $el.buffer_modified_p(b) ? "+" : " " - "|#{modified}#{$el.buffer_name(b)}\n" + + # Use ":" if modified or name has crazy chars + + bullet = (modified == "+" || name =~ /[^a-z0-9_ .-]/i) ? ":" : "-" + + txt = "#{bullet}#{modified}#{name}\n" + + # ~quoted, so go grab quote from buffer + if quoted + part = View.part(b, :context=>(prefix||2)) + txt << part.gsub(/^/, " ") + end + txt end.join('') + # Mabye move these to the bottom > views/ > notes.notes, links.notes + # "links.notes", "notes.notes", "difflog.notes" + + if quoted + return "- none found" if result == "" + end + return result + end + + case prefix # Only files (no buffers) when :u - return self.list.select{ |b| $el.buffer_file_name(b) }.map{ |b| "| #{$el.buffer_name(b)}\n" }.join('') + return self.list.select{ |b| $el.buffer_file_name(b) }.map{ |b| ": #{$el.buffer_name(b)}\n" }.join('') # Only buffer without files when 0; - return self.list.select{ |b| ! $el.buffer_file_name(b) }.map{ |b| "| #{$el.buffer_name(b)}\n" }[1..-1].join('') - - # Only files, already handled with :u - # when 1; return self.list.select{ |b| $el.buffer_file_name(b) }.map{ |b| $el.buffer_name(b) }[1..-1] + return self.list.select{ |b| ! $el.buffer_file_name(b) }.map{ |b| ": #{$el.buffer_name(b)}\n" }[1..-1].join('') when 3; return self.list.select{ |b| ! $el.buffer_file_name(b) && $el.buffer_name(b) =~ /^#/ }.map{ |b| $el.buffer_name(b) } when 4; return self.list.select{ |b| ! $el.buffer_file_name(b) && $el.buffer_name(b) =~ /^\*console / }.map{ |b| $el.buffer_name(b) } when 6; return self.list.select{ |b| $el.buffer_file_name(b) =~ /\.rb$/ }.map{ |b| $el.buffer_name(b) } - when 7; return self.list.select{ |b| $el.buffer_file_name(b) =~ /\.notes$/ }.map{ |b| $el.buffer_name(b) } + when 7; return self.list.select{ |b| $el.buffer_file_name(b) =~ /\.xiki$/ }.map{ |b| $el.buffer_name(b) } end return @@ -57,25 +91,38 @@ def self.current *name # /foo, so jump to or delete buffer... - name = name[0] - name.sub! /^\|./, '' + name = [": /"] if name == [": "] # Somehow it cuts off the slash when just "/" + name[0].sub! /^\:./, '' + + task = options[:task] + + # Right-clicked, so show options + return "* close" if task == [] # If as+delete, just delete buffer, and line - if prefix == "delete" - Buffers.delete name - View.flash "- deleted!", :times=>1 + if task == ["close"] + Buffers.delete name[0] + if name.length > 1 + Tree.to_parent + Tree.collapse + end Line.delete return end + return "<* not open" if ! View.buffer_open?(name[0]) # Switch to buffer View.to_after_bar if View.in_bar? - View.to_buffer(name) + View.to_buffer(name[0]) end - def self.names_array - self.list.map { |b| $el.buffer_name(b) }.to_a + def self.list_names options={} + list = self.list.map { |b| $el.buffer_name(b) }.to_a + + list = list.select{|o| o !~ /^ ?\*/} if options[:user_only] + + list end def self.list @@ -108,16 +155,16 @@ def self.search string, options={} # Show buffers too - wasn't as simple as just removing, because of filename indenting! next unless file - next if file =~ /_ol.notes/ + next if file =~ /_ol.xiki/ if options[:buffer].nil? # If we're not searching in one buffer - next if ["todo.notes", "files.notes"]. + next if ["notes.xiki", "links.xiki"]. member? file.sub(/.+\//, '') end # Skip if a verboten file unless options[:buffer] - next if file =~ /(\/difflog\.notes|\.log|\/\.emacs)$/ + next if file =~ /(\/difflog\.xiki|\.log|\/\.emacs)$/ end $el.set_buffer b @@ -127,11 +174,12 @@ def self.search string, options={} while(true) break unless $el.search_forward(string, nil, true) unless found_yet - found << "- @#{file.sub(/(.+)\//, "\\1\/\n - ")}\n" + found << "=#{file.sub(/(.+)\//, "\\1\/\n - ")}\n" found_yet = true end - found << " | #{Line.value}\n" + + found << " : #{Line.value}\n" Line.end end View.to started @@ -147,7 +195,6 @@ def self.search string, options={} end Tree << found - # $el.highlight_regexp string, :ls_quote_highlight end def self.from_string name @@ -168,6 +215,11 @@ def self.rename $el.rename_buffer Keys.input(options) end + # Buffers.file View.buffer + def self.file buffer + $el.buffer_file_name buffer + end + def self.name buffer $el.buffer_name(buffer) end diff --git a/lib/xiki/core/clipboard.rb b/lib/xiki/core/clipboard.rb index 9788c566..9454a5aa 100644 --- a/lib/xiki/core/clipboard.rb +++ b/lib/xiki/core/clipboard.rb @@ -9,35 +9,7 @@ class Clipboard @@hash ||= {} @@hash_by_first_letter ||= {} - def self.menu - %` - - .log/ - - docs/ - - main clipboard/ - | The "0" clipboard is where things are stored when you do these - | shortcuts: - - as+clipboard - - search+copy - - | (They're also put in the OS clipboard for convenience.) - - numbered clipboards/ - | You can type these shortcuts use the numbered clipboards - - as+1 - - as+2 - - enter+2 - - do+1 # Does search and replace from the "1" to "2" clipboard - - other clipboards/ - | You can type these shortcuts use other letters - - as+variable+a # Stores in "a" - - as+variable+b - - enter+variable+b - - search+like+variable+b - - see/ - <@ replace/ - ` - end - - def self.log key=nil + def self.names key=nil # /log/, so show keys... @@ -47,16 +19,16 @@ def self.log key=nil keys.each do |k| val = Tree.quote @@hash[k] val.gsub! /^/, ' ' - result << "| #{k}\n" + result << ": #{k}\n" end return result.empty? ? "- Nothing was copied yet!" : result end # /log/foo, so show value... - line = Line.value.sub(/^ *\| /, "") + line = Line.value.sub(/^ *: /, "") - Tree.quote @@hash[line] + Tree.quote @@hash[line], :char=>"|" end def self.copy loc=nil, txt=nil @@ -71,7 +43,7 @@ def self.copy loc=nil, txt=nil Effects.blink :left=>left, :right=>right txt = $el.buffer_substring($el.region_beginning, $el.region_end) end - self.set(loc, txt, Keys.prefix) + self.set loc, txt # , Keys.prefix) end def self.cut loc=nil @@ -113,10 +85,20 @@ def self.paste loc=nil end def self.get key='0', options={} - val = @@hash[key.to_s] + + # Default key, so grab from killring + + key = key.to_s + val = + if key == '0' + $el.current_kill(0) rescue nil + else + @@hash[key] + end if options[:add_linebreak] val = "#{val}\n" unless val[/\n$/] end + val end @@ -153,23 +135,22 @@ def self.list $el.beginning_of_buffer end - def self.set loc, str, append=nil + def self.set loc, txt #, append=nil loc = loc.to_s + + # If not standard clipboard, store in hash... + + if loc != "0" + return @@hash[loc] = txt + end + # Save in corresponding register (or append if prefix) - if append - @@hash[loc] += str - else - # Store as path - @@hash["/"] = $el.expand_file_name( $el.buffer_file_name ? $el.buffer_file_name : $el.elvar.default_directory ) - if $el.buffer_file_name - # Store as tree snippet - @@hash["="] = FileTree.snippet :txt=>str - @@hash["."] = "#{$el.file_name_nondirectory($el.buffer_file_name)}" - @@hash["\\"] = "#{$el.elvar.default_directory}\n #{$el.file_name_nondirectory($el.buffer_file_name)}" - end - @@hash[loc] = str - $el.x_select_text str if loc == "0" # If 0, store in OS clipboard + if $el.buffer_file_name + # Store as tree snippet + @@hash["="] = FileTree.snippet :txt=>txt end + $el.kill_new txt + $el.x_select_text txt if loc == "0" && Environment.gui_emacs # If 0, store in OS clipboard end def self.do_as_snake_case @@ -204,31 +185,52 @@ def self.do_as_lower_case end end + # Mapped to as+paragraph and as+rest + def self.copy_rest_of_line + left, right = View.cursor, Line.right + Clipboard[0] = View.txt(left, right) + Effects.blink :what=>[left, right] + end + def self.copy_paragraph options={} prefix = Keys.prefix if prefix == :u or options[:rest] # If U prefix, get rest of paragraph - left, right = View.paragraph(:bounds => true, :start_here => true) + left, right = View.paragraph(:bounds=>true, :start_here=>1) + + elsif prefix == :u or options[:before] # If U prefix, get rest of paragraph + right, left = View.paragraph(:bounds=>true, :end_here=>1) + + elsif prefix == :u or options[:rest_of_line] + left, right = View.cursor, Line.right else if prefix # If numeric prefix - self.as_line + Line.to_left + top = View.cursor + Line.next prefix + View.selection = top, View.cursor return end # If no prefix, get whole paragraph - left, right = View.paragraph(:bounds => true) + left, right = View.paragraph(:bounds=>true) end if options[:just_return] return [View.txt(left, right), left, right] end - $el.goto_char left - $el.set_mark right - Effects.blink(:left => left, :right => right) - Clipboard.copy("0") + + if options[:just_copy] + return Clipboard[0] = View.txt(left, right) + end + + View.selection = left, right + Effects.blink(:left=>left, :right=>right) end def self.copy_name - Clipboard.set("0", Files.stem) + path = Files.stem + Clipboard.set("0", path) + View.flash "- Copied: #{path}", :times=>3 end def self.diff_1_and_2 @@ -252,7 +254,7 @@ def self.as_thing orig = Location.new # If at end of space, grab as tree - if Line.indent.length == View.column + if Line.indent.length == View.column - 1 left = Line.left return end @@ -277,8 +279,6 @@ def self.as_thing txt = View.txt(left, right) Clipboard.set "0", txt View.to right - # View.mark left # What did this do? - Clipboard.save_by_first_letter txt orig.go @@ -291,9 +291,15 @@ def self.as_object end def self.copy_everything - Effects.blink :what=>:all + + # No prefix, so make selection of everything... + if ! Keys.prefix_u + return View.selection = [View.top, View.bottom] + end + + # Dash+, so don't select... + Clipboard.set("0", $el.buffer_string) - $el.set_mark($el.point_max) end def self.as_line many=nil @@ -301,7 +307,7 @@ def self.as_line many=nil # If Dash+, copy Foo.bar from quoted line if prefix == :- - txt = Ruby.quote_to_method_invocation + txt = Tree.construct_path View.flash "- copied #{txt}" return Clipboard.set("0", txt) end @@ -312,11 +318,14 @@ def self.as_line many=nil many ||= prefix || 1 left = Line.left right = Line.left(many+1) + line = View.txt(left, right) + Clipboard.set("0", line) Effects.blink :left=>left, :right=>right $el.set_mark(right) - Clipboard.save_by_first_letter line # Store for retrieval with enter_yank + + View.deselect end def self.enter_replacement @@ -342,13 +351,6 @@ def self.as_clipboard return end - if prefix == :- - l, r = View.range - Effects.blink :left=>l, :right=>r - Clipboard["0"] = View.selection.gsub(/^ *\|.?/, '') - return - end - Location.as_spot('clipboard') # If numeric prefix, get next n lines and put in clipboard @@ -362,13 +364,6 @@ def self.as_clipboard end Clipboard.copy("0") - Clipboard.save_by_first_letter View.selection # Store for retrieval with enter_yank - end - - def self.save_by_first_letter txt - key = txt[/[a-z]/i] - return unless key - @@hash_by_first_letter[key.downcase] = txt end def self.enter_yank @@ -378,5 +373,197 @@ def self.enter_yank View.insert value end + def self.whipe + $el.kill_region $el.mark, $el.point + end + + def self.kill + prefix = Keys.prefix # :clear=>1 + + # up+, so delete current line + prefix = 1 if prefix == :u + + if prefix == 0 + # Delete the blank line, if nothing here + return Line.delete if Line.blank? + return Line.delete(:leave_linebreak) + end + + if prefix # == :u + Line.to_left + end + + # up+, so just delete (don't save in clipboard) + + $el.kill_line prefix + end + + def self.yank + $el.yank + end + + def self.select + + # Text already selected, so just just jump to other side... + + $el.exchange_point_and_mark + + if $el.elvar.mark_active + return + end + + # up+, so just re-select what was selected last... + + if Keys.prefix_u + return $el.exchange_point_and_mark + end + + right = View.cursor + View.cursor = $el.mark + left = View.cursor + View.selection = [right, left] + return + + # Nothing selected yet, so just select + + end + + def self.init_in_client + + # Make C-c, C-x, and C-v use the os clipboard... + + from_os_code, to_os_code = nil, nil + + if Environment.xsh? && Environment.os == "osx" + from_os_code = '"pbpaste"' + to_os_code = '"copy_from_osx" "*Messages*" "pbcopy"' + end + + if Environment.xsh? && Environment.os == "linux" + txt, error = Shell.sync("xclip -o -selection clipboard", :return_error=>1) + # Only use xclip if it exists and it's not returning an error + if ! error + from_os_code = '"xclip -o -selection clipboard"' + to_os_code = '"xclip" "*Messages*" "xclip" "-selection" "clipboard"' + end + end + + return if ! from_os_code + + $el.el4r_lisp_eval %` + (progn + (defun copy-from-osx () + (shell-command-to-string #{from_os_code})) + (defun paste-to-osx (text &optional push) + (let ((process-connection-type nil)) + (let ((proc (start-process #{to_os_code}))) + (process-send-string proc text) + (process-send-eof proc))) + (when (el4r-running-p) + (let ((xiki-clipboard-txt text)) + (el4r-ruby-eval "Xiki::Clipboard.hash['='] = Xiki::FileTree.snippet :txt=>$el.elvar.xiki_clipboard_txt") + ) + ) + ) + (setq interprogram-cut-function 'paste-to-osx) + (setq interprogram-paste-function 'copy-from-osx) + ) + ` + + end + + + # Clipboard.register "1" < Returns register 1 + # Clipboard.register "1", "foo" < Sets register 1 + def self.register key, value=nil + # Fix keys ("1" to 49, etc)... + + key = key[0].sum if key.is_a? String + + # Just key, so return the register... + + return $el.get_register key if ! value + + # key, value, so set register + + $el.set_register key, value + nil + + end + + + def self.cua_rectangle_advice + + left, right = View.range + + txt = View.delete left, right + + final_spaces = txt[/ +\z/] # Grab spaces on last line + txt.gsub!(/ +$/, '') # Remove all trailing + txt << final_spaces if final_spaces # Put last ones back + + View << txt + + end + + + def self.cua_paste_advice + + # Not .notes file, so do nothing + return if View.extension != "xiki" + + # Remember where pasted + left, right = View.range + + # Cursor not at end of line (which means we pasted in the middle of a line), so do nothing + return if right != Line.right + + line = View.line + + View.cursor = left # Move to beginning of paste + + # Text we're pasting doesn't have at least one linebreak, so do nothing + return View.cursor = right if line == View.line + + # No indent or "|..." before the cursor, so go back and do nothing + before_cursor = Line.before_cursor + indent = Line.indent before_cursor + + quoted_indent = before_cursor[/^ *[|:] /] + + after_bullet = before_cursor =~ /^ *[+-] / + + return View.cursor = right if indent == "" && ! quoted_indent && ! after_bullet + + # Pull out pasted, quote it (or just indent), and put it back + txt = View.delete left, right + + # Unquote if quoted, or just unindent + txt =~ /^ *[|:] / ? + Tree.unquote!(txt) : + txt.unindent! + + # txt_indent = Line.indent txt + quoted_indent ? + txt.gsub!(/^/, quoted_indent) : + txt.gsub!(/^/, indent) + + # Line is just a blank quote, so remove indenting or quote from 1st line + if before_cursor =~ /^ *([|:] )?$/ + txt.sub! /^ *([|:] )?/, "" + + else # Line already has stuff after the quote, so add linebreak + + View << "\n" + txt.sub!(/\n\z/, '') # And > remove last linebreak + end + + # "- foo" bullet, so indent one level lower + txt.gsub!(/^/, " ") if after_bullet + + View << txt + + end + end end diff --git a/lib/xiki/core/code.rb b/lib/xiki/core/code.rb index b093269c..f1dd19e5 100644 --- a/lib/xiki/core/code.rb +++ b/lib/xiki/core/code.rb @@ -21,7 +21,6 @@ def self.location_from_proc id end def self.bounds_of_thing left=nil, right=nil - return [left, right] if left.is_a?(Fixnum) return [Line.left, Line.right+1] if left == :line @@ -41,89 +40,105 @@ def self.to_comment end def self.comment left=nil, right=nil - prefix = Keys.prefix + line = Line.value + + # No prefix add no selection, so use the line + if ! prefix && ! View.selection? + left, right = Line.left, Line.right+1 + + # Line is blank, so insert a comment... + + return self.enter_insert_comment if line =~ /^ *$/ - if prefix == 0 # If 0 prefix, comment paragraph + elsif prefix == :u # If C-u prefix, add comment on end + return self.enter_insert_comment + elsif prefix == 0 # If 0 prefix, comment paragraph left, right = View.paragraph(:bounds => true) else if prefix == 2 - a_commented = Line =~ /^ *(#|\/\/)/ + a_commented = line =~ /^ *(#|\/\/)/ b_commented = Line.value(2) =~ /^ *(#|\/\/)/ if !!a_commented ^ !!b_commented Keys.clear_prefix orig = Location.new - Code.comment Line.left(1), Line.left(2) # Toggle commenting of this line - Code.comment Line.left(2), Line.left(3) # Toggle commenting of next line + self.comment Line.left(1), Line.left(2) # Toggle commenting of this line + self.comment Line.left(2), Line.left(3) # Toggle commenting of next line orig.go return end end Line.to_left + left ||= {:default=>:region} - left, right = Code.bounds_of_thing(left, right) + left, right = self.bounds_of_thing(left, right) left, right = right, left if View.cursor == right # In case cursor is at right side end + # Nothing was selected, so select the line... + View.to left View.set_mark right + # Maybe implement this > In a .notes file, so just comment with "|" at beginning of line + $el.comment_or_uncomment_region View.range_left, View.range_right - Code.indent View.range_left, View.range_right + + # Disabled indenting > see how this feels + self.indent View.range_left, View.range_right + end # Evaluates file, paragraph, or next x lines using el4r def self.run options={} + prefix = Keys.prefix Ol.clear_pause - if prefix == :uu - path = Tree.construct_path - - load path - return View.flash "- loaded!" - end - if options[:left] left, right = options[:left], options[:right] txt = View.txt left, right elsif prefix.is_a?(Fixnum) && 0 <= prefix && prefix <= 7 - file, line = View.file, Line.number(left) txt, left, right = View.txt_per_prefix nil, :blink=>1, :remove_heading=>1 + file, line = View.file, Line.number(left) else + + prefix ||= :u if View.extension == "rb" # In .rb file, so pretend like prefix was :u, to load file + case prefix - when :u # Load file in emacsruby + when :u # Load file return self.load_this_file # These were superceded by .txt_per_prefix apparently + when :uu # Load file in Xiki shell command instance + `xiki "! load '#{View.file}'"` + return View.flash "- loaded in Xiki shell command instance!" + + when :- # Load file at point in tree + path = Tree.construct_path + load path + return View.flash "- loaded!" + when 8 # Put into file and run in console - File.open("/tmp/tmp.rb", "w") { |f| f << Notes.current_section("^>").text } - return Console.run "ruby -I. /tmp/tmp.rb", :dir=>View.dir + File.open("/tmp/tmp.rb", "w") { |f| f << Notes.current_section_object("^>").text } + return Shell.run "ruby -I. /tmp/tmp.rb", :dir=>View.dir when 9 # Pass whole file as ruby - return Console.run("ruby #{View.file_name}", :buffer => "*console ruby") + return Shell.run("ruby #{View.file_name}", :buffer => "*console ruby") else # Move this into ruby - block.rb? ignore, left, right = View.block_positions "^>" file, line = View.file, Line.number(left) end txt = View.txt(:left=>left, :right=>right).to_s - Effects.blink :left => left, :right => right + Effects.blink :left=>left, :right=>right end txt.sub! /\A( *)@ /, "\\1" # Remove @ if at beginning - txt.gsub! /^ *\| ?/, '' if txt =~ /\A *\|/ # Remove quoted lines if it's quoted - - # If C--, define the launcher - if prefix == :- - if txt =~ /\A\s*class (\w+)/ - clazz = $1 - Launcher.add TextUtil.snake_case(clazz) - end - end + txt.gsub! /^ *[|:] ?/, '' if txt =~ /\A *[|:]/ # Remove quoted lines if it's quoted orig = Location.new $el.goto_char right @@ -152,7 +167,7 @@ def self.run options={} end if exception - backtrace = exception.backtrace[0..8].join("\n").gsub(/^/, ' @') + "\n" + backtrace = exception.backtrace[0..8].join("\n").gsub(/^/, ' = ') + "\n" error = CodeTree.format_exception_message_for_tree exception.message View.insert ">>\n" View.insert "- error:#{error}\n- backtrace:\n#{backtrace}".gsub(/^/, ' ') @@ -218,36 +233,118 @@ def self.simple_eval code, file=nil, line=nil, options={} end + # xiki api > Eval ruby code + # Evaluates a string, and returns the output and the stdout string generated - def self.eval code, file=nil, line=nil, options={} + # Code.eval "p 11; 22", nil, nil, :simple=>1 # Returns 1 string, which is quoted return value or output, or unquoted formatted exception. + # + # params: + # | options : The last options param will be available to the code being eval'ed + def self.eval code, file=nil, line=nil, eval_options={}, options={} + + if file.is_a? Hash + eval_options = file + file = nil + end + return ['- Warning: nil passed to Code.eval!', nil, nil] if code.nil? # Capture stdout output (saving old stream) orig_stdout = $stdout; $stdout = StringIO.new stdout = nil exception = nil - begin # Run code + returned = nil - # TODO: Try always doing what :global does - # - see if it breaks + # Run the code... - returned = - if code.is_a? Proc - Object.module_eval &code - elsif options[:global] || ! $el - Object.module_eval code, file||__FILE__, line||__LINE__ - else - Object.module_eval code, file||__FILE__, line||__LINE__ - # $el.instance_eval code, file||__FILE__, line||__LINE__ + begin + if dir = eval_options[:dir] + Dir.chdir(dir) do + returned = self.eval_inner code, file, line, eval_options, options end - + else + returned = self.eval_inner code, file, line, eval_options, options + end rescue Exception => e exception = e end + stdout = $stdout.string; $stdout = orig_stdout # Restore stdout output + + # Is this even worth doing? + if exception && (eval_options[:pretty_exception] || eval_options[:simple]) + exception = CodeTree.draw_exception exception, code if exception + end + + # This change might be risky + stdout = nil if stdout == "" + + if eval_options[:simple] # Return one string (quoted if result, or just exception) + return exception if exception + + txt = stdout || returned + return nil if ! txt + txt = txt.to_s + txt = Tree.quote txt if eval_options[:quoted] + return txt + end + [returned, stdout, exception] end + def self.eval_inner code, filename, line, eval_options, options + target = eval_options[:target_module] || Object + + # These variables will be > passend to code > accessible by the evaled code + args, path, dir, task, items = options[:args_relative]||options[:args]||[], options[:path_relative]||options[:path], options[:dir], options[:task], options[:items] + shell_command, shell_output = options[:shell_command], options[:shell_output] + file = options[:file] + + arg1, arg2, arg3 = (args||[])[0..2] + + if eval_options[:binding] + return eval_options[:binding].eval(code, filename||__FILE__, line||__LINE__) + end + + if code.is_a? Proc + target.module_eval &code + else + # use class_eval if we change Xiki to a class instead of a module + # target.class_eval code, filename||__FILE__, line||__LINE__ + target.module_eval code, filename||__FILE__, line||__LINE__ + end + end + + def self.eval_snippet txt, file, line_number, eval_options, options + + language = txt[/^!(.)/, 1] + language = {"."=>"ruby", " "=>"javascript"}[language] + + txt.gsub!(/^!.?/, "") #> ["red"] + + # Make sure only one line break + txt = "#{txt.sub(/\n+\z/, '')}\n" + + options[:args] = options[:items] + + # ".", so evaluate as javascript... + + if language == "javascript" + # Add return if there was none + txt.sub!(/.+\n\z/, "return \\0") if txt !~ /^ *return .+\n\z/ + + txt = "print = p = console.log;\n#{txt}" + return JavascriptHandler.eval txt + end + + # " ", so evaluate as ruby... + + txt = Code.eval txt, file, line_number, {:pretty_exception=>1, :simple=>1}, options #> |||||| + txt + + end + + def self.do_as_align $el.align_regexp end @@ -410,27 +507,22 @@ def self.do_as_rspec options={} View.to_buffer buffer else # Otherwise open it and run console xiki ? - Console.run("", :dir=>dir, :buffer=>buffer) : - Console.run("bundle exec merb -i -e test", :dir=>dir, :buffer=>buffer) - # Console.run("merb -i", :dir=>dir, :buffer=>buffer) - # Console.run "merb -i -e test", :dir=>dir, :buffer=>buffer + Shell.run("", :dir=>dir, :buffer=>buffer) : + Shell.run("bundle exec merb -i -e test", :dir=>dir, :buffer=>buffer) end View.clear View.wrap(:on) if prefix == :u - # args << '-D' # Show diffs - if xiki command = "#{extra}spec #{args.join(' ')}" else args = args.map{|o| o =~ /^"/ ? o : "\"#{o}\"" }.join(",\n") # Only add quotes if not already there command = "Spec::Runner::CommandLine.run(Spec::Runner::OptionParser.parse([#{args}], $stderr, $stdout))" # Rails version (commented out - it's currently hard-coded to use merb) - # command = "#{extra}p :reload; reload!; #{command}" end View.insert command - Console.enter + Shell.enter View.to 1 orig.go unless orig.nil? || View.index == orig_index # Go back unless in same view @@ -459,12 +551,21 @@ def self.do_related_rspec end def self.load_this_file - Effects.blink :what=>:all + + Effects.blink :what=>:all, :time=>0.08 begin - load View.file + + result = load View.file + if e = result[2] + View.open :txt=>"" + View.<<(CodeTree.draw_exception(e), :dont_move=>1) + Search.forward "^ " + end + rescue Exception=>e Tree.<< "- Error:\n#{e.message.gsub /^/, ' '}!", :no_slash=>1 end + # Give it enough time to flash end def self.do_code_align @@ -479,19 +580,30 @@ def self.do_code_align # - up+do+indent # Indent to the left (by 2 spaces) # - 3+do+indent # Make indent be 6 spaces from the left (3*2) # - def self.indent_to + def self.indent_to options={} + prefix = options[:prefix] || Keys.prefix + if prefix == :- # Just indent to where it should go + Code.indent + View.no_deselect + return + end + prefix = :u if options[:left] - prefix = Keys.prefix - return Code.indent if prefix == :- # Just indent to where it should go + txt = View.selection :always=>1 + return View.flash "- Select some lines first" if ! txt - txt = View.selection - old_indent = txt[/^( *)[^ \n]/, 1] + # Find lowest indent + old_indent = txt.split("\n").reduce(999) do |acc, line| + next acc if line.blank? + indent = line[/^ */].length + indent < acc ? indent : acc + end new_indent = if ! prefix - old_indent.length + 2 + old_indent + 2 elsif prefix == :u - old_indent.length - 2 + old_indent - 2 elsif prefix.is_a?(Fixnum) prefix * 2 else @@ -505,16 +617,14 @@ def self.indent_to # Grab indent if 1st line that has text txt.gsub!(/^\s+/) { |t| t.gsub("\t", ' ') } # Untab indent - txt.gsub! /^#{old_indent}/, ' ' * new_indent + txt.gsub! /^#{' ' * old_indent}/, ' ' * new_indent txt.gsub!(/^ +$/, '') # Kill trailing spaces on lines with just spaces - View.insert txt - - if orig.line != Line.number # If we're at the end - View.set_mark - orig.go - end + left = View.cursor + right = left + txt.length + View.insert txt, :dont_move=>1 + View.selection = left, right end @@ -526,26 +636,8 @@ def self.enter_as_backslash $el.insert txt end - def self.enter_as_debug - - orig = View.range[0] - txt = View.selection :delete=>true - count = 0 - txt.gsub!(/^.+/) { |m| - if m =~ /^\s+(end|else|elsif|\})/ - m - else - count += 1; - (count & 1 == 0) ? " ol #{count}\n#{m}" : m - end - } - - View.insert txt - View.to orig - end - def self.kill_duplicates - txt = View.selection :delete=>true + txt = View.selection :delete=>true, :always=>1 l = txt.split("\n") orig = Location.new View.insert l.uniq.join("\n") + "\n" @@ -555,11 +647,14 @@ def self.kill_duplicates def self.randomize_lines txt=nil txt ||= View.selection :delete=>true + + return View.flash "- Select some lines first" if ! txt + l = txt.split("\n") - orig = Location.new + orig = View.cursor View.insert l.sort_by{ rand }.sort_by{ rand }.join("\n") + "\n" - View.set_mark - orig.go + + View.selection = orig, View.cursor end def self.do_next_paragraph @@ -596,14 +691,14 @@ def self.enter_log_thing txt def self.open_log_view options={} - prefix = Keys.prefix :clear=>true + prefix = options[:prefix] || Keys.prefix(:clear=>true) prefix = nil if options[:called_by_launch] orig = View.current if prefix == :u # up+layout+output means to go back file = Ol.file_path - buffer = "*ol" + buffer = "ol" # If already visible, just go to it if View.buffer_visible?(buffer) @@ -616,20 +711,7 @@ def self.open_log_view options={} return end - # If 2 or more windows open - if View.list.size == 2 - View.to_nth(1) # Go to 2rd - elsif View.list.size >= 3 - View.to_nth(2) - unless View.left_edge == 0 # If 3nd not at left, go to 2nd - View.to_nth(1) - unless View.left_edge == 0 # If not at left, go to first - View.to_nth(0) - end - View.create - end - end - + # If not in bar, open the bar # If buffer open (but not visible), just switch to it if View.buffer_open? buffer @@ -645,19 +727,19 @@ def self.open_log_view options={} lines = "#{file}.lines" `touch #{lines}` unless File.exists?(lines) - Console.run "tail #{prefix == :- ? '-n 100' : ''} -f #{file}", :buffer=>buffer, :dir=>'/tmp', :dont_leave_bar=>true + Shell.run "tail -n 100 -f #{file}", :buffer=>buffer, :dir=>'/tmp', :dont_leave_bar=>true Notes.mode return if self.clear_and_go_back orig end - def self.enter_log_line + def self.enter_log_line options={} return Firefox.enter_log_javascript_line if View.extension == "js" Move.to_axis if ! Line.at_right? - $el.open_line(1) unless Line.value.strip.blank? + View.<<("\n", :dont_move=>1) if Line !~ /^[ |:!]*$/ # Javascript if Tree.construct_path(:all=>1, :slashes=>1) =~ /" + return + end + + expand_options[:original_name] = TextUtil.camel_case options[:name] + sample_menus = "web sample menus" + else + sample_menus = "sample menus" + end + + # We might need to pass more options in here... - :client, for example? + txt = Expander.expand sample_menus, options[:items], expand_options # Will handle if no items or a sample menu item + + if txt + if txt =~ //i + txt = "<< ip/" + end + + return options[:output] = txt + end + + # User had items that weren't in @sample + + # Non-existant items were created, so suggest making a new menu out of them... + if options[:client] =~ /^editor\b/ + options[:no_slash] = 1 + + # Any item or ~ on a non-existant command, so show tasks... + + return options[:output] = ": Route '#{name}' not found." # if task == [] + + end + end + + end +end diff --git a/lib/xiki/core/console.rb b/lib/xiki/core/console.rb deleted file mode 100644 index a3736ca3..00000000 --- a/lib/xiki/core/console.rb +++ /dev/null @@ -1,602 +0,0 @@ -require 'open3' - -module Xiki - class Console - - @@log = File.expand_path("~/.emacs.d/console_log.notes") - - def self.menu - # All of this is deprecated, I think? - %` - - .log/ - - .tree/ - - .history/ - - api/ - > In console (asynchronously) - @Console.run "ls" - @Console.run "ls", :dir=>"/tmp" - - > Inline (synchronously) - @Console.sync "ls" - @Console.sync "ls", :dir=>"/etc" - - docs/ - You can run shell commands by typing things like this... - - > In current dir - @ $ ls - - > In other dir - @ /tmp/ - $ ls - - > Async, in any open console view - @ /tmp/ - % ls - - > Async, in other dir - @ /tmp/ - % ls - - > Async, in iTerm - @ /tmp/ - & ls - - > Commands you've run recently - << log/ - - > Commands from currently open consoles - << tree/ - ` - end - - def self.log - View.open @@log - end - - # Run the command in a console - def self.[] command - self.run command, :sync=>1 - end - - # Delegates to .run :sync=>1 - # Console.sync "pwd" - # Console.sync "pwd", :clear=>1 # Clean up stupid ^H sequences - def self.sync command, options={} - txt = self.run command, options.merge(:sync=>1) - txt.gsub!(/.\cH/, "") if options[:clean] - txt - end - - # - # Runs shell command asynchronously. - # - # Console.run "ls" - # Console.run "ls", :dir=>"/tmp/" - # - def self.run command, options={} - - dir = options[:dir] - sync = options[:sync] - buffer = options[:buffer] - reuse_buffer = options[:reuse_buffer] - - # Nil out dir if blank - dir = nil if dir && dir.length == 0 - - if dir - dir = Bookmarks.expand(dir) - # If relative dir, make current dir be on end of current - dir = "#{$el.elvar.default_directory}/#{dir}" unless dir =~ /^\// - dir = dir.gsub(/\/\/+/, '/') - - # If file, but not dir, try backing up to the dir - raise "- Directory '#{dir}' doesn't exist!" if ! File.exists? dir - - dir.sub!(/[^\/]+$/, '') if ! File.directory?(dir) - - # If dir exists, continue - if File.directory?(dir) - # Put slash on end if not there - dir = "#{dir}/" unless dir =~ /\/$/ - else # Otherwise, exit - return puts("#{dir} is not a dir") - end - else - dir = $el ? $el.elvar.default_directory : "/tmp/" - end - - if sync - - return command if options[:no_enter] - profile = File.exists?(File.expand_path('~/.profile')) ? '. ~/.profile;' : '' - stdin, stdout, stderr = Open3.popen3("#{profile}cd \"#{dir}\";#{command}") - - if txt = options[:stdin] - stdin.puts txt - stdin.close - end - - result = "" - result << stdout.readlines.join('') - result << stderr.readlines.join('') - - result.force_encoding("binary") if result.respond_to? :force_encoding - result.gsub!("\c@", '.') # Replace out characters that el4r can't handle - return result - - else - if View.in_bar? and ! options[:dont_leave_bar] - View.to_after_bar - end - buffer ||= "*console #{dir}" - - if ! reuse_buffer - buffer = $el.generate_new_buffer(buffer) - end - View.to_buffer buffer - $el.erase_buffer if reuse_buffer - $el.elvar.default_directory = dir if dir - $el.shell $el.current_buffer - - # Don't prompt with "buffer has a running process" when closing - $el.set_process_query_on_exit_flag $el.get_buffer_process($el.current_buffer), nil - - Move.bottom - if command # If nil, just open console - $el.insert command - Console.enter unless options[:no_enter] - end - end - - nil - end - - def self.open dir=nil - View.handle_bar - dir ||= $el.elvar.default_directory - dir = File.expand_path(dir)+"/" - View.to_buffer $el.generate_new_buffer("shell") - raise "dir '#{dir}' doesn't exist" unless File.directory?(dir) - $el.elvar.default_directory = dir - $el.shell $el.current_buffer - end - - def self.enter command=nil - View.insert command if command - - begin - $el.comint_send_input - rescue - # Ol << "Console.enter error here!" - end - end - - def self.console? - View.mode == :shell_mode - end - - def self.to_shell_buffer dir=nil, options={} - if dir - dir = "#{dir}/" unless dir =~ /\/$/ - pattern = /^\*console #{Regexp.quote(dir)}(<| |$)/ - else - # If already in a shell (regardless of buffer name) - return true if View.mode == :shell_mode - pattern = /^\*console/ - end - - return true if View.name =~ pattern # If already there, do nothing - - if ! dir - - # Try to find visible shell buffer in same dir and with prompt - - View.list.each do |w| - $el.set_buffer $el.window_buffer(w) - next if View.mode != :shell_mode || ! Console.prompt? - next if View.cursor != View.bottom - View.to_window(w) - return true - end - end - - if dir && dir !~ /@/ # If local dir - - # TODO Make sure there's no ssh or cd in history! - View.list.each do |w| - $el.set_buffer $el.window_buffer(w) - next if View.mode != :shell_mode || ! Console.prompt? - next if Tree.slashless(dir) != Tree.slashless(View.dir) - next if View.cursor != View.bottom - - View.to_window(w) - return true - end - - # TODO: implement similar finding a dir for remote - # else - - end - - # Deprecated: - # Try to find visible shell buffer with matching name - View.list.each do |w| - next if $el.buffer_name($el.window_buffer w) !~ pattern - View.to_window(w) - return true - end - - if Keys.prefix_u(:clear=>true) - - found = Buffers.list.find do |b| - name = Buffers.name b - next false unless name =~ pattern - - view = nil - $el.with(:save_window_excursion) do - View.to_buffer name - - next false unless Console.prompt? - - cd_dir = View.dir - cd_dir = "#{cd_dir}/" unless cd_dir =~ /\/$/ - next false unless cd_dir == dir - next false if Console.commands.join("\n") =~ /^(ssh|ftp) / - true - end - end - - if found - View.to_upper - return View.to_buffer(found) - end - end - - # Wasn't found among visible, so create new buffer - - return false if options[:no_create] # Don't create it if option says not to - - if dir =~ /@/ # If there's a @, it's remote - View.handle_bar - View.to_buffer $el.generate_new_buffer("*console #{dir}") - $el.elvar.default_directory = "/tmp" - $el.shell $el.current_buffer - if dir =~ /(.+?)(\/.+)/ # Split off dir if there - line = self.ssh_line($1) - Console.enter line - options[:cd_and_wait] ? - View.insert("cd #{$2} && ") : - Console.enter("cd #{$2}") - else - line = self.ssh_line(dir) - Console.enter line - end - else - Console.open dir - end - return true - end - - def self.do_last_command - # Code.open_log_view if Keys.prefix_u - - orig = View.index - - found = self.to_shell_buffer(nil, :no_create=>true) # If not in shell buffer, go to it - - return View.message("No *console buffer was visible") unless found - - $el.erase_buffer - $el.comint_previous_input(1) - self.enter - View.to_nth orig - end - - def self.launch_async options={} - - orig = Location.new - orig_view = View.index - path = Tree.construct_path(:list=>true) - - path[0] = Bookmarks[path[0]] if path[0] =~ /^(\.\/|\$[\w-])/ # Expand out bookmark or ./, if there - if path[0] =~ /^\// # If has dir (local or remote) - line = path.join('') - dir, command = line.match(/(.+?)[%&] (.*)/)[1..2] - self.append_log "#{command}", dir, '% ' - - return Iterm.run("cd #{dir}\n#{command}", :activate=>1) if Keys.prefix_uu - return Iterm.run("cd #{dir}\n#{command}") if Keys.prefix_u || options[:iterm] - - Console.to_shell_buffer dir, :cd_and_wait=>true - else # Otherwise, if by itself - meaning on own line? - command = Line.without_label.match(/.*?[%&] ?(.*)/)[1] - self.append_log("#{command}", dir, '% ') if command.present? - - return Iterm.run("#{command}", :activate=>1) if Keys.prefix_uu - return Iterm.run("#{command}", :activate=>1) if Keys.prefix_u && options[:iterm] - return Iterm.run("#{command}") if Keys.prefix_u || options[:iterm] - - self.to_shell_buffer # Go to shell if one is visible - end - return if command.empty? - - View.to_bottom - - View.insert command - Console.enter - - orig.go unless orig_view == View.index - end - - # Synchronous - mapped to $ launcher - def self.launch options={} - trunk = Xiki.trunk # use Tree.path instead - - # If it looks like error output, just jump to it - if trunk[-1] =~ /[^\/+]\/\| \s+from / || trunk[-1] =~ /[^\/+]\/\| +\// - return Code.open_as_file - end - - # if not under file - # raise RelinquishException.new - - # Run in current dir if no parent or @$ - if trunk[-1] =~ /^\$ / - # TODO Run in current dir? - # return - end - - # There's a dir in our chunk, so relinquish control if not fire tree - - # Handle if - # no parent in our chunk - # parent in our chunk is file tree - - line = Line.without_label :leave_indent=>true - # If indented, check whether file tree, extracting if yes - if line =~ /^\s+\$/ - orig = View.cursor - path = Tree.construct_path(:list=>true) - if path[0] =~ /@/ # If there's a @, it's remote - self.append_log path[1], path[0] - return Remote.command path - end - if FileTree.handles?(path) - while(path.last =~ /^\$ /) do # Remove all $ foo lines from path - path.pop - end - dir = path.join('') - - # If starts with ./, replace with current dir - dir.sub! /^\.\//, "#{View.dir}/" - - dir = Bookmarks[dir] - - end - View.to orig - end - line =~ / *@? ?(.*?)\$+ ?(.+)/ - dir ||= $1 unless $1.empty? - command = $2 - - return Tree.<<("- Directory doesn't exist) #{dir}", :no_slash=>1) if dir && ! File.exists?(dir) - - # Run command... - - if options[:sync] - output = Console.run command, :dir=>dir, :sync=>true - output.sub!(/\A\z/, "\n") # Add linebreak if blank - - # Clean up ^H formatting - output.gsub!(/.\cH/, "") # Add linebreak if blank - - Keys.prefix == 0 ? output.gsub!(/^/, '|') : output.gsub!(/^/, '| ') - - output.gsub!(/^\| +$/, '|') - - output.sub! /\n*\z/, "\n" # Guarantee exactly 1 linebreak at end - Tree.indent(output) - - Tree.insert_quoted_and_search output - else - View.handle_bar - Console.run command, :dir=>dir #, :buffer=>"*console #{dir}" - end - - self.append_log command, dir, '$ ' - - end - - def self.append_log command, dir, prefix='' - return if View.name =~ /_log.notes$/ - if dir.nil? - dir ||= View.dir - dir = "#{dir}/" if dir !~ /\/$/ - end - - command = command.dup - command.gsub!(/^/, prefix) unless command =~ /^ *!/ - command.gsub!(/^/, ' ') - - txt = "- #{dir}\n#{command}\n" - File.open(@@log, "a") { |f| f << txt } rescue nil - end - - def self.ssh_line path - path = path.sub /^\//, '' - path.sub! /\/$/, '' - - if path =~ /(.+):(.+)/ # If port exists (colon) - "ssh -p #{$2} #{$1}" - # Pull out and pass with -p - else - "ssh -A #{path}" - end - end - - def self.do_as_execute options={} - - if FileTree.handles? && ! Line.matches(/^\s*\|/) # If we're in a file tree - path = Tree.construct_path - - # # Run command inside of dir - # if Line.matches(/\/$/) # If a dir - # command = Keys.input :prompt=>"Do shell command on '#{file}': " - # output = Console.run(command, :dir=>path, :sync=>true) - # FileTree.insert_under(output) if options[:insert] - # return View.message "Command ran with output: #{output.strip}." - # elsif Keys.prefix_n - # View.message "Running command on multiple files isn't implemented yet." - # return - # end - - file = Line.without_label - command = Keys.input :prompt=>"Shell command on this file (_ means the filename): " - command = command =~ /\b_\b/ ? command.gsub(/\b_\b/, "\"#{file}\"") : "#{command} \"#{file}\"" - - output = Console.run(command, :dir=>File.dirname(path), :sync=>true) - Tree.under(output, :escape=>'| ') if options[:insert] - - return View.message "Command ran with output: #{output.strip}." - end - - command = Keys.input :prompt=>"Do shell command on '#{View.file_name}': " - command = "#{command} #{View.file_name}" - output = Console.run(command, :dir=>View.dir, :sync=>true) - View.insert(output) if options[:insert] - - return View.message "Command ran with output: #{output.strip}." - - end - - # Whether buffer ends with shell prompt "...$ " - def self.prompt? - right = View.bottom - left = right - 10 - left = 1 if left < 1 - txt = View.txt left, right - - txt =~ /[>#%$] \z/ - end - - def self.history bm - - dir = Bookmarks[bm] - dir = Files.dir_of dir - - console_log = IO.read(@@log) - - result = [] - match = false - console_log.split("\n").each do |l| - if l =~ /^[+-] / - next match = l =~ /\A- #{Regexp.escape dir}/ - end - - result << "#{l}" if match - end - - "@#{dir}\n"+result.reverse.uniq.join("\n")+"\n" - - end - - def self.commands - matches = $el.elvar.comint_input_ring.to_s.scan(/#\("(.+?)" /).flatten - - matches.map!{|o| o.gsub '\\"', '"'} - matches - end - - def self.custom_history - dir = View.dir - history = Console.commands - history.uniq! unless Keys.prefix_u - history = history.join("\n").gsub(/^/, '% ') - View.create :u if ! View.list_names.member?("*shell history") - View.to_buffer "*shell history" - View.kill_all - Notes.mode - - View.insert "#{history}\n" - View.to_highest - Tree.search - end - - def self.search_last_commands - bm = Keys.input(:timed => true, :prompt => "bookmark to show commands for (space for currently open): ") - return Launcher.open("console/tree/") if bm == " " - if bm == "8" - Console.log; View.to_bottom; Search.isearch nil, :reverse=>true - return - end - - Launcher.open("console/history/$#{bm}/") - end - - def self.tree *args - command = args.pop if args[-1] =~ /^\|/ - console = args.any? ? args.join("/") : nil - - if console - View.to_buffer console#.sub /\/$/, '' - return - end - - txt = "" - - $el.with(:save_excursion) do - - Buffers.list.each do |b| - next if $el.buffer_file_name b - name = $el.buffer_name b - $el.set_buffer b - next if $el.elvar.major_mode.to_s != 'shell-mode' - - next if name == "*ol" - - txt << "- #{name}/\n" - self.commands.reverse.each do |h| - txt << " | $ #{h}\n" - end - end - end - - txt - end - - def self.exit # Kills running server or process in shell - $el.comint_interrupt_subjob - end - - def self.wait_until buffer, options={} - max = options[:max] || 10 - message = options[:message] || "Launching..." - while View.txt(:buffer=>buffer) !~ options[:contains] - View.flash message, :times=>1 - max -= 1 - break if max < 0 - end - end - - def self.shell_command_per_prompt prompt, options - dir, command = options[:dir], options[:command] - - self.append_log command, dir, "#{prompt} " - - case prompt - when "$" - txt = Tree.quote Console.sync command, :dir=>dir - txt.gsub!(/^\| /, "|") if options[:prefix] == 0 - txt.gsub!(/.\cH/, "") # Add linebreak if blank - return txt - when "%" - return Console.run command, :dir=>dir - when "&" - return Iterm.run("cd #{dir}\n#{command}") - end - end - - end - - Keys.custom_history(:shell_mode_map) { Console.custom_history } -end diff --git a/lib/xiki/core/control_lock.rb b/lib/xiki/core/control_lock.rb index 2e7831a8..559cb813 100644 --- a/lib/xiki/core/control_lock.rb +++ b/lib/xiki/core/control_lock.rb @@ -1,9 +1,18 @@ module Xiki - # Simple wrapper around control_lock.el to turne + # Simple wrapper around control_lock.el to turn # it on and off (and handle the case where it's not) # installed. class ControlLock + def self.toggle + # if in noob mode, do nothing + return if Keys.noob_mode + + # in the future use green box and "xsh > Ctrl+U enabled" to let them enable it + + $el.control_lock_enable + end + def self.disable $el.control_lock_enable if self.enabled? end diff --git a/lib/xiki/core/control_tab.rb b/lib/xiki/core/control_tab.rb index fa0b5570..3ab7f1b4 100644 --- a/lib/xiki/core/control_tab.rb +++ b/lib/xiki/core/control_tab.rb @@ -7,67 +7,109 @@ class ControlTab @@edited = nil @@switch_index = 0 - @@dash_prefix_buffer = nil + @@dash_prefix_buffer, @@dash_prefix, @@ol_prefix, @@color_prefix, @@difflog_prefix = nil, nil, nil, nil, nil + @@open_windows = nil + @@original = [] + + @@clear_once = nil + @@last_escape_was_something_else = nil + + def self.last_escape_was_something_else= val + @@last_escape_was_something_else = val + end + + def self.clear_once + @@clear_once = 1 + end # Primary method. Is mapped to C-tab and does the switching. - def self.go + def self.go options={} + + # Selection exists, so just deselect + return View.deselect if View.selection? + + # In minibuffer, so just escape out + + return $el.keyboard_escape_quit if View.name =~ /^ \*Minibuf/ + + # Why was I doing this when control tab? + + # They pressed escape to get here (what are the other ways to get here?), so quit if only 1 view open... + + if options[:from_escape] + views_open = Buffers.list.map { |o| name = $el.buffer_name(o) }.select { |o| o !~ /^ ?\*/ && o != "views/" } + + # No other view open (aside from *... and views/), so just quit + return if views_open.length == 1 + + end + + Keys.remember_key_for_repeat(proc {ControlTab.go :subsequent=>1}, :movement=>1) prefix = Keys.prefix :clear=>1 + recent_few = Keys.recent_few + + first_tab_in_sequence = true + + # It'll be the first tab, if the keys before the last one weren't C-\, \ or double escape + + last_key_was_escape = recent_few[1] == 27 + + last_key_was_escape = nil if @@last_escape_was_something_else + @@last_escape_was_something_else = nil # Reset, since no longer relevant + + # Trying out > just single escape + first_tab_in_sequence = false if last_key_was_escape # One before last was / or C-/ + + first_tab_in_sequence = nil if options[:subsequent] + + if @@clear_once # If .clear_once was called recently + first_tab_in_sequence = true + @@clear_once = nil + end - first_tab_in_sequence = Keys.before_last !~ /\btab$/ # If first tab, clear edited @@edited = @@dash_prefix = @@ol_prefix = @@color_prefix = @@difflog_prefix = nil if first_tab_in_sequence - if prefix == :- || @@dash_prefix # Go to next quote in $f + + if prefix == :- || @@dash_prefix # Go to next quote in :n if @@dash_prefix_buffer - View.to_buffer @@dash_prefix_buffer # => "files.notes" + View.to_buffer @@dash_prefix_buffer # => "nav.notes" else - View.layout_files :no_blink=>1 + View.layout_nav :no_blink=>1 end - found = Move.to_quote :pipes=>1 + found = Move.to_quote if ! found View.to_highest # to beginning of file - Move.to_quote :pipes=>1 + Move.to_quote end Effects.blink @@dash_prefix = true - options = {:no_recenter=>1} # Go to other view (leave index visible) if no bar or not in bar options[:other_view] = 1 if !View.bar? || !View.is_at_left - - FileTree.launch options + Launcher.launch return end # If C-1, C-7, C-8, C-9, step through in special ways... - return self.go_in_outlog(prefix) if [9, 99, 1, 11].member?(prefix) || @@ol_prefix + return self.go_in_outlog(prefix) if [9, 99, 1, 11, 111].member?(prefix) || @@ol_prefix return self.go_in_color(prefix) if [8, 88].member?(prefix) || @@color_prefix return self.go_in_difflog(prefix) if [7, 77].member?(prefix) || @@difflog_prefix - - # if prefix == 9 # Just burry buffer - # $el.bury_buffer - # # Store original order, and windows originally opened - # @@original = buffer_list.to_a # Hide evidence that we were on top (lest it restore us) - # @@open_windows = window_list.collect {|b| window_buffer b} - # @@consider_test = lambda{|b| ! buffer_name(b)[/Minibuf/] } - # return - # end - - - # If C-u, toggle through $f... + # If C-u, toggle through :n... if prefix == :u # If U prefix (must be first alt-tab in sequence) # Go to last edited file, and store list - @@edited = $el.elvar.editedhistory_history.to_a + # @@edited = $el.elvar.editedhistory_history.to_a + @@edited = DiffLog.file_list @@edited -= View.files :visible=>1 # Exclude currently visible files $el.find_file @@edited.shift @@ -90,12 +132,18 @@ def self.go case prefix when 0 # Handled above - tabs through outlog lines @@consider_test = lambda{|b| ! $el.buffer_file_name(b) && ! $el.buffer_name(b)[/Minibuf/]} - when 1 # Only files - @@consider_test = lambda{|b| $el.buffer_file_name(b)} + + # when 1 # Handled above + + when 2 # .notes files + @@consider_test = lambda{|b| $el.buffer_name(b) =~ /\.xiki[<>0-9]*$/} + + # when 1 # Only files + # @@consider_test = lambda{|b| $el.buffer_file_name(b)} # when 3 # ...css # @@consider_test = lambda{|b| buffer_name(b) =~ /\.(css|sass)/} - when 2 # Non-files - @@consider_test = lambda{|b| ! $el.buffer_file_name(b) && ! $el.buffer_name(b)[/Minibuf/]} + # when 2 # Non-files + # @@consider_test = lambda{|b| ! $el.buffer_file_name(b) && ! $el.buffer_name(b)[/Minibuf/]} when 3 # ...css @@consider_test = lambda{|b| $el.buffer_name(b) =~ /^#/} when 4 # haml.html files @@ -106,7 +154,7 @@ def self.go $el.set_buffer b next if $el.elvar.major_mode.to_s != 'shell-mode' name = $el.buffer_name b - next if name == "*ol" + next if name == "ol" true } when 6 # Ruby files only @@ -118,18 +166,10 @@ def self.go when 67 # Tests @@consider_test = lambda{|b| $el.buffer_file_name(b) =~ /_(spec|test)\.rb$/} - # when 7 # .notes files - # @@consider_test = lambda{|b| $el.buffer_file_name(b) =~ /\.notes$/} - - # when 8 # Non-files - # @@consider_test = lambda{|b| ! $el.buffer_file_name(b) && ! $el.buffer_name(b)[/Minibuf/]} - # when 9 # js - # @@consider_test = lambda{|b| buffer_file_name(b) =~ /\.js$/} - - # when 7, 8, 9 # Handled above - else # Anything (except minibuffer) - @@consider_test = lambda{|b| ! $el.buffer_name(b)[/Minibuf/] } + @@consider_test = lambda{ |b| + $el.buffer_name(b) !~ /^ ?(Minibuf|\*)/ + } end # Remember we're starting at the top of the buffer list @@ -141,9 +181,18 @@ def self.go # If we've been typing tabs else self.restore_original_order # Restore order up to this buffer + self.move_to_next # Point to next eligible buffer + + end + + # Went through all views + if @@original == :at_end + # .restore_original_order already went back to the first, so just reset + return self.clear_once end + $el.switch_to_buffer(@@original[@@switch_index]) # Switch to eligible end @@ -156,7 +205,7 @@ def self.go_in_color prefix # This only gets called the 1st tab in the sequence (subsequent ones are routed to Dash+Tab) - Launcher.open 'mark/show/' + Launcher.open "mark/show/" return if View.txt =~ /^ - no marks found!/ @@ -168,10 +217,10 @@ def self.go_in_color prefix if View.file_visible? path # Don't split else # Else, split! - View.create :u + View.create_horizontal :u end - FileTree.launch :no_recenter=>1 + Louncher.launch :no_recenter=>1 @@color_prefix, @@dash_prefix = nil, true @@ -195,9 +244,9 @@ def self.go_in_difflog prefix if prefix # If first tab in sequence - if View.buffer_visible? "difflog.notes" # If already visible, just go there + if View.buffer_visible? "difflog.xiki" # If already visible, just go there was_open = true - View.to_buffer "difflog.notes" + View.to_buffer "difflog.xiki" else # Otherwise, open it and go to bottom DiffLog.open end @@ -207,19 +256,19 @@ def self.go_in_difflog prefix Move.to_quote :pipes=>1 - # If 1st diff isn't todo.notes, and difflog not already open! + # If 1st diff isn't notes.xiki, and difflog not already open! if ! View.file_visible?(first_diff_file) && ! was_open - View.create + View.create_horizontal View.recenter(View.line - View.number_of_lines) View.previous - FileTree.launch + Launcher.launch else - FileTree.launch :other_view=>1 + Launcher.launch :other_view=>1 end else # Subsequent times tabbed - View.to_buffer "difflog.notes" + View.to_buffer "difflog.xiki" Search.backward "^ - " @@ -242,7 +291,7 @@ def self.go_in_difflog prefix View.recenter -8 end - FileTree.launch :other_view=>1 + Launcher.launch :other_view=>1 end end @@ -251,10 +300,14 @@ def self.go_in_outlog prefix @@ol_prefix ||= prefix # Remember prefix if passed in - View.to_buffer "*ol" + View.to_buffer "ol" Move.to_end - target = @@ol_prefix == 1 ? "^ *-.*!$" : "^ *-" + target = + if @@ol_prefix == 1; "^ *-.*!$" + elsif @@ol_prefix == 11; "^ *-.* check!$" + else; "^ *-" + end Search.forward target, :go_anyway=>1, :beginning=>true if View.cursor == View.bottom @@ -265,15 +318,15 @@ def self.go_in_outlog prefix value = @@ol_prefix == 99 ? Ol.grab_value(Line.value) : nil Effects.blink - Color.mark "green" if @@ol_prefix == 11 - Launcher.launch_unified + Color.mark "green" if @@ol_prefix == 111 + Launcher.launch # Replace or add comment if there's a value - if value.any? + if value.any? && value != "!" Ol.update_value_comment value end - Color.mark "green" if @@ol_prefix == 11 + Color.mark "green" if @@ol_prefix == 111 return end @@ -283,6 +336,8 @@ def self.dash_prefix_buffer= txt end def self.restore_original_order + + return if ! @@original.is_a?(Array) # Move backwards through original list, moving each to front (0..(@@switch_index)).each do |i| $el.switch_to_buffer(@@original[@@switch_index-i]) @@ -291,10 +346,17 @@ def self.restore_original_order # Advances @@switch_index to next eligible buffer def self.move_to_next + + # Sometimes this isn't set...? + @@open_windows ||= $el.window_list.collect {|b| $el.window_buffer b} + buffer_started_at = @@switch_index @@switch_index += 1 # Move to next - self.to_next_unless_nil # Go there so test can look at buffer mode, etc + + return if @@original == :at_end + + result = self.to_next_unless_nil # Go there so test can look at buffer mode, etc # Keep moving until we find an eligible buffer (that isn't already viewed) while( @@ -306,27 +368,20 @@ def self.move_to_next # Stop moving forward if we're at end if @@switch_index >= @@original.size @@switch_index = buffer_started_at - # View.to_buffer buffer_started_at - View.beep 'None left' - break + return @@original = :at_end # To indicate to stop end - self.to_next_unless_nil + result = self.to_next_unless_nil end end def self.to_next_unless_nil to_buffer = @@original[@@switch_index] if to_buffer.nil? - View.beep 'None left' + return :at_end end $el.set_buffer(to_buffer) # Go there so test can look at buffer mode, etc end - def self.keys - Keys.set("C-") do - ControlTab.go - end - end end end diff --git a/lib/xiki/core/core_ext.rb b/lib/xiki/core/core_ext.rb index a9fe6713..0e46f72a 100644 --- a/lib/xiki/core/core_ext.rb +++ b/lib/xiki/core/core_ext.rb @@ -4,6 +4,10 @@ class Array def blank? self.empty? end + + def snippet + Xiki::Tree.pipe to_s + end end class NilClass @@ -39,4 +43,45 @@ def unindent def unindent! self.replace Xiki::TextUtil.unindent(to_s) end + + def snippet + Xiki::Tree.pipe to_s + end + + def quoted + Xiki::Tree.quote to_s + end + + def no_trailing + to_s.gsub(/ +$/, '') + end + + + + def camel_case + Xiki::TextUtil.camel_case(to_s) + end + def snake_case + Xiki::TextUtil.snake_case(to_s) + end + def hyphen_case + Xiki::TextUtil.hyphen_case(to_s) + end + def word_case + Xiki::TextUtil.word_case(to_s) + end + + +end + +class Hash + def snippet + Xiki::Tree.pipe to_s + end +end + +class NilClass + def snippet + "nil" + end end diff --git a/lib/xiki/core/cursor.rb b/lib/xiki/core/cursor.rb index 2da8ad34..0591917c 100644 --- a/lib/xiki/core/cursor.rb +++ b/lib/xiki/core/cursor.rb @@ -5,51 +5,56 @@ class Cursor def self.menu %` - > Summary - | Api for changing the cursor - | + - summary/ + | Api for changing the cursor - colors/ - @Cursor.white - @Cursor.red - @Cursor.green - @Cursor.blue - @Cursor.color "#80f" + =Cursor.white + =Cursor.red + =Cursor.green + =Cursor.blue + =Cursor.color "#80f" - shapes/ - @Cursor.bar - @Cursor.underscore - @Cursor.hollow - @Cursor.box + =Cursor.bar + =Cursor.underscore + =Cursor.hollow + =Cursor.box - colors and shapes/ - @Cursor.red_bar - @Cursor.green_underscore - @Cursor.blue_hollow - @Cursor.black_box + =Cursor.red_bar + =Cursor.green_underscore + =Cursor.blue_hollow + =Cursor.black_box - remembering and restoring cursor/ - @Cursor.remember :a - @Cursor.restore :a + =Cursor.remember :a + =Cursor.restore :a ` end def self.bar + return if ! Environment.gui_emacs $el.el4r_lisp_eval "(customize-set-variable 'cursor-type '(bar . 4))" nil end def self.box + return if ! Environment.gui_emacs + $el.message "*" $el.customize_set_variable :cursor_type, :box nil end def self.underscore + return if ! Environment.gui_emacs $el.el4r_lisp_eval "(customize-set-variable 'cursor-type '(hbar . 3))" nil end def self.hollow + return if ! Environment.gui_emacs $el.customize_set_variable :cursor_type, :hollow nil end def self.color color=nil + return if ! Environment.gui_emacs return $el.face_background(:cursor) if color.nil? Styles.define :cursor, :bg=>color @@ -99,15 +104,18 @@ def self.black_box end def self.remember symbol=:default + return if ! Environment.gui_emacs # Save is hash for later restoring (only if not there yet) @@remember[symbol] = [$el.elvar.cursor_type, $el.face_background(:cursor)] end def self.restore symbol=:default + return if ! Environment.gui_emacs before = @@remember[symbol] return Cursor.black_box unless before # Black if not found type, color = before $el.customize_set_variable :cursor_type, type + nil end end diff --git a/lib/xiki/core/deletes.rb b/lib/xiki/core/deletes.rb index 87a7b2e4..c5bb2e83 100644 --- a/lib/xiki/core/deletes.rb +++ b/lib/xiki/core/deletes.rb @@ -1,10 +1,13 @@ module Xiki class Deletes - def self.delete_whitespace + def self.delete_whitespace options={} - prefix = Keys.prefix(:clear=>true) # Number prefix means add that many lines after deleting + prefix = options[:prefix] || Keys.prefix(:clear=>true) # Number prefix means add that many lines after deleting - if prefix == :u # If U, remove whitespace within region + # up+, so remove all spaces in region... + # (do I ever use this?) + + if prefix == :u txt = View.selection :delete=>true linebreak_on_end = txt[/\n\z/] txt.gsub! /^ *[+-] /, '' @@ -15,14 +18,16 @@ def self.delete_whitespace # If at end of line, go forward, and remember to delete backward was_blank = Line.blank? - was_at_end = (Line.at_right? and (! was_blank)) - was_at_beginning = (Line.at_left? and (! was_blank)) + was_at_end = (Line.at_right? && (! was_blank)) + was_at_beginning = (Line.at_left? && (! was_blank)) if was_blank # If blank, stay on line # Do nothing elsif was_at_end $el.forward_char - elsif was_at_beginning and not View.char =~ /\s/ - $el.backward_char + elsif was_at_beginning && prefix != 0 # At beginning of line, so make blank and treat as though it was blank + View.>> "\n" + was_blank = 1 + was_at_beginning = nil else # If not at end of a line, simply delete horizontal $el.delete_horizontal_space View.insert(" " * prefix) if prefix @@ -42,6 +47,7 @@ def self.delete_whitespace if prefix View.insert("\n" * prefix) Move.backward prefix + Move.forward (prefix-1) / 2 if prefix > 0 # Move back to middle if 3 spaces or more end else $el.delete_horizontal_space @@ -49,8 +55,22 @@ def self.delete_whitespace end end + def self.forward + prefix = Keys.prefix + $el.delete_char(prefix || 1) + end def self.backward + + # Selection exists, so just delete it... + + selection = View.selection + View.deselect if selection == "" # Because in this one case it wouldn't deselect, and the selection is invisible because it's 0 chars wide + + return View.delete *View.range if selection + + # No selection... + prefix = Keys.prefix case prefix when :u @@ -62,6 +82,9 @@ def self.backward else $el.delete_backward_char(prefix || 1) end + + Keys.remember_key_for_repeat(proc {Deletes.backward}) + end end end diff --git a/lib/xiki/core/diff_log.rb b/lib/xiki/core/diff_log.rb index 39af8575..0b633d72 100644 --- a/lib/xiki/core/diff_log.rb +++ b/lib/xiki/core/diff_log.rb @@ -1,17 +1,18 @@ require 'xiki/core/hide' +require 'digest/md5' module Xiki # Will store a diff each time a file is saved. class DiffLog - @@log = File.expand_path("~/.emacs.d/difflog.notes") + @@log = File.expand_path("~/.xiki/misc/logs/difflog.xiki") @@temp_path = "/tmp/saved.txt" def self.menu " .open/ .diffs/ - .diffs/$p/ + .diffs/:p/ docs/ > Summary | The difflog tracks diffs of all the changes you make to file @@ -28,8 +29,8 @@ def self.open View.to_after_bar if View.in_bar? # If open, just switch to it and revert - if View.buffer_open?("difflog.notes") - View.to_buffer("difflog.notes") + if View.buffer_open?("difflog.xiki") + View.to_buffer("difflog.xiki") $el.revert_buffer true, true, true else # Otherwise, open it View.open(@@log) @@ -40,34 +41,48 @@ def self.open $el.recenter -4 end + + # Called by =edits def self.diffs path=nil txt = File.open(@@log, 'rb') {|f| f.read} - txt = txt.sub(/\A- /, '').split(/^- /).reverse.uniq + # Add artificial "|" delimiter, to make for easier splitting + txt.gsub! /(^[\/~])/, "|\\1" + + # Split it by "|" at beginning of lines + txt = txt.split(/^\|/) + txt.slice! 0 + txt = txt.reverse.uniq path ||= Tree.dir # Pull from tree if there + # Temp fix > remove slash from path (Tree.dir is adding slashes whet it's a file in the path) + # Longer-term fix is to make Tree.dir not add slash when quotes + path.sub! /\/$/, '' + + path = Bookmarks[path] if path if ! path regex = /^\// elsif File.file? path # File + path = Files.tilde_for_home path regex = /^#{Regexp.escape File.dirname path}\/\n - #{Regexp.escape File.basename path}/ else # Dir + path = Files.tilde_for_home path regex = /^#{Regexp.escape path}/ - path = "#{path}/" if path !~ /\/$/ + # Was doing nothing + # path = "#{path}/" if path !~ /\/$/ end - txt = txt.select{|o| o =~ regex} - - "- @#{txt.join("- @")}" + "= #{txt.join("= ")}" end # Insert old text deleted during last save def self.last_diff $el.with(:save_window_excursion) do DiffLog.open - Search.backward "^-" + Search.backward "^[^ \n]" txt = View.txt View.cursor, View.bottom end end @@ -89,7 +104,7 @@ def self.enter_old def self.enter_old_or_new old_or_new - diff = DiffLog.last_diff + diff = self.last_diff one_line_change = DiffLog.is_one_line_change? diff # Show intraline change if changed just one line and not up+ @@ -101,41 +116,69 @@ def self.enter_old_or_new old_or_new # Show lines - diff.gsub! /^ *[+:-].*\n/, "" # Only leave red and green lines if old_or_new == :old - diff.gsub! /^ +\|\+.*\n/, "" - diff.gsub! /^ +\|\-/, "" + diff = diff.scan(/^ +:-(.+)/).join("\n") else - diff.gsub! /^ +\|\-.*\n/, "" - diff.gsub! /^ +\|\+/, "" + diff = diff.scan(/^ +:\+(.+)/).join("\n") end - View << diff + + View.<< "#{diff}\n", :dont_move=>1 end # Appends diff to difflog, then saves. Mapped to as_file. - def self.save - return if View.file_name == "difflog.notes" + def self.save_newly_created + + Move.top + View >> "\n\n" + View << "* save\n | #{Notes.share_or_save_prompt_text}\n : " + + end + + def self.save options={} + file = View.file + + if View.name == "diff with saved" # If viewing diff, close it and save the actual file... + file = View.txt[/.+\n.+/].sub("\n - ", "") + View.kill + View.open file + end prefix = Keys.prefix :clear=>1 - self.save_diffs + # It's unsaved and not associated with a file yet, so prompt for where to save it + return self.save_newly_created if ! file + + diffs = self.save_diffs if prefix != :- && prefix != :u && ! options[:no_diffs] #> ||||| + View.message "" $el.save_buffer + View.message "" + + # Save to xikihub if shared stuff was edited... + + XikihubClient.save file, options + + # If there's a ~/xiki/foo.link for this file, update its timestamp + # as well (so it appears at the top of list+topics). + Notes.update_link_timestamp_if_any file + + View.message "" - if prefix == :u - sleep(0.3) - Firefox.reload - elsif prefix == 9 - Launcher.do_last_launch - end end def self.format raw, options={} - if options[:use_other_path] - path, file = raw.match(/\+\+\+ (.+\/)(.+?)\t/)[1..2] + + if options[:use_other_path].is_a? String + match = options[:use_other_path].match(/(.+\/)(.+)/) + path, file = match[1..2] + elsif options[:use_other_path] + match = raw.match(/\+\+\+ (.+\/)(.+?)\t/) + path, file = match[1..2] else - path, file = raw.match(/--- (.+\/)(.+?)\t/)[1..2] + match = raw.match(/--- (.+\/)(.+?)\t/) + raise "| No ---... line in the diff.\n| The diff command that made the input should have the flags: -U 0" if ! match + path, file = match[1..2] end # Delete paths at top @@ -149,18 +192,49 @@ def self.format raw, options={} } # Make - and + lines into -| and +| lines - raw.gsub!(/^\+(.*)/, " |+\\1") - raw.gsub!(/^-(.*)/, " |-\\1") + raw.gsub!(/^\+/, " :+") + raw.gsub!(/^-/, " :-") + raw.gsub!(/^\\ (.+)/, " : (\\1)") + + path = Files.tilde_for_home path + + if options[:children_only] + return raw.gsub(/^ :.+\n/, '') + end + + if options[:diffs_only] + return raw.gsub(/^ /, '') + end # Return with path - "- #{path}\n" + + "#{path}\n" + " - #{file}\n" + raw end + + def self.diff_strings string1, string2, options={} + + path1, path2 = "/tmp/diff1", "/tmp/diff2" + File.open(path1, "w") { |f| f << "#{string1.strip}\n" } + File.open(path2, "w") { |f| f << "#{string2.strip}\n" } + txt = Shell.sync "diff -U 0 \"#{path1}\" \"#{path2}\"" + return txt if options[:raw] + + self.format txt, options + + end + + def self.compare_with_saved + # up+up+, so do side-by-side... + if Keys.prefix_u + return Launcher.open("unsaved", :name=>"unsaved changes") + end + + if Keys.prefix_uu buffer_unsaved = View.buffer @@ -174,16 +248,31 @@ def self.compare_with_saved end - diff = self.save_diffs :dont_log=>1 + diff = self.save_diffs :just_return=>1 #> ||||| + View.message "" # To avoid message when writing diff = "" if diff.nil? + no_difference = diff.count("\n") <= 2 + + if no_difference + $el.set_buffer_modified_p nil + end + - View.to_buffer("*diff with saved*") + View.to_buffer("diff with saved") View.clear - Notes.mode + Notes.mode :wrap=>false + # View.wrap :off - View.insert diff.count("\n") > 2 ? - diff : - "> Note\n- No Differences!\n" + # No differences, so just say so... + + if no_difference #diff.count("\n") <= 2 + View.insert("- No unsaved changes!\n\nclose view/\n") + Line.previous 1 + else + + View.insert diff + "\nsave/\nsave without diff/\nrevert/\n" + Line.previous 3 + end end def self.enter_from_difflog @@ -195,21 +284,41 @@ def self.enter_from_difflog # Util function used by public functions def self.save_diffs options={} + if options[:patha] && options[:textb] patha = options[:patha] File.open(@@temp_path, "w") { |f| f << options[:textb] } else patha = View.file $el.write_region nil, nil, @@temp_path + View.message "" end - diff = Console.sync "diff -U 0 \"#{patha}\" \"#{@@temp_path}\"" - return if diff.empty? || diff =~ /: No such file or directory\n/ # Fail quietly if file didn't exist + # New file, so show all as new... + diff = + if patha - diff = self.format(diff) rescue nil - return diff if diff.nil? || options[:dont_log] + # Use blank if file doesn't exist + patha = "/dev/null" if ! File.exists?(patha) + + txt = Shell.sync "diff -U 0 \"#{patha}\" \"#{@@temp_path}\"" #> |||||| + txt_orig = txt.dup + + return if txt.empty? || txt =~ /: No such file or directory\n/ # Fail gracefully if file didn't exist + self.format(txt) rescue nil + else + # Wasn't a buffer rather than a file, so show all as new + txt = View.txt + txt.gsub! /^/, " :+" + "#{View.dir}/\n - #{View.name}\n :1\n#{txt}\n\n" + end + return diff if ! diff || options[:just_return] + + FileUtils.mkdir_p File.dirname(@@log) File.open(@@log, "a") { |f| f << diff } unless diff.count("\n") <= 2 + + txt_orig end def self.parse_tree_diffs txt @@ -220,7 +329,6 @@ def self.parse_tree_diffs txt while i < length line = txt[i] - # a_or_d = line[/^[ad]/] match = line.match(/^(.)(\d+) (\d+)/) raise "Line #{line} unexpected by DiffLog.parse_tree_diffs" if ! match a_or_d, line_number, many = match[1..3] @@ -248,19 +356,43 @@ def self.parse_tree_diffs txt result end - def self.do_compare_with prefix=nil + def self.compare_in_tree prefix ||= Keys.prefix :clear=>1 - # up+ means compare buffers in first two views + source_path = Tree.file_at_spot + dest_path = Tree.dir :file=>1 - return $el.ediff_buffers( $el.window_buffer($el.nth(0, $el.window_list)), $el.window_buffer($el.nth(1, $el.window_list))) if prefix == :u + FileTree.extract_filters! dest_path # Remove ##.../ parts of path - # Save place and grab file at spot + [source_path, dest_path].each {|o| raise ": File doesn't exist: #{o}" if ! File.exists? o } - source_path = Tree.dir_at_spot - dest_path = Tree.dir :file=>1 - $el.ediff_files source_path, dest_path + # up+, so ediff... + + return $el.ediff_files(source_path, dest_path) if prefix == :u + + # Xiki diff in one view... + + self.diff_files source_path, dest_path, :display_in_view=>1 + + end + + def self.compare_views + + # Dash+, so compare buffers in first two views... + + prefix ||= Keys.prefix :clear=>1 + + source_buffer = $el.window_buffer($el.nth(0, $el.window_list)) + dest_buffer = $el.window_buffer($el.nth(1, $el.window_list)) + + # up+, so ediff... + + return $el.ediff_buffers(source_buffer, dest_buffer) if prefix == :u + + # Xiki diff in one view... + + DiffLog.diff_files View.as_file_or_temp_file, View.as_file_or_temp_file(:buffer=>dest_path), :display_in_view=>1 end @@ -300,5 +432,229 @@ def self.last_intraline_diff txt=nil [deltaa, delteab] end + def self.diff_files source_path, dest_path, options={} + + display_in_view = options[:display_in_view] + + txt = Shell.sync "diff -U 0 \"#{dest_path}\" \"#{source_path}\"" + + return "- They're the same!" if txt.blank? + + txt = self.format txt, options + + + # They're the same, so just blink message... + + if txt == "" + return "- identical!" if ! display_in_view + return View.flash "- identical!" + end + + return txt if ! display_in_view + + # Display in a view + + View.to_buffer("do+compare+with") + View.clear + Notes.mode + View.insert txt + View.to_top + + end + + + # Mapped to Ctrl+G + def self.quit + + prefix = Keys.prefix + + # up+, so just suspend... + if prefix == :u + $el.suspend_emacs + return + end + + file = View.file + existed_already = file && File.exists?(file) + + txt = Xiki['unsaved/', :go=>1] + + modified_files = txt.scan(/^= /) + + # Current file is .xiki and the only modified file, so auto-save it + + current_file_is_modified = file && View.modified? + if modified_files.length == 1 && current_file_is_modified && View.extension == "xiki" + options = {} + options[:open_browser] = 1 if ! existed_already + self.save options + txt = "" + end + + # No files to save, or all are "no changes" or "File no longer exists?" + + if self.nothing_unsaved txt + + # Unsaved "xsh" buffer, so save it... + + self.save_xsh_session # Last file we were in (if buffer) + + # Save file and line number + self.save_go_location + + + # If xsh is open but not the one looking at, make a session for it + if View.name != "xsh" && View.buffer_open?("xsh") + $el.set_buffer "xsh" + # Why calling it a 2nd time? + self.save_xsh_session + end + + + # Save any shell commands we've run back to the external shell's history... + + txt = Shell.session_cache + if txt + txt.gsub!(/^\$ /, '') + File.open(File.expand_path("~/.xiki/misc/tmp/recent_history_internal.xiki"), "w") { |f| f << txt } + end + + $el.kill_emacs + else + View.open :txt=>"unsaved/\n#{txt.gsub /^/, ' '}", :line_found=>2, :name=>"unsaved/" + end + + nil + + end + + def self.quit_and_run commands, options={} + + if options[:dir] + dir = Shell.quote_file_maybe Bookmarks[options[:dir]] + commands = "cd #{dir}\n#{commands}" + end #> !!! + + Xsh.save_go_commands commands + self.quit + end + + def self.nothing_unsaved txt + txt == "- No files unsaved!\n" || txt !~ /^ (?!: no changes|: File no longer exists\?$)/ + end + + + def self.save_xsh_session options={} + + # Delete "G" bookmark, so we'll know to go to the session upon ^G + FileUtils.rm(File.expand_path "~/.xiki/bookmarks/g.xiki") rescue nil + + # Avoid saving certain buffers + + # Return if it has a file - it means it's not the "xsh" session (it's just a file with that name) + return if View.file + + # Save session to note in sessions.xiki... + + heading = View.extract_suggested_filename_from_txt + return if ! heading + heading.gsub!("_", " ") + + # Add "@" to headings > so it'll be shared + heading.gsub! /^/, "@ " if options[:shared] + + + sessions_file = File.expand_path "~/xiki/sessions.xiki" + txt = File.read(sessions_file) rescue "" + txt.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + + # If we reopen the session (^G), delete the old version + if $el.boundp :session_heading + txt = Notes.split(txt) + txt.delete_if{|o| o=~ /\A#{Regexp.escape($el.elvar.session_heading)}$/} + txt = txt.join + end + + # Make names unique + headings = txt.scan(/^> (.+)/).flatten + while(headings.member? heading) + heading.sub!(/(\d*)$/) { $1.to_i + 1 } + heading.sub! /1$/, '2' + end + + section = "> #{heading}\n#{View.txt.strip}\n\n\n" + txt = "#{section}#{txt}" + + File.open(sessions_file, "w") { |f| f << txt } + + # Save dir and cursor position + cursor = View.cursor + File.open(File.expand_path("~/.xiki/misc/last_session_meta.xiki"), "w") { |f| f << "#{cursor}\n#{View.dir}" } + + $el.elvar.session_heading = "> #{heading}" + + heading + + end + + def self.save_go_location + + file = View.file + + # .save_xsh_session didn't deem to save this view, so we should ignore it too + return if ! file + + dir = File.expand_path "~/.xiki/bookmarks" + FileUtils.mkdir_p dir # Make sure dir exists + # Save this file in > "g" bookmark! + File.open("#{dir}/g.xiki", "w") { |f| f << "#{file}\n" } + + # Store the shell current dir, which will be restored whin a ^G from bash is done + tmp_dir = File.expand_path "~/.xiki/misc/tmp" + FileUtils.mkdir_p tmp_dir # Make sure dir exists + File.open("#{tmp_dir}/go_location_cd_dir.xiki", "w") { |f| f << "#{Shell.dir}\n" } + + View.message "The next time you type ^G in bash, it'll go here." + + end + + def self.file_list options={} + + txt = File.read(File.expand_path("~/.xiki/misc/logs/difflog.xiki"), *Xiki::Files.encoding_binary) rescue nil + + return [] if ! txt + + # Avoids "invalid byte sequence in UTF-8" error + txt.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + + files = [] + + # :tree_format, so return navigable tree with file indented... + + if options[:tree_format] + txt.scan(/^.*\n - .*$/){|m| files << "= #{m}"} + return files.reverse.uniq + end + + # No options, so return as a list of just file paths + + txt.scan(/^(.*)\n - (.*)$/){|m| files << "#{m[0]}#{m[1]}"} + return files.reverse.uniq + + end + + + # Show diffs for just one file ("." for current) + def self.show_edits_for_bookmark + bm = Keys.input :timed=>true, :prompt=>"Enter a bookmark to search edits: " + return Launcher.open("diff log/diffs/") if bm == "8" || bm == " " + path = bm == "." ? View.file : "%#{bm}/" + return Launcher.open("#{path}\n = edits/") + end + + def self.show_edits + return Launcher.open("#{View.file}\n = edits/") + end + end end diff --git a/lib/xiki/core/dirs.rb b/lib/xiki/core/dirs.rb new file mode 100644 index 00000000..c4f1166b --- /dev/null +++ b/lib/xiki/core/dirs.rb @@ -0,0 +1,8 @@ +module Xiki + class Dirs + def self.commands + @@commands ||= File.expand_path("~/xiki") + end + end +end + diff --git a/lib/xiki/core/effects.rb b/lib/xiki/core/effects.rb index eb9eb448..9597258c 100644 --- a/lib/xiki/core/effects.rb +++ b/lib/xiki/core/effects.rb @@ -4,48 +4,45 @@ module Xiki # Makes visual things happen class Effects - def self.menu - " - > Do cool-looking things to text + MENU = " + | Do cool-looking things to text. Used by various menus. - api/ | Try these out by double-clicking on them. - Glow/ - @Effects.glow - @Effects.glow :times=>6 - @Effects.glow :what=>:paragraph - @Effects.glow :what=>:block - @Effects.glow :what=>[1, 100] - + =! Effects.glow + =! Effects.glow :times=>5 + =! Effects.glow :what=>:paragraph + =! Effects.glow :what=>:block + =! Effects.glow :what=>[1, 100] - Colors/ - @Effects.glow :color=>:fire - @Effects.glow :color=>:water - @Effects.glow :color=>:forest - @Effects.glow :color=>:rainbow - @Effects.glow :color=>:fat_rainbow + =! Effects.glow :color=>:fire + =! Effects.glow :color=>:water + =! Effects.glow :color=>:forest + =! Effects.glow :color=>:rainbow + =! Effects.glow :color=>:fat_rainbow - Fade in and out/ - @Effects.glow :fade_in=>1 - @Effects.glow :fade_out=>1 + =! Effects.glow :fade_in=>1 + =! Effects.glow :fade_out=>1 - Blink/ | Makes line blink orange. Using a longer time since the blink happens | anyway. - - @Effects.blink :time=>1 - - Some View methods that use effects/ - @View.prompt - @View.flash - @View.flash 'Saved!' + =! Effects.blink :time=>1 + - View methods/ + =! View.prompt + =! View.flash + =! View.flash 'Saved!' - docs/ > Keys | do+line+effects: make line blink | up+do+line+effects: make line blink rainbow color - - > See - << themes/ + - see/ + <= themes/ " - end # # Effects.glow :color=>:forest + # Effects.glow :fade_in=>1 + # Effects.glow :what=>[1, 100] # def self.glow options={} @@ -53,6 +50,7 @@ def self.glow options={} if what.is_a? Array left, right = what elsif what == :block + # TODO: change this to :section! ignore, left, right = View.block_positions "^[|>]" elsif what == :paragraph left, right = View.paragraph :bounds=>1 @@ -60,7 +58,6 @@ def self.glow options={} left, right = Line.left, Line.right end - # Set :times to 1 if no args and fade out times = 1 if ! options[:times] && (options[:fade_out] || options[:fade_in]) @@ -93,15 +90,27 @@ def self.glow options={} else; [1] + (down + [7] + up + [1]) * times end + delay = options[:delay] + delay ||= Environment.gui_emacs ? 0.022 : 0.042 + + + # Try this to capture ^G + $el.elvar.inhibit_quit = 1 + sequence.each do |i| $el.overlay_put over, :face, (faces[i-1] || faces[0]) - $el.sit_for 0.007 + $el.sit_for delay + + # Temp for video recording > wait for longer + # $el.sit_for 0.05 end + $el.elvar.inhibit_quit = nil + + $el.delete_overlay over end - # Sample usages: def self.blink options={} what = options[:what] what ||= :line @@ -118,33 +127,42 @@ def self.blink options={} return unless left end + if what.is_a? Array + left, right = what + end + + left = options[:left] if options[:left] right = options[:right] if options[:right] time = options[:time] || 0.04 over2 = $el.make_overlay(left, right) $el.overlay_put over2, :face, :color_rb_glow2 + # Ol "!!!" $el.sit_for time $el.delete_overlay over2 end - # Define font def self.define_styles - Styles.define :color_rb_glow1, :bg => "fec" - Styles.define :color_rb_glow2, :bg => "f90" - - Styles.define :red, :fg => "f00" - Styles.define :orange, :fg => "f80" - Styles.define :yellow, :fg => "ff0" - Styles.define :green, :fg => "0f0" - Styles.define :blue, :fg => "00f" - Styles.define :indigo, :fg => "408" - Styles.define :violet, :fg => "82e" - - Styles.define :purple, :fg => "808" - Styles.define :cyan, :fg => "f0f" + + Code.cache(:effects_define_styles) do + + Styles.define :color_rb_glow1, :bg=>"fec" + Styles.define :color_rb_glow2, :bg=>"f90" + + Styles.define :red, :fg=>"f00" + Styles.define :orange, :fg=>"f80" + Styles.define :yellow, :fg=>"ff0" + Styles.define :green, :fg=>"0f0" + Styles.define :blue, :fg=>"00f" + Styles.define :indigo, :fg=>"408" + Styles.define :violet, :fg=>"82e" + + Styles.define :purple, :fg=>"808" + Styles.define :cyan, :fg=>"f0f" + end + end end - Effects.define_styles end diff --git a/lib/xiki/core/environment.rb b/lib/xiki/core/environment.rb index cea6224d..a62c6b1f 100644 --- a/lib/xiki/core/environment.rb +++ b/lib/xiki/core/environment.rb @@ -5,33 +5,36 @@ class Environment # Environment.os def self.os - return :osx if RUBY_PLATFORM =~ /darwin/i - return :unix if RUBY_PLATFORM =~ /linux|solaris/i - return :windows if RUBY_PLATFORM =~ /mswin/i - - # if RUBY_PLATFORM =~ /darwin/i - # { :os => "unix", :implementation => "macosx" } - # elsif RUBY_PLATFORM =~ /linux/i - # { :os => "unix", :implementation => "linux" } - # elsif RUBY_PLATFORM =~ /cygwin/i - # { :os => "unix", :implementation => "cygwin" } - # elsif RUBY_PLATFORM =~ /mingw/i - # { :os => "win32", :implementation => "mingw" } - # elsif RUBY_PLATFORM =~ /mswin/i - # { :os => "win32", :implementation => "mswin" } - # elsif RUBY_PLATFORM =~ /solaris/i - # { :os => "unix", :implementation => "solaris" } - # else - # { :os => "unknown", :implementation => "unknown" } + return "osx" if RUBY_PLATFORM =~ /darwin/i + return "linux" if RUBY_PLATFORM =~ /linux/i + return "solaris" if RUBY_PLATFORM =~ /solaris/i + return "windows" if RUBY_PLATFORM =~ /mswin/i :unknown end + def self.xsh= val + $el.elvar.environment_xsh = val + end + def self.xsh? + return nil if ! $el.boundp(:environment_xsh) + $el.elvar.environment_xsh + end + def self.current_emacs_path # This is only currently implemented for the mac. # Someone, make this work for Linux also. result = `ps -ef`[/ (\/.+\/(Aquamacs|Aquamacs Emacs|Emacs)\.app)/, 1] end + + def self.emacs + $el + end + + def self.gui_emacs + @@gui_emacs ||= ($el && $el.display_graphic_p) + end + end end diff --git a/lib/xiki/core/expander.rb b/lib/xiki/core/expander.rb index f3ba86fa..9af3600e 100644 --- a/lib/xiki/core/expander.rb +++ b/lib/xiki/core/expander.rb @@ -16,7 +16,7 @@ def self.menu | | Examples: | Expander.expand "ip" # => "192.0.0.1" (assuming "ip" menu exists) - | Xiki["ip"] # Same thing + | Xiki["ip/"] # Same thing - expanding/ | Expander.expand() handles expanding paths. "Expanding" is roughly a | synonym for evaluating, running, or executing. @@ -45,9 +45,9 @@ def self.menu | (/tmp/a//) - menus that are already defined__/ | If an "ip" menu is already defined (see "defining" below), or - | exists in a dir in MENU_PATH such as ~/menu/, you can simply + | exists in a dir in XIKI_PATH such as ~/.xiki/roots/, you can simply | invoke it by name: - | Xiki["ip"] + | Xiki["ip/"] | | Many menus can be passed paths after the menu name: | Xiki["mysql/setup"] @@ -90,7 +90,11 @@ def self.menu # will we add stuff to the hash based on definitions or menu # dirs. # - # Expander.parse("a").should == {:name=>"a"} + # This stuff this method extracts should probably be limited + # to elements that determine which expander to use, or are of + # interest to multiple expanders. + # + # Expander.parse("a").should == {:name=>"a", :path=>"a"} # Expander.parse("/tmp").should == {:file_path=>"/tmp"} # Expander.parse(:foo=>"bar").should == {:foo=>"bar"} def self.parse *args @@ -109,13 +113,12 @@ def self.parse *args thing = thing[-1] end - # If 2nd arg is array, it's a list of args options[:items] = args.shift if args[0].is_a? Array # If non-string add and return... - return options.merge!(:name=>Menu.format_name(thing.to_s)) if thing.is_a? Symbol + return options.merge!(:name=>Command.format_name(thing.to_s)) if thing.is_a? Symbol return options.merge!(:class=>thing) if thing.is_a? Class raise "Don't know how to deal with #{thing.class} #{thing.inspect}" if ! thing.is_a? String @@ -124,12 +127,26 @@ def self.parse *args self.extract_ancestors thing, options # Move ...@ ancestors into options if any + # Split up all path types, to pull off options... + self.extract_task_items thing, options + # If menu-like, extract menu and items and return... - if thing =~ /^[\w _-]+(\/.*|$)/ + # Command or topic name, so set :name option + + # Todo > remove .foo + + if thing =~ /\A`?\w[\w _-]*(\/|\z)/ + path_items = Path.split thing # Split into items - options[:name] = Menu.format_name path_items.shift # First arg is name, so pull it off + options[:name] = Command.format_name path_items.shift # First arg is name, so pull it off + + # Split off extension if any... + + extension = options[:name].slice!(/\.[a-z]*$/) if options[:name] !~ /\A\./ + + options[:extension] = extension if extension # Store original path, since it could be a pattern, and set items extracted from path... @@ -137,42 +154,64 @@ def self.parse *args options[:path] = thing # Store original path, since it could be a pattern that just looks like a menu options[:items] = path_items if path_items.any? else # If already items (2nd arg was an array) + + path_old = options[:path] options[:path] = ([thing] + options[:items]).join("/") # Make path string include 2nd arg items + # Append slash to path, if old path had one at end + options[:path] << "/" if path_old =~ /\/$/ + options[:items] = path_items + options[:items] if path_items.any? # Prepend any path items 2nd arg items end return options end - thing = self.expand_file_path thing if thing =~ /^[~.$]/ # Expand out file shortcuts if any + # Eventually, remove $... + + # If just "." or "~", treat as file path + + # Expand bookmarks + + thing = self.expand_file_path thing if thing =~ /^[~.%]/ # Expand out file shortcuts if any # If not a file, it's probably a pattern, so store in line and return... return options.merge!(:path=>thing) if thing !~ /^\/($|[\w. -]+$|[\w. -]+\/)/ && thing !~ %r"^//" - # It's file-ish, so either file or menufied ("//") # If menufied (has "//"), parse and return... if thing =~ %r"^(/[\w./ -]+|)//(.*)" # If /foo// or //foo + menufied, items = $1, $2 - menufied.sub! /\.\w+$/, '' + + # Dot in filename, so assume file extension to ignore, but only if corresponding file exists... + + if File.file? menufied + menufied.sub! /\.\w+$/, '' + end menufied = "/" if menufied.blank? options[:menufied] = menufied options[:path] = thing + raise "didn't anticipate expanders yet" if options[:expanders] # Grab args + # Append to existing items + items = Path.split items - options[:items] = items if items.any? + if items.any? + options[:items] ||= [] # Set to empty if not already set + # Appening items > might cause problems + options[:items] = items + options[:items] + end return options end - # Else, assume just a file path... options[:file_path] = thing @@ -187,24 +226,67 @@ def self.parse *args # Most important method in Xiki. Called when stuff is double-clicked on. # Expander.expand "ip" # => "192.0.0.1" - # @expander/docs/expanding/ + # expander/docs/expanding/ def self.expand *args - # Break path down into attributes... + options = nil + + # If :propagate, will be more selective about what is propagated, otherwise it does nothing and whole options hash is passed in and out + options_before_propagate = self.handle_propagate_option_in args + + # If 1st arg is just a hash with sources, we're being called again so don't re-parse and re-find expands + if args[0].is_a?(Hash) # && args[0][:expanders] + options = args[0] + + else + + # Break path down into attributes + options = self.parse *args + + # Remove any pre-existing :expanders, so they don't clash and make infinite loop! + options.delete :expanders + options.delete :expanders_index + + # Figure out what type of expander (to use and set some more attributes along the way) + self.expanders options + end + + options.delete(:items) if options[:items] == [] + + # Blank arg list, so set to nil + + # ^... "grab option", so delegate to Grab.foo method... + + return Grab.call_grab_method options if options[:grab_option] - options = self.parse *args + # # Task, so make future ^X's expand with task... - # Figure out what type of expander (to use and set some more attributes along the way)... + # Command source passed in as text, so .invoke it as the literal command text + # return self.expand_literal_command(options[:command_text], options) if options[:command_text] - self.expanders options + # It's a class, so just .invoke it directly + + return Invoker.invoke *args if options[:class] expanders = options[:expanders] if ! expanders || expanders.length == 0 + options[:no_slash] = true - return "@flash/- Your indenting looks messed up!" if options[:not_well_formed] - return "@flash/- Can't launch empty line!" if options[:path].blank? - return "@flash/- No menu or pattern defined!" + + return "<* Your indenting looks messed up!" if options[:not_well_formed] + + if options[:path].blank? + + # return CommandSuggester.blank_at options[:ancestors] if options[:ancestors] + + # task, so show options... + return self.blank_line_option_items options if options[:task] #> || + + return "<* Try Ctrl+T or Ctrl+X on blank lines" + end + + return "<* No menu or pattern found!" end # Delegate to one or more expanders... @@ -214,28 +296,89 @@ def self.expand *args expanders.each do |expander| expander = expander[:expander] if expander.is_a?(Hash) # For patterns, :expanders has {:expander=>Pattern, ...} + expander.expand options - break if expander == Menu && options[:output] # Always stop going after MenuExpander, if it had output + break if expander == Command && options[:output] # Always stop going after MenuExpander, if it had output break if options[:halt] # Possibly .expands? didn't halt but .expand did options[:expanders_index] += 1 end - Launcher.append_log(options[:path]) if options[:path] && options[:expanders].find{|o| o == Menu} && ! options[:dont_log] - options[:output] + Launcher.append_log(options[:path]) if options[:path] && options[:expanders].find{|o| o == Command} && ! options[:dont_log] + txt = options[:output] + + if options[:client] == "web" && txt !~ /^\s*]/i + if options[:extension] + txt = "extension but web" + else + txt = Xiki::Html.to_html txt, options + end + end + + self.handle_propagate_option_out(options_before_propagate, options) if options_before_propagate #> ||||| + + txt + end + + def self.handle_propagate_option_in args + + # Find location of options in args + options_index = args.index{|o| o.is_a? Hash} + + return if ! options_index + + # No :propagate, so do nothing > whole options hash will be passed in and out + # The :propagate option means to be more selective about what is propagated + return if ! args[options_index][:propagate] + + # Save original and return them, so we can hold on and propagate back into them later + original_options = args[options_index] + + # Create a blank hash and copy only the important options into it, and make that be the actual options that are propagated down + options_in = {} + Options.propagate_some_inward original_options, options_in + args[options_index] = options_in + + original_options + + end + + def self.handle_propagate_option_out options_before_propagate, options + + # Just propagate out to original + Options.propagate_some_outward options, options_before_propagate + + end + + + def self.blank_line_option_items options + + option_item = options[:task] + + # Show blank line tilde options... + + menu = Notes.option_items(:context=>:blank_line) #> ||| + xik = Xik.new(menu) + + option_item = OptionItems.prepend_asterisk option_item + + txt = xik[option_item, :eval=>options] + + txt + end # Adds :expander=>TheClass to options that says it .expands? this path (represented by the options). - # Expander.expanders(:name=>"foo").should == "guess" + # Expander.expanders(:name=>"foo")[:expanders].should == [Xiki::Command] # Expander.expanders(:file_path=>"/tmp/").should == {:file_path=>"/tmp/", :expander=>FileTree} # Expander.expanders("a").should == "guess" def self.expanders *args options = args[0] # Probably a single options hash options = self.parse(*args) if ! args[0].is_a?(Hash) # If not just a hash, assume they want us to parse - [PrePattern, Menu, FileTree, Pattern, MenuSuggester].each do |clazz| + [PrePattern, Command, FileTree, TopicExpander, Pattern, CommandSuggester].each do |clazz| # For each expander clazz.expands? options break if options[:halt] end @@ -243,6 +386,7 @@ def self.expanders *args options end + # Just expands command body that was passed in (usually options[:command_text]) # Move ancestors into options if any # @@ -259,16 +403,47 @@ def self.extract_ancestors path, options={} end end + # Expander.extract_task_items "foo/~ rename/" + # Extracts "* foo" items from the path, removing them. + def self.extract_task_items thing, options={} + + path = Path.split thing + + # *... item, so pull them off and store in :task... + + # "^ foo" items are no longer used, right? + + # Eventually look only for astexix > Not also tilde > but maybe also caret? + index = path.index{|o| o =~ /^[*~^] / && o !~ /\n/} # Tasks won't have linebreaks + + return if ! index + + task = path.slice! index..-1 + path = Path.join path + + + is_grab_option = task[0] =~ /^\^/ + + task[0].sub! /^[*~^] /, '' + + is_grab_option ? + options[:grab_option] = task : + options[:task] = task + + thing.replace path + end + + # Defines menus so they can later be called with Expander.expand. - # Note that menus in a MENU_PATH dir don't need to be def'ed. + # Note that menus in a XIKI_PATH dir don't need to be def'ed. # # Xiki.def(:abc){ "some code" } # # > Future plans for how you'll be able to define menus # - "pre" pattern menus - # | Xiki.def(:pre=>1, :view=>"*ol"){...} + # | Xiki.def(:pre=>1, :view=>"foo"){...} # - normal pattern menus (without a regex) - # | Xiki.def(:view=>"*ol"){...} + # | Xiki.def(:view=>"foo"){...} # | Xiki.def(:token1=>"select"){...} # optimized via hash lookup # | Xiki.def(/^select/, :token1=>"select"){...} # optimized via hash, with pattern that runs after def self.def *args, &block @@ -284,10 +459,14 @@ def self.def *args, &block # If symbol, just treat as string args[0] = args[0].to_s if args[0].is_a? Symbol + # If :eval, use as block + block = options[:eval] if options[:eval] + if args[0].is_a? String - kind = Menu - # Menu if symbol or string... + kind = Command + + # Command if symbol or string... name = nil @@ -298,11 +477,14 @@ def self.def *args, &block args[0].sub! /(\.\w+)?\/*$/, '' name = args[0][%r"([^\/]+)/*$"] # " else # Must be normal foo/... menu path - return self.delegate_to_keys args, options, block if args[0] =~ /\+/ # If name has "+", delegate to key shortcut + + # foo+bar name, so define normal key shortcut... + + return self.def_key args, options, block if args[0] =~ /\+/ name = args[0].scan(/\w+/)[-1] # Might be a menufied path end - Menu.defs[name || "no_key"] = implementation + Command.defs[name || "no_key"] = implementation elsif args[0].is_a? Regexp @@ -314,10 +496,12 @@ def self.def *args, &block patterns = Pattern.defs end + # Turn any remaining options keys into nesting that limits it. Example: + # options: :target_extension=>".rb" + # resulting keys: [:target_extension, ".rb"] + keys = [] - options.each do |k, v| - keys += [k, v] - end + options.each{|k, v| keys += [k, v]} keys.<< :global if keys.empty? keys += [args[0], implementation] @@ -327,14 +511,13 @@ def self.def *args, &block else # For now, assume just one key is passed in, like... - # options # => {:view=>"*ol"} - key, val = options.to_a[0] # => [:view, "*ol"] + key, val = options.to_a[0] # => [:view, "foo"] PrePattern.defs[key] = {val=>implementation} # Result should look like... # PrePattern.defs=>{ # :view=>{ - # "*ol"=>implementation + # "foo"=>implementation end @@ -342,30 +525,32 @@ def self.def *args, &block end - # Just expands out ~/, ./, ../, and $foo/ at beginning of paths, + # Just expands out ~/, ./, ../, and :foo/ at beginning of paths, # leaving the rest in tact # - # Expander.expand_file_path("$x/a//b").should =~ %r".+/xiki/a//b$" - # Expander.expand_file_path("$x//") - # /projects/xiki// - # Expander.expand_file_path("$ru//") - # /Users/craig/notes/ruby/ruby.notes// + # Expander.expand_file_path("%n") + # Expander.expand_file_path("%n") + + # Expander.expand_file_path("%xiki/a//b").should =~ %r".+/xiki/a//b$" + # Expander.expand_file_path("%xiki//") + # Expander.expand_file_path("%ru//") def self.expand_file_path path - # Probably restore this line - return path if path !~ %r"^(~/|\.|\$\w)" # One regex to speed up when obviously not a match + return path if path !~ %r"^(~/|\.|%\w)" # One regex to speed up when obviously not a match # Expand out ~ return path.sub "~", File.expand_path("~") if path =~ %r"^~/" # Expand out . and .. - return path.sub $1, File.expand_path($1) if path =~ %r"^(\.+)/" - # Expand out $foo/ bookmarks - if path =~ %r"^(\$[\w]+)(/|$)" + return path.sub($1, File.expand_path($1, Shell.dir)) if path =~ %r"^(\.+)/" + + # Expand out %foo/ bookmarks + + if path =~ /^(%[\w]+)(\/|$)/ file = Bookmarks[$1] file.sub! /\/$/, '' # Clear trailing slash so we can replace consistently with dirs and files - return path.sub /\$\w+/, file + return path.sub /%\w+/, file end # TODO > Make bookmarks not emacs dependant > Use Bookmarks2 to expand bookmarks - or just update Bookmarks?! @@ -373,45 +558,92 @@ def self.expand_file_path path path # No match, so return unchanged end - def self.delegate_to_keys args, options, block + def self.def_key args, options, block - # If 2 args and 2nd is string, wrap block based on enter+ other - if args.length == 2 && menu.is_a?(String) - options.merge! :bar_is_fine=>1 + command = args[1] - menu = args[1] + # 2nd arg is string, so treat it like a command ... + # Example: Xiki.def "view+dimensions", "dimensions/", :hotkey=>1 + if args.length == 2 && command.is_a?(String) block = - if args[0] =~ /^enter\+/ - lambda{ Launcher.insert menu, options } - elsif menu =~ /^@/ # If menu is @foo..., prompt for bookmark and nest under result + if command =~ /^=/ # If command is =foo..., prompt for bookmark to nest result under lambda{ file = Keys.bookmark_as_path :include_file=>1, :prompt=>"Enter a bookmark: " - Launcher.open "#{file}\n #{menu}", options + Launcher.open "#{file}\n #{command}", options } - elsif menu =~ /^\.@/ # If menu is .@foo..., nest under current file - menu.sub! /^\./, '' - lambda{ + + elsif command =~ /^\.=/ # If command is .=foo..., nest under current file + + # For key shortcuts, if .=..., use current file, or view name... + + command.sub! /^\./, '' + ->{ bm = Keys.input :optional=>1, :prompt=>"Enter bookmark, or pause for current file." # Terminated by pause - file = bm ? Keys.bookmark_as_path(:bm=>bm, :include_file=>1) : View.file - Launcher.open "#{file}\n #{menu}", options + + # Nest under... tree path if we're in one, else view path, else buffer name... + + ancestor = FileTree.grab_file_on_line + ancestor ||= View.file || View.name + + file = bm ? Keys.bookmark_as_path(:bm=>bm, :include_file=>1) : ancestor + Launcher.open "#{file}\n #{command}", options } else - lambda{ Launcher.open menu, options } + + # Make key just open the command... + lambda{ Launcher.open command, options } end end + # Just normal definition... + args[0].gsub! '+', '_' - Keys.method_missing args[0], &block - # Store where it was defined for open+key, else we won't know (have to climb stack to do this) - definer = caller(0)[3] keys = Keys.words_to_letters args[0] - Keys.source[keys] = definer + + # foo, so save to Keys.map... + + if args[0] =~ /^[a-z]/ + # Define in nested map of keys... + path = args[0].split("_") + Keys.map((path + [block]), options) + + # Todo > remove "l" and "c" from this, since l is "list" + + # Make jump+ be search + + return unless path[0] =~ /^[j]/ # search+... and custom+... keys still need to be defined the old way + end + + # search+..., custom+..., or other first word, so map the standard emacs way... + + key = Keys.words_to_letters args[0] + + map = :global_map + + # search+..., so pull off "search+" and add to :isearch_mode_map + + # Make go+ be search + + if key =~ /^J/ + key.sub! /./, '' # Chop of S + map = :isearch_mode_map + end + + key.gsub!(/./){|o| (o.downcase.sum - 96).chr } # Convert to control chars + + # If block is a string, make wrapper to eval + if block.is_a?(String) + return Keys.define_key_that_evals map, key.inspect, "Xiki::#{block}".inspect + end + + $el.define_key(map, key, &block) nil end + end end diff --git a/lib/xiki/core/file_tree.rb b/lib/xiki/core/file_tree.rb index a09d1919..de7c3b3d 100644 --- a/lib/xiki/core/file_tree.rb +++ b/lib/xiki/core/file_tree.rb @@ -1,11 +1,23 @@ # -*- coding: utf-8 -*- +# +# > Summary +# For handling file system paths +# and the like. +# +# > .add +# ! 1 + 1 +# +# > .subtract +# ! 10 + 1 +# + + require 'xiki/core/styles' require 'xiki/core/line' require 'xiki/core/view' -require 'net/http' -require 'uri' require 'xiki/core/cursor' +require 'xiki/core/code' module Xiki # Draws a tree from a dir structure and lets you incrementally search in the tree. @@ -30,10 +42,10 @@ def self.menu | Lets you navigate your filesystem as a tree. | | Type a file path on a line and double-click on it. Here's an example - | (the "@" isn't required when there are no spaces at the beginning of the + | (the "= " isn't required when there are no spaces at the beginning of the | line). | - @/ + = / | - overview of keys/ | When you launch a path, the cursor turns blue and you go into a temporary @@ -53,22 +65,16 @@ def self.menu ` end - # TODO - # - Make search handle trees with multiple roots - - # Call this method from your init.rb to use the default key shortcuts. - def self.keys - Keys.XT { FileTree.ls } - end - def initialize @res = "" @list = [] - @file_regex = nil + @took_too_long = false + @file_regex, @reverse_sort = nil, nil + @time_recursive_started = Time.now.to_f end attr :res attr :list - attr_accessor :file_regex + attr_accessor :file_regex, :reverse_sort # Change dirs into spaces, etc def clean path, indent=0 @@ -80,9 +86,21 @@ def clean path, indent=0 # Recursively draws out tree def traverse path - entries = Dir.glob("#{View.expand_path(path)}/*", File::FNM_DOTMATCH). - select {|i| i !~ /\/\.(\.*|svn|git)$/}. # Exclude some dirs (exclude entensions here too?) - sort + + return if @took_too_long # Stop recursing if took too long + + at_root = @res == "" + + entries = Dir.glob("#{path == '/' ? '' : View.expand_path(path)}/*", File::FNM_DOTMATCH). + select {|i| i !~ /\/\.(\.*|svn|git)$/}#. # Exclude some dirs (exclude entensions here too?) + + # Sort, handling error when certain type of file + entries = entries.sort{|a, b| + a_time = File.mtime(a) rescue nil + b_time = File.mtime(b) rescue nil + next 0 if ! a_time || ! b_time + b_time <=> a_time + } # Process dirs entries.each{ |f| @@ -99,11 +117,24 @@ def traverse path @res += "#{cleaned.sub(/(^ *)/, "\\1+ ")}\n" } + # Too much time has elapsed, so raise timeout + + if Time.now.to_f - @time_recursive_started > 2 + @took_too_long = true + end + + # At root and took to long, so just show root dir listing + if at_root && @took_too_long + @res = FileTree.expand_one_dir :file_path=>File.expand_path(path) + View.message "Only showing one level, it was taking a long time" + end + end + # Returns tree of files and their contents matching the + # regex (as an array of lines). + # FileTree.grep "/projects/xiki/spec/fixtures/roots/dd/", "" def self.grep dir, regex, options={} - # args when ## => ["/projects/foo/", "hey", {}] - # args when ** => ["/projects/foo/", nil, {:files=>"index"}] # Expand out bookmark (if there is one) dir = Bookmarks.expand(dir) @@ -119,6 +150,7 @@ def self.grep dir, regex, options={} files = options[:files] if files + t.reverse_sort = files.slice! /^\*/ # If * at beginning, it means to do reverse sort files = Regexp.new(files, Regexp::IGNORECASE) if files.is_a? String t.file_regex = files end @@ -129,25 +161,27 @@ def self.grep dir, regex, options={} Tree.clear_empty_dirs! list list - end def self.grep_with_hashes path, regex, prepend='##' - Search.append_log path, "- #{prepend}#{regex}/" - View.to_buffer "*tree grep" + Search.append_log path, "- #{prepend}#{regex}" + + View.to_buffer "tree filter" View.dir = Files.dir_of(path) - View.clear; Notes.mode + View.clear + Notes.mode :wrap=>false - if File.directory?(path) - View << "- #{File.expand_path path}/\n - #{prepend}#{regex}/\n" + if File.directory?(File.expand_path path) + View << "#{Files.tilde_for_home path}\n - #{prepend}#{regex}\n" else - dir, name = path.match(/(.+\/)(.+)/)[1..2] - View << "- #{File.expand_path dir}/\n - #{name}\n - #{prepend}#{regex}/\n" + + dir, name = path.match(/(.+)\/(.+)/)[1..2] + View << "#{dir}/\n - #{name}\n - #{prepend}#{regex}\n" end View.to_bottom; Line.previous - self.launch + Launcher.launch end def self.grep_one_file(f, regex, indent) @@ -156,14 +190,13 @@ def self.grep_one_file(f, regex, indent) return result if f =~ /\.(ai|icns|png|gif|jpg|gem|ttf)$/ IO.foreach(f, *Files.encoding_binary) do |line| - # line.gsub!(/[\r\n\c@]+/, '') line.chomp! if regex - next unless line =~ regex + next unless (line =~ regex rescue nil) end result.<< line == "" ? - "#{indent}|" : - "#{indent}| #{line}" + "#{indent}:" : + "#{indent}: #{line}" end result end @@ -188,13 +221,18 @@ def self.filter_one_file file, regex=nil, options={} # If search_outline, we want to put cursor on that line when done # Remove .outline_goto_once part of line after Unified refactor is done - current_line = options[:current_line] || Search.outline_goto_once + current_line = options[:current_line]# || Search.outline_goto_once line_found, matches_count, i = nil, 0, 0 IO.foreach(file, *Files.encoding_binary) do |line| i+=1 + + # Should fix utf error + line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + line.gsub!(/[\r\n\c@]+/, '') + if current_line && line_found.nil? line_found = matches_count if i == current_line end @@ -207,14 +245,16 @@ def self.filter_one_file file, regex=nil, options={} line.sub! /^ > $/, ' >' line = line == "" ? "" : " #{line}" - result << "#{indent}|#{line}" + result << "#{indent}:#{line}" matches_count+=1 end if line_found # If line we were on was found, remember it so we can hililght it when we show the outline options[:line_found] = line_found - Search.outline_goto_once = line_found # Remove after Unified refactor end + + return ["<*"] if result.empty? + result end @@ -225,10 +265,25 @@ def self.skip def grep_inner path, regex, first_time=nil path.sub!(/\/$/, '') - entries = Dir["#{path}/*"].entries.sort + + entries = Dir.glob(["#{path}/*"], File::FNM_DOTMATCH). + select{|i| i !~ /\/\.(\.*|svn|git)$/}.entries#.sort_by{|w| w.downcase} + + # Sort, handling error when certain type of file + entries = entries.sort{|a, b| + a_time = File.mtime(a) rescue nil + b_time = File.mtime(b) rescue nil + next 0 if ! a_time || ! b_time + b_time <=> a_time + } entries = entries.select{|o| o !~ /\/(vendor|log)$/} # Exclude some dirs (why doing it here? + # Sort reverse if flag set + if reverse_sort + entries.reverse! + end + # Exclude some file extensions and dirs # Make these be in config! pass along as instance var # TMP:::: Hard-code skipping image/ dirs (for now) !!!!!!!!!!!!! @@ -290,368 +345,225 @@ def self.define_styles return if ! $el - if Styles.dark_bg? # Bullets - Styles.define :ls_bullet, - :face => 'courier', :size => "+2", # Mac - :fg => "dd7700", :bold => true - else - Styles.define :ls_bullet, - :face => 'courier', :size => "+2", # Mac - :fg => "ff7700", :bold => true - end - + Code.cache(:file_tree_define_styles) do + Styles.define :ls_bullet, :face=>'menlo', :size=>"+2", :fg=>"d70", :bold=>1 - if Styles.dark_bg? - Styles.define :quote_heading_h0, :fg=>"fff", :size=>"+8", :face=>"arial", :bold=>true - Styles.define :quote_heading_h1, :fg=>"fff", :size=>"2", :face=>"arial", :bold=>true - Styles.define :quote_heading_h2, :fg=>"fff", :size=>"-2", :face=>"arial", :bold=>true - Styles.define :quote_heading_pipe, :fg=>"444", :size=>"0", :face => "xiki", :bold=>true - Styles.define :quote_heading_h1_green, :fg=>"8f4", :size=>"2", :face=>"arial", :bold=>true + Styles.define :ls_bullet_darker, :face=>'menlo', :size=>"+2", :fg=>"555", :bold=>nil - Styles.define :quote_heading_bracket, :fg=>"4c4c4c", :size=>"-2", :face=>"arial black", :bold=>true - Styles.define :quote_heading_small, :fg=>"fff", :size=>"-2", :face => "arial black", :bold=>true + Styles.define :quote_heading_h0, :size=>"+8", :face=>"arial"#, :bold=>true + Styles.define :quote_heading_h1, :size=>"2", :face=>"arial"#, :bold=>true + Styles.define :quote_heading_h2, :size=>"-2", :face=>"arial"#, :bold=>true - Styles.define :diff_line_number, :bold => true, :size => "-2", :fg => "444444" - Styles.define :diff_red, :bg=>"400", :fg=>"ee3333", :size=>"-1" - Styles.define :diff_red_pipe, :bg=>"400", :fg=>"771111", :size=>"0", :face=>"xiki", :bold=>true + Styles.define :quote_heading_pipe, :fg=>"555", :size=>"0", :face=>"xiki", :bold=>1 # , :bg=>"unspecified" - Styles.define :diff_green, :bg=>"130", :fg=>"44dd33", :size=>"-1" - Styles.define :diff_green_pipe, :bg=>"130", :fg=>"117719", :size=>"0", :face=>"xiki", :bold=>true + Styles.define :quote_light, :fg=>"333", :size=>"0", :face=>"xiki", :bold=>true - Styles.define :diff_small, :fg => "222", :size => "-11" + Styles.define :quote_heading_h1_green, :fg=>"7c3", :size=>"2", :face=>"arial", :bold=>true - Styles.tree_keys :fg=>"#fff", :underline=>nil + Styles.define :quote_heading_bracket, :fg=>"555", :size=>"-2", :face=>"arial black", :bold=>1 - # dir/ - Styles.define :ls_dir, :fg => "888", :face => "verdana", :size => "-1", :bold => true - else - Styles.define :quote_heading_h0, :fg=>"444", :size=>"+8", :face=>"arial", :bold=>true - Styles.define :quote_heading_h1, :fg=>"444", :size=>"2", :face=>"arial", :bold=>true - Styles.define :quote_heading_h2, :fg=>"aaa", :size=>"-2", :face=>"arial", :bold=>true - Styles.define :quote_heading_pipe, :fg=>"bbb", :size=>"0", :face => "xiki", :bold=>true - Styles.define :quote_heading_h1_green, :fg=>"8f4", :size=>"2", :face=>"arial", :bold=>true - - Styles.define :quote_heading_bracket, :fg=>"bbb", :size=>"-2", :face=>"arial black", :bold=>true - Styles.define :quote_heading_small, :fg=>"fff", :size=>"-2", :face => "arial black", :bold=>true - - Styles.define :diff_line_number, :bold=>true, :size=>"-2", :fg=>"ccc" - Styles.define :diff_red, :bg=>"ffdddd", :fg=>"cc4444", :size=>"-1" - Styles.define :diff_red_pipe, :bg=>"ffdddd", :fg=>"cc4444", :size=>"0", :face=>"xiki", :bold=>true - Styles.define :diff_green, :bg=>"ddffcc", :fg=>"337744", :size=>"-1" - Styles.define :diff_green_pipe, :bg=>"ddffcc", :fg=>"337744", :size=>"0", :face=>"xiki", :bold=>true - Styles.define :diff_small, :fg=>"ddd", :size=>"-11" - - Styles.tree_keys :fg=>"#000", :underline=>1 - - # dir/ - Styles.define :ls_dir, :fg => "777", :face => "verdana", :size => "-1", :bold => true - - end - - Styles.define :quote_medium, :size=>140, :fg=>"666", :bold=>1 + Styles.define :quote_heading_small, :size=>"-2", :face=>"arial black"#, :bold=>true - # ##search/ - Styles.define :ls_search, - :fg => "ff7700", - :face => "verdana", - :size => "-2", - :bold => true - if Styles.dark_bg? # | Quoted text - Styles.define :ls_quote, - :size => "-1", - :fg => "aaa" - else - Styles.define :ls_quote, - :size => "-1", - :fg => "777" - end + Styles.define :diff_line_number, :bold=>true, :size=>"-2", :fg=>"666" + Styles.define :diff_red, :bg=>"400", :fg=>"ee3333", :size=>"-1", :bold=>nil + Styles.define :diff_red_pipe, :bg=>"400", :fg=>"711", :size=>"0", :face=>"xiki", :bold=>true - # 001| Quoted text lines - Styles.define :ls_quote_line_number, - :size => "-4", - :fg => "eee" - - # Highlight in search - Styles.define :ls_quote_highlight, - :size => "-1", - :bg => "ffff44", - :fg => "666666" - end + Styles.define :diff_green, :bg=>"130", :fg=>"4d3", :size=>"-1", :bold=>nil + Styles.define :diff_green_pipe, :bg=>"130", :fg=>"228822", :size=>"0", :face=>"xiki", :bold=>true - def self.apply_styles - $el.el4r_lisp_eval "(setq font-lock-defaults '(nil t))" - # Must go before quotes - if it goes after, it supercedes them - Styles.apply("\\(~\\)\\(.+?\\)\\(~\\)", nil, :quote_heading_bracket, :notes_label, :quote_heading_bracket) - # Styles.apply("\\(https?\\|file\\)://[a-zA-Z0-9\/.~_:;,-]+", :notes_link) # blue-ify url's + Styles.define :wrapper_pattern, :bg=>"330", :fg=>"ec0", :size=>"-1" + Styles.define :wrapper_pattern_pipe, :bg=>"330", :fg=>"770", :size=>"0", :face=>"xiki", :bold=>true - # - bullets - Styles.apply("^[ \t]*\\([+=-]\\)\\( \\)", nil, :ls_bullet, :variable) + Styles.define :diff_small, :fg=>"222", :size=>"-11" - # With numbers - Styles.apply("^ +\\(:[0-9]+\\)\\(|.*\n\\)", nil, :ls_quote_line_number, :ls_quote) - # Path-like lines and parts of lines (make gray) + Styles.define :diff_yellow, :bg=>"950", :fg=>"fa0", :size=>"-1", :bold=>nil + Styles.define :diff_yellow_pipe, :bg=>"950", :fg=>"b80", :size=>"0", :face=>"xiki", :bold=>true - # Single "@" or bullets at beginning - Styles.apply("^[ <+=@-]*\\(@\\)", nil, :ls_dir) # @, <= @, etc. + Styles.tree_letters :underline=>1 + Styles.tree_letters2 :underline=>1 - Styles.apply("^[ \t]*\\(<+=?@?\\)\\( \\)", nil, :ls_bullet, :variable) + Styles.define :ls_dir, :fg=>"999", :face=>"verdana", :size=>"-1", :bold=>true + Styles.define :task_bullet_slash, :fg=>"777", :face=>"verdana", :size=>"-1", :bold=>true + Styles.define :task_bullet, :fg=>"888" - # Remove later? - # Styles.apply("^[ <+=@-]*\\([^|\n]+/\\)$", nil, :ls_dir) # slash at end + Styles.define :quote_medium, :size=>140, :fg=>"666", :bold=>1 - # Slash after almost anything + # ##search/ + Styles.define :ls_search, :fg=>"ff7700", :face=>"verdana", :size=>"-2", :bold=>true - # foo/ lines - Styles.apply("^[ <=+-]*@?\\([~$&#a-zA-Z0-9_,?* ().:;@'-]*[^ \n]\/\\)", nil, :ls_dir) + Styles.define :ls_quote, :size=>"-1", :fg=>"999" + Styles.define :ls_quote_light, :size=>"-1", :fg=>"555", :bg=>"222" - # Covers paths in files by themselves - Styles.apply("^[ <=+-]*\\([@~$&a-zA-Z0-9_,*? ().:;<-]*\/[@\#'$a-zA-Z0-9_,*? ().:;\/<-]+\/\\)", nil, :ls_dir) # Paths with multiple slashes + # 001| Quoted text lines + Styles.define :ls_quote_line_number, :size=>"-4", :fg=>"eee" + # Highlight in search + Styles.define :ls_quote_highlight, :size=>"-1", :bg=>"ffff44", :fg=>"666666" + # Because default color is too dark for some backgrounds + Styles.define :comint_highlight_prompt, :fg=>"#06c" + end - # < next) menus/ - Styles.apply("^[ \t]*[<+-][<+=-]* [a-zA-Z0-9_,? ().:;-]+?[:)] \\(\[.@a-zA-Z0-9 ]+\/\\)", nil, :ls_dir) # label, one word, slash - Styles.apply("^[ \t]*[<+-][<+=-]* [a-zA-Z0-9_,? ().:;-]+?[:)] \\([.@a-zA-Z0-9 ]+\/[.@a-zA-Z0-9 \/]+\/\\)", nil, :ls_dir) # label, one word, path, slash + return if $el.caching + end + def self.apply_styles - # Bullets - Styles.apply("^[ \t]*[+-] [^(\n]+?) \\(.+/\\)$", nil, :ls_dir) # - hey) /what/ - Styles.apply("^[ \t]*[+-] [a-zA-Z0-9_,? ().:;-]+?: \\(.+/\\)$", nil, :ls_dir) # - hey: /what/ + Code.cache(:file_tree_apply_styles) do - # Put this one back? - # Styles.apply("^[ +-]*\\([^|\n]+/\\)$", nil, :ls_dir) # Dirs with bullets + Styles.clear - Styles.apply('\\(https?\\|file\\):/[a-zA-Z0-9\/.~_:;,?%&=|+!-#-]+', :notes_link) # Url + # return if $el.caching + # - bullets... - # :... lines (quotes) - Styles.apply("^ *\\(:\\)\\($\\| .*\n\\)", nil, :quote_heading_pipe, :ls_quote) + # Make "* foo" bullets have gray asterix + Styles.apply("^[ \t]*\\([*+-]\\)\\( \\)", nil, :ls_bullet_darker, :variable) # - fooo, + foo + Styles.apply("^[ \t]*\\([+-]\\)\\( \\)", nil, :ls_bullet, :variable) # - fooo, + foo - # |... lines (quotes) + # ~ foo + # ~ foo/ > exists at end of this method, because it has to override the following ones - # Is this one adding anything? - # Styles.apply("^ *\\(|\\)\\( *\\)", nil, :quote_heading_pipe, :ls_quote) + Styles.apply("^ +\\(:[0-9]+\\)\\(|.*\n\\)", nil, :ls_quote_line_number, :ls_quote) # :NN|foo... used any more? - Styles.apply("^ *\\(|\\)\\(.*\n\\)", nil, :quote_heading_pipe, :ls_quote) + # Path-like lines and parts of lines (make gray) - Styles.apply("^ *\\(|\\)\\(.+?\\)([+-].*[-+])", nil, :quote_heading_pipe, :ls_quote) # quoted lines: beginnings of lines - Styles.apply("^ *|.*([-+].*[+-])\\(.+\\)$", nil, :ls_quote) # quoted lines: ends of lines - Styles.apply("[+-])\\(.*?\\)([+-]", nil, :ls_quote) # quoted lines: between diffs + # Single "@" or bullets at beginning + Styles.apply("^[ <+@-]*\\(=\\)", nil, :ls_dir) # @, <= @, etc. - # | >... headings + Styles.apply("^[ \t]*\\(<+[=|@:*+~>-]?\\)", nil, :ls_bullet) # <<, <=, etc bullets - Styles.apply("^ *\\(|\\|:\\)\\( ?\\)\\(>\\)\\(\n\\| .*\n\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_bracket, :quote_heading_h1) + Styles.apply("^ *@[a-z]+", :ls_dir) # @foo < Username - Styles.apply("^ *\\(|\\)\\( \\)\\(>>\\)\\(\n\\| .*\n\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_bracket, :quote_heading_small) - # | >...! headings - Styles.apply("^ *\\(|\\)\\( ?\\)\\(>\\)\\(.*!\n\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_bracket, :quote_heading_h1_green) + # Slash after almost anything - # >... headings (indented) - Styles.apply("^ +\\(> ?\\)\\(.*\n\\)", nil, :quote_heading_bracket, :quote_heading_h1) - Styles.apply("^ +\\(> ?\\)\\(.*!\n\\)", nil, :quote_heading_bracket, :quote_heading_h1_green) + Styles.apply("^ *\\([ <=|@:*+~-]+ \\)?=?\\([+~!$%^&#a-zA-Z0-9_,?* ().:;@='<>-]*[^ \n]\/\\)", nil, nil, :ls_dir) # foo/ or <=foo/, etc. - # >> - Styles.apply("^ +\\(>>\\)\\(.*\n\\)", nil, :quote_heading_bracket, :quote_heading_h2) - Styles.apply("^ +\\(> ?\\)\\(.*:\n\\)", nil, :quote_heading_bracket, :quote_heading_h0) + Styles.apply("^ *\\([ <=+-]+ \\)?=?\\(\/\\)", nil, nil, :ls_dir) # - / - # |+... diffs - Styles.apply("^ +\\(:[0-9]+\\)$", nil, :ls_quote) - Styles.apply("^ *\\(|\\+\\|:\\+\\)\\(.*\\)", nil, :diff_green_pipe, :diff_green, :face=>"xiki") # whole lines - Styles.apply("^ *\\(|-\\|:-\\)\\(.*\\)", nil, :diff_red_pipe, :diff_red) + # Covers paths in files by themselves + Styles.apply("^ *\\([ <=+-]+ \\)?=?\\([@=~$%&a-zA-Z0-9_,*+? ().^;<>-]*\/[@=\#'$a-zA-Z0-9_,*? ().:;\/<>-]+\/\\)", nil, nil, :ls_dir) # Paths with multiple slashes - Styles.apply("^ *\\(|\\)\\(@@ .*\n\\)", nil, :quote_heading_pipe, :diff_line_number) + # < next) menus/ + Styles.apply("^[ \t]*[<+-][<+=-]* [a-zA-Z0-9_,? ().:;+-]+?[:)] \\(\[.@=a-zA-Z0-9 ]+\/\\)", nil, :ls_dir) # - label) oneword/slash + Styles.apply("^[ \t]*[<+-][<+=-]* [a-zA-Z0-9_,? ().:;+-]+?[:)] \\([.@=a-zA-Z0-9 ]+\/[.@=a-zA-Z0-9 \/]+\/\\)", nil, :ls_dir) # - label) oneword/path/slash - #Styles.apply('^[ -]*\\([ a-zA-Z0-9\/_\.$-()]*\\w/\\)$', nil, :ls_dir) # Most dirs ? Has ")" at end - Styles.apply('^ *\\(//?\\)$', nil, :ls_dir) # / - Styles.apply('^ *\\(\./\\)$', nil, :ls_dir) # ./ - end + # Bullets + Styles.apply("^[ \t]*[+-] [^(\n]+?) \\(.+/\\)$", nil, :ls_dir) # - hey) /what/ + Styles.apply("^[ \t]*[+-] [a-zA-Z0-9_,? ().:;-]+?: \\(.+/\\)$", nil, :ls_dir) # - hey: /what/ - def self.apply_styles_at_end - Styles.apply('^ *[+-] \\(##.*/\\)$', nil, :ls_search) # ##_/ - Styles.apply('^ *\\([+-] \\)?\\(@f/.*/\\)$', nil, nil, :ls_search) # ##_/ - Styles.apply('^ *[+-] \\(\*\*.*/\\)$', nil, :ls_search) # **_/ - Styles.apply('^ *\\([+-] \\)?\\(@n/.*/\\)$', nil, nil, :ls_search) # ##_/ - end + Styles.apply('\\(https?\\|file\\|xiki\\|source\\):/[a-zA-Z0-9\/.~_:;,?%&*=|+!@#()-]+', :notes_link) # http://url.com + Styles.apply('^[a-z][a-z.]+@[a-z][a-z.]+[a-z] ', :notes_link) # email@address.com Message to user - def self.handles? list=nil - if list.nil? - list ||= Tree.construct_path(:list=>true) rescue nil # Use current line by default - end + Styles.apply("^ *\\([`!:]\\)\\($\\| .*\n\\)", nil, :quote_heading_pipe, :ls_quote) # :... lines (quotes) + Styles.apply("^ *\\([\\\\]\\)\\($\\| .*\n\\)", nil, :quote_heading_pipe, :default) # :... lines (quotes) - list = list.first if list.is_a?(Array) - return 0 if self.matches_root_pattern?(Line.without_label :line=>list) - nil - end + # |... lines (quotes) - def self.matches_root_pattern? item - item =~ /^\/(\w|$)/ || # /... or / - item =~ /^\$\w/ || - item =~ %r"^(~/|\.\.?/|\$\w)" - end + Styles.apply("^ *\\(|\\)\\(.*\n\\)", nil, :quote_heading_pipe, :ls_quote) # |... lines (quotes) - # Remove this after Unified refactor? - # - # Open the line in the tree that the cursor is on. This is probably - # be mapped to C-. . - # TODO: remove ignore_prefix, and just use Keys.clear_prefix - def self.open options={} - # Ol["deprecated after Unified refactor"] - # raise "deprecated after Unified refactor - shouldn't be used!" + # | hey : you (makes colon big) + Styles.apply("^ *\\(|\\)\\( .* \\)\\(:\\)\\( .*\n\\|$\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_pipe, :ls_quote) - # If passed just a dir, open it in new view - return self.ls :dir=>options if options.is_a? String + Styles.apply("^ *\\(|\\)\\(.+?\\)([+-].*[-+])", nil, :quote_heading_pipe, :ls_quote) # quoted lines: beginnings of lines + Styles.apply("^ *|.*([-+].*[+-])\\(.+\\)$", nil, :ls_quote) # quoted lines: ends of lines + Styles.apply("[+-])\\(.*?\\)([+-]", nil, :ls_quote) # quoted lines: between diffs - original_file = View.file_name + Styles.apply("^ *\\([|:]\\)\\([()].*\n\\)", nil, :quote_heading_pipe, :ls_quote_light) # |(... or :(... lines + Styles.apply("^ *\\(||\\)\\(.*\n\\)", nil, :quote_heading_pipe, :ls_quote) # ||... or ::... lines - path = options[:path] || Tree.construct_path(:list=>true) - path_orig = path + # | >... headings + Styles.apply("^ *\\([=|:]\\)\\( ?\\)\\(>\\)\\(\n\\| .*\n\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_bracket, :quote_heading_h1) - # Split off quoted |... string if there... + # | >> + Styles.apply("^ *\\(|\\)\\( \\)\\(>>\\)\\(\n\\| .*\n\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_bracket, :quote_heading_small) + # | >...! headings + Styles.apply("^ *\\(|\\)\\( ?\\)\\(>\\)\\(.*!:?\n\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_bracket, :quote_heading_h1_green) - if path.is_a?(Array) - quote_string = path.pop[/\|(.*)/, 1] if path.last =~ /^\|/ - quote_string.sub! /^[ +-]/, '' if quote_string # Should start with space or -|+ - path = path.grep(/^[^|]/).join('') # Discard rest of |... lines - else - path =~ /(.*?)-?\+?\|(.*)/ - path, quote_string = $1, $2 if $2 - end + # |+... diffs + Styles.apply("^ +\\(:[0-9]+\\)$", nil, :ls_quote) + Styles.apply("^ *\\([|:]\\+\\)\\(.*\\)", nil, :diff_green_pipe, :diff_green, :face=>"xiki") # :+... + Styles.apply("^ *\\([|:]-\\)\\(.*\\)", nil, :diff_red_pipe, :diff_red) # :-... + Styles.apply("^ *\\([|:]\\?\\)\\(.*\\)", nil, :diff_yellow_pipe, :diff_yellow, :face=>"xiki") # :?... - # Pull number off end of path if there - path =~ /(.+):(\d+)$/ - path, line_number = $1, $2 if $2 + Styles.apply("^ *\\(|:\\)\\(.*\\)", nil, :wrapper_pattern_pipe, :wrapper_pattern, :face=>"xiki") # |:... - path = Bookmarks.expand(path) - return Files.open_in_os(path) if Keys.prefix == 0 + # ::... draw attention to lines with extra colon > in shell output, it means line's expandable + Styles.apply("^ *\\(::\\)\\(.*\n\\)", nil, :quote_heading_pipe, :ls_quote) # ::... - remote = self.is_remote?(path) - unless remote - path = File.expand_path(path) - end - # Prefix keys with specific behavior - prefix_was_u = false + Styles.apply("^ *\\([|:]\\)\\(@@ .*\n\\)", nil, :quote_heading_pipe, :diff_line_number) - prefix = Keys.prefix + Styles.apply('^ *\\(//?\\)$', nil, :ls_dir) # / + Styles.apply('^ *\\(\./\\)$', nil, :ls_dir) # ./ - case prefix - when :u # Just open file - prefix_was_u = true - Keys.clear_prefix - when "update" # Save ($ave) file - return self.save_quoted path - when "delete" # Save ($ave) file - return self.delete_file - when "all" - Keys.clear_prefix - quote_string ? # If quote, enter lines under - Tree.enter_under : - self.enter_lines(//) # If file, enter all lines - return - else - if prefix =~ /\boutline\b/ - prefix = Keys.prefix_n - Keys.clear_prefix - return self.drill_quotes_or_enter_lines path, quote_string, :prefix=>prefix - end - end + # Has to be at bottom, to override other styles... - # If numeric prefix, jump to nth window... - if (! options[:ignore_prefix]) and Keys.prefix_n and Keys.prefix != 7 - # If number larger than number of windows, open new one first - if Keys.prefix_n > View.list.size - View.to_nth(View.list.size - 1) - View.create - end - View.to_nth(Keys.prefix_n - 1) - end + # Styles for option bullets + Styles.apply("^[ \t]*\\([~^]\\)\\( \\)\\(.*\\)", nil, :ls_bullet_darker, :variable, nil) # "~ foo" or "^ foo" + Styles.apply("^[ \t]*\\([~*^]\\)\\( \\)\\(.*\/$\\)", nil, :ls_bullet_darker, :variable, :task_bullet_slash) # ~ foo/ (similar but bold) - # If there was a quoted string, remember the column... - column = View.column - if quote_string - column -= (Line.value[/^.*?\|./] || '').length - column = 0 if column < 0 + Styles.apply("^[ \t]*\\(<-\\|->\\) ", nil, :ls_bullet_darker) # "<- foo" or "-> foo" - ControlTab.dash_prefix_buffer = View.name # Remember where we were so subsequent 8+Tab can continue on from here end + end - # Open or go to file... - - if remote - self.remote_file_contents(path) # Get text from server and insert + def self.apply_styles_at_end + Styles.apply('^[ =]*\\([+-] \\)?\\(##.*/?\\)$', nil, nil, :ls_search) # ##_/ + Styles.apply('^[ =]*\\([+-] \\)?\\(\\*\\*.*/?\\)$', nil, nil, :ls_search) # **_/ - else # Normal file opening + Styles.apply('^#:.*', :ls_search) # #:foo - # See if it exists, and store contents if not? + Styles.apply('^ *\\([+-] \\)?\\(@f/.*/\\)$', nil, nil, :ls_search) + Styles.apply('^ *[+-] \\(\*\*.*/\\)$', nil, :ls_search) # **_/ + Styles.apply('^ *\\([+-] \\)?\\(@n/.*/\\)$', nil, nil, :ls_search) + end - # TODO: why are we using Location.go - isn't it just delegating to View.open? - # Does it do extra stuff? Maybe goes somewhere if dir? - # Try just calling View.open instead! + # FileTree.handles? "%foo" + # FileTree.handles? "/a" + # FileTree.handles? "~/a" + # FileTree.handles? "./a" + # FileTree.handles? "foo" + def self.handles? list=nil - if options[:other_view] - View.open path, {:other_view=>1}.merge(prefix == :- ? {:no_recenter=>1} : {}) - else - options[:same_view] ? View.open(path, :same_view=>true) : Location.go(path) - end - Effects.blink(:what=>:line) unless line_number || quote_string || path =~ /\.xiki$/ + if list.nil? + list ||= Tree.construct_path(:list=>true) rescue nil # Use current line by default end - return unless line_number || quote_string - - if line_number # If line number, go to it - $el.goto_line line_number.to_i - View.column = column - Effects.blink(:what=>:line) - elsif quote_string - - # Search for quoted |... string if it passed... - - Hide.reveal if View.hidden? - - found = View.to_quote quote_string - - unless found # If not found, suggest creating or show error - - return if self.suggest_creating quote_string - - View.beep - View.message "Didn't find: \"#{quote_string.strip}\"", :beep=>1 - return - end - - $el.beginning_of_line - - $el.recenter(0) unless prefix_was_u || options[:no_recenter] - View.column = column + list = list.first if list.is_a?(Array) - Effects.blink(:what=>:line) + return 0 if self.matches_root_pattern?(Line.without_label :line=>list) + nil + end - dir, name = path.match(/(.+\/)(.+)/)[1..2] + # FileTree.matches_root_pattern? "%f" + # FileTree.matches_root_pattern? "%f/blaa" + def self.matches_root_pattern? item - return if original_file == "search_log.notes" + item == "." || # just ~ or . + item == "~" || # just ~ or . + item =~ /^\/(\w|$)/ || # /... or / + item =~ /^%\w/ || # %bookmark - # Add to log - Search.append_log dir, "- #{name}\n | #{quote_string}" - end + item =~ %r"^(~/|\.\.?/)" end def self.suggest_creating quote_string - if quote_string =~ /^ *def self\.(.+)/ # If it's a method, suggest creating it Code.suggest_creating_method View.file, $1 return true @@ -703,8 +615,8 @@ def self.save_quoted path def self.save path, options # If no prefix, prompt to save (file must not exist since we were called) - if options[:prefix] != "update" - return if ! View.confirm "Create file #{path} ?" + if options[:prefix] != "update" && options[:task] != ["save"] && options[:task] != ["create"] + return "* create" end txt = Tree.siblings(:quotes=>1, :string=>1) @@ -733,7 +645,7 @@ def self.save path, options DiffLog.save_diffs :patha=>path, :textb=>txt File.open(path, "w") { |f| f << txt } - "@flash/- saved!" + "<* - saved!" # - Possible long-term better solution > do later # - if ! options[:prompt_response] @@ -776,7 +688,7 @@ def self.move_or_delete_via_diffs # Save current version to temporary place, and diff tmp_path = "/tmp/saved.txt" $el.write_region nil, nil, tmp_path - diff = Console.sync %`diff --old-group-format="d%df %dn%c'\012'" --new-group-format="a%dF %dN%c'\012'" --unchanged-line-format="" "#{View.file}" "#{tmp_path}"` + diff = Shell.sync %`diff --old-group-format="d%df %dn%c'\012'" --new-group-format="a%dF %dN%c'\012'" --unchanged-line-format="" "#{View.file}" "#{tmp_path}"` added, deleted = DiffLog.parse_tree_diffs diff @@ -810,12 +722,12 @@ def self.move_or_delete_via_diffs operations[0].each_with_index do |o, i| delete = operations[1][i] command = "mv \"#{delete}\" \"#{o}\"" - Console.sync command, :dir=>"/tmp/" + Shell.sync command, :dir=>"/tmp/" end else operations[1].each do |o| command = "rm \"#{o}\"" - Console.sync command, :dir=>"/tmp/" + Shell.sync command, :dir=>"/tmp/" end end @@ -849,7 +761,7 @@ def self.drill_quote path # If indented exactly 2 under, add it if current_indent == indent - candidate = "| #{line}\n" + candidate = ": #{line}\n" # If child of candidate, append candidate if one needs appending elsif current_indent == indent + 1 @@ -865,8 +777,7 @@ def self.drill_quote path result end - # Goes through files in reverse order and deletes empty dirs - def self.select_next_file + def self.select_next_file options={} $el.search_forward_regexp /[^\/]$/ $el.beginning_of_line $el.skip_chars_forward " " @@ -898,7 +809,10 @@ def self.ls options={} View.insert "\n\n"; backward_char 2 end end - dir = View.expand_path(dir) # Expand out ~ + + + # Removed this > might cause problems + # If file, back up to dir dir = Bookmarks.dir_only dir unless File.directory?(dir) # If dir or recursive @@ -911,7 +825,7 @@ def self.ls options={} if options[:open_in_bar] || options[:here] # Don't upen buffer else - View.to_buffer "*tree #{name}" + View.to_buffer "#{name}/" View.clear View.dir = Bookmarks.dir_only dir Notes.mode @@ -925,18 +839,14 @@ def self.ls options={} $el.goto_char left self.select_next_file # Start incremental search - Tree.search(:recursive => true, :left => left, :right => right) - #self.draw(dir) + Tree.filter(:recursive=>true, :left=>left, :right=>right) else # Insert with linebreak if file if File.file?(dir) - #insert dir View.insert self.filename_to_next_line(dir) # Add linebreak before filename - #insert dir.sub(/(.+)\//, "\\1/\n ") # Add linebreak before filename open_line 1 - #Tree.search Line.to_words - Tree.search(:left => Line.left, :right => Line.left(2)) + Tree.filter(:left => Line.left, :right => Line.left(2)) return end bullet = "" @@ -953,26 +863,9 @@ def self.ls options={} def self.open_in_bar options={} prefix = Keys.prefix :clear=>1 - # If numeric prefix, open nth thing in tree if prefix and prefix != :u and !(options[:ignore_prefix]) View.flash "- Don't know what this was supposed to do!" - - # Remember original view - # start = $el.selected_window - # Open tree (ignoring prefix) - # self.open_in_bar# :ignore_prefix=>true - # Find nth file in tree - # View.to_highest - - # prefix.times do - # re_search_forward "^ +[a-zA-Z0-9_.-]+$" - # end - # Go to next line if comment - - # Line.next if Line.next_matches(/^ *\|/) - # Line.to_beginning - # self.open :ignore_prefix=>true return end @@ -980,25 +873,23 @@ def self.open_in_bar options={} View.bar end View.to_nth 0 - $el.find_file Bookmarks.expand("$t") + $el.find_file Bookmarks.expand("%n") only_one_view_in_bar = prefix == :u only_one_view_in_bar = ! only_one_view_in_bar if @@one_view_in_bar_by_default - unless only_one_view_in_bar # Unless u prefix, open $tl as well (under bar) # If 2nd view isn't at left margin, open 2nd view if View.left_edge(View.list[1]) != 0 - View.create + View.create_horizontal end View.to_nth 1 - # If 2nd view isn't $f, open it - if $el.buffer_file_name( $el.window_buffer( View.list[1] ) ) != Bookmarks["$f"] - $el.find_file Bookmarks["$f"] + # If 2nd view isn't :n, open it + if $el.buffer_file_name( $el.window_buffer( View.list[1] ) ) != Bookmarks["%links"] + $el.find_file Bookmarks["%links"] end - View.to_nth 0 end @@ -1006,29 +897,34 @@ def self.open_in_bar options={} # Creates tree snippet of text in file def self.snippet options={} + txt = options[:txt] || View.selection file = options[:file] || View.file # Remove linebreak from end txt = txt.sub(/\n\z/, "") if file - txt = "#{File.dirname(file)}/\n - #{File.basename(file)}\n" + - txt.gsub(/^/, " | "). - gsub(/^ \| $/, " |") # Remove trailing spaces on blank lines + dir = File.dirname file + dir = Files.tilde_for_home dir + + txt = "#{dir}/\n - #{File.basename(file)}\n" + + txt.gsub(/^/, " : "). + gsub(/^ \: $/, " :") # Remove trailing spaces on blank lines "#{txt}\n" else - # "- From #{buffer_name}:\n" + txt.gsub(/^/, " #") - "- From #{View.name}:\n" + txt.gsub(/^/, " | ") + "- From #{View.name}:\n" + txt.gsub(/^/, " : ") end end # Recursively display dir in tree # Insert dir contents at point (usually in existing tree) def self.dir options={} + return Files.open_in_os(Tree.construct_path) if Keys.prefix == 0 Tree.plus_to_minus_maybe Line.to_left prefix = Keys.prefix + if prefix == 8 || prefix == "all" self.dir_recursive elsif prefix == "delete" @@ -1065,7 +961,7 @@ def self.dir_recursive $el.goto_char left # isearch_forward self.select_next_file - Tree.search(:recursive => true, :left => left, :right => right) + Tree.filter(:recursive => true, :left => left, :right => right) end # New Unified way to show list of dirs. @@ -1075,9 +971,10 @@ def self.expand_dir_recursively options indent = "" - # Get tree + # Get file tree t = self.new - dir.sub!(/\/$/, '') + dir.sub!(/\/$/, '') if dir != "/" + t.traverse Bookmarks.expand(dir) # Adjust indent @@ -1088,34 +985,28 @@ def self.expand_dir_recursively options end - def self.dir_one_level options={} Line.to_left - line = Line.value - indent = line[/^ */] + " " # Get indent + indent = "#{Line.indent} " dir = Bookmarks.expand(Tree.construct_path) - remote = self.is_remote?(dir) - unless remote - # If C-2, just open in dired - if Keys.prefix == 2 - # TODO: Open in 1st window - View.to_after_bar - $el.find_file dir - return - end + # If C-2, just open in dired + if Keys.prefix == 2 + # TODO: Open in 1st window + View.to_after_bar + $el.find_file dir + return end dirs, files = self.files_in_dir(dir, options) # Get dirs and files in it if files.empty? && dirs.empty? if ! File.exists? dir # If doesn't exist, show message - return Tree << " - - @mkdir/ - | ...dir '#{dir}' doesn't exist. Create it? - " + return Tree << "* create dir/" + # - =mkdir/ + # | ...dir '#{dir}' doesn't exist. Create it? end View.flash "- Directory is empty"#, :times=>2 Move.to_end @@ -1126,21 +1017,18 @@ def self.dir_one_level options={} # Change path to proper indent dirs.collect!{|i| i.sub(/.*\/(.+)/, "#{indent}+ \\1/")} - - # Change path to proper indent files.collect!{|i| i.sub(/.*\/(.+)/, "#{indent}+ \\1")} Line.next - left = $el.point + left = View.cursor # Move .notes files to top - files = files.select{|i| i =~ /\.notes$/} + files.select{|i| i !~ /\.notes$/} both = options[:date_sort] || options[:size_sort] ? files + dirs : dirs + files View.insert(both.join("\n") + "\n") - right = $el.point - $el.goto_char left + right = View.cursor + View.cursor = left Line.to_beginning @@ -1150,36 +1038,33 @@ def self.dir_one_level options={} Color.mark "light" end - Tree.search(:left=>left, :right=>right, :always_search=>true) + Tree.filter(:left=>left, :right=>right, :always_search=>true) end # New Unified way to show one dir. def self.expand_one_dir options - dir = options[:file_path] - dirs, files = self.files_in_dir dir # Get dirs and files in it + dir = options[:file_path] + dirs, files = self.files_in_dir dir, options # Get dirs and files in it if files.empty? && dirs.empty? if ! File.exists? dir # If doesn't exist, show message - return " - - @mkdir/ - | ...dir '#{dir}' doesn't exist. Create it? - ".unindent + return "* create dir/" end - return "@flash/- Directory is empty" + + return "<* Directory is empty" end - # Add bullets and slashes + # For now, try bullets for both dirs.collect!{|i| i.sub(/.*\/(.+)/, "+ \\1/")} files.collect!{|i| i.sub(/.*\/(.+)/, "+ \\1")} - # Move .notes files to top - files = files.select{|i| i =~ /\.notes$/} + files.select{|i| i !~ /\.notes$/} + # Figure out > why no :date_sort when normal dir expand - both = options[:date_sort] || options[:size_sort] ? - files + dirs : dirs + files - result = both.join("\n") + "\n" + # Temporarily always do dirs first > see how it feels + both = files + dirs + result = both.join("\n") + "\n" # TODO How to deal with focus? - return option telling them line to put cursor on - or, maybe, make that happen somehow on the other side of the call? # if options[:focus] @@ -1192,54 +1077,16 @@ def self.expand_one_dir options end - # def self.enter_snippet - # start = selected_window - # snippet = self.snippet - # self.open_in_bar - - # loc = Location.new - # Line.to_left - # #orig = $el.point - - # # If at indented line - # while(Line.matches(/^ /)) - # Line.previous - # end - - # # Check to see if it's the same file - # if snippet[/.+\n.+\n/] == buffer_substring($el.point, Line.left(3)) - # Line.next 2 - # View.insert "#{snippet.sub(/.+\n.+\n/, '')}\n" - # # Check to see if it's just in same dir - # elsif snippet[/.+\n/] == buffer_substring(point, Line.left(2)) - # Line.next - # View.insert "#{snippet.sub(/.+\n/, '')}\n" - # else - # View.insert "#{snippet}\n" - # end - - # loc.go - # #goto_char orig - - # select_window(start) - # end - - # Enter tree of selection at the spot (saved by AS). - def self.enter_at_spot - snippet = self.snippet - Location.jump("0") - View.insert snippet - Location.save("0") # So any more will be entered after - end # Enter what's in clipboard with | to on the left margin, with appropriate indent - def self.enter_quote txt=nil + def self.enter_quote txt=nil, options={} prefix = Keys.prefix :clear=>1 # Skip forward if on heading Line.to_left - if Line[/^\|/] + + if Line[/^[:|]/] Move.to_end $el.newline end @@ -1248,7 +1095,8 @@ def self.enter_quote txt=nil dir = Tree.construct_path rescue nil - if self.dir? && self.handles? # If current line is path + # Current line is dir path... + if self.dir? && self.handles? Tree.plus_to_minus_maybe indent = Line.indent dir = Tree.construct_path @@ -1256,7 +1104,6 @@ def self.enter_quote txt=nil t = t.gsub(/^#{dir}/, '') if t.sub!(/\A\n/, '') # If no dir left, indent one over t.gsub!(/^ /, '') - # t = t.gsub(/^ /, '') end Tree.add_pluses_and_minuses t, '-', '-' Line.next @@ -1264,18 +1111,19 @@ def self.enter_quote txt=nil return end - # If empty line, just enter tree... + # Empty line, so just enter tree... if Line.blank? - if prefix == :u || txt =~ /\A +[-+]?\|[-+ ]/ # If C-u or whole thing is quoted already, unquote - txt = txt.split("\n").grep(/\|/).join("\n") - return $el.insert(txt.gsub(/^ *[-+]?\|([-+ ]|$)/, "")) # Remove | ..., |+...., |, etc + if prefix == :u || txt =~ /\A *[-+]?[:|][-+ ]/ # If C-u or whole thing is quoted already, unquote + txt = txt.split("\n").grep(/:|/).join("\n") + return View.insert(txt.gsub(/^ *[:|]([-+ ]|$)/, "")) # Remove | ..., |+...., |, etc end - start = $el.point + start = View.cursor txt = Clipboard.get("=") indent = prefix || 0 # Indent prefix spaces, or 2 + txt = txt.gsub(/^/, " " * indent) View.insert txt @@ -1284,28 +1132,45 @@ def self.enter_quote txt=nil return end + # Dash+, so start with blank... ?? + # Line has content, so indent under... - txt = txt.unindent # Unindent - # TODO: don't unindent if up+? + txt = txt.unindent unless options[:leave_indent] indent = Line.indent # Get current indent - on_quoted_line = Line.matches /^ +\|/ + + target_quote = Line[/\A *([|:!])/, 1] + existing_quote = txt[/\A *([|:!])/, 1] + Line.next - # Indent one level further unless on comment already - unless on_quoted_line + if existing_quote && ! options[:leave_quote_chars] + txt.gsub!(/^ *[|:!] ?/, '') + end + + if ! target_quote + # Indent one level further unless on comment already indent = "#{indent} " end indent += " " * Keys.prefix_or_0 # If numeric prefix, add to indent txt = txt.sub /\n+\z/, '' # Remove last \n - # Quote unless already quoted - quote = txt =~ /^\|/ ? '' : "| " + quote = target_quote || existing_quote + quote ||= ':' if options[:char] || prefix == :u || prefix == :- + quote ||= '|' + + quote = options[:char] if options[:leave_quote_chars] - txt = txt.gsub /^/, "#{indent}#{quote}" - txt = txt.gsub /^( *\|) $/, "\\1" # Remove blank lines + txt = txt.gsub /^/, "#{indent}#{quote} " + txt = txt.gsub /^( *[|:#]) $/, "\\1" # Remove trailing spaces + + if prefix == :- + View.insert "#{indent}#{quote} \n" + Move.backward + return + end View.insert "#{txt}\n" end @@ -1314,36 +1179,7 @@ def self.enter_as_search indent = Line.indent Move.to_end View.insert "\n#{indent} - ###{Clipboard["0"]}/" - Launcher.launch_unified - end - - # Remove the following lines indented more than the current one - - # Expand if dir, or open if file - def self.launch options={} - line = Line.value - - indent = Line.indent - list = nil - path = Tree.construct_path # Get path - - without_label = Line.without_label - if Launcher.wrapper path # run files followed by slash as menus - # Do nothing if it returned true - elsif without_label =~ /^ *\$ / # $ shell command inline (sync) - Console.launch :sync=>true - elsif without_label =~ /^ *%( |$)/ # % shell command (async) - Console.launch_async - elsif without_label =~ /^ *&( |$)/ # % shell command in iterm - Console.launch_async :iterm=>1 - elsif line =~ /^[^|\n]* (\*\*|##)/ # **foo or ## means do grep - self.grep_syntax indent - elsif self.dir? # if has slash - foo/ is a dir (if no | before) - self.dir - else - options[:other_view] = 1 if Keys.prefix == :- - self.open options - end # => {:no_recenter=>1} + Launcher.launch end # Grabs matching lines in file and starts hide search @@ -1356,8 +1192,9 @@ def self.outline_pattern extension=nil, options={} "^\\s*(task|def|class) " elsif extension == "js" "(^ *(function)| = function\\()" - elsif extension =~ /^notes|deck$/ - "^[\\|>]( |$)" + elsif extension =~ /^notes|deck|xiki$/ + # "^[\\|>]( |$)" + "^>( |$)" else "^[^ \\t\\n]" end @@ -1368,12 +1205,10 @@ def self.outline_pattern extension=nil, options={} # # ...and @outline________ # - # > Don't insert, just return + # Don't insert, just return # p FileTree.enter_lines /foo/, :just_return=>1, :path=>View.file def self.enter_lines pattern=nil, options={} - $xiki_no_search = false - # If prefix is 6, delegate to Git to enter methods by date if options[:prefix] == 6 txt = Git.methods_by_date(Tree.path[-1]).join("\n") @@ -1382,7 +1217,7 @@ def self.enter_lines pattern=nil, options={} end # If dir, delegate to C-. (they meant to just open it) - return Launcher.launch_unified if self.dir? + return Launcher.launch if self.dir? Tree.plus_to_minus @@ -1392,15 +1227,21 @@ def self.enter_lines pattern=nil, options={} if options[:path] options[:path] else - bm = Keys.input(:timed => true, :prompt => "Enter bookmark to show outline for: ") - File.expand_path Bookmarks.expand(bm, :just_bookmark => true) + bm = Keys.input(:timed=>true, :prompt=>"Enter bookmark to show outline for: ") + if bm == "." + View << "outline/" + Launcher.launch + return + end + + File.expand_path Bookmarks.expand(bm, :just_bookmark=>1) end #Files.directory? Bookmarks.expand("h", :just_bookmark=>true) # If it's a dir, delegate to Open Tree - if path =~ /\/$/ - self.ls :here=>true, :dir => path + if File.directory? path + self.ls :here=>true, :dir=>path, :recursive=>1 return end @@ -1428,9 +1269,10 @@ def self.enter_lines pattern=nil, options={} matches = "" indent_more = ' ' + # commit: Works? Get enter+lines to work with remote files if path =~ /^\/\w+@/ contents = Remote.file_contents path - Tree.under contents, :escape=>'| ', :no_slash=>1 + Tree.under contents, :escape=>': ', :no_slash=>1 return end @@ -1454,10 +1296,31 @@ def self.enter_lines pattern=nil, options={} line_found = matches_count if i == current_line end + # .rb or .js file, so extract embedded xiki notes > To show in outline > "> ...." + + if ["rb", "js"].member?(extension) && line =~ /^ *(#|\/\/) > ./ + matches_count += 1 + if line =~ /^ *(#|\/\/) > \./ + heading = line[/\.(.+)/, 1] + path = Notes.heading_to_path heading + next matches << " + #{path}\n" + end + heading = line[/>.+/] + next matches << " #{heading}\n" + end + next unless line =~ pattern + line = line == "" ? "" : " #{line}" line.sub! /^ > $/, ' >' - matches << "#{indent}#{indent_more}|#{line}\n" + quoted_line = "#{indent}#{indent_more}:#{line}\n" + + # If :no_dups and a dup, skip + if options[:no_dups] && matches.end_with?(quoted_line) + next + end + + matches << quoted_line matches_count+=1 end @@ -1473,11 +1336,11 @@ def self.enter_image image tmp_dir = "/tmp/insert_image" Dir.mkdir tmp_dir if ! File.exists? tmp_dir - width, height = Console.sync("identify \"#{image}\"").match(/(\d+)x(\d+)/)[1..2] + width, height = Shell.sync("identify \"#{image}\"").match(/(\d+)x(\d+)/)[1..2] max = 300 if width.to_i > max || height.to_i > max dest = tmp_dir+"/"+File.basename(image).sub(".", "_#{max}.") - Console.sync %`convert "#{image}" -resize #{max}x#{max} "#{dest}"`, :dir=>"/tmp" + Shell.sync %`convert "#{image}" -resize #{max}x#{max} "#{dest}"`, :dir=>"/tmp" else dest = image end @@ -1492,20 +1355,20 @@ def self.enter_image image def self.file_not_found_from_template path - # If .../menu/foo.rb + # If .../roots/foo.rb - if path =~ /.*\/menu\/(.+)\.rb$/ # " + if path =~ /.*\/roots\/(.+)\.rb$/ # " name = $1 - txt = File.read "#{Xiki.dir}/etc/templates/menu_template.rb" + txt = File.read "#{Xiki.dir}/misc/templates/menu_template.rb" elsif path =~ /.*\/(.+)_spec\.rb$/ # " name = $1 - txt = File.read "#{Xiki.dir}/etc/templates/template_spec.rb" + txt = File.read "#{Xiki.dir}/misc/templates/template_spec.rb" else name, extension = path.match(/([^\/]+)\.(.+)$/)[1..2] rescue [nil, nil] extension = path.match(/[^\/]+\.(.+)$/)[1] rescue nil - [File.expand_path("~/xiki_stuff/templates/"), Bookmarks["$x/etc/templates"]].each do |dir| + [File.expand_path("~/xiki_stuff/templates/"), File.expand_path("~/.xiki/misc/templates")].each do |dir| file = "#{dir}/template.#{extension}" next unless File.exists? file txt = File.read file @@ -1516,13 +1379,13 @@ def self.file_not_found_from_template path if txt View.flash "- File doesn't exist, start with this...", :times=>4 txt.gsub!(/\{\{(.+?)\}\}/) { eval $1 } # Expand {{these}} - Xiki.dont_search - return Tree.<< txt, :escape=>"| ", :no_slash=>1 + raise "- Commenting this could cause problems!!!" + return Tree.<< txt, :escape=>": ", :no_slash=>1 end # If no templates matched - View.flash "- File doesn't exist!", :times=>2 - Tree.<< "|", :no_slash=>1 + View.flash "- __File doesn't exist!", :times=>2 + Tree.<< ":", :no_slash=>1 Move.to_end View << ' ' end @@ -1535,35 +1398,41 @@ def self.tree options={} options.merge!(:recursive=>1) if prefix == :u # If up+, expand dir recursively - if prefix == :uu # If up+up+, insert /the/path// - Line << "#{Bookmarks["$#{Keys.input(:timed=>1)}"]}/" - Launcher.go_unified + if prefix == :- # If up+up+, insert /the/path// + Line << "#{Bookmarks["%#{Keys.input(:timed=>1)}"]}/" # " + Launcher.launch_or_collapse return end - $xiki_no_search = false + dir = + if options[:dir] + options[:dir] + else - bm = options[:bm] || Keys.input(:timed=>true, :prompt=>"Enter bookmark to show tree of: ") + bm = options[:bm] || Keys.input(:timed=>true, :prompt=>"Enter bookmark to show tree of: ") - options.merge!(:focus=>View.file_name) if bm == "." - dir = Keys.bookmark_as_path :bm=>bm, :include_file=>1 + options.merge!(:focus=>View.file_name) if bm == "." + bm = Keys.bookmark_as_path :bm=>bm, :include_file=>1 # Change . to current dir, etc + Files.tilde_for_home bm + end # If file, delegate to FileTree.enter_lines... - if File.file? dir - return FileTree.enter_lines nil, :path=>dir + # commit: open+bookmark+slash + dir = "/" if dir == :slash + + if File.file? File.expand_path(dir) + # Commenth this out because of with+file > Might cause problem when inserting all? + + History.open_current :file=>dir, :outline=>1 + + return end # If dir, delegate to .ls... - # Put back slash if was dir - dir = Bookmarks.dir_only dir - dir << "/" unless dir =~ /\/$/ - return if dir == :bookmark_doesnt_exist - dir = "/" if dir == :slash - dir = Bookmarks.dir_only(dir) if options[:recursive] options.merge!(:dir=>dir) self.ls options @@ -1582,42 +1451,30 @@ def self.parent parent end - # Returns a regexp to match the acronym - def self.is_remote? path - return path =~ /^\/\w+@/ - end - # Returns the files in the dir + # FileTree.files_in_dir "/tmp/d", :date_sort=>1 def self.files_in_dir dir, options={} - if self.is_remote?(dir) - self.remote_files_in_dir(dir) - else - dir = FileTree.add_slash_maybe dir - all = Dir.glob("#{dir}*", File::FNM_DOTMATCH). - select {|i| i !~ /\/\.(\.*|svn|git)$/}. # Exclude some dirs (exclude entensions here too?) - select {|i| i !~ /\/\.#/}.sort - - if options[:date_sort] - all = all.sort{|a,b| File.mtime(b) <=> File.mtime(a)} - elsif options[:size_sort] - all = all.sort{|a,b| File.size(b) <=> File.size(a)} - end + dir = FileTree.add_slash_maybe dir - dirs = all.select{|i| File.directory?(i)}#.sort - files = all.select{|i| File.file?(i)}#.sort - [dirs, files] - end - end + all = Dir.glob("#{dir}*", File::FNM_DOTMATCH). + select {|i| i !~ /\/\.(\.*|svn|git|DS_Store)$/}. # Exclude some dirs (exclude entensions here too?) + select {|i| i !~ /\/\.#/} - def self.remote_file_contents file - path, file = file.match(/(.+\/)(.+)/)[1..2] - Remote.dir path, file # Delegate to Remote.dir - end + # Sort by date, size, or alphabetical... + + all = all.sort{|a, b| + a_time = File.mtime(a) rescue nil + b_time = File.mtime(b) rescue nil + next 0 if ! a_time || ! b_time + b_time <=> a_time + } + + return all if options[:merged] + + dirs, files = all.partition{|f| File.directory?(f)} + + [dirs, files] - def self.remote_files_in_dir dir - res = Remote.dir(dir) - res.map!{|i| "#{dir}#{i}"} - [res.select{|i| i =~ /\/$/}.map{|i| i.sub(/\/$/, '')}, res.select{|i| i !~ /\/$/}] end def self.url_from_path verb, path @@ -1651,19 +1508,20 @@ def self.tree_path_or_this_file dir_only=false Tree.construct_path : View.file - # path = Files.just_dir(path) if dir_only + path = Bookmarks[path] + if dir_only path = path =~ /\/$/ ? path : File.dirname(path)+"/" end path end - def self.do_create_dir + # Grab file path on current line (climbs tree if there is one). + # Returns nil if none or not a file + def self.grab_file_on_line + return nil if ! self.handles? - path = self.tree_path_or_this_file :dir_only - - `mkdir -p "#{path}"` - View.flash "- Created: #{path}" + Tree.construct_path end # Indent txt to be one level lower than current line @@ -1672,18 +1530,21 @@ def self.one_view_in_bar_by_default= to end def self.copy_path options={} - Effects.blink :what=>:line + # Return dir of view's file if at left margin, U, or not ^[|@-] - if Line !~ /^ +[|@+-]/ # || Keys.prefix_u# It will never be :u, because the prefix is cleared before this is called + if Line !~ /^ *[~\/|=+-]/ path = View.file else - path = Xiki.trunk.last # Grab from wiki tree + path = Tree.path.last # Grab from wiki tree path.sub! /\/$/, '' if Line.value !~ /\/$/ # If current line doesn't have trailing slash, remove it end + FileTree.extract_filters! path + View.flash "- copied: #{path}", :times=>2 - return Clipboard["0"] = path + Clipboard["0"] = path + nil end # Adds extra line if we're at the end of the file. @@ -1695,74 +1556,20 @@ def self.extra_line_if_end_of_file end end + # Add slash to the variable def self.add_slash_maybe dir dir =~ /\/$/ ? dir : "#{dir}/" end - def self.grep_syntax indent - Tree.plus_to_minus_maybe - dir = Tree.construct_path - - raw = Tree.construct_path(:labels=>true, :list=>true) - files = contents = nil - - if raw.join('') =~ /\*\*(.+)##(.+)/ # *foo means search in files - files, contents = $1, $2.sub!(/\/$/, '') - elsif raw.last =~ /\*\*(.+)/ # *foo means search in files - files = $1 - elsif raw.last =~ /##(.+)/ # ##foo means search in filenames - contents = $1.sub!(/\/$/, '') - if raw[-2] =~ /\*\*(.+)/ # If prev is **, use it - files = $1 - elsif raw[-2] =~ /(.+[^\/])$/ - - # If file.foo/##, search in the one file... - - contents = Regexp.new(contents, Regexp::IGNORECASE) - list = self.filter_one_file Bookmarks.expand(dir), contents, :indent=>" " - end - end - - - files.sub!(/\/$/, '') if files - options = {} - options.merge!({:files => files}) if files - - unless list # If not already gotten - Search.append_log dir, "- ###{contents}/" - - # Do actual search... - - list = self.grep dir, contents, options - list.shift # Pull off first dir, so they'll be relative - end - Line.to_next - - left = $el.point - tree = list.join("\n") + "\n" - View.insert tree.gsub(/^/, indent) - right = $el.point - $el.goto_char left - - # Do appropriate incremental search... - - if Line.matches(/^\s*$/) # Do nothing - elsif Line.matches(/^\s+\|/) - if Search.outline_goto_once # If we want to go back to a line - Line.next Search.outline_goto_once - Search.outline_goto_once = nil - end - - Tree.search :left=>left, :right=>right - elsif contents # If we searched for something (and it wasn't just the dir) - Move.to_quote - Tree.search :left=>left, :right=>right, :recursive_quotes=>true - else - Move.to_junior - Tree.search :left=>left, :right=>right, :recursive=>true + # Add slash to the variable + def self.add_slash_maybe! dir + if dir !~ /\/$/ + dir.replace "#{dir}/" end + dir end + def self.move_dir_to_junior Keys.prefix_times.times do orig = Location.new @@ -1805,35 +1612,29 @@ def self.dir? txt=nil end # Restructuring for Unified... - def self.delete_file path - - # in_file_being_deleted = Line !~ /^[ +-]/ || Keys.prefix_u - - # If not on tree, must want to delete this file - # if in_file_being_deleted - # path = View.file - # else - # path = Tree.construct_path - # end - - # path = View.expand_path path + def self.cd path + Shell.cd path + View.flash "- current dir set", :times=>1 + end + def self.delete_file path, options={} - # Still has dependencies on editor. First step for if we want to delete - # from a non-editor client - make below editor lines just not run when - # not :client =~ ^editor\b. + # Still has dependencies on editor. First step for if we want to delete + # from a non-editor client - make below editor lines just not run when + # not :client =~ ^editor\b. return View.flash("- File doesn't exist: #{path}", :times=>5) if ! File.exists?(path) - View.flash "- Delete file for sure?" - answer = Keys.input :chars=>1, :prompt=>"For sure delete?: #{path}" #" - - return unless answer =~ /y/i + if ! options[:no_prompt] + View.flash "- Delete file for sure?" + answer = Keys.input :chars=>1, :prompt=>"For sure delete?: #{path}" #" + return unless answer =~ /y/i + end executable = File.directory?(path) ? "rm -r" : "rm" command = "#{executable} \"#{path}\"" - result = Console.run command, :sync=>true + result = Shell.run command, :sync=>true if (result||"").any? View.beep View.message "#{result}" @@ -1842,17 +1643,19 @@ def self.delete_file path # Eventually only do if :client =~ ^editor\b - Tree.kill_under + Tree.collapse Effects.glow :fade_out=>1 Line.delete Line.to_beginning + # Remember to delete again when C-. pressed... + View.message "File deleted" end def self.move_latest_screenshot_to dest_path, dest_dir - desktop = Bookmarks['$dt'] + desktop = Bookmarks['%dt'] latest_screenshot = `ls -t #{desktop} "Screen shot*"`[/Screen shot .*/] @@ -1860,12 +1663,25 @@ def self.move_latest_screenshot_to dest_path, dest_dir command = "mv \"#{desktop}#{latest_screenshot}\" \"#{dest_path}\"" - result = Console.run command, :sync=>true + result = Shell.run command, :sync=>true View.message result end def self.move_to - self.copy_to :move=>1 + self.copy_to(:move=>1) + end + + def self.move_up + # Moves line to above all siblings + prefix = Keys.prefix + line, column, indent = View.line, View.column, Line.indent + + txt = Line.delete + Search.backward "^#{indent.sub ' ', ''}[^\t \n]" + Line << "\n#{txt}" + Line << "\n" if prefix == :u + View.line, View.column = line+1, column + nil end def self.copy_current_file_to options={} @@ -1878,7 +1694,6 @@ def self.copy_current_file_to options={} verb = options[:move] ? "Move" : "Copy" dest_path = Keys.bookmark_as_path :prompt=>"#{verb} current file to which bookmark? " return if ! dest_path.is_a? String - # dest_path = File.dirname dest_path dest_path = Bookmarks[dest_path] @@ -1886,7 +1701,7 @@ def self.copy_current_file_to options={} command = "#{executable} \"#{source_path}\" \"#{dest_path}\"" - result = Console.run command, :sync=>true + result = Shell.run command, :sync=>true return View.beep result if (result||"").any? # If output isn't as expected, beep and show error @@ -1903,15 +1718,15 @@ def self.copy_current_file_to options={} def self.copy_to options={} prefix = options[:prefix] || Keys.prefix - Keys.clear # Error if not in a file tree return self.copy_current_file_to options if ! FileTree.handles? dest_path = Tree.construct_path + dest_path = Bookmarks[dest_path] - dest_dir = dest_path.sub(/(.+\/).*/, "\\1") + dest_dir = dest_path.sub(/(.+\/).*/, "\\1") # /dir/file -> /dir/ slash = dest_dir =~ /\/$/ dest_dir = File.expand_path dest_dir dest_dir = "#{dest_dir}/" if slash @@ -1922,7 +1737,7 @@ def self.copy_to options={} end arg = options[:move] ? {:delete=>1} : {} - source_path = Tree.dir_at_spot arg + source_path = Tree.file_at_spot arg stem = File.basename source_path # Cut of path of source indent = Line.indent @@ -1954,7 +1769,7 @@ def self.copy_to options={} command = "#{executable} \"#{source_path}\" \"#{dest_dir}#{command_dest_stem}\"" - result = Console.run command, :sync=>true + result = Shell.run command, :sync=>true if (result||"").any? # If output isn't as expected, beep and show error View.beep @@ -1972,46 +1787,44 @@ def self.copy_to options={} Effects.glow :fade_in=>1 Line.previous if prefix == 1 end + + if ! dest_is_dir # If copying to existing file, do something visually distinctive + View.flash options[:move] ? "- moved!" : "- copied!" + end end - def self.rename_file - # If dired mode, use wdired - return $el.wdired_change_to_wdired_mode if $el.elvar.major_mode.to_s == "dired-mode" + # Just puts "=rename/" underneath item the cursor is on... + def self.command_on_filename command - column = View.column + Tree.<< "#{Line.indent}= #{command}/", :no_slash=>1, :no_search=>1 + Move.to_end - if ! Line[/^ *[+-] /] # Error if not indented and ^/- / - View.beep - return View.message "TODO: implement renaming current file?" - end - source_path = Tree.construct_path - is_dir = source_path =~ /\/$/ - source_path.sub! /\/$/, '' + return "" - new_name = Keys.input( - :prompt=>"Rename #{source_path} to what?: ", - :initial_input=>(Keys.prefix_u? ? File.basename(source_path) : "") - ) + # Old implementation > it gave just the extension as a starting point - dest_path = "#{source_path.sub(/(.+\/).+/, "\\1#{new_name}")}" + # Duplicate line, putting =rename after indent... - command = "mv \"#{source_path}\" \"#{dest_path}\"" + line = Line.value - Console.run command, :sync=>true + has_dot = line =~ /\./ + has_dot ? + line.sub!(/^( *)([+-] )?.+(\..+)/, "\\1=#{command}/\\3") : + line.sub!(/^( *)([+-] )?.+/, "\\1=#{command}/") - indent = Line.indent - Effects.glow :fade_out=>1 - Line.delete - View.insert((is_dir ? "#{indent}- #{new_name}/\n" : "#{indent}+ #{new_name}\n"), :dont_move=>true) + Tree.<< line, :no_slash=>1, :no_search=>1 + Move.to_end + Search.backward("\\.") if has_dot # Move cursor to before extension + + "" - View.column = column - Effects.glow :fade_in=>1 end def self.open_as_upper where=false orig_u = Keys.prefix_u view = View.current - path = Tree.construct_path(:list=>true) + path = Tree.path[-1] + FileTree.extract_filters! path if where == :lowest Move.to_window 9 @@ -2029,43 +1842,102 @@ def self.open_as_upper where=false where ? View.next : View.previous end - self.open :path=>path, :same_view=>true + Launcher.open_file path View.to_window(view) if orig_u end def self.to_outline options={} - prefix = Keys.prefix :clear=>true - extension = View.extension + prefix = options[:prefix] || Keys.prefix(:clear=>true) - if (prefix == :u || prefix == :-) && ! Notes.enabled? - args = [View.txt, View.line] - end - current_line = Line.number - mode = View.mode + # @x/x/x view, so use "@u/..." to show outline... + + name = View.name + + if xikihub_view = name[/^xiki\.\w+\/(@.+)/, 1] + Launcher.open xikihub_view + return + end path = View.file - View.to_buffer("*tree outline") - View.clear; Notes.mode if path dir, file = path.match(/(.+\/)(.+)/)[1..2] - txt = "- #{dir}\n - #{file}\n" + extension = File.extname(path).sub(".", "") + end + + link_filename = Notes.expand_link_file(path) + + if ! options[:not_topic_outline] + + # In a ~/xiki/foo.xiki file, so expand topic instead of showing file outline... + + if extension == "xiki" && dir == File.expand_path("~/xiki")+"/" || link_filename + + file = link_filename if link_filename # Use link > if it exists + topic = File.basename(file, ".*").gsub("_", " ") + Line.value + View.name + + # Jump up to heading and grab it, so we can move the cursor to it + + cursor = View.cursor + Line.to_right + Search.backward "^> " + original_heading = Line.value + + # Remove @ or @foo + TopicExpander.delete_username_from_heading original_heading + + View.cursor = cursor + + Launcher.open topic, :bar_is_fine=>1, :original_heading=>original_heading, :prefix=>prefix + return + + elsif extension == "xiki" + + # .xiki file in other dir, so use "/dir/foo//" for outline + + path = Files.tilde_for_home path + + path = path.sub(/\.xiki$/, "//") + + Launcher.open path, :bar_is_fine=>1 + return + end + end + + if (prefix == :u || prefix == :-) && ! Notes.enabled? + args = [View.txt, View.line] + end + current_line = Line.number + mode = View.mode + + View.to_buffer("outline/") + View.clear + Notes.mode + + View.wrap :off + + if path + txt = "#{dir}\n - #{file}\n" View.insert txt View.to_top self.select_next_file else - View.insert "- buffer/\n" + View.insert "buffer/\n" View.to_top end case prefix when 2 # Prompt to add @... menu under file Move.to_end - Xiki.insert_menu + Launcher.insert_menu + when 3 # Prompt to add @... menu under file + Line << "\n =outline/history/" + return when 4 # Get ready to run $... shell command on file $el.delete_char(1) View << "$ " - ControlLock.disable when 5 # Unique self.enter_lines nil, :current_line=>current_line, :unique=>1 when 6 # List methods by date (use 'git blame') @@ -2073,21 +1945,30 @@ def self.to_outline options={} when 7 Move.to_end View << "\n @info" - Launcher.launch_unified + Launcher.launch when 8 Launcher.enter_all + + when 9 + self.enter_lines(/^> (feature|important) > /i, :current_line=>current_line) + when 0 # Just show path if 0+... - when :- # Just show # foo... comments... - self.enter_lines(/(^ *(def|function) |(^| )(#|"|\/\/) .+\.\.\.$)/, :current_line=>current_line) - when :u + when :u # Just show # foo... comments... + if mode == :notes + self.enter_lines /^> .*:$/, options.merge(:current_line=>current_line) + return + end + # when :uu # Just show # foo... comments... + self.enter_lines(/(^ *(def|function) |(^| )(#|>|"|\/\/) .+\.\.\.$)/, :current_line=>current_line) + when :- # Show outline down to current line if mode == :notes self.enter_lines /^> .*:$/, options.merge(:current_line=>current_line) return end txt, line = Search.deep_outline *args - Tree.<< txt, :line_found=>line, :escape=>'| ', :no_slash=>1 + Tree.<< txt, :line_found=>line, :escape=>': ', :no_slash=>1 else self.enter_lines nil, options.merge(:current_line=>current_line) end @@ -2099,42 +1980,76 @@ def self.skip_dirs dir, skip_these (@skip ||= {})[dir] = skip_these end - - def self.expands? options + def self.expands? options #> nil return unless options[:file_path] (options[:expanders] ||= []).push self end def self.expand options - prefix = options[:prefix] - file_path = options[:file_path] + # Ctrl+X, so delegate to extension notes + if options[:ctrlx] + return TopicExpander.expand_by_extension options + end + + #> Might cause problems > added items to file path! - Path.unescape! file_path + prefix, task = options[:prefix], options[:task] + file_path = options[:file_path] - # If as+delete... + #> Might cause problems > added items to file path! - if prefix == "delete" - return self.delete_file file_path + if items = options[:items] + file_path << items.join("/") end # If ##foo/ or **foo/ filter at end (removing all as we check)... - filters_at_end = self.extract_filters! file_path # Removes ## and ** filters, returning any that were at the end - - return options[:output] = self.expand_filter(filters_at_end, options) if filters_at_end + # Removes ## and ** filters, returning any that were at the end #> ["##dir, name"] + filters_at_end = self.extract_filters! file_path - # If it's a $... shell command... + # If right-click, set prefix to item... - if file_path =~ %r"^(/[\w./ -]+/)([$%&]) (.+)" # If /foo// or //foo - options.merge!(:dir=>$1, :prompt=>$2, :command=>$3) - return options[:output] = self.expand_shell(options) + if filters_at_end + txt = self.expand_filter(filters_at_end, options) #> {:client=>"editor/emacs", :target_view=>"tree filter", :dir=>"/Users/craig/Dropbox/xiki/", :file_path=>"/Users/craig/Dropbox/xiki/", :expanders=>[Xiki::FileTree], :expanders_index=>0, :no_slash=>1} + return options[:output] = txt end # If quoted line, pull it off into :quote... + # Handling |... like a quote > If it has a quote > Ask for @replace... here?! + # "@replace/pipe as quote" + + # If a linebreak (means pipes?), tell them to use colons... + + # > Todo > Don't complain if linebreaks are nested under heading! + + if options[:client] =~ /^editor/ && Path.unescape(file_path) =~ /\n/ + + # Multi line items underneath embedded xiki commands are allowed + file_with_embedded, args = self.extract_prepended_file_path file_path + + # if ! args || Path.split(args[0]) =~ /\n/ + # return options[:output] = " + # > Use colons, not pipes + # : Use colons at the beginnings of lines to navigate + # : to those lines in files, not pipes. (Colon-quoted lines under + # : files now mean to navigate.) In a future Xiki version, + # : pipe-quoted lines under files will mean to over-write the file + # : contents with the pipe lines. + # " + # end + + end + + # Maybe only do if client is editor + # And maybe check actual line? + + # Where path coming from > why not escaped the second time + if quote = Path.extract_quote(file_path) ControlTab.dash_prefix_buffer = View.name + # options[:quote] = Path.unescape(quote) options[:quote] = quote end @@ -2142,47 +2057,277 @@ def self.expand options options[:line_number] = line_number end - # If it's a file... + # If it's an existing file... if File.file? file_path options[:no_slash] = 1 + extension = File.extname file_path + + # Task, so return or process the task... - return options[:output] = self.filter_one_file(file_path).join("\n") if prefix == "outline" + if task + if options[:quote] + # : foo/~ save, so do save + return options[:output] = self.save(file_path, options) if task == ["save"] + # : foo, so show "~ save/" + return options[:output] = "* save" + end - return options[:output] = Tree.quote(File.read(file_path, *Files.encoding_binary)) if prefix == "all" - return options[:output] = self.save(file_path, options) if prefix == "update" && options[:quote] + # File tasks menu... + + menu = %` + * search + ! Search.enter_search + * contents + ! txt = File.read options[:file_path] + ! txt.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + ! Tree.quote txt + * outline + ! FileTree.filter_one_file(options[:file_path]).join("\\n") + + * file/ + + rename + ! FileTree.command_on_filename "rename" + + copy + ! FileTree.command_on_filename "copy" + + copy group + ! siblings = Tree.siblings + ! Clipboard["0"] = "{\#{siblings.join(",")}}" + ! "" + + prompt here + ! Tree.<< "$ ", :no_search=>1, :no_slash=>1 + ! Move.to_end + + + touch + ! #options[:file_path] + ! FileUtils.touch options[:file_path] + ! "<* - touched file" + + bookmark + ! Bookmarks.save + + restore version + ! latest_file = History.latest_file File.basename(options[:file_path]) + ! "todo > implement this > replace latest file to it if exists > probably confirm first" + * edit with/ + + sublime + ! Shell.command "subl \#{options[:file_path]}" + ! Ol "options[:file_path]", options[:file_path] + ! "" + + vim + ! DiffLog.quit_and_run "vim \#{options[:file_path]}" + ! "" + + emacs + ! # Probably delete -Q? + ! DiffLog.quit_and_run "emacs -nw \#{options[:file_path]}" + + default editor + ! DiffLog.quit_and_run "\#{FileTree.default_external_editor} \#{options[:file_path]}" + ! # DiffLog.quit_and_run "\#{ENV['EDITOR']} \#{options[:file_path]}" + + xsh + ! Ol.a options + ! "- todoz!" + ! View.open options[:file_path] + * delete + ! Keys.remember_key_for_repeat(proc {Launcher.launch :task=>["delete"], :no_prompt=>1}) + ! FileTree.delete_file options[:file_path], options + * path to clipboard + ! FileTree.copy_path + * with path/ + `.unindent + + clipboard_menu = self.clipboard_menu + + menu.sub!("* with path/\n", clipboard_menu) + + menu = Xik.new menu + + task[0] = "* #{task[0]}" if task[0] + + result = menu.expand task, :eval=>options + return options[:output] = result || "" + + end # If editor, tell it to open the file... - if options[:client] =~ /^editor\// - txt = "@open file/#{file_path}" + if options[:client] =~ /^editor\b/ + txt = "=open file/#{file_path}" + txt << "/:#{options[:line_number]}" if options[:line_number] # If a quote, pass line number after a colon! - txt << "/|#{options[:quote]}" if options[:quote] # If a quote, pass line number after a colon! + + # Should be this! + txt << "/|#{Path.escape options[:quote]}" if options[:quote] # If a quote, pass line number after a colon! + return options[:output] = txt end # If API, return contents... + + options[:returned_file_contents] = 1 return options[:output] = File.read(file_path) # If not editor, |... will be ignored for now. Any reasons would api pass this in? Probably to create non-existant files? Maybe grab lines indented under it? Maybe return line number of it? end - # If a dir... + # If an existing dir... if File.directory? file_path - # TODO: maybe deprecate C-8 prefix for this, only make it enter+all ("all") - if prefix == 8 || prefix == "all" - return options[:output] = self.expand_dir_recursively(options) + FileTree.add_slash_maybe! file_path + + if task + + require "#{Xiki.dir}roots/sample_menus/sample_menus_index.rb" # if !defined?(SampleMenus) + + if task == ["rake"] + options[:nest] = 1 + options[:no_task] = 1 + rake = Shell.command("rake -T -A", :dir=>file_path) + rake.gsub!(/ $/, '') + return options[:output] = Tree.quote(rake) + elsif task[0] == "rake" + rake_task = task[1][/rake (\w+)/, 1] + return DiffLog.quit_and_run "rake #{rake_task}" + end + + # Dir tasks menu... + + # ~ examples/ + # ! FileTree.example_commands options + # menu = Xik.new menu + # ~ modified date sort + # ! FileTree.dir :date_sort=>true + # ! "" + menu = %` + * search + ! Tree.<< "- ##", :no_search=>1 + ! #View.column = -1 + ! Line.to_right + ! "" + * all files + ! FileTree.expand_dir_recursively :file_path=>options[:file_path] + * delete + ! Keys.remember_key_for_repeat(proc {Launcher.launch :task=>["delete"], :no_prompt=>1}) + ! FileTree.delete_file options[:file_path], options + + * recent commands/ + ! options[:nest] = 1 + ! options[:no_task] = 1 + ! txt = Shell.external_plus_sticky_history + * recent in dir/ + ! options[:nest] = 1 + ! options[:no_task] = 1 + ! Shell.history options[:file_path] + * file/ + + rename + ! FileTree.command_on_filename "rename" + + prompt here + ! Tree.<< "$ " + ! Line.to_right + ! "" + + bookmark + ! Bookmarks.save + + + cd + ! FileTree.cd options[:file_path] + + touch + ! FileUtils.touch options[:file_path] + ! "<* - touched file" + + + filenames search + ! ControlLock.disable + ! Tree.<< "- **/", :no_search=>1 + ! View.column = -1 + ! "" + + all contents + ! txt = FileTree.grep(options[:file_path], "").join("\\n") + ! txt.sub(/.+\\n/, "") # Delete 1st line > the redundant dir + + + exit and cd + ! Shell.exit_and_cd options[:file_path] + + make/ + + some files and dir + ! options[:no_search] = 1 + ! File.write "\#{options[:file_path]}/a.txt", "aaa\\naaa\\n" + ! File.write "\#{options[:file_path]}/b.txt", "bbb\\nbbb\\n" + ! File.write "\#{options[:file_path]}/c.xiki", "> heading\\nccc\\nccc\\n\\n> .another\\n! args\\n" + ! Dir.mkdir("\#{options[:file_path]}/d") rescue nil + ! File.write "\#{options[:file_path]}/d/d.txt", "ddd\\nddd\\n" + ! "- a.txt\\n- b.txt\\n- c.xiki\\n- d/\\n - d.txt" + + a few files + ! options[:no_search] = 1 + ! File.write "\#{options[:file_path]}/a.txt", "aaa\\naaa\\n" + ! File.write "\#{options[:file_path]}/b.txt", "bbb\\nbbb\\n" + ! File.write "\#{options[:file_path]}/c.xiki", "> heading\\nccc\\nccc\\n\\n> another\\nc c c\\n" + ! "- a.txt\\n- b.txt\\n- c.xiki" + + txt + ! "+ foo.txt\\n\#{Tree.quote SampleMenus.by_extension("txt"), :indent=>" "}" + + rb + ! "+ foo.rb\\n\#{Tree.quote SampleMenus.by_extension("rb"), :indent=>" "}" + + py + ! "+ foo.py\\n\#{Tree.quote SampleMenus.by_extension("py"), :indent=>" "}" + + js + ! "+ foo.js\\n\#{Tree.quote SampleMenus.by_extension("js"), :indent=>" "}" + + dir + ! "+ foo/" + + * path to clipboard + ! FileTree.copy_path + * with path/ + `.unindent + + menu << "\n" + + # Append dir tasks... + + clipboard_menu = self.clipboard_menu + menu.sub!("* with path/\n", clipboard_menu) + + # / and :mouse, so return whole menu... + + # return options[:output] = menu.txt_without_code if task == [] && options[:mouse] + + # /something, so expand menu... + + task[0] = "* #{task[0]}" if task[0] + menu = Xik.new menu + result = menu.expand task, :eval=>options + + options[:nest] = 1 if [["* dir"], ["* more"]].member?(task) + + return options[:output] = result || "" + + # If no output, it should just continue on... + # Might cause problems? + end + # No task, so just expand dir... + return options[:output] = self.expand_one_dir(options) end - # File doesn't exist... - if options[:file_path] =~ /\/$/ - return options[:output] = self.suggest_mkdir(file_path) + # A file or dir that doesn't exist?... + + # Parent is a file, so must be a file note + + ancestor_file = Files.ancestor_file_or_directory file_path + + # if File.file? File.dirname(file_path) + + if File.file? ancestor_file + return options[:output] = TopicExpander.expand_by_extension(options) #> ||| end + + # ~ create dir, so create it... + + if task == ["create dir"] + FileUtils.mkdir_p options[:file_path] + return options[:output] = "<* created!" + end + + # Non-existing dir (whether task or not), so show "* create dir"... + + return options[:output] = "* create dir/" if options[:file_path] =~ /\/$/ + # Quote means to create the file (with or without "update" prefix)... return options[:output] = self.save(file_path, options) if options[:quote] @@ -2190,9 +2335,30 @@ def self.expand options # File path (no quote)... # If enter+all, do what? - return options[:output] = "@flash/- file doesn't exist!" if prefix == "all" || prefix == "outline" + if prefix == "all" || prefix == "outline" + options[:no_slash] = 1 + return options[:output] = ": file doesn't exist!" + end + + # task, so show options for non-existant file... - options[:output] = "@open file/#{file_path}" # So just open the new file + return self.tasks_for_new_file options if task + + # if task == "" + # return options[:output] = "~ create/" + # elsif task == "create" + # return options[:output] = " + # : Content of the + # : file to create... + # " + # end + + # Not existing file or dir, so just open new file + + # If .rb file with embedded xiki notes, Expand them + return if self.expand_embedded_notes options #> || + + options[:output] = "=open file/#{file_path}" # What about when :client !~ ^editor # - can probably assume there won't be an update prefix @@ -2200,32 +2366,351 @@ def self.expand options end - def self.suggest_mkdir file_path - " - @mkdir/ - | Dir '#{file_path}' doesn't exist. Create it? - ".unindent + # + # > Test .extract_prepended_file_path + # ! p FileTree.extract_prepended_file_path "/tmp/dir/foo.rb/> Something/else" + # : ["/tmp/dir/foo.rb", "> Something/else"] + # + # ! p FileTree.extract_prepended_file_path "/tmp/dir/foo.txt/> Something/else" + # : nil + # + def self.extract_prepended_file_path file_path + + # See if the path is a foo.js|rb plus other stuff (args) + + extensions = [".rb", ".js"] + extensions_regex = "(?:#{extensions.join('|').gsub(".", "\\.")})" + + match = file_path.match(/^(.+?\w#{extensions_regex})\/(.+)/) + + return if ! match + + file_with_embedded, args = match[1..2] end + # + # > Test .expand_embedded_notes + # ! FileTree.expand_embedded_notes options + # + def self.expand_embedded_notes options + + # For now, just assume .rb or .js files + + + + # > Todo > extract > to self.extract_prepended_file_path + file_with_embedded, args = self.extract_prepended_file_path options[:file_path] + + return false if ! file_with_embedded + + args = Path.split args + + # Do nothing if no existing rb file + + # Return false if we didn't handle + return false if ! File.exists? file_with_embedded + + # Extract xiki notes from file + + txt = File.read file_with_embedded + txt.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + txt = self.extract_embedded_notes txt + + # Expand action if it is one... + + options_in = {} + + txt = Notes.drill txt, args[0], options_in #> ||| + + # Ctrl+X, so just navigate + if options[:ctrlx] || (args[0] =~ /^>/ && args[1] =~ /\n/) + + # Handle navigating manually + View.open file_with_embedded + heading = options_in[:heading_found] + + Search.forward "^ *\\(#\\|//\\) #{Search.quote_elisp_regex heading}", :from_top=>1, :beginning=>1 + + return true + end + + options_in[:items] = args + Tree.unquote(txt) + + if args[0] !~ /^>/ + txt = TopicExpander.expand_action(Tree.unquote(txt), nil, options_in) + end - def self.expand_shell options options[:no_slash] = 1 - Console.shell_command_per_prompt options[:prompt], options + options[:output] = txt + + # Return true since we handled + return true + + end + + + # + # > Next steps? + # - Maybe a little of this + # - Maybe a little of that + # + # > .Test .extract_embedded_notes + # ! txt = " + # ! Some code + # ! # > Hi + # ! # Hello + # ! ".unindent + # ! FileTree.extract_embedded_notes txt + # + # > .Test .extract_embedded_notes on javascript + # ! txt = " + # ! Some code + # ! // > Hi + # ! // Hello + # ! ".unindent + # ! FileTree.extract_embedded_notes txt + # + def self.extract_embedded_notes txt + + txt = txt.split("\n") + + result, accumulating = "", false + txt.each do |line| + if line =~ /^ *(#|\/\/) > ./ + # Heading line, so start accumulating + accumulating = true + elsif line =~ /^ *(#|\/\/)/ + # Comment line, so keep accumulating if doing so already + else + # Other line, so stop accumulating + accumulating = false + end + + result << "#{line.sub(/^ *(#|\/\/) ?/, '')}\n" if accumulating + end + result + + end + + + def self.expand_file_extension_options command + + Tree << "= #{command}" + + # foo/bar, so just insert and run + if command =~ /\// + return Launcher.launch + end + + # Put just file options under + + txt = Xiki[command] + + file_options = txt.scan(/> \(file option\) .+/).join("\n") + file_options.downcase! + + file_options.gsub!(/^.+?\) /, '') + file_options.gsub!(/^\./, '+ ') + Tree.<< file_options, :hotkey=>1 + + # Todo > Tree.filter on items + # Maybe use + + end + + def self.clipboard_to_here options={} + + clipboard = Clipboard[0] + kind = FileTree.clipboard_file_kind! clipboard + + source = File.expand_path clipboard + dest = Tree.path[-1] + dest = File.expand_path dest + FileTree.extract_filters! dest + + # Move or copy file... + + if options[:move] + FileUtils.mv source, dest + else + FileUtils.cp_r source, dest + end + + # Dest was dir, so show stem of source... + # Dest was file, so just flash copied... + + past_tense = options[:move] ? "moved" : "copied" + + dest =~ /\/$/ ? "- #{File.basename source}" : "<* - #{past_tense} here" + + end + + # Returns "dir", "file", or "" if neither. + # Also trims and unbookmarks the arg. + def self.clipboard_file_kind! clipboard + return "" if ! clipboard + + return "" if clipboard =~ /\n/ + clipboard.strip! + return "" if ! FileTree.matches_root_pattern? clipboard + + # Do based on what's in clipboard + # Make items based on whether path has file, dir, or neither... + + # Hmm > how about when > file doesn't exist? + # Just look at path > ends in slash? + + clipboard.replace Bookmarks[clipboard] + + clipboard =~ /\/$/ ? "dir" : "file" + end + + + def self.clipboard_file_diff + + clipboard = Clipboard[0] + + kind = FileTree.clipboard_file_kind! clipboard + return "- Not implemented yet > diffing dir!" if kind != "file" + + source = File.expand_path clipboard + dest = Tree.path[-1] + dest = File.expand_path dest + FileTree.extract_filters! dest + + DiffLog.diff_files dest, source, :diffs_only=>1 + + end + + def self.clipboard_menu + + clipboard = Clipboard[0] + + # See whether clipboard holds dir or file + kind = self.clipboard_file_kind! clipboard + return "" if kind == "" + + # Here so we can search for it + # + move file here, + copy file here + # + move dir here, + copy dir here + clipboard_menu = " + * with path/ + + move #{kind} here + ! FileTree.clipboard_to_here :move=>1 + + copy #{kind} here + ! FileTree.clipboard_to_here :copy=>1 + + + move to #{kind} + + copy to #{kind} + + + diff + ! FileTree.clipboard_file_diff + ".unindent + + # Todo + # Not a file or dir, so show nothing + + clipboard_menu + end + + + def self.suggest_mkdir file_path + "* create dir/" end + def self.tasks_for_new_file options + + options[:no_slash] = 1 + task = options[:task] + + file_path = options[:file_path] + + extension = File.extname(file_path)[/\w+/] + + + if task == [] + # Special treatment for .rb > 2 options + return options[:output] = "* create script\n* create class" if extension == "rb" + + return options[:output] = "* create" + end + + # Special treatment for .rb... + + case "#{extension}/#{task[0]}" + # when "rb/script" # The default behavior is fine + when "rb/create class" + clazz = TextUtil.camel_case File.basename options[:file_path], ".*" + return options[:output] = Tree.quote(%` + class #{clazz} + def self.menu *args + " + hello + " + end + end + `) + end + + # No extension, so look up by the full name... + + if ! extension + file_name = File.basename file_path + + # Todo > move this out into a file + # file format: + # Dockerfile/ + # | FROM ubuntu:latest + # | RUN apt-get update + # Rakefile/ + # | require 'rake' + # | ... + txt = { + "Dockerfile"=>%` + # Sample Dockerfile from xsh + FROM ubuntu:latest + RUN apt-get update + RUN apt-get install -y ruby + RUN apt-get install -y nodejs npm + RUN sudo gem install sinatra + + # add application sources + COPY . /app + RUN cd /app; npm install + + # Expose the default port + EXPOSE 5000 + + # Start command + CMD ["nodejs", "/app/web.js"] + `.unindent + }[file_name] + + return options[:output] = Tree.quote(txt) if txt + end + + require "#{Xiki.dir}roots/sample_menus/sample_menus_index.rb" # if !defined?(SampleMenus) + txt = SampleMenus.by_extension(extension) + + return options[:output] = Tree.quote(txt) + end # Removes all ##.../ and **.../ strings. Returns those that # are at the end. # - # FileTree.extract_filters("/projects/foo/##hey/") - def self.extract_filters! file_path + # FileTree.extract_filters!("/projects/foo/##hey/") + # FileTree.extract_filters!("/projects/foo/**ls/bar.txt") + def self.extract_filters! file_path # Clean ##... and **... out of the path + patterns_at_end = [] # Treat \/ as part not a slash delimiter (gsub it to 021 temporarily) file_path.gsub!("\\/", "\021") # While a (rightmost) filter exists - while file_path =~ %r"^([^|]*/)((##|\*\*).+?/)" + + while file_path =~ %r"^([^|]*/)((##|\*\*)[^/]+/?)" start, filter = $1.length, $2 # If it was at end, store it so we can do filter, and ignore the rest @@ -2236,20 +2721,23 @@ def self.extract_filters! file_path file_path.slice! start, filter.length # Delete it end + file_path.gsub!("\021", "\\/") patterns_at_end.any? ? patterns_at_end : nil end - - def self.expand_filter filters, options + def self.expand_filter filters, options #> ["##dir, name"] raise "We're not yet set up to handle multiple ## or ** filters of the same type. Should be easy though, just run both regex's as you grep." if filters.length > 2 + options[:no_slash] = 1 + file_path = options[:file_path] content_filter, file_filter = nil, nil filters.each do |filter| is_content_filter = filter =~ /^##/ - filter = filter[/^..(.+).$/, 1] + filter = filter[/^..(.+)$/, 1] + filter.sub! /\/$/, '' is_content_filter ? content_filter = filter : file_filter = filter @@ -2265,7 +2753,10 @@ def self.expand_filter filters, options list = self.grep file_path, content_filter, options list.shift # Pull off first dir, so they'll be relative - list = list.join "\n" # + "\n" + list = list.join "\n" + + return "<*" if list.blank? + list end @@ -2277,15 +2768,19 @@ def self.expand_filter filters, options # FileTree.examine("/tempizzle/what.txt") # [false, :dir] # Doesn't exist, but looks like a file path (has an extension) def self.examine path - exists = File.exists? path + path_without_slash = path.sub(/\/$/, '') + exists = File.exists? path_without_slash kind = if exists - File.file?(path) ? :file : :directory - else + File.file?(path_without_slash) ? :file : :directory + else # If it has a dot, assume it's a file path =~ /\.\w+\/?/ ? :file : :directory end + # Remove slash if a file + path.sub!(/\/$/, '') if kind == :file && path =~ /\/$/ + [exists, kind] end @@ -2317,6 +2812,113 @@ def self.to_html txt txt end + def self.hop_file + + file = View.file + if ! file + if View.name == "active links/" + View.to_highest + Search.forward "^ " + Tree.collapse + Launcher.launch + + return "" # View.beep "- Todo > jump back to top and expand" + end + + return View.beep "- This view isn't saved in a file!" + end + + file = Files.tilde_for_home file + + View.to_buffer "active links/" #, :dir=>dir + + View.clear + Notes.mode + View.wrap :off + + View.>> "active links/\n #{file}\n\n\n" + View.line = 2 + + Launcher.launch + + end + + def self.default_external_editor + editor = ENV['EDITOR'] + # If the variable is set, just use it + return editor if editor + + # In mac > default to "open -t" + if Environment.os == "osx" + return "open -t" + end + + # In linux or other > default to "less" + "nano" + + end + + + def self.open_with_external_editor file, options={} + + if file =~ /\/:/ + file, quote = file.split("/: ", 2) + end + + file_quoted = file =~ /^[a-z_\/.-]+$/i ? file : "\"#{file}\"" + + editor = self.default_external_editor + commands = "#{editor} #{file_quoted}" + + line = options[:line] + + if quote + + # Jump to quote, after Looking in the file to find which line number it's on + txt = File.read file + txt.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + + line = View.find_snippet_in_txt txt, quote + end + + # If GUI editor, don't quit + if editor == "open -t" + return Shell.command commands + + elsif editor =~ /\Asubl(ime)?\b/ + # Remove -w or --wait flag (would make xiki lock up) + editor = editor.sub(/ --?w(ait)?\b/, '') + commands = "#{editor} #{file_quoted}" + commands = "#{commands}:#{line}" if line + return Shell.command commands + + + elsif editor =~ /\Aatom\b/ + # Remove -f or --foreground flag (would make xiki lock up) + editor = editor.sub(/ --?f(oreground)?\b/, '') + + commands = "#{editor} #{file_quoted}" + commands = "#{commands}:#{line}" if line + return Shell.command commands + + + elsif editor == "vim" + commands = "#{commands} +#{line}" if line + + elsif editor == "emacs" + commands = "#{editor} +#{line} #{file_quoted}" + + end + + # Assume terminal editor, and so just quit + + return DiffLog.quit_and_run commands + + nil + + end + + end FileTree.define_styles end diff --git a/lib/xiki/core/files.rb b/lib/xiki/core/files.rb index 61a0e50a..2b7c08cc 100644 --- a/lib/xiki/core/files.rb +++ b/lib/xiki/core/files.rb @@ -57,7 +57,7 @@ def self.menu 'm' => '/app/models/', 'v' => '/app/views/', 'c' => '/app/controllers/', - 'n' => '.notes', + 'n' => '.xiki', } # Lets user open a file @@ -92,7 +92,7 @@ def self.copy $el.rename_file(from, to) elsif Keys.prefix_uu command = "cp -R \"#{from}\" \"#{to}\"" - Console.run command, :sync => true + Shell.run command, :sync => true else $el.copy_file(from, to) end @@ -149,14 +149,14 @@ def self.open_tail if ! file || Keys.prefix_u bm = Keys.input(:timed=>true, :prompt=>"Enter bookmark of file to tail (or period for current file): ") - file = (bm == ".") ? View.file : Bookmarks["$#{bm}"] + file = (bm == ".") ? View.file : Bookmarks[":#{bm}"] end - Console.run "tail -f #{file}", :buffer => "*tail of #{file}" + Shell.run "tail -f #{file}", :buffer=>"tail of #{file}" end def self.edited_array - $el.elvar.editedhistory_history.to_a + DiffLog.file_list end def self.current times=nil @@ -192,17 +192,8 @@ def self.open_last def self.open_edited case Keys.prefix - when :u, 8; Launcher.open("- edited/tree/") - else Launcher.open("- edited/") - end - end - - def self.open_history - case Keys.prefix - when nil; Keys.prefix = nil; Launcher.open("- Files.history/") - when 0; Launcher.open("- Files.history_tree/") - when :u; Launcher.open("- Files.history_tree 7/") - else Launcher.open("- Files.history_tree #{Keys.prefix}/") + when :u, 8; Launcher.open("edited/tree/") + else Launcher.open("edited/") end end @@ -215,22 +206,45 @@ def self.open_in_os path=nil # If we're in a file tree, use path if path.nil? && FileTree.handles? - path = Xiki.trunk[-1] + path = Tree.path[-1] end path ||= View.file path ||= View.dir - $el.shell_command("open \"#{path}\"") + path = Bookmarks[path] + + # TODO: make this multi-platform > Equivalent command for linux? + + `open \"#{path}\"` end - def self.do_load_file - $el.revert_buffer(true, true, true) rescue nil + def self.revert options={} + + if View.name == "diff with saved" # If viewing diff, close it and save the actual file... + file = View.txt[/.+\n.+/].sub("\n - ", "") + View.kill + + # Way to move to the buffer that doesn't show warnings + $el.set_buffer $el.get_file_buffer(file) + end + + # Revert in a way they can roll back to + $el.insert_file_contents View.file, true, nil, nil, true View.message "Reverted file" - return if ! Keys.prefix_u - View.message $el.auto_revert_mode ? "Enabled Auto-revert" : "Disabled Auto-revert" + prefix_u = Keys.prefix_u + + # Called via key shortcut but no prefix, so disable auto-revert + if ! prefix_u && options[:from_key_shortcut] + $el.auto_revert_mode 0 + return + end + + return if ! prefix_u + + View.flash $el.auto_revert_mode ? "- Enabled Auto-revert!" : "- Disabled Auto-revert!" end def self.do_clean_quotes @@ -239,28 +253,38 @@ def self.do_clean_quotes end end - # Use File.basename - # def self.name path # Extract name from path - # path.sub(/.+\/(.+)/, "\\1") # Cut of path - # end + def self.enter_file options={} + + message = "File to insert: " + View.flash message + path = Keys.input "#{message}", :timed=>1 + + # They typed a word, so expand it as a bookmark + path = Bookmarks["%#{path}", :raw_path=>1] if path =~/^\w/ + + is_dir = File.directory? path + path = FileTree.add_slash_maybe path if is_dir - # Use File.dirname - # def self.dir path # Extract dir from path - # return "" unless path =~ /\// - # path.sub(/(.+)\/.*/, "\\1") # Cut of path - # end - def self.enter_file - path = File.expand_path(Keys.bookmark_as_path(:include_file=>1)) - path << "/" if File.directory? path - View.insert(path) + # :double_slash, so make it end in 2 slashes + path.sub!(/\/*$/, "//") if options[:double_slash] + + + View.insert path + + # enter+modified, so expand + + if options[:expand] + Launcher.launch :date_sort=>true + end + end # This is currently mac-specific def self.open_last_screenshot - dirs = `ls -t #{Bookmarks["$dt"]}` - screenshot = Bookmarks["$dt"]+dirs[/.+/] + dirs = `ls -t #{Bookmarks["%dt"]}` + screenshot = Bookmarks["%dt"]+dirs[/.+/] self.open_as screenshot, "Adobe Illustrator" @@ -277,7 +301,7 @@ def self.open_in_window file=nil file ||= FileTree.tree_path_or_this_file - file = File.expand_path file + file = File.expand_path Bookmarks[file] # Else, reveal current file command = "open --reveal \"#{file}\"" @@ -296,7 +320,7 @@ def self.dir_of path def self.append path, txt - return if View.name =~ /_log.notes$/ + return if View.name =~ /_log.xiki$/ path = File.expand_path path txt = "#{txt.strip}\n" File.open(path, "a") { |f| f << txt } @@ -307,16 +331,25 @@ def self.in_dir path Dir.entries(path).select{|o| o !~ /^\.+$/} end - def self.open_nth nth - View.layout_files :no_blink=>1 + def self.open_nth nth, options={} + prefix = Keys.prefix # :clear=>1 + + if prefix == :u + Bookmarks.go "," + return + end + + prefix == :u || options[:bm] == "t" ? + View.open("%n") : + View.open("%links") if nth != 0 View.to_highest - nth.times { Move.to_quote :pipes=>1 } + nth.times { Move.to_quote :colons=>1 } end - Effects.blink - Launcher.launch_unified + Effects.blink if View.list.length > 1 + Launcher.launch end def self.delete_current_file @@ -328,7 +361,7 @@ def self.delete_current_file command = "rm \"#{dest_path}\"" - result = Console.run command, :sync=>true + result = Shell.run command, :sync=>true if (result||"").any? View.beep View.message "#{result}" @@ -341,7 +374,7 @@ def self.delete_current_file if /^1\.9/===RUBY_VERSION def self.encoding_binary - [{:encoding => 'binary'}] + [{:binmode=>true}] end else def self.encoding_binary @@ -349,10 +382,48 @@ def self.encoding_binary end end - end -end + # Returns a unique version of the filename. + # Appends ".1" if the filename already exists. + # Or, if that already exists, ".2" or ".3" etc. + # Files.unique_name "/etc/passwd" + # /etc/passwd2 + def self.unique_name path + return path if ! File.exists? path + i, limit = 2, 1000 + + extension = path.slice! /\.[a-z]+$/i + while i < limit + break if ! File.exists? "#{path}#{i}#{extension}" + i += 1 + end + "#{path}#{i}#{extension}" + end + + # Replaces home with tilde + # Files.tilde_for_home "/Users/craig/projects/" + # ~/projects/ -if $el - $el.el4r_lisp_eval("(require 'recentf)") - $el.el4r_lisp_eval("(recentf-mode 1)") + def self.tilde_for_home path, options={} + return path if ! path.is_a?(String) + home = ENV['HOME'] + return path if ! home + + path = path.sub(/\A#{Regexp.quote home}/, "~") + path.gsub!(' ', '\ ') if options[:escape] + path + end + + def self.ancestor_file_or_directory file + file = file.dup + + return file if File.exists? file + + while file.sub! /(.*)\/.*/, "\\1" + return file if File.exists? file + end + + + end + + end end diff --git a/lib/xiki/core/git.rb b/lib/xiki/core/git.rb index fef62bd0..a365d83f 100644 --- a/lib/xiki/core/git.rb +++ b/lib/xiki/core/git.rb @@ -1,59 +1,154 @@ module Xiki + # This just has the consolidated (post-Unified) methods that # are used by key shortcuts (since methods in menu/git.rb might # not be loaded yet). class Git class << self - attr_accessor :jump_line_number + attr_accessor :line_found + end + + def self.status_to_hash txt + + # txt example: + # AM a.txt + # M committed.txt + # A d/d.txt + # D deleteme.txt + # ?? e.txt + + result = {} + + # Pull out lines like these to :untracked... + # ?? a.txt + + untracked = txt.scan(/^(\?\?) (.+)/) + untracked.each{|o| o[0] = "untracked" } + result[:untracked] = untracked + + # Pull out lines like these to :unadded... + # AM a.txt + # M committed.txt + # D deleteme.txt + + unadded = txt.scan(/^.(\w) (.+)/) + unadded.each{|o| o[0] = {"M"=>"modified", "D"=>"deleted"}[o[0]] } + result[:unadded] = unadded + + # Pull out lines like these to :added... + # AM a.txt + # A d/d.txt + # R rename.txt -> renamed.txt + + added = txt.scan(/^(\w). (.+)/) + + added.each{|o| o[0] = {"A"=>"new file", "R"=>"renamed"}[o[0]] } + result[:added] = added + + # result example: + # :unadded => [ + # ["modified", "a.txt"], + # ["deleted", "deleteme.txt"], + # ], + # :added => [ + # ["new file", "a.txt"], + # ["renamed", "rename.txt -> renamed.txt"] + # ], + # :untracked => [ + # ["untracked", "e.txt"] + # ] + + result + end def self.branch_name dir=nil + dir ||= Tree.closest_dir - Console.run("git status", :sync=>true, :dir=>dir)[/# On branch (.+)/, 1] + + branch, error = Shell.command "git rev-parse --abbrev-ref HEAD", :dir=>Files.dir_of(dir), :return_error=>1 + + # Error with "unknown revision" means no revisions yet, so return branch as MASTER... + return "no commits yet?" if error =~ /^fatal: ambiguous argument 'HEAD': unknown revision/ + + # Unknown error, so return branch as nil... + return nil if error + + # No error, so return branch... + branch.strip + end def self.do_push prefix = Keys.prefix :clear=>true - file = Keys.bookmark_as_path :prompt=>"Enter a bookmark to git diff in: " - branch = self.branch_name file - - menu = " - #{file} - @git/ + dir = Keys.bookmark_as_path :prompt=>"Enter a bookmark to git diff in: " + branch = self.branch_name dir + txt = " + #{dir} + $ git - push/#{branch}/ - diff/ ".unindent if prefix == :- - View.insert menu + View.insert txt Line.previous - Launcher.launch_unified + Launcher.launch else View.bar if prefix == "outline" - Launcher.open(menu) + Launcher.open txt, :buffer_name=>"git diff/", :buffer_dir=>dir end nil end + def self.do_log + + prefix = Keys.prefix :clear=>1 + + dir = Keys.bookmark_as_path :prompt=>"Enter a bookmark to git diff in: " + + txt = " + #{dir} + $ git log + ".unindent + + Launcher.open txt, :buffer_name=>"git log" + + nil + end + + def self.do_status + + prefix = Keys.prefix :clear=>1 + dir = Keys.bookmark_as_path :prompt=>"Enter a bookmark to git diff in: " + + txt = " + #{dir} + $ git status + ".unindent + + Launcher.open txt, :buffer_name=>"git log" + + nil + end + + def self.do_compare_repository file = View.file - # Temporarily set the line number in a temporary place - Git.jump_line_number = View.line - Launcher.open "#{file}\n @git/diff/" - Git.jump_line_number = nil + Launcher.open "#{file}\n = git/diff/", :line_found=>View.line "" end def self.toplevel_split path + dir = Shell.sync "git rev-parse --show-toplevel", :dir=>Files.dir_of(path) - dir = Console.sync "git rev-parse --show-toplevel", :dir=>path return nil if dir =~ /^fatal: / dir.strip! @@ -67,5 +162,38 @@ def self.toplevel_split path [dir, relative] end + # Massages output of "git diff" or "git show" into :... quotes. + # Git diff args should be like this? > --oneline -U1 + def self.format_as_quote txt, options={} + + # Remove junk at beginning before leading diff + txt.sub!(/.*?^@@ /m, '@@ ') if options[:one_file] + + txt.gsub!(/^index .+\n/, '') + txt.gsub!(/^diff .+\n/, '') + txt.gsub!(/^--- .+\n/, '') + txt.gsub!(/^\+\+\+ /, '@@ ') + + txt.gsub!(/^/, ':') + txt.gsub!(/ $/, '') + txt + end + + def self.do_status + dir = Keys.bookmark_as_path :prompt=>"Enter a bookmark to show git status: " + + branch = Xiki::Git.branch_name dir + menu = " + #{dir} + $ git + + push/#{branch}/ + + status/ + ".unindent.strip + Launcher.open menu, :buffer_dir=>dir + + nil + end + + end end diff --git a/lib/xiki/core/grab.rb b/lib/xiki/core/grab.rb new file mode 100644 index 00000000..ca0a644d --- /dev/null +++ b/lib/xiki/core/grab.rb @@ -0,0 +1,347 @@ +module Xiki + class Grab + def self.call_grab_method options + + first = options[:grab_option][0] + + self.send(TextUtil.snake(first), options) + end + + def self.options options + Tree.collapse + Launcher.launch :task=>[]#, :hotkey=>1 + "" + end + + def self.add options + + line = Line.value + options_in = {} + + # At left margin or equals sign + if Line.indent(line) == "" || line =~ /^ *=/ + # so indenting under is fine + else + # Otherwise, over-ride indent to be same as current line + options_in[:no_indent] = 1 + end + + Notes.add_note_prompt options_in + + "" + end + + + def self.source options + Command.to_menu + nil + end + + def self.hide options + # Do nothing, siblings should be already hidden? + nil + end + + def self.quote_selection options={} + + View.deselect + txt = View.delete *View.range + + # Already quoted, at left margin, so just unquote... + + if txt =~ /\A *\|/ + txt.gsub!(/^( *)\| ?/, "\\1") + + View.<< txt, :dont_move=>1 + return + end + + # Not quoted, so quote it... + + indent = txt[/\A */] + + txt = txt.gsub(/^#{indent}/, "\\0| ") + txt.gsub!(/^( *\|) $/, "\\1") # Remove trailing linebreaks + + if ! options[:go] + txt.gsub! /^/, " " + txt = "\n#{txt}" + end + + View.<< txt, :dont_move=>1 + View.remove_last_undo_boundary + + end + + def self.to_grab_target_view txt + + # Var wasn't set, so tell caller to continue... + + return false if ! $el.boundp(:grab_target_view) + + grab_target_view = $el.elvar.grab_target_view + + # Var was set, so jump to target view and insert there... + + View.kill + View.to_buffer grab_target_view + + # Make blank line after, if stuff on this line + + if ! Line.blank? + Line.to_right + View << "\n" + end + + View.<< txt.sub(/\n+\z/, "") + true # Tell caller we handled it + end + + + def self.go_key options={} + return self.quote_selection(:go=>1) if View.selection? + + path = Tree.path + last = Path.split path[-1] + line = Line.value + + # ^G on "topic > heading" inline, so run as a xiki command... + + if last.length == 1 && last[0] =~ /\A[a-z][a-z0-9 ]+ > \.?[a-z0-9]/i + return Launcher.launch(:task=>["navigate"]) + end + + # ^G on ":search > heading" inline, so run as a xiki command... + + if last.length == 1 && last[0] =~ /\A:[a-z][a-z ]+ > [a-z]/i + View.flash "- Not sure what ^G on ':search > Heading' should do yet", :times=>5 + return + end + + + # ^G on :search result, so ... + + if last.length == 2 && last[0] =~ /^:[a-z ]+$/i + + + if last[1] =~ /^\w/ # || last[-1] =~ /^@\w/ + + # Control G, so just change to heading! + options = {:dont_run_action=>1} + path = Path.join(last).sub(/^:/, '') + Expander.expand path, options + heading = options[:heading_found] + heading = heading.sub! /(^> @\w+ )/, '> ' + + # Change to heading + Line.sub!(/^( *)[+-] (.+)/, "\\1#{heading}") + + return "" + end + + # ^G on "> Heading" under topic, so navigate... + + if last[-1] =~ /^> / || last[-1] =~ /^@\w/ + + # Change from heading back to path + + path = "foo" + Line.sub!(/^( *)(.+)/, "\\1+ #{Notes.heading_to_path last[-1]}") + + return "" + end + end + + + # ^G under "foo..." (autocomplete), so collapse first... + + if last.length == 2 && last[0] =~ /\A[a-z0-9][a-z0-9 ]*\.\.\.\z/ + Tree.collapse_upward :replace_parent=>1 + return self.go_key options + end + + # ^G on "topic", and not "topic/", so run as a xiki command... + + if Topic.matches_topic_syntax?(last[0]) + + # ^G on "topic" (with no slash at end) + if last.length == 1 + return Launcher.launch(:task=>["view source"]) + end + end + + # /topic, so just .expand... + + view_dir = View.dir + + + path_without_filters = path[-1].dup + FileTree.extract_filters! path_without_filters + + if FileTree.handles?(path_without_filters) + + # /foo/, so just cd to the dir + + # Could be /tmp, "/tmp/$ pwd", or "/tmp/$ pwd/item" + + dir, quote, line = Bookmarks[path_without_filters], nil, nil + command = nil + + dir.sub!(/\/$/, '') + + + # Add quotes unless it's all slashes and letters + dir_quoted = dir =~ /^[a-z_\/.-]+$/i ? dir : "\"#{dir}\"" + + dir_without_snippet, snippet = dir.split("/: ", 2) + + if File.file? dir_without_snippet + return FileTree.open_with_external_editor Path.unescape(dir) + end + + # Dir + if File.directory? dir + commands = "cd #{dir_quoted}\n" + return DiffLog.quit_and_run commands + end + + "- Not file or directory!" + end + + if last[0] == "markers" && last.length > 1 + return Tree.<< "- Don't know what ^G on marker means yet!", :no_slash=>1, :hotkey=>1 + end + + # $ ls, so cd to dir or edit file > hard-coded support for ls, for now > abstract out for other commands later... + + if last[0] == "$ ls" && last.length > 1 + + dir = last[1..-1].map{|o| o.sub(/\/$/, '')}.map{|o| o.sub(/^: /, '')}.join("/") + + file = "#{Shell.dir}#{dir}" + + if File.directory? file + commands = "cd #{Shell.quote_file_maybe file}" + else + + FileTree.open_with_external_editor file + return "" + end + + return DiffLog.quit_and_run commands #> ||| + end + + + # ^G on item under "xiki/", so collapse and run with :go! + + if last.length == 2 && last[0] == "xiki" + + Xiki[last[1], :task=>["view source"]] + return "" + + end + + # ^G on "> Heading" under topic, so navigate... + + if last[-1] =~ /^> / || last[-1] =~ /^@\w/ + Launcher.launch :task=>["view source"] + return "" + end + + + # Blank line, so say to use ^O instead... + + if last == [] + View.flash "Try Ctrl+T or Ctrl+X on blank lines" + return "" + end + + # Not a shell command, so do nothing... + + if last[-1] !~ /^[$%&] (.+)/ + Launcher.launch :go=>1 + return "" + end + + # Shell command in any ancestor, so run back in bash... + + command = $1 + ancestors = options[:ancestors] || Tree.ancestors + + # /dir/$..., so grab command and dir... + + (ancestors||[]).reverse.each do |ancestor| + # Only continue if it's a file path + next if ! FileTree.handles?(ancestor) + + dir = Bookmarks[ancestor] + dir.sub! /\/\/.*/, "/" # Under menufied, so change "/foo//bar" to "/foo/" before cd'ing + + dir = Shell.quote_file_maybe dir + + # Todo > Do this, when there are args? + # # Pop off any others at end that are $... or ~... + # while last[-1] =~ /^[~$%&]/ + # last.pop + # end + + commands = "cd #{dir}\n#{command}" + return DiffLog.quit_and_run commands + + end + + # $..., so grab command, and figure out the dir... + + self.prepend_cd_if_changed command + + DiffLog.quit_and_run "#{command}\n" #> ||| + end + + + def self.prepend_cd_if_changed command, options={} + + dir = options[:dir] || Shell.dir + dir.sub!(/\/$/, '') if dir != "/" + + if dir != View.app_dir + dir = "\"#{dir}\"" if dir !~ /^[a-z_\/.-]+$/i + command.replace "cd #{dir}\n#{command}" + end + + end + + + def self.save options + # Saves a note + + # No "> Foo" at beginning, so prompt them to type it and do nothing + return if Notes.prompt_for_heading_if_missing + + # Convert to 2 level indenting + Notes.indent_note_under_heading + + # A heading exists, so convert to 2-level indenting, then save... + + # Delegate to ~ save + Launcher.launch(:task=>["save"]) + + "" + end + + + def self.content_editor + + # Untitled view, so save file as session before opening in editor + DiffLog.save_xsh_session + + if ! View.file + return + end + + View.auto_revert + FileTree.open_with_external_editor(View.file, :line=>View.line) + end + + + end +end diff --git a/lib/xiki/core/hide.rb b/lib/xiki/core/hide.rb index d6b75ab1..a02da0ed 100644 --- a/lib/xiki/core/hide.rb +++ b/lib/xiki/core/hide.rb @@ -205,12 +205,18 @@ def self.reset def self.hide_by_indent indent=nil indent ||= Keys.prefix - indent = -1 if indent == :u + if indent == :u + self.show + $el.widen + + $el.set_selective_display nil + return + end # If no prefix, use indent of current line if indent.nil? indent = Line.matches(/^ */).size - # If currently indented to that level, go one deeper + # If currently indented to that level, if $el.elvar.selective_display && indent == ($el.elvar.selective_display - 1) indent += 2 end @@ -256,6 +262,13 @@ def self.reveal self.hide_by_indent :u # If hidden by indent end + # Hides using narrowing + def self.hide left, right + $el.narrow_to_region left, right + nil + end + + end Hide.init end diff --git a/lib/xiki/core/hint.rb b/lib/xiki/core/hint.rb new file mode 100644 index 00000000..714c0953 --- /dev/null +++ b/lib/xiki/core/hint.rb @@ -0,0 +1,65 @@ +module Xiki + class Hint + + def self.top_box txt + + txt = txt.strip + + # Make blank space at top + + height = txt.scan(/\n/).length + 4 + + Move.top + View << "\n" * (height+1) + Move.to_end + + View.refresh + $el.sit_for 0.3 + + + # 2. Draw green box, and fade in + Move.top + View.delete 1, (height+2) + + View << "\n#{txt.gsub(/^./, " \\0")}\n\n (type any key)\n\n" + + right = View.cursor-2 + right = Line.left -1 + Overlay.face :diff_green, :left=>1, :right=>right + + View.line = height + Overlay.face :fade3, :left=>Line.left, :right=>Line.right + + View.line = height+2 + + Move.to_end + + # Key pressed, so hide it + + key = Keys.input :timed=>1, :raw=>1, :prompt=>" " + + # Delete it first + Line.to_start + View.delete 1, View.cursor + + View << "\n" * (height+1) + Move.to_end + View.refresh + $el.sit_for 0.3 + + # Delete lines, one at a time + (height+1).times do + View.delete 1, 2 + View.refresh + $el.sit_for 0.03 + end + Files.revert + View.message "" + + Move.to_end + + key # Return the key they pressed + + end + end +end diff --git a/lib/xiki/core/history.rb b/lib/xiki/core/history.rb index 0d59bf19..75a6f785 100644 --- a/lib/xiki/core/history.rb +++ b/lib/xiki/core/history.rb @@ -7,13 +7,13 @@ def self.menu # Is this even called anymore? " > Files - << @edited/ - << @recent_files/ - << @not saved/ + << =edited/ + << =recent files/ + << =unsaved/ > See - << @log/ - << @last/ + << =log/ + << =last/ " end @@ -25,10 +25,25 @@ def self.prefix_times end def self.open_current options={} + if options[:paths] paths = options[:paths] + elsif path = options[:file] + path = File.expand_path(path) + + paths = [path] elsif options[:prompt_for_bookmark] bm = Keys.input(:timed => true, :prompt => "Enter bookmark to show content for: ") + + # Hard-coded + # eventually do this whenever a dir > do outline for _menu inside dir, and optionally treat as topic if exists + # - maybe > borrow > code for list+outline + + if bm == "h" + return Launcher.open "xikihub rails" + end + + path = Bookmarks.expand(bm, :just_bookmark => true) return View.beep("- Bookmark '#{bm}' not found!") if ! path path = File.expand_path(path) @@ -49,6 +64,7 @@ def self.open_current options={} end if options[:enter_here] # If entering in current file + path = paths.to_s # If it's a dir, delegate to Open Tree if path =~ /\/$/ @@ -69,13 +85,13 @@ def self.open_current options={} View.open paths[0] end - View.to_buffer("*tree of current") + View.to_buffer("outline/") View.clear; Notes.mode raise "Thought this wouldn't happen :(" if paths.length > 1 dir, file = paths[0].match(/(.+\/)(.+)/)[1..2] - View << "- #{dir}\n - #{file}\n" + View << "#{dir}\n - #{file}\n" View.to_top Keys.clear_prefix @@ -83,38 +99,17 @@ def self.open_current options={} if options[:all] FileTree.enter_lines(//) elsif options[:outline] || options[:prompt_for_bookmark] + + Ol "Errors here when directory!!!" + FileTree.enter_lines else - Tree.search :recursive => true + Tree.filter :recursive => true end end end - def self.open_edited - # TODO: Is this used any more? > moved to @edited - times = self.prefix_times - View.to_buffer("*tree of edited") - View.clear; notes_mode - View.insert Tree.paths_to_tree($el.elvar.editedhistory_history.to_a[0..(times-1)]) - View.to_top - Keys.clear_prefix - FileTree.select_next_file - Tree.search :recursive => true - end - - def self.open_history - times = self.prefix_times - View.to_buffer("*tree of history") - View.clear; notes_mode - - self.insert_history times - View.to_top - Keys.clear_prefix - FileTree.select_next_file - Tree.search :recursive => true - end - def self.insert_history times View.insert Tree.paths_to_tree($el.elvar.recentf_list.to_a[0..(times-1)]) end @@ -124,7 +119,7 @@ def self.enter_history self.insert_history self.prefix_times right = $el.point orig.go - Tree.search :recursive => true, :left => $el.point, :right => right + Tree.filter :recursive => true, :left => $el.point, :right => right end def self.insert_viewing times @@ -138,114 +133,62 @@ def self.enter_viewing self.insert_viewing self.prefix_times right = $el.point orig.go - Tree.search :recursive => true, :left => $el.point, :right => right + Tree.filter :recursive => true, :left => $el.point, :right => right end - def self.setup_editedhistory - - return if ! $el + def self.backup_file - $el.el4r_lisp_eval %q< - (progn - ; Settings - (setq editedhistory-log "~/.editedhistory") - - ; Runs upon startup. Load log file into memory if it exists. Run this manually if you edit the log file. - (defun editedhistory-load () - (if (file-readable-p editedhistory-log) - ; Read from file into memory - (with-temp-buffer - (insert-file-contents editedhistory-log nil nil nil t) - (setq editedhistory-history - (car (read-from-string (buffer-substring (point-min) (point-max))) ))) - ; Otherwise, initialize var - (set 'editedhistory-history nil))) - - ; Saves on exit: 'editedhistory-history' to the file 'editedhistory-log' - (add-hook 'kill-emacs-hook 'editedhistory-save) - (defun editedhistory-save () (interactive) - (with-temp-buffer - (erase-buffer) - (insert (pp-to-string editedhistory-history) ) - (if (file-writable-p editedhistory-log) - (write-region (point-min) (point-max) editedhistory-log) - ) - (kill-buffer (current-buffer))) - nil) - - ; Runs upon save: Track modified files - (defun editedhistory-remember-file () - ; Remove from list in case it's already there (we want to add to beginning) - (when (boundp 'editedhistory-history) - (setq editedhistory-history (delete buffer-file-name editedhistory-history) ) - ; Add to list - (add-to-list 'editedhistory-history buffer-file-name)) - nil) ; Return nil so we won't block writing - (add-hook 'write-file-hooks 'editedhistory-remember-file) - - ; Load upon startup, to add hooks - (define-minor-mode editedhistory-mode - "Toggle editedhistory mode" - :global t - :group 'editedhistory - - ; Load if not yet loaded - (pp (boundp 'editedhistory-loaded-p)) - (unless (boundp 'editedhistory-loaded-p) - (setq editedhistory-loaded-p t) - (pp editedhistory-loaded-p) - ; Load log file into memory - (editedhistory-load))) - - ; Load mode - (editedhistory-mode t) - ) - > - end + dir = File.expand_path "~/.xiki/misc/versions" + FileUtils.mkdir_p dir # Guarantee dir exists - def self.backup_file - bm = Bookmarks['$bak'] - return View.beep "- First, set the 'bak' bookmark to a dir!" if bm == "$bak" + prefix = Keys.prefix - unless bm.any? # If no bookmark, just show error - View.beep - return View.message("Error: create a bookmark named 'bak' first, in a dir where you backups will go.") - end + # :-, so always use current file, not file path cursor is on... - path = Keys.prefix_u? ? + path = prefix == :- ? View.file : FileTree.tree_path_or_this_file name = path.sub(/.+\//, '') + name = "#{dir}/#{name}.#{Time.now.strftime('%Y-%m-%d.%H-%M')}" + if prefix == :u + txt = Keys.input :prompt=>"Comment for this version: " + name = "#{name}.#{TextUtil.snake txt}" + end - # Copy file xx - $el.copy_file path, "#{bm}#{name}.#{Time.now.strftime('%Y-%m-%d.%H-%M')}" + # Copy file + $el.copy_file path, name - message = "backed up to $bak: '#{name}'" - View.flash "- #{message}", :times=>3 + message = "backed up to: #{Bookmarks.bookmarkify_path name}" + View.flash "- #{message}"#, :times=>3 View.message "Successfully #{message}" end + def self.latest_file file_name + Dir["#{File.expand_path("~/.xiki//misc/versions")}/#{file_name}.????-??-??.??-??*"].last # " + end + def self.diff_with_backup + # If up+, do interactive ediff... + if Keys.prefix_u - $el.ediff_files Dir["#{Bookmarks['$bak']}#{View.file_name}*"].last, View.file + $el.ediff_files Dir["#{File.expand_path("~/.xiki/misc/versions")}#{View.file_name}*"].last, View.file return end - backup = Dir["#{Bookmarks['$bak']}#{View.file_name}.????-??-??.??-??"].last # " + backup = self.latest_file View.file_name - return View.beep("- No backup exists in $bak/") if ! backup + return View.beep("- No backup exists in :x/misc/versions/") if ! backup file = View.file - # Replace current version with last + # If 8+, replace current version with backup... + if Keys.prefix == 8 - return if ! View.confirm "Replace current file with backup?" - txt = File.read backup - line = View.line - View.kill_all - View << txt - View.line = line + return if ! View.confirm "Replace with contents of backup? Afterwards you can save to make it permanent." + orig = Location.new + $el.insert_file_contents backup, nil, nil, nil, true + orig.go return end @@ -255,13 +198,13 @@ def self.diff_with_backup # Reverse the files file, backup = backup, file if Keys.prefix == :- - diff = Console.run "diff -U 0 \"#{backup}\" \"#{file}\"", :sync=>true + diff = Shell.run "diff -w -U 0 \"#{backup}\" \"#{file}\"", :sync=>true return Launcher.show "- No Differences!" if diff.blank? diff = DiffLog.format diff, :use_other_path=>1 - View.to_buffer("*diff with saved*") + View.to_buffer("*diff with version*") View.clear Notes.mode @@ -269,8 +212,41 @@ def self.diff_with_backup diff : "| Alert\n- ~No Differences~\n" + View.to_top + + end + + def self.list + regex = Regexp.quote View.name + file = View.file + + Launcher.open "~/.xiki/misc/versions/\n - **^#{regex}\./" + + # Todo > nest "versions/" command under it? + end + + def self.init_in_client + + $el.el4r_lisp_eval %` + (progn + (defun xiki-history-find-file-handler () + (el4r-ruby-eval "Xiki::History.log") + ) + (add-hook 'find-file-hook 'xiki-history-find-file-handler) + ) + ` + + end + + def self.log + + tmp_dir = File.expand_path "~/.xiki/misc/logs" + FileUtils.mkdir_p tmp_dir # Make sure dir exists + file = "#{tmp_dir}/opened_files_log.xiki" + File.open(file, "a") { |f| f << "#{View.file}\n" } + end end - History.setup_editedhistory + end diff --git a/lib/xiki/core/html.rb b/lib/xiki/core/html.rb new file mode 100644 index 00000000..5b72dca0 --- /dev/null +++ b/lib/xiki/core/html.rb @@ -0,0 +1,151 @@ +module Xiki + class Html + + def self.to_html_tags txt + + html = "" + + txt = txt.gsub /^( *)([+-] )?(\w[\w ]*\/)(.+)/, "\\1\\3\n\\1 \\4" # Preprocess to break foo/Bar into separate lines + + previous = [] + + Tree.traverse(txt) do |l, path| + + last = l.last + next if !last # Blank lines + + self.add_closing_tags html, l, previous # If lower than last, add any closing tags + + last = Line.without_label :line=>last + if last !~ /^ *\| / && last =~ /([^*\n]+)\/$/ + tag = $1 + html.<<(" " * (l.length-1)) unless l[-2] =~ /[ +-]*pre\/$/ + + next html << "<#{tag}>\n" + end + + last.sub! /^\| ?/, '' + + if last =~ /\.\.\.$/ # If "Lorem..." or "Lorem ipsum..." etc. make progressively longer + old_length = last.length + last.gsub!(/\w+/){|o| @@lorem[o.downcase] || o} + last.sub!(/\.\.\.$/, '') if last.length != old_length # Remove ... if we replaced something + end + + parent = l[-2] + html.<<(" " * (l.length-1)) unless parent =~ /[ +-]*pre\/$/ + + html << "#{last}\n" + end + + + self.add_closing_tags html, [], previous + + html + end + + def self.add_closing_tags html, l, previous + + if l.length <= previous.length + left = l.length-1 + left = 0 if left < 0 + close_these = previous[left..-1] + close_these.reverse.each_with_index do |tag, i| + + next if ! tag # Was throwing error for icon/... in @bootstrap when hero exists + + next if tag =~ /^ *\|.+\/$/ # Skip if it's a quoted foo/ line + + tag.sub! /^\| ?/, '' + tag = Line.without_label :line=>tag + next if tag !~ /(.*\w)\/$/ && tag !~ /^<([^<\n]*[\w"'])>$/ + tag = $1 + tag = tag.sub(/ \w+=.+/, '') + next if ["img"].member? tag + html << " " * (previous.length - i - 1) + html << "\n" + end + end + previous.replace l + end + + + + def self.default_css options={} + txt = "" + txt.<< "\n" if ! options[:no_tags] + txt + end + + # Turns a xiki tree with items like tag names into corresponding html tags. + # + # Html.to_html_tags "p/\n hi\n" + # "

\n hi\n

\n" + # Html.default_css + # Html.default_css :no_tags=>1 + + # def self.tidy html, tag=nil + def self.tidy html, options={} + + tag = options[:tag] + + + File.open("/tmp/tidy.html", "w") { |f| f << html } + + more_options = "--indent-attributes y" if options[:wrap_attributes] + + result = `tidy #{more_options} y --new-blocklevel-tags a --wrap-attributes y --output-html y --indent auto --indent-spaces 2 --tidy-mark 0 --force-output 1 -i --wrap 0 -o /tmp/tidy.html.out /tmp/tidy.html` + html = IO.read("/tmp/tidy.html.out") + + html.gsub! /\n\n+/, "\n" + + if tag == "" # It's the whole thing, do nothing + elsif tag == "head" + html.sub! /.+?\n/m, '' + html.sub! /^<\/head>\n.+/m, '' + else + html.sub! /.+?\n/m, '' + html.sub! /^<\/body>\n.+/m, '' + end + + html.gsub!(/ +$/, '') + html.gsub! /^ /, '' unless html =~ /^ and elements onto separate lines + html.gsub!(/( *).+> *<.+/) do |line| + indent = $1 + line.gsub! /> *\n#{indent}<" + end + + html + + # Try Nokogiri and xsl - Fucking dies part-way through + # return Nokogiri::XML(kids).human.sub(/\A<\?.+\n\n/, '').gsub(/^/, '| ') + # return Nokogiri::XML("#{kids}").human.gsub(/^/, '| ') + # return Nokogiri::XML(kids).human.gsub(/^/, '| ') + + # Try REXML (gives errors) + # doc = REXML::Document.new("#{kids}") + # out = StringIO.new; doc.write( out, 2 ) + # return out.string + + end + + + + end +end + diff --git a/lib/xiki/core/image.rb b/lib/xiki/core/image.rb index a709135d..95ce79a8 100644 --- a/lib/xiki/core/image.rb +++ b/lib/xiki/core/image.rb @@ -1,7 +1,3 @@ -begin - require 'ftools' -rescue Exception=>e; end - module Xiki class Image def self.menu_before *args @@ -28,14 +24,14 @@ def self.menu end def self.>> file, txt=nil - txt ||= "@img/#{file}" + txt ||= "=img/#{file}" Move.to_end self.<< file, txt, :enter_junior=>1 Move.backward end def self.<< file, txt=nil, options={} - txt ||= "@img/#{file}" + txt ||= "=img/#{file}" # Copy to place with unique name, so a cached version doesn't get displayed... tmp_dir = "/tmp/img_tmp/" diff --git a/lib/xiki/core/invoker.rb b/lib/xiki/core/invoker.rb index cfb9d826..a35c00eb 100644 --- a/lib/xiki/core/invoker.rb +++ b/lib/xiki/core/invoker.rb @@ -2,7 +2,7 @@ module Xiki class Invoker # Invokes actions on menu source classes. # - # The actioun is often the .menu method, but could alternateyl be handled + # The actioun is often the .menu method, but could alternately be handled # by the MENU constant or the foo.menu file. Also, MENU or foo.menu may route # to a different method, in which case that will be invoked. # @@ -10,9 +10,11 @@ class Invoker # - Make it delegate back to RubyHandler for running stuff # - Make it have specific methods, for each type of eval below: # - handler.check_class_defined? - def self.invoke clazz, args, options={} + def self.invoke clazz, args=[], options={} args ||= [] + args = Path.split args if args.is_a? String + menu_found = nil # Possible values: nil, :constant, :file, :method (if :method, it overwrites the previous value, which is fine) action_method = nil @@ -20,23 +22,26 @@ def self.invoke clazz, args, options={} code, clazz_name, dot_menu_file = options[:code], options[:clazz_name], options[:dot_menu_file] - # Load class... - - # Assume clazz is a file for now + # Prepare by loading or reloading class... # Just always reload for now (no caching) + # Assume clazz is a file for now returned, out, exception = Code.eval code, clazz, 1, :global=>1 - return CodeTree.draw_exception exception, code if exception + if exception + options[:no_search] = 1 + return CodeTree.draw_exception exception, code + end - # TODO: wrap modules around depending on dir (based on :last_source_dir)? + # Prepend module name if any... - mod = self.extract_ruby_package code + mod = self.extract_ruby_module code if code clazz_name = "#{mod}::#{clazz_name}" if mod clazz_name = "Xiki" if clazz_name == "Xiki::Xiki" # Xiki is a module, so it grabbed it twice. If we want to support other modules, do something generic here, but we may not need to. - clazz = Code.simple_eval("defined?(#{clazz_name}) ? #{clazz_name} : nil", nil, nil, :global=>1) + # Before this, 'clazz' is a file path + clazz = Code.simple_eval("defined?(#{clazz_name}) ? #{clazz_name} : nil", nil, nil, :global=>1) if clazz_name # Call .menu_before if exists... @@ -59,7 +64,7 @@ def self.invoke clazz, args, options={} if clazz.const_defined? :MENU menu_found = :constant menu_text = clazz::MENU - elsif File.file?(dot_menu_file) + elsif dot_menu_file && File.file?(dot_menu_file) menu_found = :file menu_text = File.read dot_menu_file end @@ -71,7 +76,6 @@ def self.invoke clazz, args, options={} dotified = [] if menu_text - menu_text = menu_text.unindent if menu_text =~ /\A[ \n]/ txt = Tree.children menu_text, args, options @@ -80,7 +84,25 @@ def self.invoke clazz, args, options={} if ! txt || txt == "- */\n" dotified = Tree.dotify menu_text, args elsif menu_found == :constant || menu_found == :file # If there was autput from MENU or foo.menu, eval !... lines - MenuHandler.eval_when_exclamations txt, options + MenuHandler.eval_exclamations txt, options + end + end + + + # If MENU_OBSCURED exists, use it to get children or route... + + menu_obscured = clazz.const_defined? "MENU_OBSCURED" + if menu_obscured + menu_obscured = clazz.const_get "MENU_OBSCURED" + menu_obscured = menu_obscured.unindent if menu_obscured =~ /\A[ \n]/ + + txt_from_obscured = !txt && args.any? ? # Only use children if it's not the root + Tree.children(menu_obscured, args, options) : nil + + if txt_from_obscured + txt = txt_from_obscured + else + dotified = Tree.dotify menu_obscured, args, dotified end end @@ -88,10 +110,12 @@ def self.invoke clazz, args, options={} menu_hidden = clazz.const_defined? "MENU_HIDDEN" if menu_hidden - returned = clazz.const_get "MENU_HIDDEN" + menu_hidden = clazz.const_get "MENU_HIDDEN" + menu_hidden = menu_hidden.unindent if menu_hidden =~ /\A[ \n]/ + + # Try getting children from MENU_HIDDEN, if it has any... - # Only do dotifying if not already done? - dotified = Tree.dotify returned.unindent, args, dotified + dotified = Tree.dotify menu_hidden, args, dotified end # If MENU|foo.menu not found or didn't handle path, call routed method or otherwise .menu with args... @@ -161,7 +185,7 @@ def self.invoke clazz, args, options={} # # TODO: Unified: comment out for now - just comment out since we're doing no caching # # reload 'path_to_class' - # Menu.load_if_changed File.expand_path("~/menu/#{snake}.rb") + # Command.load_if_changed File.expand_path("~/xiki/roots/#{snake}.rb") # Call .menu_after if it exists... @@ -183,7 +207,7 @@ def self.invoke clazz, args, options={} return nil if some_method_ran # Only say "no output" if didn't call .menu, .menu_before|_after, or other action # For now, let's try not doing this - txt = "@flash/- no output!" if options[:client] =~ /^editor\b/ + txt = "" if options[:client] =~ /^editor\b/ end txt @@ -202,7 +226,7 @@ def self.actionify args, boolean_array boolean_array[i] } - action = actions.last || "menu" + action = actions.any? ? actions[-1].dup : "menu" action.gsub! /[ -]/, '_' action.gsub! /[^\w.]/, '' @@ -211,9 +235,15 @@ def self.actionify args, boolean_array [action, variables] end - def self.extract_ruby_package txt + def self.extract_ruby_module txt - txt = txt.sub /^ *class .+/m, "" # Remove everything after 1st class... line + txt = txt.sub /\A( *class .+?\n).+/m, "\\1" # Remove everything after 1st class... line, so it doesn't look at internal irrelevant module statements in a script. + + # If it's class Foo::Bar, pull it out of there + # .commit/Fix federico bug, mistaking ...:: for a package + if mod = txt[/class ([\w:]+)::/, 1] + return mod + end txt = txt.scan(/^ *module (.+)/).map{|o| o[0]}.join("::") diff --git a/lib/xiki/core/json.rb b/lib/xiki/core/json.rb new file mode 100644 index 00000000..62dfb156 --- /dev/null +++ b/lib/xiki/core/json.rb @@ -0,0 +1,29 @@ +class Json + def self.extract_json_header txt + + header = "" + + # "{{" at beginning, so remove 1st and return no header... + + if txt =~ /\A\{\{/ + txt.sub!(/./, '') + return nil # No header exists + end + + # No "{" at beginning, so return no header + if txt !~ /\A\{/ + return nil + end + + # Single json line at top, so extract it as header + if txt =~ /\A.+\}$/ + return txt.slice! /.+$\n?/ + end + + # Multi-line json header, so extract until 1st "}" on a line by itself + txt.slice!(/.+?\n}$\n?/m) + + + # return header == "" ? nil : header + end +end diff --git a/lib/xiki/core/key_bindings.rb b/lib/xiki/core/key_bindings.rb deleted file mode 100644 index 8ac685ee..00000000 --- a/lib/xiki/core/key_bindings.rb +++ /dev/null @@ -1,708 +0,0 @@ -module Xiki - class KeyBindings - - # Define all keys - def self.keys - Menu.init - - self.as_keys - self.open_keys - self.enter_keys - self.to_keys - self.layout_keys - self.do_keys - self.isearch - self.misc - - Keys.add_menubar_items - end - - def self.as_keys - # A: as... - # Use A prefix for: remembering, saving - - Keys.as_axis { Line.to_left } # C-a C-a - beginning of line (C-a by default in emacs) - # $el.define_key :global_map, $el.kbd("C-a C-a"), :beginning_of_line - Keys.as_bookmark { Bookmarks.save } # remember bookmark - Keys.as_clipboard { Clipboard.as_clipboard } # - # Keys.as_directory { FileTree.copy_path } # copy dir to clipboard from tree - Keys.as_delete { Launcher.as_delete } # copy dir to clipboard from tree - Keys.as_everything { Clipboard.copy_everything } - Keys.as_file { DiffLog.save } # save (or, with prefix, save as) - Keys.as_history { History.backup_file } # creates backup - Keys.as_indented { CodeTree.as_indented } - Keys.as_job { Macros.record } # start recording macro - Keys.as_kill { Clipboard.cut(0); Location.as_spot('killed') } # cut) - Keys.as_line { Clipboard.as_line } - Keys.as_menu { Menu.as_menu } - Keys.as_nav { Notes.as_nav } - Keys.as_open { Launcher.as_open } # copy object / symbol at point - Keys.as_paragraph { Clipboard.copy_paragraph } # copy paragraph - Keys.as_quick { Bookmarks.save :q } # like AB but uses different temporary namespace - Keys.as_rest { Clipboard.copy_paragraph(:rest => true) } - Keys.as_spot { Location.as_spot } # remember point in file - Keys.as_todo { Notes.as_todo } - Keys.as_update { Launcher.as_update } - # U - - # TODO: make this be as_variable? - # like: Keys.as_name { Clipboard.copy } # copies using key (prompted for) - - - Keys.as_variable { Clipboard.copy } # Copy to variable - - - # Think of another key for backing it up? - - Keys.as_window { View.save } # remember window configuration as name - Keys.as_you { Clipboard.as_thing } # copy object / symbol at point - - # Y - # Z - #Keys.A0 { Clipboard.copy("0") } # As 0: copy as key "0" - Xiki.def("as+1"){ Clipboard.copy("1") } # As 1 - Xiki.def("as+2"){ Clipboard.copy("2") }; Xiki.def("as+3"){ Clipboard.copy("3") }; Xiki.def("as+4"){ Clipboard.copy("4") } - Xiki.def("as+5"){ Clipboard.copy("5") }; Xiki.def("as+6"){ Clipboard.copy("6") }; Xiki.def("as+7"){ Clipboard.copy("7") } - end - - def self.open_keys - # O: open... - # Use O prefix for: opening, jumping to files - - # Keys.OO { $el.open_line $el.elvar.current_prefix_arg || 1 } # OO - open line (O's emacs default) - Keys.open_a_calendar { $el.calendar } - #Keys.OAD { Svn.jump_to_diff } - Keys.open_as_file { Code.open_as_file } - Keys.open_as_elisp { $el.find_function_at_point } # jump to definition of lisp function - Keys.open_as_highest { FileTree.open_as_upper } - Keys.open_as_lowest { FileTree.open_as_upper(:lowest) } - Keys.open_as_utf { $el.revert_buffer_with_coding_system('utf-8'.to_sym) } - Keys.open_as_2 { FileTree.open_as_upper(:second) } - Keys.open_as_root { Files.open_sudo } - Keys.open_a_shell { Console.open } - Xiki.def("open+as+tail"){ Files.open_tail } - Keys.open_bookmark { Bookmarks.go } - - - Keys.open_current { Launcher.open("current/") } # open buffer list - - - Keys.open_diffs { DiffLog.open } # shows diffs of what you've edited - Keys.open_edited { Files.open_edited } # show recently edited files - Keys.open_file { Files.open } - # G: leave unmapped for escape - Xiki.def("open+history", "recent files/") - Keys.open_in_browser { Browser.open_in_browser } - Keys.open_in_left { View.open_in_bar } - Keys.open_in_os { Files.open_in_os } - Keys.open_in_right { View.open_in_right } - Keys.open_in_window { Files.open_in_window } # Expose file in OS folder - Keys.open_just { Files.open_just } # TODO: When we fix @current/ to show these (try running as-is and see how it uses the old code-tree) - Keys.open_key { Keys.jump_to_code } # jump to ruby code of key definition - Keys.open_list_appointments { View.bar; Launcher.open("- Agenda.menu/") } - Keys.open_list_bookmarks { Launcher.open("bookmarks/list/") } - Keys.open_list_clipboard { Launcher.open("clipboard/log/") } - # Keys.open_last_error { Code.show_el4r_error } - Keys.open_list_faces { Styles.list_faces } - # Keys.open_list_flashes { Launcher.open "- view/flashes/" } - Keys.open_lisp_info { $el.info "elisp" } # Open manual - - Xiki.def "open+list+log", ".@git/log/" # Show git diffs o 1 file - - Keys.open_last_outlog { OlHelper.open_last_outlog } # Show git diffs for a bookmark - Keys.open_log_push { Git.show_log } # Show git diffs for a bookmark - Keys.open_last_screenshot { Files.open_last_screenshot } - # Keys.open_log_tree { Rails.tree_from_log } - Keys.open_list_databases { Launcher.open('- Couch.databases/') } - # Keys.open_list_models { Launcher.open("- Merb.models/") } - Keys.open_list_javascript { View.beep "- Changed to: open+menu+JN!" } - # Keys.open_list_names { Clipboard.list } - Xiki.def("open+list+notes", "notes/list/") - - Keys.open_list_ruby { View.beep "- Changed to: open+menu+RN!" } - - Keys.open_list_technologies { Launcher.open("technologies/") } # open first hyperlink on page - Keys.open_last_urls { Launcher.open "last/urls/" } - Keys.open_menu { Xiki.open_menu } # Open all menus and show them - Keys.open_new_file { View.new_file } - Xiki.def("open+not+saved", "not saved/") - Keys.open_over { $el.open_line $el.elvar.current_prefix_arg || 1 } # OO - open line (O's emacs default) - Keys.open_point { Bookmarks.go(nil, :point => true) } - Keys.open_quick { Bookmarks.open_quick } # like OB but uses different temporary namespace - Keys.open_related_test { Code.open_related_rspec } - Keys.open_related_file { Code.open_related_file } - # S - Keys.open_search { Search.outline_search } # hide search via outline - - # Bring this back, for opening dir tree when bookmark is to file? - or make that up+open+bookmark? - # Keys.open_tree { FileTree.tree } # draw a tree, prompting for bookmark tag - - Keys.open_up { View.show_dir } # open enclosing dir - Keys.open_visualize { Code.do_list_ancestors } # show currently open files and buffers - Keys.open_windows { View.restore } # open window configuration by name - Keys.open_xiki_docs { Help.display_docs } - Keys.open_xiki_help { Launcher.open("- Help.menu/") } # - # Y - # Z - - Xiki.def("open+1"){ Files.open_nth 1 }; Xiki.def("open+2"){ Files.open_nth 2 }; Xiki.def("open+3"){ Files.open_nth 3 }; Xiki.def("open+4"){ Files.open_nth 4 }; Xiki.def("open+5"){ Files.open_nth 5 } - Xiki.def("open+6"){ Files.open_nth 6 }; Xiki.def("open+7"){ Files.open_nth 7 }; Xiki.def("open+8"){ Files.open_nth 8 }; Xiki.def("open+9"){ Files.open_nth 9 } - - Xiki.def("open+0"){ Files.open_nth 0 } # Open 0: open line in $f that cursor is on - - Xiki.def("open+8"){ History.open_current :all => true, :prompt_for_bookmark => true } # Like do_outline, but inserts all - end - - def self.enter_keys - # E: enter... - # Use E prefix for: inserting - - # TODO find different word? - # - Because "enter" can be confused with the enter key? - # - ideas: embed, emit, entry - # Keys.EE { Line.to_right } # EE - end of line (E's emacs default) - Keys.enter_all { Launcher.enter_all } - Keys.enter_bookmark { FileTree.tree(:here=>true) } - - Keys.enter_clipboard { Clipboard.paste("0") } # paste - - # Keys.enter_docs { Line.enter_docs } # Maybe restore this, but haven't been using it - # Or, make this be "enter+from+difflog?" - Keys.enter_diff { DiffLog.enter_from_difflog } - Keys.enter_end { Line.to_right } # C-e C-e - end of line (C-e by default in emacs) - # $el.define_key :global_map, $el.kbd("C-e C-e"), :end_of_line - Keys.enter_file_path { Files.enter_file } # Given a bookmark - Keys.enter_firefox_tabs { Launcher.insert('browser/tabs/') } # Given a bookmark - Keys.enter_history { DiffLog.enter_from_difflog } # Save point and go to difflog to search - Keys.enter_insert_date { View.enter_date } - Keys.enter_insert_comment { Code.enter_insert_comment } # insert date string (and time if C-u) - Keys.enter_insert_new { DiffLog.enter_new } # Enter Old: enter newly-deleted from last save - Keys.enter_insert_ruby { code = Keys.input(:prompt=>"Enter ruby code to eval and insert results: "); View.insert(eval(code).to_s)} - Xiki.def("enter+insert+search"){ Search.enter_insert_search } - - Keys.enter_insert_old { DiffLog.enter_old } # Enter Old: enter newly-deleted from last save - - Keys.enter_insert_words { PauseMeansSpace.go } - - Keys.enter_junior { Notes.enter_junior } - Keys.enter_key { Keys.insert_code } - Keys.enter_log_1 { View << "Ol[1]" } - Xiki.def("enter+log+check"){ View << "Ol[\"check!\"]\n" } - Keys.enter_log_ancestors { Code.enter_log_ancestors } - Keys.enter_list_databases { Launcher.insert('- Couch.databases/') } - Keys.enter_like_edits { Search.enter_like_edits } - Keys.enter_log_javascript { Firefox.enter_log_javascript_line } - Keys.enter_log_stack { Code.enter_log_stack } - Keys.enter_log_line { Code.enter_log_line } - Keys.enter_log_out { Code.enter_log_out } - # Keys.enter_list_ruby { Launcher.insert("technologies/ruby/") } - - Keys.enter_log_time { Code.enter_log_time } - Keys.enter_like_url { Firefox.enter_as_url } - Keys.enter_like_variable { insert "\#{#{Clipboard.get(0)}}" } - - Keys.enter_menu { Xiki.insert_menu } - Keys.enter_note { Notes.enter_note } - Xiki.def("enter+note"){ Notes.enter_note } - Keys.enter_outline { Launcher.enter_outline } # in tree, enter methods or headings - - # Keys.enter_push { Git.code_tree_diff(:enter=>true) } # Commit to repos, push, etc - Keys.enter_point { Notes.bullet } # Commit to repos, push, etc - Keys.enter_quote { FileTree.enter_quote } - Keys.enter_row { View.insert_line } - - # Keys.enter_search { Search.enter_search } - Xiki.def("enter+source"){ Snippet.insert } - - Xiki.def("enter+todo"){ View.enter_upper } - # Keys.enter_tree { FileTree.tree(:here=>true) } - Xiki.def("enter+upper"){ View.beep "- Changed to: enter+todo - make this be enter+under? (like enter+quote, but not a quote)!" } - Keys.enter_value { Clipboard.paste } - # W - Keys.enter_whitespace { Code.enter_whitespace } - # X - # Y - # Z - Xiki.def("enter+1"){ Clipboard.paste(1) } # Enter 1 - Xiki.def("enter+2"){ Clipboard.paste(2) } # Enter 2 - Xiki.def("enter+3"){ Clipboard.paste(3) } - Xiki.def("enter+4"){ Clipboard.paste(4) }; Xiki.def("enter+5"){ Clipboard.paste(5) }; Xiki.def("enter+6"){ Clipboard.paste(6) } - Xiki.def("enter+7"){ Clipboard.paste(7) }; Xiki.def("enter+7"){ Clipboard.paste(8) }; Xiki.def("enter+7"){ Clipboard.paste(9) } - end - - def self.do_keys - # D: do... - # Use D prefix for: things that modify text or execute code - - # Keys.D { insert "Apparently this is necessary to remap C-d" } - # Keys.DD { $el.delete_char $el.elvar.current_prefix_arg || 1 } # DD - delete character (D's emacs default) - # Keys.do_as_camelcase { Clipboard.do_as_camel_case } # change word to camel case (LikeThat) - Keys.do_as_execute { Console.do_as_execute } # Run shell command on tree - Keys.do_as_html { Firefox.do_as_html } - Keys.do_as_browser { Firefox.exec_block } - Keys.do_as_javascript { Javascript.run } - # Keys.do_as_launched { Launcher.do_as_launched } - # Keys.do_as_php { Php.run } - Keys.do_as_menu { Menu.do_as_menu } # Grab item after '@' and run it by itself - Keys.do_as_python { Python.run_block } - # Do, take numeric prefix for before and after - Keys.do_add_space { Code.add_space } - Keys.do_as_test { Code.do_as_rspec } - Keys.do_as_wrap { Block.do_as_wrap } - Keys.do_as_quote { Notes.do_as_quote } - Keys.do_as_xul { Firefox.do_as_xul } - # B - Keys.do_backward { View.beep "- Changed to: up+H!" } # delete word backward - Keys.do_code_align { Code.do_code_align } # Based on input character, all matches line up - Keys.do_click_back { Firefox.back } - Keys.do_create_directory { FileTree.do_create_dir } - # Keys.do_click_hyperlink { Firefox.click } # compare with last AV version - - Keys.do_current_file { Files.delete_current_file } - - Keys.do_compare_history { History.diff_with_backup } # compare with last AV version - - Keys.do_code_indent { Code.indent } - Keys.do_count_matches { View.count_matches } - Keys.do_copy_name { Clipboard.copy_name } # Copy file name (without extension and path) - Keys.do_colors_off { $el.font_lock_mode } # toggles - Keys.do_clean_quotes { Files.do_clean_quotes } # Fix special chars - - Xiki.def("do+compare+repository"){ Git.do_compare_repository } - - Keys.do_compare_saved { DiffLog.compare_with_saved } - - Keys.do_copy_to { FileTree.copy_to } - Keys.do_compare_views { DiffLog.do_compare_with :u } - Keys.do_compare_with { DiffLog.do_compare_with } - Keys.DC1 { Clipboard.diff_1_and_2 } # Compare contents of clipboards "1" and "2" - - Keys.do_delete { $el.delete_char $el.elvar.current_prefix_arg || 1 } # DD - delete character (D's emacs default) - # Probably rename this to not "expand", now that that is kind of the new word for "launch" - Keys.do_expand { $el.dabbrev_expand nil } # expand abbreviation / autocomplete - Keys.do_forward { $el.kill_word(Keys.prefix || 1) } # delete word forward - - # H - # G: leave unmapped for escape - Keys.do_here { Launcher.do_last_launch :here=>1 } # collapse current menu and run again - Keys.do_indent { Code.indent_to } - Keys.do_job { Macros.run } # do last macro - Keys.do_kill_all { Effects.blink :what=>:all; View.kill_all } # kill all text in buffer - Keys.do_kill_indented { CodeTree.do_kill_indented } # Delete menu or file or whatever (just passes "0") prefix - Keys.do_kill_matching { Search.kill_filter } - Keys.do_kill_nonmatching { Search.kill_filter } - Keys.do_kill_paragraph { View.kill_paragraph } # kill all text in buffer - Keys.do_kill_rest { CodeTree.kill_rest } # kill adjacent lines at same indent as this one - Keys.do_kill_siblings { CodeTree.kill_siblings } # kill adjacent lines at same indent as this one - Keys.do_kill_trailing { View.gsub!(/[ ]+$/, "") } # Deletes trailing whitespace - Keys.do_list_ancestors { View.beep "- Changed to: do+visibility!" } # Moved to do+visibility - Keys.do_load_browser { Browser.reload } - Keys.do_last_command { Console.do_last_command } - Keys.do_line_duplicate { Line.duplicate_line } - Keys.do_load_file { Files.do_load_file } # U prefix will auto-update / auto-refresh to relflect changes - Keys.do_lines_having { # delete lines matching a regex - unless $el.elvar.current_prefix_arg - delete_matching_lines( Keys.input(:prompt => "Delete lines having: ") ) - else - delete_non_matching_lines( Keys.input(:prompt => "Delete lines not having: ") ) - end - } - Keys.do_lines_jumble { Code.randomize_lines } # Shuffle lines - Keys.do_linebreaks_linux { $el.set_buffer_file_coding_system :unix } - Keys.do_line_next { Line.move :next } - Keys.do_line_previous { Line.move(:previous) } - Keys.do_lines_reverse { $el.reverse_region($el.region_beginning, $el.region_end) } - Keys.do_lines_sort { Line.do_lines_sort } - - Keys.do_lines_toggle { Line.do_lines_toggle } # Swap next N lines - - Keys.do_lines_unique { Code.kill_duplicates } # Uniqify, delete duplicates - Keys.do_linebreaks_windows { $el.set_buffer_file_coding_system :dos } - Keys.do_move_to { FileTree.move_to } - Keys.do_name_buffer { Buffers.rename } - Keys.do_notes_colors { FileTree.apply_styles; Notes.apply_styles; FileTree.apply_styles_at_end } - Keys.do_number_enter { Incrementer.enter } - Keys.do_name_file { FileTree.rename_file } - Keys.do_number_increment { Incrementer.increment } - Keys.do_number_one { Incrementer.start } - Keys.do_next_paragraph { Code.do_next_paragraph } # Move line to start of next paragraph - Keys.do_name_search { Search.do_name_search } - Keys.do_outline { History.open_current :outline=>true, :prompt_for_bookmark=>true } - # Keys.do_push { Git.code_tree_diff } # Commit to repos, push, etc - Xiki.def("do+push"){ Git.do_push } # Commit to repos, push, etc - Keys.do_query { Search.query_replace } # do query replace - Keys.do_run { Code.run } # run code as ruby - Keys.do_status { Git.do_status } - - Keys.do_tree { FileTree.tree(:recursive=>true) } # draw filesystem tree for current dir or bookmark - Keys.do_upper { Launcher.do_last_launch } - # V - $el.define_key :global_map, $el.kbd("C-d C-v"), :cua_set_rectangle_mark # Keys.do_vertical, do+vertical - Keys.do_whitespace { Deletes.delete_whitespace } # delete blank lines - # X - Keys.do_you { $el.delete_char $el.elvar.current_prefix_arg || 1 } # Delete character - Keys.do_zip_next { Files.zip } - Keys.set("C-d C-.") { # Do .: Go to point/bookmark starting with "." and run it (like pressing C-. on that line) - input = Keys.input(:timed => true) - $el.with(:save_window_excursion) do - Bookmarks.go(".#{input}") - Launcher.launch_unified - end - } - Keys.set("C-d C-/") { Code.comment } - - Xiki.def("do+1"){ Search.query_replace_nth "1", "2" } - Xiki.def("do+2"){ Search.query_replace_nth "2", "1" } - Xiki.def("do+3"){ Search.query_replace_nth "3", "4" } - Xiki.def("do+4"){ Search.query_replace_nth "4", "3" } - - end - - def self.to_keys - # T: to... - # Use T prefix for: moving cursor, jumping to specific points - - $el.el4r_lisp_eval(%Q`(global-set-key (kbd "C-\'") \'repeat)`) - - # Keys.TT { $el.transpose_chars $el.elvar.current_prefix_arg } # TT - toggle character (T's emacs default) - Keys.to_axis { Move.to_axis } # to beginning of file - # B - Keys.to_backward { View.beep "- Changed to: up+back!" } # move backward one word - Keys.to_column { Move.to_column } # to x coordinate - ie column - # D - Keys.to_end { Move.to_end } # To end of line - # F - Keys.to_forward { View.beep "- Changed to: up+forward!" } # move forward one word - Keys.to_highest { View.to_highest } # to beginning of file - Keys.to_indent { Move.to_indent } - Keys.to_junior { Move.to_junior } - Keys.to_kind { Move.to_other_bracket } # to matching bracket, etc - Keys.to_lowest { View.to_bottom } # move to end - Xiki.def("to+menu"){ Menu.to_menu } - Keys.to_next { Move.to_next_paragraph } # to next paragraph - Keys.to_outline { FileTree.to_outline } - Keys.to_previous { Move.to_previous_paragraph } # to beginning of previous paragraph - Keys.to_quote { Move.to_quote } # move to next ...|... quote - Keys.to_row { Move.to_line } # go to nth line, relative to top of window - Keys.to_spot { Location.to_spot } - - Keys.to_toggle { View.toggle } # TT - toggle character (T's emacs default) - - Keys.to_up { Tree.to_parent } # to parent (last line indented less) - Keys.to_visible { View.to_relative } # go to nth line, relative to top of window - Keys.to_words { Line.to_beginning } # move to start of words on line - # X - # Z - - Xiki.def("to+0"){ View.to_nth_paragraph 0 } - Xiki.def("to+1"){ View.to_nth_paragraph 1 } - Xiki.def("to+2"){ View.to_nth_paragraph 2 } - Xiki.def("to+3"){ View.to_nth_paragraph 3 } - Xiki.def("to+4"){ View.to_nth_paragraph 4 } - Xiki.def("to+5"){ View.to_nth_paragraph 5 } - Xiki.def("to+6"){ View.to_nth_paragraph 6 } - Xiki.def("to+7"){ View.to_nth_paragraph 7 } - Xiki.def("to+8"){ View.to_nth_paragraph 8 } - Xiki.def("to+9"){ View.to_nth_paragraph 9 } - - Keys.set("C-t C-/") { Code.to_comment } - - end - - def self.layout_keys - # L: layout... - # Use L prefix for: adjusting the layout, changing what is visible - - # Keys.LL { View.recenter } # LL - recenter (L's emacs default) - Keys.layout_all { View.hide_others } - Keys.layout_balance { 3.times { View.balance } } # balance windows - Keys.layout_create { View.create } # open new view - Xiki.def "layout+dimensions", "dimensions/", :letter=>1 - - Keys.layout_expand { View.enlarge } - # F - Keys.layout_files { View.layout_files } - Keys.layout_hide { View.hide } # - Keys.layout_indent { Hide.hide_by_indent } # only show lines indented less than x - Keys.layout_jump { View.shift } - Keys.layout_kill { View.kill } - Keys.layout_look { View.recenter } # LL - recenter (L's emacs default) - Xiki.def "layout+mark", "mark/", :letter=>1 - Xiki.def("layout+next"){ View.next(:blink=>true) } - Keys.layout_outlog { View.layout_outlog } - Keys.layout_previous { View.previous(:blink=>true) } - # Q - Keys.layout_right { View.to_upper(:blink=>true) } # Go to view to the right - Keys.layout_search { Keys.prefix_u? ? Search.find_in_buffers(Keys.input(:prompt=>"Search all open files for: ")) : Hide.search } - Keys.layout_todo { View.layout_todo } # show bar on left with the quick bookmark named "-t" - Keys.layout_uncover { Hide.reveal } # Reveal all hidden text - Xiki.def "layout+visibility", "window/visibility/", :letter=>1 - Keys.layout_wrap { $el.toggle_truncate_lines } # wrap lines - # X - # Y - Keys.layout_zoom { $el.narrow_to_region($el.region_beginning, $el.region_end) } # show selection only - - Xiki.def("layout+0"){ View.recenter_top } # Layout 0: scroll so cursor is 0 lines from top af window - Xiki.def("layout+1"){ Move.to_window(1, :blink=>true) } # Layout 1 - Xiki.def("layout+2"){ Move.to_window(2, :blink=>true) } # Layout 2 - Xiki.def("layout+3"){ Move.to_window(3, :blink=>true) }; Xiki.def("layout+4"){ Move.to_window(4, :blink=>true) } - Xiki.def("layout+5"){ Move.to_window(5, :blink=>true) }; Xiki.def("layout+6"){ Move.to_window(6, :blink=>true) }; Xiki.def("layout+7"){ Move.to_window(7, :blink=>true) }; Xiki.def("layout+8"){ Move.to_window(8, :blink=>true) } - Xiki.def("layout+9"){ Move.to_last_window(:blink=>true) } - - # Todo: if prefix passed, expand window, but leave other windows open with that much space in each - # Keys.LCR { Colors.highlight } # Layout Tree: show bar on left with the quick bookmark named "-t" - - # Todo: - # - Make it recognize :n when opening - # - If you put it at end of path or as tree node, it should make it into path - # - So look at it when opening path and optionally jump - # - narrow block to region: - end - - # Control keys during isearch - def self.isearch - Keys.search_axis { Search.to_left } - - - Xiki.def("search+bookmark"){ Search.bookmark } - # B: leave unmapped for back - - Xiki.def("search+copy"){ Search.isearch_copy } # Clipboard (copy) or search+commands (if no search) - - Keys.search_delete { Search.isearch_delete } # Delete - Keys.search_enter { Search.enter } # Enter: insert clipboard, replacing match - Keys.search_frontward { Search.go_to_end } # Forward - Keys.search_g { Search.cancel } # Stop searching - # have_... - $el.define_key :isearch_mode_map, $el.kbd("C-h"), nil - Keys.search_have_after { Search.isearch_move_to "$t", :append=>1 } - Keys.search_have_bullet { Search.have_label } - Keys.search_have_case { Search.isearch_have_case } - Keys.search_have_edges { Search.just_edges } # Delete everything but chars at edges of match - Keys.search_have_files { View.beep "- Changed to: search+have+navigation!" } - Keys.search_have_highest { Search.isearch_restart :top } - Keys.search_have_javascript { Search.isearch_have_outlog_javascript } - Keys.search_have_line { Search.have_line } # copy line back to search start - Keys.search_have_mock { Search.isearch_have_outlog :method=>".mock" } - Keys.search_have_nav { Search.isearch_move_to "$f" } - - Keys.search_have_outlog { Search.isearch_have_outlog } - Keys.search_have_push { Git.search_just_push } # When search match - - Keys.search_have_right { Search.have_right } - Keys.search_have_spot { Search.insert_at_spot } - Keys.search_have_todo { Search.isearch_move_to "$t" } - Keys.search_have_variable { Search.insert_var_at_search_start } - Keys.search_have_within { Search.isearch_have_within } # Grab everything except chars on edges - - # AVAILABLE: search_i ? (when nothing searched for) - - # I: leave unmapped - had issues using it (messes up position) - # just_... - $el.define_key :isearch_mode_map, $el.kbd("C-j"), nil - Keys.search_just_adjust { Search.isearch_just_adjust } - Keys.search_just_bookmark { Search.just_bookmark } - Keys.search_just_case { Search.isearch_just_case } # make match be camel case - Keys.search_just_difflog { Search.jump_to_difflog } # find last string in difflog - Keys.search_just_edits { Search.just_edits } # Search in diff of edits to this file - Keys.search_just_files { Search.isearch_restart "$f" } # isearch for this string in $f - Keys.search_just_have { Search.just_select } # select match - Keys.search_just_integer { Search.stop; Search.isearch "[0-9][0-9.]*", :regex=>1 } - - Keys.search_just_menu { Search.just_menu } - # Keys.search_just_mark { Search.just_marker } - Keys.search_just_next { Search.isearch_restart :next } - Keys.search_just_outlog { Search.isearch_restart "$o" } - Keys.search_just_previous { Search.isearch_restart :previous } - Keys.search_just_query { Search.isearch_query_replace :match } # replace - Keys.search_just_right { Search.isearch_restart :right } # Search in top-right view - # Keys.search_just_search { Search.isearch_just_search } # Add "##search" line in tree for match - Keys.search_just_special { Search.isearch_just_special } # Add "##search" line in tree for match - Keys.search_just_todo { Search.isearch_restart "$t" } # isearch for this string in $t - - Keys.search_just_variable { Search.isearch_just_surround_with_char '#{', '}' } - Keys.search_just_wrap { Ol << 'search_just_wrap'; toggle_truncate_lines } # make match be snake case - - Keys.search_just_yellow { Search.just_orange } - Keys.search_kill { Search.cut } # cut - - $el.define_key :isearch_mode_map, $el.kbd("C-l"), nil - - Keys.search_like_clipboard { - reverse = Search.was_reverse - match = Search.stop - Search.isearch Clipboard[0], :reverse=>reverse - } # make match be camel case - Keys.search_like_delete { Search.like_delete } # Delete all lines that contain the match - Keys.search_like_file { Search.isearch_open } - Keys.search_like_menu { Launcher.search_like_menu } - Keys.search_like_outlog { Search.isearch_have_outlog :no_label=>1 } - Keys.search_line_pull { Search.isearch_move_line } - Keys.search_like_quote { Search.isearch_google :quote=>true } - Keys.search_like_repository { Git.search_repository } # When not searching - - Keys.search_like_synonyms { Search.search_thesaurus } - # Keys.search_like_timer { Search.search_like_timer } - - # Keys.search_like_thesaurus { Search.search_thesaurus } - Keys.search_last_urls { Launcher.open("- Launcher.urls/") } - Keys.search_like_variable { Search.just_name } - Keys.search_like_web { Search.isearch_google } # make match be snake case - Keys.search_like_xiki { View.open "$x/#{Search.stop.strip}" } - - # Use search_navigated instead - # Keys.search_last_launched { Search.search_last_launched } - # Keys.search_log { Search.search_log } - # M: leave unmapped for stop - Keys.search_next { Search.isearch_next } # Next, or navigated (if nothing searched for yet) - Keys.search_outline { Search.isearch_outline } # Outline - Keys.search_paths { Search.isearch_paths } # Just go to previous line - # P: leave unmapped for previous - # Q: leave unmapped for quoting - # R: leave unmapped for reverse - # S: leave unmapped for search - Keys.search_to { Search.isearch_to } # To: open file / jump to method - Keys.search_usurp { Search.isearch_pull_in_sexp } # usurp: pull sexp into search string - Keys.search_value { Search.insert_at_search_start } # Value: copy value back to search start - # W: leave unmapped for pulling into search - - Keys.search_xiki { Search.xiki } # search+xiki+__ mapped inside this method - - - # Y: leave unmapped for yank - Keys.search_zap { Search.zap } # zap - delete up until search start - - # Surround with characters (quotes and brackets) - - $el.define_key(:isearch_mode_map, $el.kbd("C-'")) { Search.isearch_just_surround_with_char '"' } - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-'")) { Search.isearch_just_surround_with_char "'" } - - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-,")) { Search.isearch_surround_with_tag } - - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-SPC")) { Search.isearch_just_surround_with_char " " } - - $el.define_key(:isearch_mode_map, $el.kbd("C-`")) { Search.isearch_just_surround_with_char "~" } - - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-/")) { Search.isearch_just_comment } - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-=")) { Search.just_increment } # search+just+Plus - $el.define_key(:isearch_mode_map, $el.kbd("C-j C--")) { Search.just_increment(:decrement=>true) } # search+just+Minus - - $el.define_key(:isearch_mode_map, $el.kbd("C-9")) { Search.isearch_just_surround_with_char '(', ')' } - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-9")) { Search.isearch_just_surround_with_char '[', ']'} - - $el.define_key(:isearch_mode_map, $el.kbd("C-h C-[")) { Search.isearch_just_surround_with_char '[', ']' } - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-[")) { Search.isearch_just_surround_with_char '{', '}' } - - $el.define_key(:isearch_mode_map, $el.kbd("C-h C-'")) { Search.insert_quote_at_search_start } - - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-1")) { Search.enter(Clipboard[1]) } # isearch_just_1 - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-2")) { Search.enter(Clipboard[2]) } # isearch_just_2 - $el.define_key(:isearch_mode_map, $el.kbd("C-j C-3")) { Search.enter(Clipboard[3]) } # isearch_just_3 - $el.define_key(:isearch_mode_map, $el.kbd("C-h C-1")) { Search.isearch_query_replace Clipboard[1] } - $el.define_key(:isearch_mode_map, $el.kbd("C-h C-2")) { Search.isearch_query_replace Clipboard[2] } - - - $el.define_key(:isearch_mode_map, $el.kbd("C-h C-0")) { Search.isearch_query_replace Clipboard[0] } - - $el.define_key(:isearch_mode_map, $el.kbd("C-1")) { Search.isearch_or_copy("1") } - $el.define_key(:isearch_mode_map, $el.kbd("C-2")) { Search.isearch_or_copy("2") } - $el.define_key(:isearch_mode_map, $el.kbd("C-3")) { Search.isearch_or_copy("3") } - $el.define_key(:isearch_mode_map, $el.kbd("C-4")) { Search.isearch_or_copy("4") } - $el.define_key(:isearch_mode_map, $el.kbd("C-5")) { Search.isearch_or_copy("5") } - $el.define_key(:isearch_mode_map, $el.kbd("C-6")) { Search.isearch_or_copy("6") } - $el.define_key(:isearch_mode_map, $el.kbd("C-7")) { Search.isearch_or_copy("7") } - $el.define_key(:isearch_mode_map, $el.kbd("C-8")) { Search.isearch_or_copy("8") } - - $el.define_key(:isearch_mode_map, $el.kbd("C-=")) { $el.isearch_yank_char } # Add one char from isearch - $el.define_key(:isearch_mode_map, $el.kbd("C--")) { Search.subtract } # Remove one char from isearch - $el.define_key(:isearch_mode_map, $el.kbd("C-/")) { $el.isearch_delete_char } # Remove last action from search results - $el.define_key(:isearch_mode_map, $el.kbd("C-,")) { Search.isearch_query_replace } # Replace all occurrences - - $el.define_key(:isearch_mode_map, $el.kbd("C-\\")) { Search.hide } # Hide: hide non-matching - - $el.define_key(:isearch_mode_map, $el.kbd("C-0")) { Search.isearch_pause_or_resume } # isearch_just_0 - - # Safe mapping of C-m to Search.isearch_m (works when el4r is down) - $el.el4r_lisp_eval(%`(defun isearch-m () (interactive) - (if (eq (process-status el4r-process) 'run) (el4r-ruby-eval "::Xiki::Search.isearch_m") (isearch-exit))) - `.unindent) - - $el.define_key :isearch_mode_map, $el.kbd("C-m"), :isearch_m # search+menu (done in a really safe way, so Return in isearch doesn't break when el4r goes down) - - end - - def self.map_control_return - - # Aquamacs-specific: make cua not map C-return key - $el.define_key(:cua_global_keymap, $el.kbd(""), nil) if $el.boundp(:cua_global_keymap) - - Keys.set("") { Launcher.go } # control-return, control-enter - end - - def self.map_command_return - return if ! $el.boundp(:osx_key_mode_map) - - $el.define_key(:osx_key_mode_map, $el.kbd("")) { Launcher.go } - end - - # Not called by default - def self.map_meta_return - Keys.set("") { Launcher.go } # command-return, command-enter - end - - def self.misc - - $el.define_key :global_map, $el.kbd("C-S-v"), :scroll_down - - # Single character definitions - Keys.B { Move.backward } - Keys.F { Move.forward } - Keys.Q { Keys.timed_insert } - - # These keys "launch" things (same thing as double-clicking) - Keys.set("C-.") { Launcher.go_unified } # control period - self.map_command_return - self.map_control_return - - if $el.locate_library "ruby-mode" - $el.el_require :ruby_mode - $el.define_key :ruby_mode_map, $el.kbd("C-\\") do - Hide.show - Hide.hide_unless /^ *(def|class|module|create_table|it|describe) / - $el.recenter -2 - Hide.search - end - end - $el.el_require :cc_mode - - # Unmap keys in modes that interfere - $el.el4r_lisp_eval("(require 'shell)") - $el.define_key :shell_mode_map, $el.kbd("C-d"), nil # shell-mode etc. special C-d shortcuts over-ride xiki - $el.define_key :objc_mode_map, $el.kbd("C-d"), nil - $el.define_key :c_mode_map, $el.kbd("C-d"), nil - $el.el4r_lisp_eval("(require 'dired)") - $el.define_key :dired_mode_map, $el.kbd("C-o"), nil - $el.define_key :java_mode_map, $el.kbd("C-d"), nil - - begin - $el.el_require :php_mode - $el.define_key :php_mode_map, $el.kbd("C-d"), nil - rescue Exception=>e - end - - # C-l in ediff mode - $el.defun(:ediff_disable_C_l) { $el.define_key(:ediff_mode_map, $el.kbd("C-l"), nil) } - $el.add_hook :ediff_keymap_setup_hook, :ediff_disable_C_l - - ControlTab.keys - - Keys.set("M-0") { Styles.font_size 120 } - Keys.set("M-=") { Styles.zoom } - Keys.set("M--") { Styles.zoom :out=>1 } - - View.sensible_defaults - - end - - end -end diff --git a/lib/xiki/core/key_shortcuts.rb b/lib/xiki/core/key_shortcuts.rb new file mode 100644 index 00000000..9f3e2942 --- /dev/null +++ b/lib/xiki/core/key_shortcuts.rb @@ -0,0 +1,862 @@ +module Xiki + class KeyShortcuts + + # Define all keys + def self.keys + + Menu.init + + self.window_keys + self.list_keys + + self.as_keys + self.enter_keys + + self.hop_keys + self.content_keys + + self.do_keys + self.run_keys + + self.jump_keys # time > (0.073769) + self.misc # time > (0.0451) + + end + + def self.as_keys + + # This breaks > probably unset C-a first, so we can go back to this + + Xiki.def("as+axis"){ Move.hop_left_key } + Xiki.def("as+idea"){ Notes.as_task } + Xiki.def("as+line"){ Clipboard.as_line } + + Xiki.def("as+before"){ Clipboard.copy_paragraph(:before=>true) } + Xiki.def("as+under"){ Clipboard.copy_paragraph(:rest=>1) } + Xiki.def("as+following"){ Clipboard.copy_rest_of_line } + + + Xiki.def("as+directory"){ FileTree.copy_path } # copy dir to clipboard from tree + + Xiki.def("as+task"){ View.beep '- changed to as+note!' } + Xiki.def("as+note"){ Notes.as_task } + Xiki.def("as+quote"){ Notes.as_task :link=>1 } + + Xiki.def("as+remembered"){ Location.as_spot } # remember point in file + Xiki.def("as+select"){ Clipboard.select } + Xiki.def("as+version"){ History.backup_file } # creates backup + Xiki.def("as+macro"){ Macros.record } # start recording macro + + Xiki.def("as+open"){ Launcher.as_open } + Xiki.def("as+everything"){ View.beep '- changed to > content+all!' } + Xiki.def("as+you"){ Clipboard.as_thing } # copy object / symbol at point + + # as+N > Open nth quote in :t + # shold do this instead? do+N > execute nth label in current page? + (0..9).each do |n| + Xiki.def("as+#{n}"){ View.to_nth_fraction n } + end + end + + def self.list_keys + # O: open... + # Use O prefix for: opening, jumping to files + + Xiki.def("list+xiki", :noob=>1){ Launcher.open "xiki/" } + Xiki.def("list+search", :noob=>1){ Ol "Todo > Perform sync first!"; Launcher.open "xiki/" } + + Xiki.def("list+history"){ Launcher.open("history/") } + + Xiki.def("list+diffs", :noob=>1){ DiffLog.open } # shows diffs of what you've edited + Xiki.def("list+changes"){ DiffLog.show_edits } # Diffs in particular file + + Xiki.def("list+interactions", :noob=>1){ Launcher.open "interactions/" } + + Xiki.def("list+views", :noob=>1){ Launcher.open("views/") } + Xiki.def("list+recent", "recent/") + Xiki.def("list+edited"){ Files.open_edited } # show recently edited files + Xiki.def("list+bookmarks", :noob=>1){ Launcher.open "bookmarks/" } + + Xiki.def("list+trail"){ Code.do_list_ancestors } + + Xiki.def("list+unsaved"){ DiffLog.compare_with_saved } + + Xiki.def("list+notes", :noob=>1){ Launcher.open("notes") } + Xiki.def("list+links", :noob=>1){ Launcher.open("links/") } + Xiki.def("list+key"){ FileTree.hop_file } + Xiki.def("list+files", :noob=>1){ Launcher.open("files/") } + + Xiki.def("list+quick"){ Launcher.open_topic } + Xiki.def("list+markers", :noob=>1){ Launcher.open("#{View.file}\n = markers/", :bar_is_fine=>1) } + Xiki.def("list+outline", :noob=>1){ FileTree.to_outline } + Xiki.def("list+appointments", :noob=>1){ Launcher.open("~/xiki/notes.xiki\n - ##^> 201/", :bar_is_fine=>1) } # Appointments and other important tasks + + + # G: leave unmapped for escape + + Xiki.def("list+preferences+bar"){ + $el.elvar.xiki_bar_hidden = $el.elvar.xiki_bar_hidden ? nil : true + } + Xiki.def("list+preferences+name"){ Buffers.rename } + Xiki.def("list+preferences+coloring"){ Styles.toggle } + + + Xiki.def("list+wrap"){ Block.do_as_wrap } # Wrap lines in paragraph + + Xiki.def("list+go"){ Launcher.open("jump to dir/") } + + Xiki.def("list+just+methods"){ Launcher.open("links/methods/") } + Xiki.def("list+just+routes"){ Launcher.open("links/routes/") } + Xiki.def("list+just+outlog"){ Launcher.open("links/outlog/") } + Xiki.def("list+just+comments"){ Launcher.open("links/comments/") } + Xiki.def("list+just+xiki"){ Launcher.open("links/xiki/") } + + # list+N > Open nth quote in :n + (0..9).each do |n| + Xiki.def("list+#{n}"){ Links.to_nth_file n } + end + end + + + def self.enter_keys + # E: enter... + # Use E prefix for: inserting + + Xiki.def("enter+end"){ Move.hop_right_key } + + Xiki.def("enter+bullet"){ Notes.bullet } + Xiki.def("enter+comment"){ Code.enter_insert_comment } + + Xiki.def("enter+space"){ Code.enter_whitespace } + + Xiki.def("enter+line"){ View.insert_line } + Xiki.def("enter+under"){ Notes.enter_junior } + Xiki.def("enter+web"){ Search.enter_insert_search } + Xiki.def("enter+marker"){ Notes.enter_note } + + + Xiki.def("enter+key"){ Keys.insert_code } + + Xiki.def("enter+heading"){ Notes.insert_heading } + + Xiki.def("enter+note"){ Tasks.enter_upper } + Xiki.def("enter+file"){ Files.enter_file } # in tree, enter methods or headings + Xiki.def("enter+directory"){ Files.enter_file :expand=>1 } + Xiki.def("enter+outline"){ Launcher.enter_outline } # in tree, enter methods or headings + + Xiki.def("enter+xiki"){ Launcher.open_topic :insert=>1 } + + Xiki.def("enter+quote"){ FileTree.enter_quote } + Xiki.def("enter+value"){ FileTree.enter_quote nil, :char=>":" } + + Xiki.def("enter+prompt"){ View.insert_shell_prompt } + Xiki.def("enter+group"){ $el.cua_set_rectangle_mark } + + Xiki.def("enter+after"){ Tasks.enter_after } + + Xiki.def("enter+item+time"){ Line.insert_time } + Xiki.def("enter+item+date"){ Line.insert_date } + + Xiki.def("enter+item+replacement"){ Search.insert_before_and_after } + Xiki.def("enter+item+new"){ DiffLog.enter_new } # Enter Old: enter newly-deleted from last save > diff + Xiki.def("enter+item+old"){ DiffLog.enter_old } # Enter Old: enter newly-deleted from last save > diff + + # Todo > restore elsewhere + Xiki.def("enter+item+eval"){ code = Keys.input(:prompt=>"Enter ruby code to eval and insert results: "); View.insert(eval(code).to_s)} + Xiki.def("enter+item+javascript"){ Firefox.enter_log_javascript_line } # Xiki.def("enter+log+javascript"){ Firefox.enter_log_javascript_line } + + Xiki.def("enter+item+log"){ Code.enter_log_line } # Xiki.def("enter+log+line"){ Code.enter_log_line } + Xiki.def("enter+item+important"){ Code.enter_log_line :exclamation=>1 } # Xiki.def("enter+log+line"){ Code.enter_log_line } + Xiki.def("enter+item+stack"){ Code.enter_log_stack } # Xiki.def("enter+log+stack"){ Code.enter_log_stack } + + Xiki.def("enter+item+continue"){ Code.enter_log_line :txt=>"continue here!!" } # Xiki.def("enter+log+line"){ Code.enter_log_line } + Xiki.def("enter+item+performance"){ Code.enter_log_time } # Xiki.def("enter+log+time"){ Code.enter_log_time } + + # enter+N > Go to nth heading + (1..9).each do |n| + Xiki.def("enter+#{n}"){ + View.to_highest + (n-1).times do + Notes.to_block :key=>1 + end + View.recenter_top_key + "" + } + end + + end + + + def self.content_keys + + Xiki.def("content+save", :noob=>1){ DiffLog.save } + Xiki.def("content+web", :noob=>1){ XikihubClient.web } + Xiki.def("content+close", :noob=>1){ View.kill } + + Xiki.def("content+untitled", :noob=>1){ View.new_file } + Xiki.def("content+open", :noob=>1){ View.open Keys.input(:prompt=>"File to open: ") } + Xiki.def("content+directory"){ Bookmarks.go nil, :date_sort=>1 } + Xiki.def("content+prompt", :noob=>1){ Launcher.open_prompt } + Xiki.def("content+revert"){ Files.revert :from_key_shortcut=>1 } # Revert file + + Xiki.def("content+filter", :noob=>1){ Search.outline_search } + Xiki.def("content+jump"){ Move.to_line } # Goto nth line in file + + Xiki.def("content+notes", :noob=>1){ Notes.open_todo } # Open notes.xiki + Xiki.def("content+links", :noob=>1){ Notes.open_todo :bookmark=>"%links" } # Open links.xiki + + Xiki.def("content+highlight"){ View.highlight } + Xiki.def("content+all"){ Clipboard.copy_everything } + Xiki.def("content+group"){ Clipboard.copy_paragraph } # copy paragraph + Xiki.def("content+items"){ CodeTree.as_indented } + + Xiki.def("content+tasks", :noob=>1){ Launcher.tasks_menu_on_bookmark :bm=>"." } + + Xiki.def("content+bookmark", :noob=>1){ Bookmarks.save } + Xiki.def("content+keys"){ Xiki::View.toggle_bar_mode } + Xiki.def("content+zoom", :noob=>1){ CodeTree.kill_siblings :cross_blank_lines=>1 } # kill adjacent lines at same indent as this one + + Xiki.def("content+editor"){ Grab.content_editor } + + Xiki.def("content+yours"){ View.open "%y", :stay_in_bar=>1 } + Xiki.def("content+memorize", :noob=>1){ View.open "~/xiki/memorize.xiki" } + Xiki.def("content+quick", :noob=>1){ View.open "%q" } + + Xiki.def("content+views"){ View.link_views } + + + # content+N > Go to nth fraction of the screen (ie content+5 to jump to middle) + (1..9).each do |n| + Xiki.def("content+#{n}"){ Files.open_nth n } + end + + end + + + def self.run_keys + # D: do... + # Use D prefix for: things that modify text or execute code + + Xiki.def("run+recent"){ Shell.recent_history_external nil, :from_key_shortcut=>1 } + Xiki.def("run+macro"){ Macros.run } # do last macro + Xiki.def("run+note"){ Launcher.do_task } + Xiki.def("run+up"){ Launcher.do_last_launch :here=>1 } + + Xiki.def("run+indent"){ Code.indent_to } + # Xiki.def("run+comment"){ Code.comment } + Xiki.def("run+eval"){ Code.run } # run code as ruby + Xiki.def("run+selected"){ Clipboard.select; View.deselect } + + + Xiki.def("run+key"){ Keys.jump_to_code } + + Xiki.def("run+highlight+red"){ Color.mark "red" } + Xiki.def("run+highlight+orange"){ Color.mark "orange" } + Xiki.def("run+highlight+yellow"){ Color.mark "yellow" } + Xiki.def("run+highlight+green"){ Color.mark "green" } + Xiki.def("run+highlight+blue"){ Color.mark "blue" } + Xiki.def("run+highlight+light"){ Color.mark "light" } + Xiki.def("run+highlight+white"){ Color.mark "white" } + + Xiki.def("run+highlight+show"){ Color.show } + Xiki.def("run+highlight+all", "highlight/all/") + + Xiki.def("run+highlight+next"){ Color.next_key } + Xiki.def("run+highlight+previous"){ Color.previous_key } + + Xiki.def("run+highlight+delete"){ Color.delete } + Xiki.def("run+highlight+clear"){ Color.clear; Color.clear_light } + + # Restore? > shows options on a bookmarked file + + Xiki.def("run+outline"){ History.open_current :outline=>true, :prompt_for_bookmark=>true } + + Xiki.def("run+query"){ Search.query_replace } # do query replace + + + Xiki.def("run+as+align"){ Code.do_code_align } # Based on input character, all matches line up + Xiki.def("run+as+execute"){ Shell.do_as_execute } # Run shell command on tree + Xiki.def("run+as+html"){ Firefox.do_as_html } + Xiki.def("run+as+browser"){ Firefox.exec_block } + Xiki.def("run+as+javascript"){ Javascript.run } + Xiki.def("run+as+command"){ Command.do_as_menu } # Grab item after '@' and run it by itself + Xiki.def("run+as+outline"){ Command.do_as_outline } # Grab item after '@' and run it by itself + Xiki.def("run+as+python"){ Python.run } + + Xiki.def("run+as+space"){ Code.add_space } + Xiki.def("run+as+test"){ Code.do_as_rspec } + Xiki.def("run+as+wrap"){ View.beep 'Moved to > list+wrap' } + Xiki.def("run+as+quote"){ Notes.do_as_quote } + Xiki.def("run+as+xul"){ Firefox.do_as_xul } + Xiki.def("run+as+deck"){ Deck.enable_arrow_keys } + Xiki.def("run+as+remote"){ Remote.save_file } + + Xiki.def("run+delete+all"){ Effects.blink :what=>:all; View.kill_all } # kill all text in buffer + + Xiki.def("run+delete+indented"){ CodeTree.do_kill_indented } + Xiki.def("run+delete+siblings"){ View.beep 'Moved to > list+zoom' } # List notes + Xiki.def("run+delete+trailing"){ View.gsub!(/[ ]+$/, "") } # Deletes trailing whitespace + + Xiki.def("run+delete+unsaved"){ View.beep '- changed to content+zap!' } + + Xiki.def("run+delete+matching"){ Search.kill_matching } + Xiki.def("run+delete+nonmatching"){ Search.kill_filter } + + Xiki.def("run+list+faces"){ Styles.list_faces } # list + Xiki.def("run+list+unsaved"){ Launcher.open("unsaved/") } + + Xiki.def("run+jump+up"){ FileTree.move_up } + + Xiki.def("run+forward"){ Line.move_word :forward } + Xiki.def("run+back"){ Line.move_word :backward } + + + Xiki.def("run+version+repository"){ Git.do_push } + Xiki.def("run+version+log"){ Git.do_log } + Xiki.def("run+version+diff"){ Git.do_compare_repository } + Xiki.def("run+version+status"){ Git.do_status } + + Xiki.def("run+version+backups"){ History.list } # Show all backed-up versions + Xiki.def("run+version+changes"){ History.diff_with_backup } # compare with last AV version + + Xiki.def("run+version+next"){ DiffLog.compare_views } + + # run+N > Run nth label in :t + (0..9).each do |n| + Xiki.def("run+#{n}"){ Launcher.do_last_launch :nth=>n } + end + + end + + + def self.hop_keys + + # Use H prefix for: moving cursor, jumping to specific points + + Xiki.def("hop+next"){ Move.to_next_paragraph } # to next paragraph + Xiki.def("hop+previous"){ Move.to_previous_paragraph :skip_if_top=>1 } # to beginning of previous paragraph + + Xiki.def("hop+forward"){ Notes.to_block :key=>1 } + Xiki.def("hop+back"){ Notes.to_block :up=>1, :key=>1 } + + + Xiki.def("hop+to"){ Search.hop_to } + + Xiki.def("hop+edge"){ View.to_relative :line=>1 } # go to nth line, relative to top of window (up+ for middle, up+up+ for bottom) + Xiki.def("hop+aligned"){ View.to_relative } # go to nth line, relative to top of window (up+ for middle, up+up+ for bottom) + + Xiki.def("hop+indent"){ Move.to_indent } + Xiki.def("hop+side"){ View.content_right } + Xiki.def("hop+last"){ Move.to_last_window(:blink=>true) } + + Xiki.def("hop+gap"){ Search.forward "\n\n\n+" } + + # hop+climb is a possible alternative + Xiki.def("hop+up"){ Tree.to_parent :key=>1 } # to parent (last line indented less) + + Xiki.def("hop+hack", :noob=>1){ Deletes.backward } + Xiki.def("hop+down"){ Move.to_next_paragraph :prefix=>:uu } + + Xiki.def("hop+output"){ View.layout_outlog } + Xiki.def("hop+marker"){ Notes.next_marker } # go to nth line, relative to top of window (up+ for middle, up+up+ for bottom) + Xiki.def("hop+clear"){ View.layout_outlog :prefix=>:u } + + Xiki.def("hop+remembered"){ Location.hop_remembered } + + Xiki.def("hop+kind"){ Move.to_other_bracket } # to matching bracket, etc + + Xiki.def("hop+quote"){ Move.to_quote } # move to next |... quote + + Xiki.def("hop+junior"){ Move.to_junior } # go to nth line, relative to top of window (up+ for middle, up+up+ for bottom) + Xiki.def("hop+words"){ Line.to_beginning } # move to start of words on line + # X + # Z + + # hop+N > Go to nth visible paragraph (ie jump+5 to jump to the 5th paragraph) + (1..9).each do |n| + Xiki.def("hop+#{n}"){ View.to_nth_paragraph n } + end + + end + + def self.do_keys + # Use for: dealing with windows + + Xiki.def("do+delete"){ Deletes.forward } + + Xiki.def("do+file"){ FileTree.tree :recursive=>1 } + Xiki.def("do+comment"){ Code.comment } + Xiki.def("do+indent"){ Hide.hide_by_indent } # only show lines indented less than x + + Xiki.def("do+next"){ Line.move :next } + Xiki.def("do+previous"){ Line.move :previous } + + Xiki.def("do+kill"){ Line.delete_line_key } + + Xiki.def("do+selection+sort"){ Line.do_lines_sort } + Xiki.def("do+selection+reverse"){ $el.reverse_region($el.region_beginning, $el.region_end) } + Xiki.def("do+selection+unique"){ Code.kill_duplicates } # Uniqify, delete duplicates + Xiki.def("do+selection+jumble"){ Code.randomize_lines } # Shuffle lines + + Xiki.def("do+selection+having"){ # delete lines matching a regex + unless $el.elvar.current_prefix_arg + $el.delete_matching_lines( Keys.input(:prompt => "Delete lines having: ") ) + else + $el.delete_non_matching_lines( Keys.input(:prompt => "Delete lines not having: ") ) + end + } + Xiki.def("do+selection+linux"){ $el.set_buffer_file_coding_system :unix } # Use unix linebreaks + Xiki.def("do+selection+windows"){ $el.set_buffer_file_coding_system :dos } + + Xiki.def("do+open+browser"){ Browser.open_in_browser } + Xiki.def("do+open+left"){ View.open_in_bar } + Xiki.def("do+open+os"){ Files.open_in_os } + Xiki.def("do+open+window"){ Files.open_in_window } # Expose file in OS folder + + # Do > restore? > prompts for bookmark to show edits for + + Xiki.def("do+marker"){ Notes.open_todo; Notes.next_marker :nth=>1 } + Xiki.def("do+repeat"){ Line.duplicate } + + Xiki.def("do+view"){ + View.open Keys.input(:prompt=>"name of the view: ", :timed=>1) + # , :txt=>"\n\n\n\n" + Notes.mode + } # Make view with specific name + + Xiki.def("do+quote"){ Launcher.open("views/", :task=>["quoted"]) } + Xiki.def("do+update+numbers"){ View.beep "changed to > tasks+reoder" } + Xiki.def("do+highlights"){ OlHelper.highlight_executed_lines } + + Xiki.def("do+word"){ View.selection = $el.bounds_of_thing_at_point(:word).to_a } + + Xiki.def("do+align+left"){ X "iterm/position/left" } + Xiki.def("do+align+right"){ X "iterm/position/right" } + Xiki.def("do+align+top"){ X "iterm/position/top" } + Xiki.def("do+align+bottom"){ X "iterm/position/bottom" } + + Xiki.def("do+align+full"){ X "iterm/position/full" } + + Xiki.def("do+align+west"){ X "iterm/position/left edge" } + Xiki.def("do+align+east"){ X "iterm/position/right edge" } + Xiki.def("do+align+north"){ X "iterm/position/top edge" } + Xiki.def("do+align+south"){ X "iterm/position/bottom edge" } + + + Xiki.def("do+align+detached"){ X "iterm/position/square" } + + Xiki.def("do+align+margin"){ X "iterm/position/margin" } + + Xiki.def("do+align+1"){ X "iterm/position/corner 1" } + Xiki.def("do+align+2"){ X "iterm/position/corner 2" } + Xiki.def("do+align+3"){ X "iterm/position/corner 3" } + Xiki.def("do+align+4"){ X "iterm/position/corner 4" } + + + + Xiki.def("do+browser+reload"){ Browser.reload } + Xiki.def("do+browser+url"){ View.<< Browser.url } + + + Xiki.def("do+zoom"){ View.zoom } # show selection only + + Xiki.def("do+xiki+methods"){ Launcher.open("#{Xiki.dir}\n - ##^ *def /") } + Xiki.def("do+xiki+javascript"){ Launcher.open("#{Xiki.dir}\n - ##^ *function /") } + Xiki.def("do+xiki+files"){ FileTree.tree :recursive=>1, :bm=>"xiki" } + Xiki.def("do+xiki+keys"){ Launcher.open("#{Xiki.dir}lib/xiki/core/key_shortcuts.rb\n - ##^ *Xiki.def..\\w+\\+/") } + Xiki.def("do+xiki+tests"){ Launcher.open("#{Xiki.dir}spec/\n - ##^ *(describe|it) /") } + Xiki.def("do+xiki+comments"){ Launcher.open("#{Xiki.dir}\n - ##^ *[#;] /") } + Xiki.def("do+xiki+here"){ Launcher.open("#{Xiki.dir}\n - ##^ *Ol \"(continue|test) here/") } + Xiki.def("do+xiki+later"){ Launcher.open("#{Xiki.dir}\n - ##^ *Ol \"continue later/") } + Xiki.def("do+xiki+notes"){ Launcher.open("xiki/") } + + # Todo > do+1, do+2 > like open+N, but take from :t + + # do+N > Run nth label in current file + (1..9).each do |n| + Xiki.def("do+#{n}"){ Launcher.do_last_launch :nth=>n, :here=>1 } + end + + end + + + def self.window_keys + # Use for: adjusting the layout, changing what is visible + + Xiki.def("window+close", :noob=>1){ View.hide :kill_if_only=>1 } + + Xiki.def("window+top", :noob=>1){ View.to_highest } + Xiki.def("window+bottom", :noob=>1){ View.to_bottom } + Xiki.def("window+up", :noob=>1){ View.page_up } + Xiki.def("window+down", :noob=>1){ View.page_down } + + Xiki.def("window+link", :noob=>1){ Notes.as_file } # Save in :n + Xiki.def("window+quote"){ Notes.as_file :prompt_for_label=>1 } # Save in :n, with note + + Xiki.def("window+horizontal"){ View.create_horizontal } + Xiki.def("window+vertical"){ View.create_vertical } + Xiki.def("window+next"){ View.next } + Xiki.def("window+previous"){ View.previous } + + Xiki.def("window+align"){ View.recenter } # LL - recenter (L's emacs default) + Xiki.def("window+edge"){ View.recenter_top_key } + Xiki.def("window+wrap", :noob=>1){ $el.toggle_truncate_lines; View.message "" } # wrap lines + + Xiki.def("window+ide", :noob=>1){ View.layout_todo_and_nav } + + Xiki.def("window+justify"){ View.balance :narrow_bar=>1 } + Xiki.def("window+rebalance"){ View.balance } + Xiki.def("window+full"){ View.hide_others } + Xiki.def("window+zoom"){ View.enlarge } + + Xiki.def("window+go"){ $el.save_place_kill_emacs_hook; DiffLog.save_go_location } # Used to be > content+go + + Xiki.def("window+open"){ Launcher.as_open } + + Xiki.def("window+markers"){ Move.to_window(1); Launcher.open('note markers/', :bar_is_fine=>1) } + Xiki.def("window+search", :noob=>1){ View.open :txt=>"Todo ^S on the current file > to show shared search results" } + + Xiki.def("window+xiki"){ View.open :txt=>"Todo ^X on the current file > to show notes > Borrow > content+tasks" } + + Xiki.def("window+1"){ Move.to_window(1) } + Xiki.def("window+2"){ Move.to_window(2) } + Xiki.def("window+3"){ Move.to_window(3) }; Xiki.def("window+4"){ Move.to_window(4) } + Xiki.def("window+5"){ Move.to_window(5) }; Xiki.def("window+6"){ Move.to_window(6) }; Xiki.def("window+7"){ Move.to_window(7) }; Xiki.def("window+8"){ Move.to_window(8) } + Xiki.def("window+9"){ Move.to_last_window(:blink=>1) } + Xiki.def("window+0"){ View.recenter_top } # Layout 0: scroll so cursor is 0 lines from top of window + + end + + + def self.jump_keys + + Code.cache(:search_keys) do + + + # jump+ keys > doing things while searching... + + $el.define_key :isearch_mode_map, "\\C-h", nil + $el.define_key :isearch_mode_map, "\\C-t", nil + $el.define_key :isearch_mode_map, "\\C-s", nil + + Xiki.def("jump+copy", :eval=>"Search.isearch_copy") + + Xiki.def("jump+kill", :eval=>"Search.isearch_clear") # cut (or search at point of last cut, if no search) + + Xiki.def("jump+go", :eval=>"Search.isearch_go") # cut (or search at point of last cut, if no search) + + Xiki.def("jump+value", :eval=>"Search.insert_at_search_start") # Value: copy value back to search start + Xiki.def("jump+pull", :eval=>"Search.isearch_pull") # Pull back to search start (or jump+push if nothing searched for yet) + Xiki.def("jump+zap", :eval=>"Search.zap") # zap - delete up until search start + + Xiki.def("jump+open", :eval=>"Search.xiki") # To: open file / jump to method + Xiki.def("jump+notes", :eval=>"Search.isearch_tasks") + Xiki.def("jump+links", :eval=>"Search.isearch_links") # isearch for this string in :n + + Xiki.def("jump+files", :eval=>"Search.bookmark") # when no search, prompt for input + + Xiki.def("jump+exchange", :eval=>"Search.isearch_query_replace") # Outline (or jump+outlog if nothing searched for yet) + # Jump+i > available + + Xiki.def("jump+usurp", :eval=>"Search.isearch_pull_in_sexp") # usurp: pull sexp into search string + + Xiki.def("jump+before", :eval=>"Search.isearch_or_copy('1')") + Xiki.def("jump+after", :eval=>"Search.isearch_or_copy('2')") + + Xiki.def("jump+diffs", :eval=>"Search.isearch_diffs") # Delete (or jump+difflog if no search) + + # Todo > define > make it do what isearch-yank-line + # - but > define here so it'll show up in menu + + Xiki.def("jump+have+case", :eval=>"Search.isearch_have_case") + Xiki.def("jump+have+edges", :eval=>"Search.just_edges") # Delete everything but chars at edges of match + + Xiki.def("jump+have+line", :eval=>"Search.have_line") # Line wrap without exiting isearch + Xiki.def("jump+have+remembered", :eval=>"Search.insert_at_spot") + Xiki.def("jump+have+javascript", :eval=>"Search.isearch_have_outlog_javascript") + + # Todo > shuffle to new key + + Xiki.def("jump+have+move", :eval=>"Search.isearch_move_line") # Move line to where search started + Xiki.def("jump+have+output", :eval=>"Search.isearch_have_outlog") + + Xiki.def("jump+have+text", :eval=>"Search.insert_at_search_start :prepend=>'Ol.a '") + Xiki.def("jump+have+next", :eval=>"Search.isearch_move_to '%n', :insert_after=>1") + + Xiki.def("jump+have+path", :eval=>"Search.isearch_move_to '%n', :include_file_context=>1") + Xiki.def("jump+have+variable", :eval=>"Search.insert_var_at_search_start") + + Xiki.def("jump+have+web", :eval=>"Search.isearch_move_to '%n', :prepend=>\"google/\n \"") + Xiki.def("jump+have+go", :eval=>"Search.have_go") + + Xiki.def("jump+have+filter", :eval=>"Search.isearch_just_search") # Add "##match/" line in current tree + + Xiki.def("jump+have+search", :eval=>"Search.insert_at_search_start :prepend=>'##'") + Xiki.def("jump+have+diffs", :eval=>"Search.isearch_move_to '%n', :diffs=>1") + Xiki.def("jump+have+after", :eval=>"Search.isearch_move_to '%n', :prepend=>':+'") + Xiki.def("jump+have+before", :eval=>"Search.isearch_move_to '%n', :prepend=>':-'") + Xiki.def("jump+have+yellow", :eval=>"Search.isearch_move_to '%n', :prepend=>':?'") + + # Just so it's consistent with ^H^H when deleting selection + Xiki.def("jump+have+hit", :eval=>"Search.isearch_clear") + Xiki.def("jump+have+quote", :eval=>"Search.isearch_move_to '%links', :prompt_label=>1") + + # I: leave unmapped - had issues using it (messes up position) + Xiki.def("jump+to+after", :eval=>"Search.query_replace_with_2") + Xiki.def("jump+to+case", :eval=>"Search.isearch_just_case") # make match be camel case + Xiki.def("jump+to+delete", :eval=>"Search.like_delete") # Filter > Delete lines that contain the match, between here and end of paragraph + + Xiki.def("jump+to+top", :eval=>"Search.isearch_restart :top") # Restart search at top + Xiki.def("jump+to+edge", :eval=>"Search.isearch_restart :edge") # Restart search at top + + + Xiki.def("jump+to+kill", :eval=>"Search.just_kill") + + Xiki.def("jump+to+integer", :eval=>"Search.stop; Search.isearch '[0-9][0-9.]*', :regex=>1") + Xiki.def("jump+to+links", :eval=>"Search.isearch_move_to '%links'") + Xiki.def("jump+to+notes", :eval=>"Search.isearch_move_to '%n'") + + Xiki.def("jump+to+search", :eval=>"Search.isearch_move_to '%n', :prepend=>'##', :as_regex=>1") # Swap the match with what's in the clipboard (put the match where the search started) Xiki.def("jump+to+search", :eval=>"Search.search_just_swap") # Swap the match with what's in the clipboard (put the match where the search started) + + Xiki.def("jump+to+reverse", :eval=>"Search.search_just_swap") # Swap the match with what's in the clipboard (put the match where the search started) + + Xiki.def("jump+to+order", :eval=>"Search.isearch_just_adjust") # Swap/toggle the two characters in match + Xiki.def("jump+to+variable", :eval=>"Search.isearch_just_surround_with_char '\#{', '}'") + Xiki.def("jump+to+push", :eval=>"Git.search_just_push") + Xiki.def("jump+to+bullet", :eval=>"Search.have_label") + + Xiki.def("jump+to+quote", :eval=>"Search.isearch_google :quote=>true") + + Xiki.def("jump+to+web", :eval=>"Search.isearch_google :quote=>true") + + Xiki.def("jump+to+highlight+this", :eval=>"Search.just_orange") # Highlight just match as orange + # Todo > implement these + Xiki.def("jump+to+highlight+all", :eval=>"Search.highlight_all_found") # Highlight all matches + Xiki.def("jump+to+highlight+lines", :eval=>"Search.highlight_matching_lines") # Highlight all lines that match + # jump+to+y < available + + + # jump+show+ keys... + # Alternative words considered > see send set show save share snatch seek + + + Xiki.def("jump+show+command", :eval=>"Launcher.search_like_menu") + Xiki.def("jump+show+difflog", :eval=>"Search.jump_to_difflog") # find last string in difflog + Xiki.def("jump+show+filename", :eval=>"Search.isearch_open") # Open match as filename + + Xiki.def("jump+show+links", :eval=>"Search.isearch_restart '%links', :as_here=>1") + Xiki.def("jump+show+parens", :eval=>"Search.isearch_just_surround_with_char '(', ')'") # When search match + Xiki.def("jump+show+right", :eval=>"Search.isearch_restart :right") # Search in top-right view + Xiki.def("jump+show+brackets", :eval=>"Search.isearch_just_surround_with_char '[', ']'") + Xiki.def("jump+show+special", :eval=>"Search.isearch_just_special") # Jump to next special character + + + + Xiki.def("jump+show+more", :eval=>"Search.recenter_to_top") + Xiki.def("jump+show+output", :eval=>"Search.isearch_restart '%o'") + Xiki.def("jump+show+quote", :eval=>"Search.isearch_just_surround_with_char '\"'") + + Xiki.def("jump+show+after", :eval=>"Search.isearch_just_after") + + Xiki.def("jump+show+expanded", :eval=>"Search.like_expanded") + Xiki.def("jump+show+notes", :eval=>"Search.isearch_restart '%n', :as_here=>1") + + # Todo > restore? + # Xiki.def("jump+show+variable", :eval=>"Search.just_name") + # Xiki.def("jump+show+variable", :eval=>"Search.isearch_just_surround_with_char '\#{', '}'") + + Xiki.def("jump+show+web", :eval=>"Search.isearch_google") # make match be snake case + Xiki.def("jump+show+xiki", :eval=>"View.open \"%xiki/\#{Search.stop.strip}\"") + + end + + # Pre-loading for caching done, don't do full-on el4r stuff... + + return if $el.caching + + $el.define_key(:isearch_mode_map, $el.kbd("C-t C-]")) { Search.just_increment } # jump+to+Plus > alternative for terminal + $el.define_key(:isearch_mode_map, $el.kbd("C-t C-_")) { Search.just_increment(:decrement=>true) } # jump+to+Minus > alternative for terminal + $el.define_key(:isearch_mode_map, $el.kbd("C-_")) { Search.subtract } # Remove one char from isearch > alternative for terminal + $el.define_key(:isearch_mode_map, $el.kbd("C-]")) { $el.isearch_yank_char } # alternative for terminal + $el.define_key(:isearch_mode_map, $el.kbd("C-\\"), :isearch_abort_really) + + # $el.define_key(:isearch_mode_map, "\C-l", :isearch_repeat_forward) + $el.define_key(:isearch_mode_map, "\C-j", :isearch_repeat_forward) + + $el.define_key(:isearch_mode_map, "\e"){ Search.cancel } # Escape + + $el.el4r_lisp_eval %` + (progn + + ; Has to be defined in lisp, since passing C-@ fails (it's null, and that's what we use as a delimiter) + ; Definition for jump+Space + (define-key isearch-mode-map (kbd "C-@") (lambda () (interactive) + (el4r-ruby-eval "Xiki::Search.isearch_highlight_match") + )) + ; Like isearch-abort, but aborts even if partially matching search in progress + (defun isearch-abort-really () + (interactive) + (discard-input) + (setq isearch-success nil) + (isearch-cancel) + ) + + ) + ` + + end + + def self.misc + + Xiki.def("search+", :eval=>"XikihubClient.search_key") + + Xiki.def("go+", :eval=>"Grab.go_key") + Xiki.def("tasks+", :eval=>"Launcher.options_key") + + Xiki.def("xiki+", :eval=>"Topic.xiki_key") + + Xiki.def("quit+", :noob=>1, :eval=>"DiffLog.quit") + Xiki.def("backward+", :eval=>"Move.backward_key") + Xiki.def("forward+", :eval=>"Move.forward_key") + + Xiki.def("previous+", :eval=>"Move.previous") + Xiki.def("next+", :eval=>"Move.next") + + Keys.set(""){ Move.previous } + Keys.set(""){ Move.next } + Xiki.def("open+", :eval=>"Launcher.launch_or_collapse") # expand+ > Ctrl+x + + Keys.set("") { Launcher.launch_or_collapse } # Meta+Return, Alt+Return, Option+Return + + + # Enable key that 'xiki' dictation command is mapped to + Keys.set("M-X"){ Voice.launch } # Option+Shift+X + + Keys.set("M-E"){ View.expose } # Option+Shift+E + Keys.set("M-N"){ X "content/notes" } # Option+Shift+N + Keys.set("M-L"){ X "content/links" } # Option+Shift+L + Keys.set("M-C"){ X "content/close" } # Option+Shift+L + + + # Pre-loading for caching done, don't do full-on el4r stuff... + + $el.define_key :isearch_mode_map, "\C-q", :isearch_quote_char # This is necessary so "C-s C-q" won't quit + $el.define_key :minibuffer_local_map, "\C-q", :quoted_insert # So C-q still quotes control chars in the minibuffer + + # Right-clicking in mode-line do "expose linked" + + # Clicking in minibuffer > map it to something else > isearch? + if $el.boundp(:minibuffer_inactive_mode_map) + $el.define_key(:minibuffer_inactive_mode_map, $el.kbd(""), :isearch_forward) + end + + + # Two escapes in a row > show currently open views + Keys.set("\e\e"){ View.expose } # Make two quick escapes switch to the last view + Keys.set("C-k"){ Keys.expand [] } + + # Enabling control lock > ^U > up+, uplift+, uniform+, uplifted+, umbrella+, utility + Keys.set("C-u"){ ControlLock.toggle } + + # Maybe swap these > C-- for negative > and > C-] for universal + + $el.define_key(:global_map, $el.kbd("M--"), :universal_argument) # Required when control lock off + + # Freaking beeps > when not in control lock + $el.define_key(:global_map, $el.kbd("C--"), :universal_argument) # Works when control lock on + $el.define_key(:global_map, $el.kbd("C-_"), :universal_argument) # Required when control lock off + #$# Is this Ctrl+/? > C-/? + $el.define_key(:global_map, $el.kbd("C-]"), :negative_argument) + + $el.define_key(:global_map, $el.kbd("C-;")){ Launcher.open("files/") } # ^', so show files from :n + $el.define_key(:global_map, $el.kbd("C-'")){ Keys.timed_insert } + + Keys.set("C-."){ Keys.repeat } # Repeat last command typed (like vim "."), except for trivial ones + Keys.set("C-,"){ Keys.repeat :movement=>1 } # Repeat last command typed (like vim "."), except for trivial ones + + Keys.set("\eb"){ Move.backward_word_key } # Option+E > M-e + Keys.set("\ef"){ Move.forward_word_key } # Option+F > M-f + + Keys.set("\ew"){ Move.backward_delete_word } # Option+W > M-w + Keys.set("\ed"){ Move.forward_delete_word } # Option+W > M-w + + Keys.set("\en"){ Move.next :prefix=>:u } # Option+N > M-n + Keys.set("\ep"){ Move.previous :prefix=>:u } # Option+P > M-p + + Keys.set("C-\\"){ Keys.docs } + + $el.define_key :global_map, $el.kbd("C-z"), :undo + + $el.define_key :lisp_interaction_mode_map, $el.kbd("C-;"), nil # Make lisp mode not interfere + + # C-l in ediff mode + $el.defun(:ediff_disable_C_l) { $el.define_key(:ediff_mode_map, $el.kbd("C-l"), nil) } + $el.add_hook :ediff_keymap_setup_hook, :ediff_disable_C_l + + # Find alternative, since C-x is now expand+ + # xiki+1, xiki+2, etc (meaning C-x C-2) + # (1..9).each do |n| + # $el.define_key(:global_map, $el.kbd("C-x C-#{n}")){ Launcher.do_last_launch :nth=>n, :here=>1 } + # end + + # Make Ctrl+X be expand, and make cua-mode play nicely with it + # C+X, C+Space > do what C-x C-x used to do + + # Add a fallback keymap with "Ctrl+X " mapped to kill-region. + # added where? : emulation-mode-map-alists + + $el.el4r_lisp_eval %` + (progn + (setq truncate-partial-width-windows nil) + ;(set 'default-truncate-lines t) + + ; Add a keymap with "Ctrl+X" mapped to kill-region, that pre-empts cua--keymap-alist when there's a selection. + (defvar xiki-cua--keymap-alist + '((cua--ena-region-keymap keymap + (24 . kill-region) ; ^X + (3 . cua-copy-region) ; ^C + ))) + + (add-to-ordered-list 'emulation-mode-map-alists 'xiki-cua--keymap-alist 200) + + (defun xiki-launch () (interactive) (el4r-ruby-eval "Xiki::Topic.xiki_key")) + + (defun xiki-choose () (interactive) (el4r-ruby-eval "Xiki::Keys.expand ['content']")) + ) + ` + + $el.define_key(:cua__cua_keys_keymap, "\C-x", :xiki_launch) # expand+ + $el.define_key(:cua__cua_keys_keymap, "\C-c", :xiki_choose) # expand+ + + $el.define_key(:global_map, "\C-j", :isearch_forward) + + # Todo > automatically populate this in yours.rb > when new install + # Define one key, so it's obvious they should use yours+ for user keys + # Xiki.def("yours+foo"){ View.open :txt=>"> Keys starting with ^Y\nYou may want to define your own keys as something like yours+foo\n\nMeaning ^Y then ^X, where ^X is anything you want.\n\n> Example\nXiki.def('yours+hi'){ View << 'hi there'} # ^Y ^H\n\n\n" } + + + end + + # Defines right-click + def self.right_click + + # When not gui emacs, invoke what ctrl+T does... + + # if ! Environment.gui_emacs + + # In case the terminal maps right-click to mouse-3 + $el.define_key(:global_map, $el.kbd(""), $el.el_lambda(:interactive=>"e") {|e| + $el.mouse_set_point(e) + Launcher.options_key + }) + + # In case the terminal maps right-click to mouse-2 + $el.define_key(:global_map, $el.kbd(""), $el.el_lambda(:interactive=>"e") {|e| + $el.mouse_set_point(e) + Launcher.options_key + }) + + end + + end +end diff --git a/lib/xiki/core/keys.rb b/lib/xiki/core/keys.rb index 685f8eb9..8f553868 100644 --- a/lib/xiki/core/keys.rb +++ b/lib/xiki/core/keys.rb @@ -1,5 +1,6 @@ require 'xiki/core/pause_means_space' require 'xiki/core/line' +require 'xiki/core/xi' require 'xiki/core/text_util' module Xiki @@ -8,160 +9,631 @@ class Keys @@key_queue = [] # For defining menus (must be done in reverse) @@source ||= {} # Stores source for new unified key defs + @@accumulate = nil + + # Stores definition of new expanding keys. Structure: + # "view" => { + # "create" => #, + # "hide" => #, + # }, + # "quit" => #, + + # Default key menu order... + + def self.map_default + { + "content"=>nil, + "window"=>nil, + "list"=>nil, + + "do"=>nil, + "run"=>nil, + + "hop"=>nil, + "as"=>nil, + "enter"=>nil, + "search"=>nil, + "custom"=>nil, + } + end - def self.source - @@source + def self.map_default_noob + { + "content"=>nil, + "window"=>nil, + "list"=>nil, + + "jump"=>nil, + } + end + + @@map ||= Keys.map_default + @@map_noob ||= Keys.map_default_noob + @@noob_mode = nil + + # Saves by into ~/xiki/roots/conf/ by replacing + # the line (copying the default conf over first, if + # it's not there yet. + def self.set key, value + + # Read in file... + + # user_conf = Bookmarks["%x/roots/conf/#{command}.conf"] + user_conf = File.expand_path("~/.xiki/roots/conf/#{command}.conf") + FileUtils.mkdir_p File.dirname user_conf # In case it doesn't exist yet + + txt = File.read(user_conf) rescue nil + + # If not there, read from default... + + if ! txt + txt = File.read(File.expand_path("~/.xiki/roots/#{command}/default.conf")) rescue nil + end + + # Update file accordingly + + result = txt.sub! /^(#{key}:).*/, "\\1 #{value}" + + # No replacement means it somehow wasn't in the file, so append to the end... + + txt << "\n#{key}: #{value}\n" if ! result + + # Write file... + + File.open(user_conf, "w") { |f| f << txt } + + nil + end + + def self.noob_mode value=nil + + # No value, so return the result... + + if value == nil + # Memo-ize it, so we don't look it up every time + if @@noob_mode == nil + value = Conf.get "xsh", "key shortcuts" + @@noob_mode = ! value || value == "noob" + end + + $el.elvar.xiki_noob_mode = @@noob_mode # Cache in elisp var, so bar can get it + + return @@noob_mode + end + + # Value passed so set it in the cache and on the disk... + + @@noob_mode = value + $el.elvar.xiki_noob_mode = @@noob_mode # Cache in elisp var, so bar can get it + Conf.set "xsh", 'key shortcuts', (value ? 'noob' : 'advanced') + end + + # Called when expanding key shortcuts are pressed. + def self.expand path + + prefix = Keys.prefix + + # In noob mode, and ^A, ^E, ^R, or ^D, so just run simplified version... + + if Keys.noob_mode + case path + when ["as"] + return Move.hop_left_key + when ["enter"] + return Move.hop_right_key + when ["run"] + return Shell.recent_history_external nil, :from_key_shortcut=>1 + when ["do"] + return Deletes.forward + when ["hop"] + return Deletes.backward + end + end + + # See if user types anything right after + key = Keys.input(:optional=>true, :chars=>1, :prompt=>"") #> ||| + + if key =~ /\A.\w\z/ + # Chop off second and put it back on queue + second = key.slice!(/.$/) + $el.elvar.unread_command_events = [second.sum] + end + + # [] and key was "k", so do keys+kill... + if path == [] and key == "k" + Keys.remember_key_for_repeat(proc {Clipboard.kill}) + return Clipboard.kill + end + + # [] and key was "m", so do keys+more... + if path == [] and key == "m" + return Launcher.open "keys/\n + more/", :hotkey=>1 + end + + # Key pressed quickly, so recurse... + + if key && key != "," + hash = Xi.hget(@@map, *path) + + name = hash.keys.find{|o| o =~ /^#{key}/} + path << name + + # If Proc, run it... + + if hash[name].is_a? Proc + self.remember_key_for_repeat path # , :prefix=>prefix # Remember key pressed > so C-. can repeat it... + + # Eventually handle also + # Keys.prefix arg + # :task arg + return hash[name].call + end + + # Else, recurse... + + return self.expand path #> || + end + + # No shortcut in time, so display menu... + + # If nothing, use "keys" menu + path = ["keys"] if path == [] + + options = {:bar_is_fine=>1} + if key != "," + options.merge! :hotkey=>1, :letter_when_not_found=>1 + end + + # First arg is apparently the command and what the buffer will be named + command = path.join("/")+"/" + buffer_name = path.join("+")+"/" + Launcher.open command, options.merge(:buffer_name=>buffer_name) + + end + + def self.kill # args, options + View.kill if View.name == "keys/" + Clipboard.kill end + def self.more args, options + + txt = %` + + moving around/ + | You can type the arrow keys to move the cursor around. + | + | Often much of what you'll do in Xiki is use the arrow keys and + | type Ctrl+X to expand and collapse things. + | + | Also, the arrow keys can help you escape out of odd situations, + | like when you start typing a key shortcut and become confused. + | (Note that the escape key doesn't escape out of things, due to + | the limitations of shell consoles.) + + copying and pasting/ + | You can use these keys to copy, paste, cut: + | + | Ctrl+C, Ctrl+V, Ctrl+X + | + | To select some text before copying or cutting, type Ctrl+Space + | and then move the arrow keys to select. + + undoing/ + | You can type Ctrl+Z to undo. + | + | To redo + | + | To select some text before copying or cutting, type Ctrl+Space + | and then move the arrow keys to select. + + mouse/ + | If you're using a terminal that has mouse support (like iTerm), + | you can you the mouse to... + | + | - click to reposition the cursor + | - double-click to expand + | - right-click to see a tasks menu + `.unindent + + txt << "\n" + txt << + if Keys.noob_mode + %` + + advanced key shortcuts mode + ! Keys.noob_mode false + ! options[:line_found] = 17 + ! options[:no_search] = 1 + ! options[:no_slash] = 1 + ! " + ! | Advanced mode enabled. Many useful shortcuts are now set: + ! | + ! | ^A as+... shortcuts + ! | ^E enter+... shortcuts + ! | ^R run+... shortcuts + ! | ^D do+... shortcuts + ! | + ! | Type ^A, ^E, ^R and ^D twice to get the original behavior: + ! | + ! | ^A ^A beginning of line + ! | ^E ^E end of line + ! | ^R ^R recent commands + ! | ^D ^D delete character + ! | + ! | Type ^K to see the current shortcuts, or to switch back to noob mode. + ! + ! = close/ + ! " + `.unindent + + else + %` + + noob key shortcuts mode + ! Keys.noob_mode true + ! options[:line_found] = 12 + ! options[:no_search] = 1 + ! options[:no_slash] = 1 + ! options[:no_slash] = 1 + ! " + ! | Noob mode enabled. These key shortcuts are now set: + ! | + ! | ^A beginning of line + ! | ^E end of line + ! | ^R recent commands + ! | ^D delete character + ! | + ! | These hide many advanced keys, but are simpler for new users. + ! | + ! | Type ^K to see the current shortcuts, or to switch back to advanced mode. + ! + ! = close/ + ! " + `.unindent + end + + options_in = options.merge(:eval=>1) + txt = Xik.new(txt).expand args, options_in + + # Propagate some options back + Options.propagate_some_outward options_in, options + + txt + + end + + # Implements menu for each key, (=view, =open, etc). + def self.menu_expander path, options={} + + # Downcase if capital from noob mode + + path[0].sub!(/^([A-Z])/){$1.downcase} if path[0] + + view = View.name + + # Remember whether in "command/" view, because we'll close it later + in_own_view = options[:name].sub(/^\`/, '')+"/" == view.sub(/\+\w+/, '') + + options[:hotkey] = 1 if options[:was_letter] + + # keys/copying and pasting/, so do special handling of this item... + + return self.kill if path[0] == "k" + return self.more(path[1..-1], options) if path[0] == "more" + + return Launcher.open("help/") if path[0] == "help" + + # Special args of keys menu + + map = self.noob_mode ? @@map_noob : @@map + item = Xi.hget(map, *path) + + # Key combo wasn't initially found in noob map, so look in full map via the single letter... + + if ! item && self.noob_mode + siblings = Xi.hget(@@map, *path[0..-2]) + item = siblings.find{|k, v| k =~ /^#{path[-1]}/}[1] + end + + if ! item + options[:no_search] = 1 + return "<* Key not defined" + end + + # More items, so show next keys... + + if item.is_a?(Hash) + + txt = "" + item.each do |k, v| + next if ! v + next if k !~ /^[a-z]/i # Remove items that don't start with words + next if path == [] && k == "keys" + + txt << "+ #{k}" + txt << "/" if v.is_a? Hash + txt << "\n" + end + + # Add blank lines if "view" menu + txt = self.decorate_items path, txt + + return txt + end + + # Leaf key (proc), so run it... + + # Task, so show options or navigate... + + if task = options[:task] + return "* source\n* run" if task == [] + if task == ["source"] + file, line = item.source_location # => ["/projects/xiki/lib/xiki/core/key_shortcuts.rb", 755] + View.open file + View.line = line + return "" + end + end + + # Kill view if topic/ + View.kill if in_own_view + + # Remember key pressed > so C-. can repeat it... + + self.remember_key_for_repeat path + item.call + + nil - def self.menu - %` - - .log/ - - .history/ - - docs/ - - Summary/ - > Xiki Shortcuts - | Xiki has keyboard shortcuts predefined for doing all kinds of things. - | Each keyboard shortcut has a mnemonic. Check out the "Keys" menu bar - | menu for a quick look at them. - - | And Xiki lets you define your own keyboard shortcuts. This line makes - | Control-e Control-n insert "Steve". - - | Keys.EN { View << "Steve" } - - | For more about defining your own keyboard shortcuts see: - - @keys/api/ - - > Xiki's "type the acronym" approach - | Each xiki keyboard shortcut has a mnemonic that helps you - | simultaneously remember what it does and how to type it. - - | For example, given this mnemonic: - - | layout_create - - | you type: - - | Control-l Control-c (l for "layout" and c for "create") - - > Reasons for this appoarch - - More possible shortcuts/ - | The approach of having single character key shortcuts (e.g. Control-a) - | works nicely for apps that have a small number of shortcuts. But it - | becomes less elegant when more shortcuts are used (Ctrl-a, Alt-a, - | Ctrl-Shift-a). - - | The "type the acronym" approach with just the Control - | key allows for very a large number of key shortcuts that are less - | prone to get confused with one another. - - - Less to remember/ - | A mnemonic clues you into what the keyboard shortcut does and how to type it, - | so it's all you need to remember. There's no need to separately remember a keyboard shortcut and what it does (a - | challenging part of most keyboard shortcut schemes having a large number of - | shortcuts, which xiki attempts to avoid). - - | Doesn't sound like standard emacs shortcuts? Here's an explanation about - | how xiki deals with existing emacs shortcuts. - - - emacs_shortcuts/ - | TODO add stuff about how C-a turns into C-a C-a, etc. - | Mention how this lets a large number of key shortcuts without interfering - | with emacs shortcuts. - | But an admitted downside is it affects 6 existing emacs shortcuts - | and makes you type them twice. - | In practice the annoyance caused by this isn't as bad as it initially may seem - | Consider using to_axis instead of C-a C-a and to_end instead of C-e C-e. - - > Six categories - | As you can see by looking at the "Keys" menu in the menu bar, there are - | six main categories of key shortcuts. - - - Descriptions of each category/ - - to: jumping to specific points - - open: opening things - - layout: views and windows - - as: remembering things - - enter: inserting things - - do: executing things - - > Examples - | Here are some of the most commonly used shortcuts in each category. - | (Double-click a category to see them.) - - - to/ - | to+highest: Jump to top of file - | to+lowest: Jump to bottom of file - | to+axis: Jump to beginning of line - | to+end: Jump to end of line - - open/ - | open+bookmark: view a bookmark - | open+tree: view a tree of a directory - | open+current: shows currently open files - | open+edited: shows recently edited files - | open+history: shows recently viewed files - | open+menu: opens view that lets you type a menu (type "-" to see all) - - layout/ - | layout+create: Create a new view - | layout+hide: Hide this view - | layout+next: Go to next view - | layout+previous: Go to previous view - | layout+kill: Close the current file - - as/ - | as+clipboard: Copy (after doing Control-space on the other side) - | as+kill: Cut (after doing Control-space on the other side) - | as+bookmark: remember this file as a bookmark - - enter/ - | enter+clipboard: Paste - - do/ - | do+tree: view an expanded tree of a directory - - miscellaneous/ - | Control-tab: cycles through files - - | For all keyboard shortcuts, see where they're key_bindings.rb, where they're - | defined: - - @$xiki/lib/xiki/core/key_bindings.rb - - > Keyboard shortcuts while searching - | The seventh category, "search" has special behavior. See: - - @search/docs/ - - .api/ - - define/ - > Defining key shortcuts - - acronym/ - > Define Control-d Control-h usyng an "acronym" - | Keys.do_hi { Line << "hey there" } - - | The acronym is "do hi". Acronyms makes it easy to remember both - | the keys to type, and what they do. This is the recommended way - | to define keys in Xiki. - - basic/ - > Define Control-d Control-h - | Keys.DH { View << "foo" } - - specific files/ - > Map Control d Control-h only in .rb files - | Keys.do_hi(:ruby_mode_map) { View << "foooo" } - - meta/ - > Map M-z - | Keys._z { View << "foooo" } - - input/ - > Get input from the user - - a string/ - @Keys.input - @Keys.input :prompt=>"Name: " - - timed/ - | Collects input until user pauses - @Keys.input :timed=>1 - | Same, but returns nothing if user doesn't begin right away - @p Keys.input :optional=>1 - - just one char/ - @Keys.input :chars=>1 - - history/ - > Get the history of keys typed - @Keys.log - @Keys.log :raw=>1 # Raw char codes - ` + end + + def self.decorate_with_spacing path, txt + + # Advanced mode, so add spaces... + + if ! self.noob_mode + + if path == [] + txt.gsub!(/^\+ (xiki|open|quit|go|tasks|search)\n/, "") # Remove quit and xpand + txt.gsub!(/(^\+ (shared|options|topic|backward|forward|previous|next)\n)+/, "") # Remove backward, forward, previous, next + txt.gsub!(/\+ [dh]/, "\n\\0") + + elsif path == ["content"] + txt.gsub!(/\+ [unhftye]/, "\n\\0") + elsif path == ["window"] + txt.gsub!(/\+ [lhtnoefc]/, "\n\\0") + elsif path == ["list"] + txt.gsub!(/\+ [sdngwop]/, "\n\\0") + + elsif path == ["hop"] + txt.gsub!(/\+ [ftlsaim]/, "\n\\0") + elsif path == ["run"] + txt.gsub!(/\+ [miehv]/, "\n\\0") + elsif path == ["run", "version"] # run+version + txt.gsub!(/\+ [bn]/, "\n\\0") + elsif path == ["run", "delete"] # run+delete + txt.gsub!(/\+ [ifm]/, "\n\\0") + elsif path == ["run", "highlight"] # run+highlight + txt.gsub!(/\+ [nsd]/, "\n\\0") + elsif path == ["enter", "item"] # enter+item + txt.gsub!(/\+ [rlc]/, "\n\\0") + + elsif path == ["do"] + txt.gsub!(/\+ [ltqnk]/, "\n\\0") + elsif path == ["do", "selection"] # do+selection + txt.gsub!(/\+ [ct]/, "\n\\0") + + elsif path == ["as"] + txt.gsub!(/\+ [cbdr]/, "\n\\0") + elsif path == ["enter"] + txt.gsub!(/\+ [bjhqi]/, "\n\\0") + + elsif path == ["jump"] + txt.gsub!(/\+ [vtbeh]/, "\n\\0") + end + + return + end + + + # Noob mode, so add spacing to simplified item subset... + + if path == [] + txt.gsub!(/^\+ (quit|hop|as|enter|do|run)\/?\n/, "") # Remove quit and xpand + txt.gsub!(/\+ (content|window|list)/){ "+ #{$1.capitalize}" } + elsif path == ["window"] + txt.gsub!(/\+ [ulq]/, "\n\\0") + elsif path == ["list"] + txt.gsub!(/\+ [lsvm]/, "\n\\0") + elsif path == ["content"] + txt.gsub!(/\+ [unb]/, "\n\\0") + end + + end + + @@descriptions = { + []=>%` + Typing ESC usually cancels or takes you back to the last screen. + Hold Ctrl and type the first letter of one of the items below. + (Example: Ctrl+C for "Content"). You can type these shortcuts + at any time - it's not necessary to type Ctrl+K first. + `.unindent+"\n", + # Hold Ctrl and type the first letter of one of the items below. + # (Example: Ctrl+C for "Content"). You can type these shortcuts + # at any time - it's not necessary to type Ctrl+K first. + + # Keyboard shortcuts. Right now, and anywhere in xsh, you can + # type the first letter of one of the below words while holding + # Ctrl (^W for window, ^C for content, or ^L for list). + + # The arrow keys always move around. Try using the arrow keys + # then Ctrl+X to expand and collapse these items: + + ["list"]=>"Lists of recent files and other stuff.", + + ["as"]=>"Saving and remembering.", + ["enter"]=>"Inserting stuff.", + + ["hop"]=>"Moving around within views and between views.", + + ["jump"]=>"Going to specific places.", + + ["window"]=>"Basic window handling.", + ["do"]=>"Various actions.", + + ["run"]=>"Running and processing things.", + ["jump"]=>"Shortcuts you can use during a Ctrl+S search.", + ["custom"]=>"Related to the kind of file you're viewing.", + + ["run", "version"]=>"Diffing and listing versions.", + + } + + def self.decorate_items path, txt + + self.decorate_with_spacing path, txt + + # Maybe don't show > require right-click? + description = @@descriptions[path] + + # keys/ in advanced mode, so don't show the long decription + description = nil if path == [] && ! self.noob_mode + + # Only show descriptions when in noob mode + if description && self.noob_mode + description = Tree.pipe(description).strip + description.sub! /\|\z/, "" # Remove "|" from last blank line in description + txt = "#{description}\n#{txt}" + end + + # Special extra text for root "keys"... + + if path == [] + if self.noob_mode + + # | ESC usually takes you back to where you were last. + txt << "\n"+" + + More/ + + Help + + | The arrow keys always move around. + | Typing Ctrl+K twice kills the line. + | Notice the shortcuts in the bottom bar as well. + ".unindent + # | See bottom bar for more (press an arrow key if it says to). + # | ^K ^K kills a line. + # | ESC goes back to where you were last. + # Don't need > it's under "more/" + # | ^Space starts selecting text. + else + txt << "\n+ more/\n+ help" if path == [] + end + end + + txt + end + + + # Sets key shortcuts in @@map, and defines root shortcuts + # that read from them. + def self.map_reset + @@map = self.map_default + end + + def self.map_noob + @@map_noob + end + + def self.map keys=nil, options={} + + return @@map if keys.blank? # Just return map if no keys + + name = keys[0] + + initial = name[0] + if keys[1].is_a? Proc + + # Single key combination (a proc), so define it directly... + + map = options[:map] || :global_map + $el.define_key map, "\\C-#{initial}", &keys[1] + + elsif keys[1].is_a?(String) && keys.length == 2 + + # Single key combination (a string, so define it directly... + + map = options[:map] || :global_map + Keys.define_key_that_evals map, "\"\\C-#{initial}\"", "Xiki::#{keys[1]}".inspect + + elsif ! @@map[name] + + # Multiple key combo, so define root key as expander, only if not defined yet... + + if ! ["s", "c"].member? initial + map = options[:map] || :global_map + self.define_key_that_evals map, "\"\\C-#{initial}\"", "\"Xiki::Keys.expand ['#{name}']\"" + end + + # Define menu, that will optionally pop up if they're slow... + + Xiki.def(name) do |path, options| + Keys.menu_expander [name]+path, options + end + + end + + # Keys, so set in map... + + Xi.hset @@map, *keys + + # Also save to @@map_noob if option + + if options[:noob] + Xi.hset @@map_noob, *keys + end + + end + + def self.define_key_that_evals map, key, code + elisp = "(define-key #{TextUtil.hyphen_case map.to_s} #{key} (lambda () (interactive) (el4r-ruby-eval #{code})))" + + $el.el4r_lisp_eval elisp + nil + end + + # Have user type in key. Returns... + # ["as", "enter"], + def self.prompt_for_key + + combo, found = [], nil + + while ! found + val = Xi.hget(@@map, *combo) + + if ! val + View.flash "- Key not defined: #{combo}!" + raise "key not defined" + end + + # Hash, so ask for another key and narrow down... + + if val.is_a? Hash + key = Keys.input :chars=>1, :prompt=>"Key shortcut: " + word = val.keys.find{|o| o =~ /^#{key}/} + if ! word + View.flash "- Key not defined: #{combo+[key]}!" + return + # raise "key not defined" + end + combo << word + else + found = val + end + end + + [combo, found] + + end + + + + def self.source + @@source end def self.api @@ -200,92 +672,6 @@ def self.api end - # Handles Keys.to_foo etc. - - def self.method_missing(meth, *args, &block) - return if ! $el - - # Accept it if block but no args - meth = meth.to_s - - meth_orig = meth.dup - - - # Delegate to super unless arg is single symbol - unless args.size == 0 or (args.size == 1 and args[0].is_a? Symbol) - return super(meth, *args, &block) - end - - meth_title = meth.gsub('_', ' ').gsub(/\b\w/) {|s| s.upcase} - menu, item = meth_title.match(/(.+?) (.+)/)[1..2] if meth_title =~ /. ./ - - # If 1st word is 'isearch', use it as map - if meth =~ /^search_/ - @@key_queue << ["Search", item] - meth.sub! /^search_/, '' - meth = self.words_to_letters meth - args = [:isearch_mode_map] - elsif meth =~ /[A-Z]/ # If capital letters - # Don't convert - elsif meth =~ /_/ # Add menu item, if more than one word - - if args.size == 0 # If global keymap - # Make lisp function - $el.defun(meth.to_sym, :interactive=>true) do - block.call - end - @@key_queue << [menu, item] - end - - # Change 'to_foo' to 'TF' etc - meth = self.words_to_letters meth - end - - keys_raw = self.translate_keys meth # Translate to 'C-t C-f' etc - - keys = $el.kbd keys_raw # Translate to actual control keys - - map = :global_map # Default to global keymap - - # Use keymap if symbol passed as 1st arg - map = args.shift if args[0] and args[0].class == Symbol - # If they just passed a string, use it as code - if args and (args.size == 1) and !block - self.map_to_eval keys_raw, args[0] - return - end - - # # Add menus for key shortcuts - seems kind of pointless in retrospect - # if map == :global_map && meth_orig =~ /^[a-z]/ - # Launcher.add(meth.downcase, &block) - # Launcher.add(meth_orig, &block) # if meth_orig =~ /^[a-z]/ - # end - - # Define key - begin - self.define_key map, keys_raw, keys, &block - "- key was defined: #{keys_raw}" - rescue Exception => e - return if map != :global_map || meth !~ /([A-Z])([A-Z]?)./ # Only global map and 2 control keys - message = e.message - prefix = message[/"Key sequence .+? starts with non-prefix key (.+?)"/, 1] - return if prefix.nil? - - prefix = $el.kbd(prefix) - - begin # If it appears to be a prefix key (already defined) - $el.global_unset_key prefix - # $el.define_key map, keys, &block - self.define_key map, keys_raw, keys, &block - # self.define_key map, keys, &block - - rescue Exception => e - Ol << "e (inner): #{e.inspect}" - end - - end - end - def self.map_to_eval keys_raw, code $el.el4r_lisp_eval" (global-set-key (kbd \"#{keys_raw}\") (lambda () (interactive) @@ -310,7 +696,6 @@ def self.translate_keys txt end def self.set *args, &block - # Keys is always first arg keys = args.shift if args.size > 0 # If 2nd arg, use it @@ -326,8 +711,9 @@ def self.set *args, &block # Keys.input # Terminated by enter # Keys.input "Type something: " # Keys.input :chars=>1 # Just one char - # Keys.input :timed=>1 # Terminated by pause + # Keys.input :timed=>1 # Terminated by pause (and convert control to alpha) # Keys.input :optional=>1 # Terminated by pause + # Keys.input :optional=>1, :chars=>1, :seconds=>1, :prompt=>"" # Wait 1 second, seeing if anything was typed # - A pause at the beginning will result in no input (nil) def self.input *args @@ -337,63 +723,62 @@ def self.input *args return self.input_with_choices(options) if options[:choices] - Cursor.remember :before_input - - Cursor.hollow + # Cursor.remember :before_input prompt ||= options[:prompt] || "Input: " - if options[:chars] - char = $el.char_to_string( - self.remove_control($el.read_char(prompt))).to_s - Cursor.restore :before_input + if options[:chars] && ! options[:optional] + char = $el.char_to_string(self.remove_control($el.read_char(prompt))).to_s + subsequent = self.read_subsequent_chars + char += subsequent.inject(""){|acc, o| acc+o.chr} + return char end # If simple un-timed input, just get string and return it... unless options[:timed] || options[:optional] - Cursor.restore :before_input c = $el.read_string(prompt, options[:initial_input]) return c end # :timed or :optional option... - keys = "" + keys = options[:raw] ? [] : "" - $el.elvar.inhibit_quit = true c = nil # If not optional, wait for input initially unless options[:optional] - c = $el.read_char(prompt) - keys = self.to_letter(c) + c = $el.el4r_lisp_eval("(let ((inhibit-quit t)) (read-char #{prompt.inspect}))")#.to_s + c = self.to_letter(c) if ! options[:raw] + keys << c end - if c == 7 - Cursor.restore :before_input - $el.elvar.inhibit_quit = nil - $el.keyboard_quit + # No, leave it alone, since it's timed (mouse won't interfere) + seconds = options[:seconds] || 0.35 + while(c = $el.el4r_lisp_eval("(let ((inhibit-quit t)) (read-char \"#{prompt}#{keys}\" nil #{seconds.to_f}))")) + + c = self.to_letter(c) if ! options[:raw] + keys << c + + break if options[:optional] && options[:chars] == 1 end - while(c = $el.read_char("#{prompt}#{keys}", nil, 0.35)) - keys += self.to_letter(c) - if c == 7 - Cursor.restore :before_input - $el.elvar.inhibit_quit = nil - $el.keyboard_quit - end + # Read subsequent chars returned right after + subsequent = self.read_subsequent_chars + if options[:raw] + keys += subsequent + else + keys += subsequent.inject(""){|acc, o| acc+o.chr} end - $el.elvar.inhibit_quit = nil - Cursor.restore :before_input + $el.message "" # If nothing, return nil keys == "" ? nil : keys end - # TODO: finish - look at spec def self.input_with_choices options prompt = options[:prompt] ? "#{options[:prompt]} " : "" prompt << options[:choices].map{|i| @@ -403,6 +788,8 @@ def self.input_with_choices options options[:choices].find{|i| i.first =~ /^#{c}/}[1] end + # Converts character number to char or "C-..." string. + # p Keys.to_letter 32 def self.to_letter ch, options=nil verbose = options && options[:verbose] return nil if ch.nil? @@ -478,32 +865,27 @@ def self.insert_code end def self.jump_to_code - keys = $el.read_key_sequence("Type some keys, to jump to the corresponding code: ") - # If was defined with unified, pull from Keys.source + # Defined in Keys.map, so jump to proc... - letters = Keys.sequence_to_string keys - if source = Keys.source[letters] - file, line = source.split ':' - Location.go file - View.to_line line.to_i - return - end + combo, prok = Keys.prompt_for_key + if prok + file, line = prok.source_location + View.open file + View.to_line line + + # Jump to source of method inside the proc... + + if Keys.prefix != :u && line = Line.value[/\{ ?(\w+\.\w+?) ?\}/, 1] + Search.open_file_and_method line + end - proc = self.proc_from_key keys - if proc.nil? - $el.beep - return View.message("Key isn't mapped in Xiki") end - file, line = Code.location_from_proc proc - file = "#{Xiki.dir}#{file}" unless file =~ /^\// - Location.go file - View.to_line line.to_i - Effects.blink(:what=>:line) end def self.proc_from_key keys + code = $el.prin1_to_string($el.key_binding(keys)) # If it is a call to elisp id = code[/el4r-ruby-call-proc-by-id.+?([_0-9]+)/, 1] @@ -512,58 +894,81 @@ def self.proc_from_key keys ObjectSpace._id2ref(id.to_i) end + def self.timed_insert_handle_char char + if char == "\x7F" # If delete, just do a delete + return $el.delete_backward_char 1 + end + View.insert char + end + def self.timed_insert options={} + prefix = Keys.prefix - # If prefix of 0, insert in a way that works with macros - case prefix - when nil # Do nothing - when :u # Do pause for space - PauseMeansSpace.go - return - when 0 + + # 0 prefix, so insert in a way that works with macros... + + if prefix == 0 View.insert Keys.input(:prompt => "Insert text to insert: ") return - else # If other prefix, insert single char n times - c = View.read_char("Insert single char to insert #{prefix} times: ").chr - prefix.times do - View.insert c - end - return end + # Selection exists, so delete it first + View.delete *View.range if View.selection? + Cursor.remember :before_q Cursor.box - # This is slow in mac emacs 23/24 :( - # Cursor.green + + beginning = View.cursor + + prompt = options[:prompt] || "insert text (pause when finished): " # Get first char and insert - c = $el.read_char("insert text (pause to exit): ").chr - inserted = "#{c}" + c = $el.read_char(prompt).chr + + # Do nothing if it was escape + + return if c == "\e" - View.insert c + inserted = "#{c}" + self.timed_insert_handle_char c # While no pause, insert more chars - while(c = $el.read_char("insert text (pause to exit): ", nil, 0.36)) + delay = options[:delay] || 0.28 + while(c = $el.read_char(prompt, nil, delay)) inserted += c.chr - View.insert c.chr + self.timed_insert_handle_char c.chr end $el.elvar.qinserted = inserted - $el.message "input ended" + $el.message "" + + txt = View.txt beginning, View.cursor + + # Save txt as string to > .remember_key_for_repeat... + + if prefix == :u + View.cursor = beginning + self.remember_key_for_repeat(proc {View >> txt}) + else + self.remember_key_for_repeat(proc {View << txt}) + end Cursor.restore :before_q # Store in hash by first letter for use by enter_yank - Clipboard.save_by_first_letter inserted # Store for retrieval with enter_yank + # Numeric prefix, so repeat that many times... - end + if prefix.is_a? Fixnum + + (prefix-1).times do + View.<< txt + end + + end + + inserted - def self.as name - Clipboard.copy("#{name}") - Bookmarks.save("$#{name}") - Bookmarks.save("$_#{name}") - View.save("#{name}") end def self.insert_from_q @@ -658,6 +1063,7 @@ def self.prefix_uu end def self.clear_prefix + return if ! $el $el.elvar.current_prefix_arg = nil end @@ -669,22 +1075,26 @@ def self.clear_prefix # /notes/ruby/index.notes # Keys.bookmark_as_path :prompt=>"Enter something" # Show message # Keys.bookmark_as_path :bm=>"ru" # Don't prompt - # Keys.bookmark_as_path :bm=>"." # Current dir works + # Keys.bookmark_as_path :bm=>"." # Current file + # Keys.bookmark_as_path :bm=>".." # Current dir + # Keys.bookmark_as_path :bm=>"..." # Parent dir # /projects/xiki/lib/xiki/core/ def self.bookmark_as_path options={} - bm = options[:bm] || Keys.input(:timed=>true, :prompt=>options[:prompt]||"Enter a bookmark: ") + + bm = options[:bm] || Keys.input(:timed=>1, :prompt=>options[:prompt]||"Enter a bookmark: ") + return if bm == "\e" if bm == " " # If space, return special token return :space elsif bm == "/" # If slash, return special token return :slash - elsif bm == "x" # If slash, return special token - return Xiki.dir - elsif bm == "," # If slash, return special token - return :comma + elsif bm == "." # Dash means the current file + dir = View.file + dir = Bookmarks.dir_only(dir, :with_slash=>1) unless options[:include_file] + return dir elsif bm =~ /^\.+$/ # If .+ do tree in current dir - dir = View.dir :force_slash - (bm.size - 1).times do + dir = View.dir :force_slash=>1 + (bm.size - 2).times do dir.sub! /\/$/, '' # Remove / on end if there dir.sub! /[^\/]+$/, '' # Remove dir end @@ -692,16 +1102,16 @@ def self.bookmark_as_path options={} return dir end - dir = Bookmarks.expand "$#{bm}" + dir = Bookmarks.expand "%#{bm}" if dir.nil? # If no dir, return nil View.beep "- Bookmark '#{bm}' doesn't exist." return :bookmark_doesnt_exist end unless options[:include_file] - dir = Bookmarks.dir_only dir - dir << "/" unless dir =~ /\/$/ + dir = Bookmarks.dir_only dir, :with_slash=>1 end + dir end @@ -717,35 +1127,51 @@ def self.prefix_times prefix=self.prefix, &block result end - def self.add_menubar_items - @@key_queue.reverse.each do |i| - Menu.add_menubar_item [Menu::ROOT_MENU, i[0]], i[1], "#{i[0].downcase}-#{i[1].downcase.gsub(' ', '-')}" + def self.char + + # Is it an issue to comment this out? + + # Why doesn't work? + $el.elvar.control_lock_disable_once = true # Because we need to distinguish between C-n and n, etc. + $el.elvar.overriding_local_map = $el.elvar.xiki_az_keymap # So C-x and C-c will be single chars + + ch_initial = $el.read_key_sequence "", nil, nil, true + + $el.elvar.overriding_local_map, $el.elvar.control_lock_disable_once = nil, nil + + # Char was a vector (or more than 1 char), so just execute corresponding command (probably a click)... + if $el.vectorp(ch_initial) || ch_initial.length > 1 + $el.command_execute ch_initial + return end - @@key_queue = [] - end - def self.char - $el.elvar.inhibit_quit = true - ch_initial = $el.read_event.to_s - $el.elvar.inhibit_quit = nil + ch_initial = "#{$el.string_to_char ch_initial}" # Get it into weird format we used before switching to read_key_sequence if ch_initial =~ /^\d+$/ # If a number, assign it to raw + ch_raw = ch_initial.to_i + if 134217825 <= ch_raw and ch_raw <= 134217850 # If meta (out of elisp range) return ["meta_#{(ch_raw - 134217728).chr}".to_sym, nil] end # Special check for C-. and other sequences - ch = if ch_raw == 67108910 - :control_period - elsif ch_raw >= 67108912 && ch_raw <= 67108921 # If between C-0 and C-9 - (ch_raw - 67108864).chr - elsif ch_raw == 67108911 - :control_slash - else - # If char is over the elisp max, try to interpret it as Meta - $el.char_to_string(ch_raw) - end + ch = + if ch_raw == 67108910 + :control_period + elsif ch_raw >= 67108912 && ch_raw <= 67108921 # If between C-0 and C-9 + (ch_raw - 67108864).chr + elsif ch_raw == 67108911 + :control_slash + + elsif ch_raw == 28 + :control_backslash + + else + # If char is over the elisp max, try to interpret it as Meta + $el.char_to_string(ch_raw) + end + return [ch, ch_raw] elsif ['left', 'right', 'up', 'down', ].member?(ch_initial) @@ -769,9 +1195,10 @@ def self.char else # Probably a mouse event return [nil, nil] end - end + # Keys.words_to_letters "hey_you" + # HY def self.words_to_letters txt TextUtil.camel_case(txt).gsub(/[a-z]/, '') end @@ -780,21 +1207,15 @@ def self.last nth=1 $el.el4r_lisp_eval("(elt (recent-keys) (- (length (recent-keys)) #{nth}))").to_s end - def self.before_last - $el.el4r_lisp_eval("(elt (recent-keys) (- (length (recent-keys)) 2))").to_s - end + # Returns codes for ast few keys pressed, in the order: + # [recent, one ago, two ago] + def self.recent_few + + return $el.recent_keys.to_a.reverse - def self.history - $el.view_lossage - View.flash "- Showed recently-typed keys in other view!", :times=>4 end def self.isearch_prefix shortcut_length=2 - # TODO Make search.stop set Keys.prefix (call .isearch_prefix) - # Keys.prefix = self.isearch_prefix - # and don't call .isearch_prefix - - # What about shortcut_length though? Would we get weird results for search_foo_foo shortcuts? - just try it for now # Return it if character before key shortcut was C-u or C-0 - C-9... char = Keys.last(shortcut_length+1).to_i @@ -809,11 +1230,10 @@ def self.human_readable txt txt.split(/[^a-z]/i).map{|o| "Control-#{o[/./].upcase}"}.join(" ") end - - private def self.define_key map, keys_raw, keys, &block + map ||= :global_map $el.define_key map, keys, &block self.define_key_extra map, keys_raw, &block end @@ -836,15 +1256,482 @@ def self.log options={} # Turn into letters codes = codes.map{|o| Keys.to_letter o, :verbose=>1 } - codes = codes.map{|o| "- #{o}\n" }.join("") + codes = codes.map{|o| "| #{o}\n" }.join("") codes.gsub!(/ $/, " space") end codes end + # Keys.sequence_to_string "\f\x03" def self.sequence_to_string keys keys.split('').map{|o| Keys.to_letter(o.sum).upcase}.join('') end + + # Filters a list if items based on one or a few keys. + # To match, it finds the note file starting + # with the first char, and containing the second. So, "r" or + # "rb" would match "ruby". + def self.fuzzy_filter list, keys + + regex = Regexp.new "^#{keys}" # First, try exact match, starting at the beginning + keys = keys.split(//) + first = keys.shift + found = list.find{|o| o =~ regex} + + return found if found + + regex = "^#{first}" # Then, try starts with 1st letter, and any combination in order + keys.each{|o| regex << ".*#{o}"} + regex = Regexp.new regex + found ||= list.find{|o| o =~ regex} + found + end + + def self.load_keys_dir + dir = File.expand_path "~/xiki/keys/" + + return if ! File.directory? dir # If dir no there, just don't load it + + Dir.new(dir).entries.grep(/^[^.]/).each do |shortcut_name| + next if shortcut_name == "reload.rb" || shortcut_name == "keys_index.rb" + + pluses = shortcut_name[/\w+/].gsub("_", "+") + Xiki.def(pluses) do + file = "#{dir}/#{shortcut_name}" + txt, out, exception = Code.eval File.read(file), file, 1, :pretty_exception=>1, :target_module=>::Xiki + View.open("exception", :txt=>"> Error while running #{pluses.gsub("_", "+")}\n#{exception}") if exception + end + end + nil + end + + # Expands lines like these. The 1st runs the key shortcut, + # the 2nd edits it (assumes it's in the keys/ dir, not defined + # the old way). + # + # do+something + # do+something. + def self.expand_plus_syntax path, options + + key, txt = path + key_orig = key.dup + key.gsub! "+", "_" + + extension = key.slice!(/\.\w*$/) + + file = File.expand_path "~/xiki/keys/#{key}.rb" + + file_exists = File.exists? file + + if options[:prefix] == "open" && file_exists # as+open, so just jump to it + View.open file + return + end + + # foo+bar.*, so insert show or save the source... + + if extension + + # foo+bar., so redirect to with rb + + if extension == "." + + Line.sub! /$/, "rb" + Launcher.launch + return + end + + if ! txt + + # foo+bar., so insert text... + + if ! file_exists + + # Check for whether it's defined the old way. Borrow > open+key."] + + proc = self.proc_from_acronym key + + # If mapped the old way, just jump to it... + + if proc + Code.jump_to_proc_source proc + return + end + + return "| # Doesn't exist yet.\n| # Add some code here to define this shortcut.\n| View >> 'hi'" + end + + # Insert source... + + options[:no_slash] = 1 + txt = Tree.quote(File.read(file), :char=>"|") + return txt + + else + + # foo+bar./| code, so save... + + File.open(file, "w") { |f| f << txt } + + self.load_keys_dir # Reload in case it's new + + return "<* - saved!" + + end + end + + # foo+bar, so run it... + + if ! file_exists # If defined the old way, run it + proc = self.proc_from_acronym key + + if ! proc + View.flash "- Not mapped yet. Expand again to create!", :times=>4 + return "=replace/line/\n #{key_orig}." + end + + if options[:prefix] == "open" + Code.jump_to_proc_source(proc) + return + end + + proc.call + end + + txt, out, exception = Code.eval File.read(file), file, 1, :pretty_exception=>1, :target_module=>::Xiki + + View.open("exception", :txt=>"> Error while running #{key_orig}\n#{exception}") if exception + end + + # Returns the proc that is mapped to an acronym. + # Keys.proc_from_acronym "layout create" + def self.proc_from_acronym acronym + meth = Keys.words_to_letters acronym + keys_raw = Keys.translate_keys meth # Translate to 'C-t C-f' etc + keys = $el.kbd keys_raw # Translate to actual control keys + proc = Keys.proc_from_key keys + proc + rescue + nil + end + + # Mapped to C-k. Usually people will pause after, and it'll just + # show =keys/. + def self.k_key + + # Just open the menu + Launcher.open "keys/", :bar_is_fine=>1, :hotkey=>1 + + # # If we want a delay when C-k (probably not the best tradeoff) + # Keys.expand [] + end + + @@last_key, @@last_prefix = nil, nil + @@last_key_movement, @@last_prefix_movement = nil, nil + + def self.repeat options={} + + prefix = Keys.prefix :clear=>1 + + # Keys.clear_prefix if prefix.is_a? Fixnum + + if options[:movement] + Keys.prefix = @@last_prefix_movement + prock = @@last_key_movement.is_a?(Proc) ? + @@last_key_movement : + Xi.hget(@@map, *@@last_key_movement) + + # In the future, this could contain strings + else + Keys.prefix = @@last_prefix + prock = @@last_key.is_a?(Proc) ? + @@last_key : + Xi.hget(@@map, *@@last_key) + end + + times = prefix.is_a?(Fixnum) ? prefix : 1 + + times.times do + prock.is_a?(Proc) ? + prock.call : $el.el4r_ruby_eval("Xiki::#{prock}") + end + + end + + # Remembers this key to repeat it when C-., except for trivial keys + def self.remember_key_for_repeat path, options={} + + # Set default flags for what it should do... + + prefix = Keys.prefix + kind = :action # Assume C-. to repeat. Set to :movement later if appropriate + + # Some keys are :movement's (C-, to repeat), or not recorded at all... + + if path.is_a? Array + case path[0] + when "next", "previous", "forward", "backward" + return if ! prefix # No point remembering if no prefix + when "enter" + return if ["end"].member? path[1] # Don't remember + when "as" + if ["select"].member? path[1] # Certain ones are actions + kind = :movement + end + return if ["save", "axis"].member? path[1] # Don't remember + when "window" + return if ["middle", "edge"].member? path[1] # Don't remember + return if path[1] =~ /^[0-9]+$/ + + if ["kill", "close"].member? path[1] # Certain ones are actions + kind = :action + else + kind = :movement + end + when "list" + return if ["unsaved", "zoom"].member? path[1] # Don't remember + when "run" + + return if ["save"].member? path[1] # Don't remember + when "hop" + return if ["top", "bottom"].member?(path[1]) # Don't remember + return if path[1] =~ /^[0-9]$/ + if ["hack"].member? path[1] # Certain ones are actions + kind = :action + else + kind = :movement + end + when "content" + return if ["zap", "start", "end"].member? path[1] # Don't remember + + if ["close"].member? path[1] # Certain ones are actions + kind = :action + else + kind = :movement + end + end + end + + kind = :movement if options[:movement] + + # Store for later C-. or C-,... + + if kind == :movement + @@last_key_movement = path + @@last_prefix_movement = prefix == :uu ? 16 : prefix + return + end + + @@last_key = path + @@last_prefix = prefix + end + + def self.el4r_init + $el.el4r_lisp_eval " + (progn + ; Just defined, so we can use it as a local map + (setq xiki-az-keymap + '(keymap + (3 . next-line) + (24 . previous-line) + (27 . keyboard-quit) ; Makes escape work + ) + ) + + ; Does nothing + (defun xiki-noop () (interactive) + ) + + ; Temporarily map Ctrl+A (etc) as single char commands... + + ; This map can be temporarily enabled to make Ctrl+A (etc) be + ; part of a single command. The keys are mapped to xiki-noop + ; since, when the map is used, the pre-command-hook will change + ; the command to something else anyway. + + (setq xiki-az-control-keymap + '(keymap + (1 . xiki-noop) + (2 . xiki-noop) + (3 . xiki-noop) + (4 . xiki-noop) + (5 . xiki-noop) + (6 . xiki-noop) + (7 . xiki-noop) + (8 . xiki-noop) + (9 . xiki-noop) + (10 . xiki-noop) + (11 . xiki-noop) + (12 . xiki-noop) + (13 . xiki-noop) + (14 . xiki-noop) + (15 . xiki-noop) + (16 . xiki-noop) + (17 . xiki-noop) + (18 . xiki-noop) + (19 . xiki-noop) + (20 . xiki-noop) + (21 . xiki-noop) + (22 . xiki-noop) + (23 . xiki-noop) + (24 . xiki-noop) + (25 . xiki-noop) + (26 . xiki-noop) + ) + ) + ; Wraps and toggles + (setq xiki-az-control-keymap-toggler + `((xiki-filter-hotkey . ,xiki-az-control-keymap)) + ) + (add-to-ordered-list 'emulation-mode-map-alists 'xiki-az-control-keymap-toggler 200) + ) + " + end + + def self.accumulate? + @@accumulate + end + + def self.accumulated + @@accumulated + end + + def self.accumulate + + @@accumulate = true # Make key definitions build up elisp instead of run it as it comes + @@accumulated = "" + + yield + + @@accumulate, @@accumulated = nil, nil + nil + + end + + # Mapped to jump+menu and do+menu. Prompts for a bookmark for a + # dir, then opens or jumps to the menu.xiki in it. + def self.open_or_run_project_menu options={:action=>"open"} + + # Prompt for bookmark... + + bm = Keys.input :timed=>1, :prompt=>"Enter a bookmark: " + + if bm == "." + + # "." bookmark, so look for it in current dir and upwards... + + # Climb up until no dirs left + dir = "#{View.dir}/tmp" # Add on 'tmp' so first pass checks the actual dir + + found = nil + + while dir != "/" + dir = File.dirname dir + file = "#{dir.sub(/\/$/, '')}/menu.xiki" + break found = file if File.exists? file + end + + return View.flash("- No 'menu.xiki' file found in this dir or any ancestor dirs!", :times=>3) if ! found + end + + # Expand bookmark + file = Bookmarks["%#{bm}"] + file.sub! /\/$/, '' + + # do+menu, so just by adding double slashes... + + if options[:action] == "run" + + if File.directory?(file) + # Dir, so just add "//" at end... + file = "#{file}//" + else + file.sub! /\.\w+$/, "//" + end + + return Launcher.open file + + end + + # jump+menu, so find file and open it... + + # It's a file, so just open the file? + return View.open(file) if File.file? file + + # It's a dir, so open menu.notes, or menu.xiki + ["menu.xiki", "menu.notes"].each do |name| + menu_file = "#{file}/#{name}" + return View.open(menu_file) if File.exists?(menu_file) + end + + View.open :txt=>%`> Not found\nNo "menu.xiki" or "menu.notes" file found in:\n\n#{file}` + + end + + + def self.read_subsequent_chars + result = [] + # Read other chars typed right after + while(char = $el.read_char("", nil, 0.005)) do + result << char + end + result + end + + def self.press_any_key options={} + + # Read next key, escape sequence, or mouse click and just throw them away + + # Read 1st char + + View.message options[:message]||" " # Keeps "a-" etc from showing up in the bottom + result = [$el.read_char] + + result += self.read_subsequent_chars + + result + + end + + def self.docs options={} + + key = options[:key] + + # View.to_view View.unique_name("bar") + View.to_view "bar", :clear=>1 + Notes.mode + View.keys_bar_mode + + View >> "bar\n\n" + + txt = " + > Open + > Go + > Tasks + + > Xiki + > Search + + > Content + > Keys + > Quit + ".unindent + + + if ! key + Tree.<< txt + return + end + + + Tree.<< txt, :no_search=>1 + + Search.forward key + Launcher.launch :no_search=>1 + + + end + end end diff --git a/lib/xiki/core/launcher.rb b/lib/xiki/core/launcher.rb index 47b9bc8f..79a2aed5 100644 --- a/lib/xiki/core/launcher.rb +++ b/lib/xiki/core/launcher.rb @@ -2,35 +2,20 @@ require 'xiki/core/requirer' require 'xiki' -require 'sourcify' # slow - (0.144676) -require 'ruby_parser' -require 'file-tail' - -Xiki::Requirer.require_gem 'activesupport', :name2=>'active_support/ordered_hash' -Xiki::Requirer.require_gem 'httparty', :optional=>1 # Not super-important - -Xiki::Requirer.require_gem 'haml', :optional=>1 # slow - (0.136328) -# autoload(:Haml, "haml") - - module Xiki class Launcher CLEAR_CONSOLES = [ - "*ol", - "*output - tail of /tmp/ds_ol.notes", - "*visits - tail of /tmp/visit_log.notes", - "*console app", - ] + "ol", + ] - # TODO: put this in better place - search notes for "emacs.d" - @@log = File.expand_path("~/.emacs.d/menu_log.notes") + @@log = File.expand_path("~/.xiki/misc/logs/xiki_commands_log.xiki") # Use @launcher/options/show or launch/ to enable. - # Look in /tmp/output.notes + @@just_show = false - @@launchers ||= ActiveSupport::OrderedHash.new + @@launchers ||= {} @@launchers_procs ||= [] @@launchers_parens ||= {} @@menus ||= [{}, {}] # [.menu files, .rb files] @@ -39,6 +24,7 @@ def self.menus @@menus end + # Deprecated! def self.menu_keys (@@menus[0].keys + @@menus[1].keys).sort.uniq #.select do |possibility| end @@ -54,10 +40,10 @@ def self.menu - docs/ > Summary | Launcher is the class that handles "launching" things (double-clicking - | on a line, or typing Ctrl-enter). + | on a line, or typing Ctrl+X). - api/ > Open menu in new buffer - @ Launcher.open "computer" + = Launcher.open "computer" > Insert monu @ Launcher.insert "computer" # Assumes you're on a blank line @@ -102,15 +88,15 @@ def self.last path=nil, options={} # Root passed, so show all matches - paths = paths.select{|o| o =~ /^- #{Notes::LABEL_REGEX}#{path}./} + paths = paths.select{|o| o =~ /^- #{Notes::MARKER_REGEX}#{path}./} bullet = options[:quoted] ? "|" : "-" if options[:exclude_path] - paths.each{|o| o.sub! /^- (#{Notes::LABEL_REGEX})#{path}\//, "#{bullet} \\1"} + paths.each{|o| o.sub! /^- (#{Notes::MARKER_REGEX})#{path}\//, "#{bullet} \\1"} paths = paths.select{|o| o != "#{bullet} "} else - paths = paths.map{|o| o.sub /^- #{Notes::LABEL_REGEX}/, '@'} + paths = paths.map{|o| o.sub /^- #{Notes::MARKER_REGEX}/, '@'} end paths = paths.reverse.uniq paths.delete_if{|o| o == "| \n"} @@ -118,17 +104,25 @@ def self.last path=nil, options={} end # Called by menu to show log contents, with newest first. - def self.log + def self.log options={} lines = IO.readlines self.log_file - # If parent, narrow down to just it - trunk = Xiki.trunk + # foo/=log, so narrow down to just this menu... + + trunk = Tree.path if trunk.length > 1 && trunk[-2] != "menu/history" # Show all if under this menu lines = lines.select {|o| o.start_with? "- #{trunk[-2]}"} end - lines.reverse.uniq.map{|o| o.sub /^- /, '<< '}.join + # Remove indented lines (shouldn't have been logged with unescaped linebreaks)... + + bullets = options[:arrows] ? "<< " : "=" + + lines = lines.reverse.uniq.map{|o| o.sub /^/, bullets} + lines = lines.select{|o| o =~ /^[^ ]/} # Only grab unindented lines (in case there's weirdness in the log) + + lines.join end def self.log_file @@ -176,7 +170,7 @@ def self.add_menu root, hash, block return elsif menu =~ /\A[\w \/-]+\z/ # If it's a menu to delegate to self.add root do |path| - Menu.call menu, Tree.rootless(path) + Command.call menu, Tree.rootless(path) end return end @@ -191,664 +185,130 @@ def self.add_menu root, hash, block raise "Don't know how to deal with: #{root}, #{hash}, #{block}" end - def self.launch_or_hide options={} - # If no prefixes and children exist, delete under - if ! Keys.prefix and ! Line.blank? and Tree.children? - Tree.minus_to_plus - Tree.kill_under - return - end - - # Else, launch - self.launch options - end - - def self.hide - Tree.kill_under - end - - # Deprecated in favor of .launch_unified - # Call the appropriate launcher if we find one, passing it line - def self.launch options={} - - # Committed on purpose, to draw attention to unported stuff. - Ol.stack 2 - Ol["old .launch is deprecated!!!!!!"] - - # Add linebreak at end if at end of file and none - Line.<<("\n", :dont_move=>1) if Line.right == View.bottom - - Tree.plus_to_minus unless options[:leave_bullet] - - Line.sub! /^\.$/, './' - Line.sub! /^~$/, '~/' - - # Maybe don't blink when in $__small_menu_box!" - Effects.blink(:what=>:line) if options[:blink] - line = options[:line] || Line.value # Get paren from line - label = Line.label(line) - - if line =~ /^ *@$/ - matches = Launcher.menu_keys - Tree.<< matches.sort.map{|o| "<< #{o.gsub '_', ' '}/"}.join("\n"), :no_slash=>1 - return - end - - # Special hooks for specific files and modes - return if self.file_and_mode_hooks - - $xiki_no_search = options[:no_search] # If :no_search, disable search - - is_root = false - - if line =~ /^( *)[+-] [^\n\(]+?\) (.+)/ # Split off label, if there - line = $1 + $2 - end - if line =~ /^( *)[+-] (.+)/ # Split off bullet, if there - line = $1 + $2 - end - if line =~ /^ *@ ?(.+)/ # Split off @ and indent if @ exists - is_root = true - line = $1 - end - - # Special case to turn launchers back on - return self.show_or_launch if line == "launcher/setup/show or launch/" - # Try each potential regex match... - - @@launchers.each do |regex, block| - # If we found a match, launch it - if line =~ regex - group = $1 - - # Run it - if @@just_show - Ol << "- regex: #{regex.to_s}\n- group: #{group}" - else - - begin - block.call line - rescue RelinquishException - next # They didn't want to handle it, keep going - rescue Exception=>e - # Show error and sourche of block - Tree.<< CodeTree.draw_exception(e, block.to_source), :no_slash=>true - end - - end - $xiki_no_search = false - return true - end - end - - # If current line is indented and not passed recursively yet, try again, passing tree... - - if Line.value =~ /^ / && ! options[:line] && !is_root # If indented, call .launch recursively - - # Use Xiki.branch here? (breaks up by @'s) - - # merge together (spaces if no slashes) and pass that to launch - - list = Tree.construct_path :list=>true, :ignore_ol=>1 # Get path to pass to procs, to help them decide - - found = list.index{|o| o =~ /^@/} and list = list[found..-1] # Remove before @... node if any - merged = list.map{|o| o.sub /\/$/, ''}.join('/') - merged << "/" if list[-1] =~ /\/$/ - - # Recursively call again with full path - return self.launch options.merge(:line=>merged) - - # What was this doing, did we mean to only pass on :no_search?? - # return self.launch options.slice(:no_search).merge(:line=>merged) - end - - if self.launch_by_proc # Try procs (currently all trees) - return $xiki_no_search = false - end - - # If nothing found so far, don't do anything if... - if line =~ /^\|/ - View.beep - return View.message "Don't know what to do with this line" - end - - # See if it's a menu... + # Collapses and re-runs line in current view, or in :t (sometimes jumping to the nth labeled line). + def self.do_last_launch options={} - self.set_env_vars line + prefix = options[:prefix] || Keys.prefix(:clear=>true) - result = self.try_menu_launchers line, options - self.unset_env_vars - return if result + View.kill if View.name == "tasks markers/" - # Do "autocomplete" - show all menus that start with this... + # Clear out time of last log, so it always shows heading + Ol.clear_pause - if line =~ /^([\w -]*)$/ || line =~ /^([\w -]*)\.\.\.\/?$/ + orig = View.index + orig_file = View.file - # if line =~ /^([\w -]*)(\.\.\.)?\/?$/ - # TODO just check for exact match in dir, and load it if no launcher yet! + filter_to_current_file = prefix == :u - root = $1 - root.gsub!(/[ -]/, '_') if root - matches = self.menu_keys.select do |possibility| - possibility =~ /^#{root}/ - end - if matches.any? - if matches.length == 1 - match = matches[0].gsub '_', ' ' - Line.sub! /^([ @+-]*).*/, "\\1#{match}" - Launcher.launch - return - end + Ol.filter = orig_file if filter_to_current_file - Line.sub! /\b$/, "..." + # If ol buffer open, grab line number so we can restore it after + ol_cursor_position = nil - View.under matches.sort.map{|o| "<< #{o.gsub '_', ' '}/"}.join("\n") - return - end + if View.buffer_open? "ol" + View.to_buffer "ol" + ol_cursor_position = Line.number if ! View.at_bottom end - # If just root line, load any unloaded launchers this completes and relaunch... - - # Failed attempt to not auto-complete if slash - # It's tough because we still want to load! - # Don't do if ends with slash? - does this mean it won't load unloaded? - - if line =~ /^([\w -]+)\/?$/ && ! options[:recursed] - root = $1 - root.gsub!(/[ -]/, '_') if root - - ["~/menu3", Bookmarks["$x/lib/xiki/tools"]].each do |dir| - - matches = Dir[File.expand_path("#{dir}/#{root}*")] - - if matches.any? - matches.sort.each do |file| - iroot = file[/\/(\w+)\./, 1] - next if @@menus[0][root] || @@menus[1][root] # Skip if already loaded - require_menu(file) # if File.exists? file - end - return self.launch :recursed=>1 # options.slice(:no_search).merge(:line=>merged) - end - + if ! options[:dont_launch] + CLEAR_CONSOLES.each do |buffer| + View.clear buffer end end - if root = line[/^[\w -]+/] - - Xiki.dont_search - # Maybe make the following print out optionally, via a 'help_last' block? - Tree << " - | There's no \"#{root}\" menu yet. Create it? You can start by - | adding items right here, or you can create a class. - <= @menu/create/here/ - <= @menu/create/class/ - <= @menu/install/gem/ - " + if options[:here] # || prefix ==:uu + View.to_nth orig else - View.flash "- No launcher matched!" - end - $xiki_no_search = false - end - - def self.try_menu_launchers line, options={} - # If there's a /@ in the path, cut it off - line.sub! /.+\/@/, '' - - root_orig = root = line[/^[\w -]+/] # Grab thing to match - root = TextUtil.snake_case root if root - - self.append_log line - trunk = Xiki.trunk - - # If menu nested under dir or file, chdir first - - orig_pwd = nil - if trunk.size > 1 && closest_dir = Tree.closest_dir - orig_pwd = Dir.pwd # Where ruby pwd was before - - if root == "mkdir" - Dir.chdir "/tmp/" - elsif File.directory?(closest_dir) || is_file = File.file?(closest_dir) # If dir path - closest_dir = File.dirname closest_dir if is_file - - Dir.chdir closest_dir - - # If file, make path only have dir - # remove file - - else # If doesn't exist - Tree.<< "> Dir doesn't exist. Create it?\n@mkdir/\n" - return true - end - end - - # If there is a matching .menu, use it - - out = nil - if block_dot_menu = @@menus[0][root] - - if @@just_show - Ol.line "Maps to .menu file, for menu: #{root}\n - #{block_dot_menu}\n - #{block_dot_menu.to_source}" - View.flash "- Showed launcher in $o", :times=>4 - return true # To make it stop trying to run it - end - - begin - out = Tree.output_and_search block_dot_menu, :line=>line #, :dir=>file_path - ensure - Dir.chdir orig_pwd if orig_pwd - end - - # If .menu file matched but had no output, and no other block to delegate to, say we handled it so it will stop looking - - if ! out - require_menu File.expand_path("~/menu3/#{root}.rb"), :ok_if_not_found=>1 - if ! @@menus[1][root] - Tree << " - | This menu item does nothing yet. You can update the .menu file to - | give it children or create a class to give it dynamic behavior: - <= @menu/create/class/ - " - return true - end - end - return true if out # Output means we handled it, otherwise continue on and try class - end - # If there is a matching .rb for the menu, use it - - if block_other = @@menus[1][root] # If class menu - - if @@just_show - Ol.line << "Maps to class or other block, for menu: #{root}\n - #{block_other}\n - #{block_other.to_source}" - View.flash "- Showed launcher in $o", :times=>4 - return true # To make it stop trying to run it - end - - begin - Tree.output_and_search block_other, options.merge(:line=>line) #, :dir=>file_path - ensure - Dir.chdir orig_pwd if orig_pwd - end - - return true - end - - # - # @Unified > Kind of cool that it tries class if capital *after* it - # tries for registered menus. Probably do this after refactor as well. - # - - # If uppercase, try invoking on in-memory class - if root_orig =~ /^[A-Z]/ - - if @@just_show - Ol["Maps to in-memory class for: #{root}"] - View.flash "- Showed launcher in $o", :times=>4 - return true # To make it stop trying to run it - end - - begin - lam = lambda do |path| - Launcher.invoke root_orig, path - end - - # Launcher.invoke__ - # do |path| - # # Make class me camel case, and change Launcher.invoke to Menu.call - # end - - Tree.output_and_search lam, options.merge(:line=>line) #, :dir=>file_path - ensure - Dir.chdir orig_pwd if orig_pwd - end - - return true - end - - # Pull into other function? - # re-use code that calls class wrapper - - false # No match, keep looking - end - - def self.launch_by_proc list=nil - list = Tree.construct_path(:list=>true) # Get path to pass to procs, to help them decide - - # Try each proc - @@launchers_procs.each do |launcher| # For each potential match - condition_proc, block = launcher - if found = condition_proc.call(list) # If we found a match, launch it - if @@just_show - Ol << condition_proc.to_source - else - block.call list[found..-1] - end - return true + View.open(options[:bookmark] || "%n", :stay_in_bar=>1) + if prefix.is_a?(Fixnum) && prefix > 0 + View.line = prefix end end - return false - end - - def self.init_default_launchers - - self.add(/^\$ /) do |l| # $ shell command inline (sync) - Console.launch :sync=>true, :path=>l - end - - self.add /^%( |$)/ do # % shell command (async) - Console.launch_async - end - self.add /^&( |$)/ do # % shell command in iterm - Console.launch_async :iterm=>1 - end - - # %\n | multiline\n | commands - Launcher.add /^\%\// do # For % with nested quoted lines - path = Tree.construct_path :list=>1 - - next if path[-1] !~ /^\| / - - txt = Tree.siblings :string=>1 - - orig = Location.new - Console.to_shell_buffer - View.to_bottom - Console.enter txt - orig.go - end - - # Deprecated - self.add(/^(http|file).?:\/\/.+/) do |path| - Launcher.append_log "- http/#{path}" - - prefix = Keys.prefix - Keys.clear_prefix + # If do+1 or do+2 etc, move to nth thing to run... - url = path[/(http|file).?:\/\/.+/] - if prefix == "all" - txt = RestTree.request("GET", url) - txt = Tree.quote(txt) if txt =~ /\A<\w/ - Tree.under Tree.quote(txt), :no_slash=>1 - next + if options[:nth] || options[:label] + # Remember where to go back to, if we were in $todo + if orig_file == Bookmarks["%n"] + line, column = View.line, View.column end - url.gsub! '%', '%25' - url.gsub! '"', '%22' - prefix == :u ? $el.browse_url(url) : Firefox.url(url) - end - - self.add(/^\$[^ #*!\/]+$/) do |line| # Bookmark - View.open Line.without_indent(line) - end - - self.add(/^(p )?[A-Z][A-Za-z]+\.(\/|$)/) do |line| - line.sub! /^p /, '' - Code.launch_dot_at_end line - end - - - # Various lines that mean run as ruby - # p ... - # puts ... - # etc. - self.add(/^(p|y|pp|puts|Ol) /) do |line| - CodeTree.run line, :quote=>1 - end - self.add(/^(ap) /) do |line| - CodeTree.run line, :quote=>1 - end - - self.add(/^print\(/) do |line| - Javascript.launch - end - - self.add(/^pg /) do |line| - line.sub! /^pg /, '' - CodeTree.run "puts JSON.pretty_generate(#{line})" - end - - self.add(/^ *$/) do |line| # Empty line - View.beep - View.message "There was nothing on this line to launch." - end - - self.add(/^\*$/) do |line| # *... buffer - Line.sub! /.+/, "all" - - Launcher.launch_unified - end - - self.add(/^\*./) do |line| # *... buffer - name = Line.without_label.sub(/\*/, '') - View.to_after_bar - View.to_buffer name - end - - # Must have at least 2 slashes! - self.add(/^[^\|@:]+\/\w+\/[\/\w\-]+\.\w+:\d+/) do |line| # Stack traces, etc - # Match again (necessary) - line =~ /([$\/.\w\-]+):(\d+)/ - path, line = $1, $2 - - if path =~ /^(\w.*)/ || path =~ /^\.\/(.+)/ + # Always do highest + View.to_highest - path = $1 + if options[:nth] + options[:nth].times{ Notes.next_marker } + elsif options[:label] - local_path = "#{View.dir}/#{path}".sub "//", "/" - xiki_path = "#{Xiki.dir}/#{path}".sub "//", "/" - if File.exists? local_path - path = local_path - elsif File.exist? xiki_path - path = xiki_path - end + # Find "- foo) bar", "- ) foo", or "- )\nbar" + Notes.jump_to_label options[:label] - else - return ".flash - File doesn't exist!" if ! File.exists? path + Notes.next_line_when_marker end - View.open path - View.to_line line.to_i - end - - # Xiki protocol to server - self.add(/^[a-z-]{2,}\.(com|net|org|loc|in|edu|gov|uk)(\/|$)/) do |line| # **.../: Tree grep in dir - self.web_menu line - end - - self.add(/^localhost:?\d*(\/|$)/) do |line| - self.web_menu line - end - - self.add(/^ *(Ol\.line|Ol << )/) do - View.layout_outlog :called_by_launch=>1 - end - - # Example code in method comments, such as: - # @/tmp/foo.rb - # | class Foo - # | # C-return to run this example: - # | # Foo.bar - # | def self.bar - # p 1 + 2 - # 1.should == 2 - # rails/ - # generate/ - # start/ - Launcher.add /^class (\w+).+\/\#.+/ do |path| - # Remove comment and run - txt = Line.value - - comment_indent, txt = txt.match(/^( +# )(.+)/)[1..2] + return if options[:go] - result = Code.eval txt, View.file, Line.number(left) - if result[2] - result = CodeTree.draw_exception(result[2], txt) - result.strip! - result.gsub! /^/, "#{comment_indent} " - Line.<< "\n#{result}", :dont_move=>1 - next + # Do nothing if went to the end without finding anything + if View.cursor == View.bottom + View.to_highest + View.line, View.column = line, column if line + return View.flash "- No lines are marked. Use layout+mark." end - result = result[0] || result[1] - next if ! result - - result = result.to_s - result.gsub! /^/, "#{comment_indent} " - Line.<< "\n#{result}", :dont_move=>1 - end - - Launcher.add /^class (\w+)\/def self.menu\/(.+)/ do |path| - clazz, path = path.match(/^class (\w+)\/def self.menu\/(.+)/)[1..2] - - path = "#{TextUtil.snake_case clazz}/#{path}".gsub("/.", '/') - - Tree << Menu[path] - end - - Launcher.add /^ +<+@ .+/ do - Menu.root_collapser_launcher - end - - Launcher.add /^ +<+ .+/ do - Menu.collapser_launcher - end - - Launcher.add /^ +<+= .+/ do - Menu.replacer_launcher - end - - Launcher.add /^[a-z]+\+[a-z+]+\/?$/ do |path| - txt = %` - | If you were told to "type #{path}", it is meant that you should - | "type the acronym" while holding down control. This means - | you should type: - | - | #{Keys.human_readable(path)} - ` - Tree.<< txt, :no_slash=>1 - end - - # Some menu launchers... - - # Proc Launchers (obsolete now that climbed path is passed to regex's)... - - # RestTree - condition_proc = proc {|list| RestTree.handles? list} - Launcher.add condition_proc do |list| - RestTree.launch :path=>list - end - - # FileTree - condition_proc = proc {|list| FileTree.handles? list} - Launcher.add condition_proc do |list| - FileTree.launch end - # CodeTree - condition_proc = proc {|list| CodeTree.handles? list} - Launcher.add condition_proc do |list| - CodeTree.launch :path=>list - end - # UrlTree - condition_proc = proc {|list| UrlTree.handles? list} - Launcher.add condition_proc do |list| - UrlTree.launch :path=>list + if prefix == 0 + Move.backward + Line.to_beginning + return end - end - # Deprecated after Unified - def self.file_and_mode_hooks - if View.mode == :dired_mode - filename = $el.dired_get_filename - # If dir, open tree - if File.directory?(filename) - FileTree.ls :dir=>filename - else # If file, do full file search? - History.open_current :all => true, :paths => [filename] - end - return true - end - if View.name =~ /^\*ol/ # If in an ol output log file - OlHelper.launch - Effects.blink(:what=>:line) - return true - end - return false - end + line_value = Line.value - def self.do_last_launch options={} - # Clear out time of last log, so it always shows heading - Ol.clear_pause + # Better plan > always collapse (only does something if something under it)... - orig = View.index + Tree.to_parent if Line =~ /^ +- backtrace:$/ # If we went to "- backtrace:", go up again - # If *ol buffer open, grab line number so we can restore it after - ol_cursor_position = nil - if View.buffer_visible? "*ol" - View.to_buffer "*ol" - ol_cursor_position = Line.number if ! View.at_bottom + if options[:dont_launch] + Line.to_beginning + return end - # File.open("/tmp/simple.log", "a") { |f| f << "final ol_cursor_position: #{ol_cursor_position}\n" } - - CLEAR_CONSOLES.each do |buffer| - View.clear buffer - end + # Collapse if children of this line + Launcher.launch_or_collapse :just_collapse=>1 - prefix = Keys.prefix :clear=>true + Effects.blink - if prefix ==:u || options[:here] || prefix ==:uu - View.to_nth orig + if (options[:here] && ! options[:nth]) || prefix == :- # :here is only used by do+up + Launcher.launch else - Move.to_window 1 - if prefix.is_a? Fixnum - View.line = prefix - end - end - - line = Line.value - - # Go to parent and collapse, if not at left margin, and buffer modified (shows we recently inserted) - if ! Color.at_cursor.member?("color-rb-light") #&& line !~ /^ *[+-] / # and not a bullet - if line =~ /^ / - prefix ==:uu ? - Tree.to_parent(:u) : - Tree.to_parent - end - Tree.kill_under + Launcher.launch(:no_search=>true) #> | end - Effects.blink - - # If on >... line, just eval section as ruby... - - if line =~ /^>/ - Code.run - return - end + return if prefix == :- - prefix == :- ? - Launcher.launch(:no_search=>true) : - Launcher.launch_unified(:no_search=>true) + Ol.filter = nil if filter_to_current_file if ol_cursor_position - View.to_buffer "*ol" + View.to_buffer "ol" View.line = ol_cursor_position end + View.line, View.column = line, column if line + + # Don't go to orig if :n > and we already went to a file... + + return if options[:bookmark] == "%links" && View.file != Bookmarks["%links"] + View.to_nth orig - end - # Used any more? - should be replaced by menu log - delete this - def self.urls - txt = File.read File.expand_path("~/.emacs.d/url_log.notes") - txt = txt.split("\n").reverse.uniq.join("\n") + # do+expand, so reflect value in comment, if line is "Ol foo" or "ol[foo]" + View.layout_outlog(:prefix=>:-) if prefix == :u && Line =~ /^ *Ol( |!|\.>>)/ end def self.enter_last_launched @@ -869,7 +329,7 @@ def self.last_launched_menu elsif bm == ";" || bm == ":" || bm == "-" # What does this mean? "nav history/:/" else - "nav history/$#{bm}/" + "nav history/:#{bm}/" end end @@ -899,20 +359,16 @@ def self.invoke clazz, path, options={} raise "No class '#{clazz || camel}' found in launcher" if clazz.nil? - # TODO: Unified: comment out for now - just comment out since we're doing no caching - # reload 'path_to_class' - Menu.load_if_changed File.expand_path("~/menu3/#{snake}.rb") - - +Ol["oh, this path is an array: #{path}!"] if path.is_a?(Array) + Ol["Changed > might cause problems?!"] args = path.is_a?(Array) ? - path : Menu.split(path, :return_path=>1) + path : Path.split(path, :return_path=>1) + # path : Menu.split(path, :return_path=>1) # Call .menu_before if there... method = clazz.method("menu_before") rescue nil - self.set_env_vars path - if method code = "#{camel}.menu_before *#{args.inspect}" returned, out, exception = Code.eval code @@ -920,8 +376,6 @@ def self.invoke clazz, path, options={} return CodeTree.draw_exception exception, code if exception if returned - # TODO: call .unset_env_vars before this and other below places we return - returned = returned.unindent if returned =~ /\A[ \n]/ return returned end @@ -960,8 +414,7 @@ def self.invoke clazz, path, options={} txt = Tree.children tree, args if txt && txt != "- */\n" - # Pass in output of menu as either: - # ENV['output'] + # Pass in output of menu as # 1st parameter: .menu_after output, *args return self.invoke_menu_after clazz, txt, args end @@ -1022,7 +475,6 @@ def self.invoke clazz, path, options={} txt_orig = txt txt = CodeTree.returned_to_s(txt) # Convert from array into string, etc. - self.unset_env_vars txt = txt.unindent if txt =~ /\A[ \n]/ @@ -1030,8 +482,6 @@ def self.invoke clazz, path, options={} txt = self.invoke_menu_after clazz, txt_orig, args_orig - self.unset_env_vars - txt end @@ -1046,8 +496,6 @@ def self.invoke_menu_after clazz, txt, args return CodeTree.draw_exception exception, code if exception if returned - # TODO: call .unset_env_vars before this and other below places we return - returned = returned.unindent if returned =~ /\A[ \n]/ return returned end @@ -1073,14 +521,13 @@ def self.add_class_launchers classes end def self.append_log path - return if View.name =~ /_log.notes$/ + return if View.name =~ /_log.xiki$/ path = path.sub /^[+-] /, '' # Remove bullet path = "#{path}/" if path !~ /\// # Append slash if just root without path return if path =~ /^(h|log|last)\// - path = "- #{path}" File.open(@@log, "a") { |f| f << "#{path}\n" } rescue nil end @@ -1091,9 +538,7 @@ def self.insert txt, options={} View.insert txt $el.open_line(1) - return Launcher.launch options if options[:not_unified] - - Launcher.launch_unified options + Launcher.launch options end def self.show menu, options={} @@ -1104,17 +549,22 @@ def self.show menu, options={} # # Launcher.open "ip" def self.open menu, options={} - return self.insert(menu, options) if options[:inline] - $el.sit_for 0.25 if options[:delay] || options[:letter] # Delay slightly, (avoid flicking screen when they type command quickly) + return self.insert(menu, options) if options[:inline] View.to_after_bar if View.in_bar? && !options[:bar_is_fine] - dir = View.dir + dir = options[:buffer_dir] || View.dir + + buffer = options[:buffer_name] + + if ! buffer + # For buffer name, handle multi-line strings + buffer = menu.sub(/.+\n[ -]*/m, '').gsub(/[.,]/, '') + buffer = buffer.sub(/^[+-] /, '') + buffer = buffer.sub(/^= ?/, '') + end - # For buffer name, handle multi-line strings - buffer = menu.sub(/.+\n[ -]*/m, '').gsub(/[.,]/, '') - buffer = "@" + buffer.sub(/^[+-] /, '') View.to_buffer buffer, :dir=>dir View.clear @@ -1135,7 +585,7 @@ def self.open menu, options={} if options[:choices] View.to_highest - Tree.search + Tree.filter return end @@ -1145,170 +595,26 @@ def self.open menu, options={} return end - # Deprecated - return Launcher.go_unified if options[:unified] - - return Launcher.launch options if options[:not_unified] - - Launcher.launch_unified options - end - - def self.method_missing *args, &block - - arg = args.shift + options.merge! :launcher_open=>1 - if block.nil? - if args == [] # Trying to call menu with no args - return Menu.call arg.to_s - end - if args.length == 1 && args[0].is_a?(String) # Trying to call menu with args / path? - return - end - end + Launcher.launch options - raise "Menu.#{arg} called with no block and no args" if args == [] && block.nil? - self.add arg.to_s, args[0], &block end - def self.wrapper path - # If starts with bookmark, expand as file (not dir) - - path = Bookmarks.expand path, :file_ok=>1 + # + # Adds a menu (command) for each file in tools? + # + def self.load_tools_dir - # TODO: make generic - # TODO: load all the adapters and construct the "rb|js" part of the regex + dir = "#{Xiki.dir}lib/xiki/tools" + Files.in_dir(dir).each do |f| - # Don't match if it's quoted (after a pipe) - match = path.match(/^([^|]+\/)(\w+)\.(rb|js|coffee|py|notes|menu|haml)\/(.*)/) - if match - dir, file, extension, path = match[1..4] - # TODO: instead, call Launcher.invoke JsAdapter(dir, path), path - self.send "wrapper_#{extension}", dir, "#{file}.#{extension}", path - return true # Indicate we handled it - end + next if f !~ /^[a-z].*\..*[a-z]$/ || f =~ /__/ + next if f =~ /\.menu$/ - # For matches to filename instead of extensions? - match = path.match(/^([^|]+\/)(Rakefile)\/(.*)/) - if match - dir, file, path = match[1..4] - # TODO: instead, call Launcher.invoke JsAdapter(dir, path), path - self.send "wrapper_#{file.downcase}", dir, file, path - return true # Indicate we handled it - end - - return false - - end - - def self.wrapper_rb dir, file, path - output = Console.run "ruby #{Xiki.dir}/etc/wrappers/wrapper.rb #{file} \"#{path}\"", :sync=>1, :dir=>dir - - # Sensible thing for now is to just do literal output - # output = Tree.children output, path if path !~ /^\./ - - # How to know when to do children?! - # Because it called .menu, and menu had no args - # Make it set env var? - - # output = Tree.children output, path - - Tree << output - end - - def self.wrapper_js dir, file, path - output = Console.run "node #{Xiki.dir}etc/wrappers/wrapper.js \"#{dir}#{file}\" \"#{path}\"", :sync=>1, :dir=>dir - output = Tree.children output, path - Tree << output - end - - def self.wrapper_coffee dir, file, path - txt = CoffeeScript.to_js("#{dir}#{file}") - tmp_file = "/tmp/tmp.js" - File.open(tmp_file, "w") { |f| f << txt } - - output = Console.run "node #{Xiki.dir}etc/wrappers/wrapper.js \"#{tmp_file}\" \"#{path}\"", :sync=>1, :dir=>dir - output = Tree.children output, path - Tree << output - end - - def self.wrapper_notes dir, file, path - if match = path.match(/^(\| .+)(\| .*)/) - heading, content = match[1..2] - # [nil, nil])[1..2] - else - heading, content = [path, nil] - end - - heading = nil if heading.blank? - - # heading, content = (path.match(/^(\| .+)(\| .*)/) || [nil, nil])[1..2] - - dir = "#{dir}/" if dir !~ /\/$/ - output = Notes.drill "#{dir}#{file}", heading, content - Tree << output - end - - def self.wrapper_menu dir, file, path - heading, content = (path.match(/^(\| .+)(\| .*)?/) || [nil, nil])[1..2] - - # output = Menu.drill "#{dir}/#{file}", heading, content - - # output = Tree.children File.read(file), Tree.rootless(path) - output = Tree.children File.read("#{Bookmarks[dir]}/#{file}"), path - - Tree << output - end - - def self.wrapper_py dir, file, path - output = Console.run "python #{Xiki.dir}etc/wrappers/wrapper.py \"#{dir}#{file}\" \"#{path}\"", :sync=>1, :dir=>dir - output = Tree.children output, path if path !~ /^\./ - Tree << output - end - - def self.wrapper_haml dir, file, path - - engine = Haml::Engine.new(File.read "#{dir}#{file}") - - foos = ["foo1", "foo2", "foo3"] - o = Object.new - o.instance_eval do - @foo = "Foooo" - @foos = foos - end - - txt = engine.render(o, "foo"=>"Fooooooo", "foos"=>foos) - - Tree << Tree.quote(txt) - end - - def self.wrapper_rakefile dir, file, path - - # If just file passed, show all tasks - - if path.blank? - txt = Console.sync "rake -T", :dir=>dir - - txt = txt.scan(/^rake (.+?) *#/).flatten - - Tree << txt.map{|o| "- #{o}/\n"}.join - return - end - - # Task name passed, so run it - - path.sub! /\/$/, '' - Console.run "rake #{path}", :dir=>dir - nil - end - - def self.reload_menu_dirs - dir = "#{Xiki.dir}lib/xiki/tools" - Files.in_dir(dir).each do |f| - next if f !~ /^[a-z].*\..*[a-z]$/ || f =~ /__/ - path = "#{dir}/#{f}" - stem = f[/[^.]*/] - self.add stem, :menu=>path + path = "#{dir}/#{f}" + require path end "- reloaded!" @@ -1325,23 +631,21 @@ def self.reload_menu_dirs def self.like_menu item, options={} return if item.nil? - menu = Keys.input :timed=>true, :prompt=>"Enter menu to pass '#{item}' to (space it's the menu): " + menu = Keys.input :timed=>true, :prompt=>"Pass '#{item}' to which command? (or space if it's the command): " return self.open(item, options) if menu == " " # Space means text is the menu - matches = self.menu_keys.select do |possibility| - possibility =~ /^#{menu}/ - end + matches = Xiki::Command.completions menu if matches.length == 1 return self.open("#{matches[0]}/#{item}", options) end - self.open(matches.map{|o| "- #{o}/#{item}\n"}.join(''), options.merge(:choices=>1)) + self.open(matches.map{|o| "#{o}/#{item}\n"}.join(''), options.merge(:choices=>1)) right = View.cursor Move.to_previous_paragraph - Tree.search :left=>View.cursor, :right=>right + Tree.filter :left=>View.cursor, :right=>right end def self.search_like_menu @@ -1351,56 +655,49 @@ def self.search_like_menu def self.as_update Keys.prefix = "update" - Launcher.launch_unified :leave_bullet=>1 + Launcher.launch :leave_bullet=>1 end def self.as_delete Keys.prefix = "delete" - Launcher.launch_unified + Launcher.launch end def self.as_open Keys.prefix = "open" - Launcher.launch_unified + Launcher.launch end def self.enter_all return FileTree.enter_lines(/.*/) if Line.blank? Keys.prefix = "all" - Launcher.launch_unified + Launcher.launch end # Shortcut for passing "outline" prefix and launching. def self.enter_outline - return FileTree.enter_lines if Line.blank? # Prompts for bookmark to insert if blank line - # If there's a numeric prefix, add it - Keys.add_prefix "outline" - Launcher.launch_unified - end + # Blank line, so insert file from bookmark first... - def self.set_env_vars path - return if ! $el + if Line.blank? - ENV['prefix'] = Keys.prefix.to_s + message = "File to insert outline for: " + View.flash message + path = Keys.input "#{message}: ", :timed=>1 - args = path.is_a?(Array) ? - path : Menu.split(path, :return_path=>1) + path = Bookmarks["%#{path}"] if path =~/^\w/ # They typed a word, so expand it as a bookmark - # If line not quoted, just use single line + View.insert path - quoted = args.find{|o| o =~ /^\|/} - if ! quoted - return ENV['txt'] = args[-1] end - # Quoted lines + # If there's a numeric prefix, add it - txt = Tree.leaf("|") # Cheat to make it grab quoted - ENV['txt'] = txt.length > 1_000_000 ? "*too long to put into env var*" : txt + Launcher.launch :task=>["outline"] end + def self.web_menu line Line << "/" unless Line =~ /\/$/ url = "http://#{line}" @@ -1415,42 +712,77 @@ def self.web_menu line end end - def self.unset_env_vars - ENV['prefix'] = nil - ENV['txt'] = nil - end + # def self.expand options={} + def self.launch_or_collapse options={} - # Thin wrapper around how .launch_or_hide is called from key shortcuts. - def self.go - Ol.clear_pause - Launcher.launch_or_hide :blink=>true - end + return Grab.quote_selection if View.selection? + + line = Line.value - def self.go_unified Ol.clear_pause # If no prefixes and children exist, collapse - if ! Keys.prefix && ! Line.blank? && Tree.children? && View.name != "*ol" - Tree.minus_to_plus - Tree.kill_under - return + + if ! Line.blank? && View.name != "ol" && ! options[:go] + + + # There's a child right under this line, so just collapse it + if Tree.children? + Tree.minus_to_plus + Tree.collapse + return + + # There's a child at the end of the paragraph, so collapse it + elsif line =~ /^ *\|/ + # Or, collapse children at end, if any + indent = Line.indent line + + ignore, right = View.paragraph(:bounds=>true) + orig = View.cursor + View.cursor = right-1 + if Line =~ /^#{indent} / + Tree.to_parent + Tree.collapse + View.cursor = orig + return + end + + # Go back to where we were, And do nothing + View.cursor = orig + + end + end - # Else, launch - self.launch_unified + + return if options[:just_collapse] + + + self.launch options.select{|key, value| [:go].include?(key)} #> || end - # While implementing, mapped to Command+Return. - # After refactor, map to Ctrl+Return as well. - def self.launch_unified insert_options={} - line = Line.value - return if self.bullet_prefix_handling line + # Invoked by C-x, via Launcher.go. + # Gets info from view, delegates to Expander.expand, then inserts result. + def self.launch insert_options={} + + line, line_number = Line.value, Line.number + + Line.<<("\n", :dont_move=>1) if Line.right == View.bottom # If at end of view add linebreak, in none. - options = {:client=>"editor/emacs"} + options = insert_options.merge(:client=>"editor/emacs") + + # Treat -foo as same as foo (and change it) + + if line =~ /^-[a-z]/i + # This may be too simple > maybe delegate to a method that does stuff specific to the commands? + # and/or > make the single letter equivalent have more logic! + Line.sub!(/^-/, '') # Change variable and the line itself + line.sub!(/^-/, '') + end begin - path = Tree.path_unified + path = Tree.path rescue Exception=>e # Maybe make this be WellformedTreeException if e.is_a?(RuntimeError) && e.message =~ /well-formed tree/ @@ -1459,85 +791,340 @@ def self.launch_unified insert_options={} end end + path_last = Path.split(path[-1]) + + # Commit: swap colon and pipe quotes + if prefix = Keys.prefix; options[:prefix] = prefix; end + + # In case launching a "~ task" item, delete the siblings (the'll be added back later)... + + option_items_orig = self.delete_option_item_siblings path #> ||| + + # "foo/" line (and it's the parent), so pretend like they did ^G on it + if path_last.length == 1 && Line =~ /^ *([=+-] )?[a-z][a-z0-9 ]*\/$/i # || line =~ /^ += ?[a-z][a-z ]*\/$/i + options[:go] = 1 + end + + # Maybe nest these within new :limits option (and delete before + # passing to menu) to avoid too many options passed into menus. + # If this change is made, be sure to make pattern launchers look + # in the new place: + # /projects/xiki/lib/xiki/core/ + # - pattern.rb + # | if nested = value[options[key]] + if view = View.name; options[:target_view] = view; end + if file = View.file; options[:target_extension] = File.extname(file); end + + # Set :dir, based on path ancestors, or else Shell.dir + # Ol "dir", dir + dir = Tree.closest_dir(path[0..-2]) if path.is_a?(Array) + options[:dir] = dir || Shell.dir + + return if self.process_target_bullets_before line # If the line has << and other arrow-ish bullets + + self.adjust_line_number_maybe path, options + + if options[:path_append] # Append item to path (used for :task) + path[-1] << "/" if path[-1] =~ /[^\/]$/ # Append slash if content that doesn't end in slash + path[-1] << options[:path_append] + end + - # # Stopping doing this - anyone still relying on it?! - # # - Fix conf! - # if line =~ /^ *\|/ - # options[:txt] = Tree.siblings :quotes=>1, :string=>1 - # end + + # Expand command... txt = Expander.expand path, options + # File, so append slash + + # Ol "options", options #> {:no_search=>true, :client=>"editor/emacs", :target_view=>"notes.xiki", :target_extension=>".xiki", :dir=>"/Users/craig/", :file_path=>"/tmp/d/", :expanders=>[Xiki::FileTree], :expanders_index=>1, :no_slash=>1, :output=>" - d/\n - d/\n + d.txt\n + a.txt\n + c.xiki\n + b.txt"} + if options[:file_path] && ! options[:task] && txt !~ /\A~/ && ! options[:no_slash] + insert_options[:add_slash] = 1 + end - return if txt == nil + # Todo > possibly this several times, in case they ask for multiple? + # (maybe max out at like 10 times) + txt = self.expand_again_if_beg txt, options + txt = txt.to_s if txt != nil && ! txt.is_a?(String) - return if self.process_directives txt + # Propagate certain options set by implementation, meant to control how it's inserted... - # Pull certain options out if passed by implementation, meant to control how it's inserted + Options.propagate_some_outward options, insert_options - options.each{|k, v| insert_options[k] = v if [:no_slash, :no_search].include?(k)} + # Re-add task items and restore cursor if requested to put output under task... + + # :nest, so insert under?? + + if options[:task] && options[:nest] && option_items_orig #> nil + # Keep using letters if under task + insert_options[:hotkey] = 1 if ! options[:no_task] + Line.next + View << option_items_orig + View.line = line_number + end + + return if txt.blank? + + # Automatically repress slash if were on >... or |... line - # Automatically repress slash if were on ^... or |... line insert_options[:no_slash] = 1 if options[:args] && options[:args].last =~ /(^[>|:]|\n)/ - self.jump_line_number insert_options, options + # "* foo" task item, so don't insert slashes after, and use hotkey search + + if txt =~ /\A\s*\* / + # Suppress adding slash if doing a task on a quote + insert_options[:no_slash] = 1 if options[:quote] + # Suppress adding slash, unless on a file path (it will only add if it's actually a dir, which we want) + insert_options[:no_slash] = 1 if ! options[:file_path] + + insert_options[:hotkey] = 1 + end + + # Todo > merge together with below? + # First > try moving 3 lines below to after + # Wait! > I get it > this is based in bullets in output + # and > the below is based on bullets in the expanded line + return if self.process_output_bullets txt, options, insert_options txt = txt.to_s if txt return if txt.blank? + $el.deactivate_mark # So aquamacs doesn't highlight something after double-click + + # Todo > merge together with above? + self.process_target_bullets_after line, txt, insert_options # Delete stuff if bullet was <~ or <+! + + + # There was output, so change + to - + + Tree.plus_to_minus unless insert_options[:leave_bullet] || insert_options[:task] + + # Root item is just words (search), so don't append slash... + options[:no_slash] = 1 if Topic.matches_topic_syntax? path[-1] + + # Snippet with 8 lines or less, so insert output under last "|..." line... + + if path_last[-1] =~ /\n.+\n/ && path_last[-1].scan("\n").length <= 8 + bounds = Tree.sibling_bounds(:must_match=>"\\|") + View.cursor = bounds[3] - 1 + end Tree.<< txt, insert_options nil end - def self.jump_line_number insert_options, options - if jump_line_number = options[:jump_line_number] - insert_options[:line_found] = jump_line_number + def self.adjust_line_number_maybe path, options + # It's a file path with numbers, so adjust the number first + # What was this supposed to do? + end + + def self.expand_again_if_beg txt, options + + return txt if ! txt.is_a? String + + return txt if txt !~ /\A=beg\/(.+)\/\z/ # Only try to do something if menu returned =beg/.../ + + beg = $1 + + return if ! options[:items] || ! options[:items][-1] # Only try to do something if there's a path + # It only makes sense to beg for siblings when there's a path (since we + # wouldn't want to pass the siblings of the root menu as the path) + + options.delete :output # Clear out, because handler won't over-write output + + # Can have siblings we can grab if it's |... or is just one item (unescaped) + line = Line.without_label + can_have_siblings = line =~ /^[|:]/ || Path.split(line).length == 1 + + # Remember whether ends in slash and not quoted - means begged item should be appended to path, not replaced + itemish = line !~ /^ *\|/ # && line =~ /\/$/ + + if ! can_have_siblings # If no siblings, just add \n, so they'll no we sent all the lines + options[:items][-1] = "#{options[:items][-1]}\n" + + # Grab siblings and pass as last arg + elsif beg == "quoted" # Consecutive quoted siblings + options[:items][-1] = Tree.siblings :string=>1 + elsif beg == "neighbors" # Siblings, not crossing blank lines + siblings = "#{Tree.siblings(:include_label=>1).join("\n")}\n" + itemish ? + options[:items].<<(siblings) : + options[:items][-1] = siblings # Includes current line + elsif beg == "siblings" # =beg/siblings/ + # Siblings, crossing blank lines + siblings = "#{Tree.siblings(:cross_blank_lines=>1, :include_label=>1, :children=>1)}" + itemish ? + options[:items].<<(siblings) : + options[:items][-1] = siblings # Includes current line + else + return "| Menu begging for: #{beg}\n| (it returned #{txt.inspect})" end + + Expander.expand options end - # Called by .launch_unified to do appriate thing if result starts - # with @open file/, @flash/, or something else that instructs the editor to - # take an action. - def self.process_directives txt - # Shelved for now - # if txt =~ /\A@back up\/(.*)/ # Means delete backward in tree - probably find a better name - # txt = $1 - if txt =~ /\A@open file\/(.*)/ + # Jump up to $... or =... line, and insert text + def self.collapse_items_up_to_dollar txt, options={} + Tree.to_parent + + line = Line.value + while(line =~ /^\s/ && line !~ /^ *\$/ && line !~ /^ *=/) do + Tree.to_parent + line = Line.value + end + + Tree.collapse :no_plus=>1 + Line.sub! /( *).*/, "\\1#{txt}" + nil + end + + # Called by .launch to do appriate thing if output starts with + # =replace/, =open file/, =flash/, delete if not used any more? + + # <$$ foo, so exit and run the command (if in shell console)... + + if txt =~ /\A<\$\$ / + command = txt.sub /\A<\$\$ /, '' + return View.<<("Don't know how to handle $$... when not run from shell.") if Environment.gui_emacs + DiffLog.quit_and_run command + return true + end + + # <$ foo, so move output up to $... line and replaces it... + # This might not be used any more > after making $... handle themselves + + if txt.strip =~ /\A<\$ (.+)\z/ txt = $1 - # if |..., pull off line and go to it - quote = Path.extract_quote(txt) + self.collapse_items_up_to_dollar txt + Launcher.launch + return true + end - if line_number = Path.extract_line_number(txt) - line_number = line_number.to_i + # <@ foo/, so replace item with output... - # Before we open, calculate difference between cursor's line and :... line number - if char = Line[/^ +\|([+-])/, 1] - siblings = Tree.siblings :before=>1 - line_number += siblings.select{|o| o =~ /^\|#{Regexp.escape char}/}.length - end + if txt.strip =~ /\A<@ (.+)\z/ + # Not sure what this does > behaves as though it doesn't have a bullet? + txt = $1 + indent = Line[/^ +=?/] # Put '=' back on if it was there + Line.sub! /.*/, "#{indent}#{txt}" + return true + end + + # <<< foo/, so replace item with output and launch... + + if txt.strip =~ /\A(<<+) (.+)\z/ + + angles, txt = $1.length, $2 + + # Extra angle brackets, so jump up and kill for each one... + + # (angles-3).times do + (angles-2).times do + break if Line.indent.length == 0 + Tree.to_parent end + Tree.collapse if angles > 2 + + indent = Line[/^ +=? ?/] # Put '=' back on if it was there + Line.sub! /.*/, "#{indent}#{txt}" - View.open txt + Launcher.launch - if line_number - View.line = line_number - elsif quote - View.to_quote quote.sub(/./, '') # 1st char of quote is a redundant space or + or something else + return true + end + + # If padding at beginning, and one of these, do .unindent + + # <: and <+ replace siblings or line... + + if txt =~ /\A<([+:])\n/ + arg = $1 + to = arg == "+" ? "=replace/line/" : "=replace/siblings/" + txt.sub! /../, to + end + + # =replace/... + + if txt =~ /\A= ?replace\/(.*)/ + arg = $1 + + # Remove the 1st line, and unindent the rest + txt.sub! /\A=.+\n/, '' + txt.gsub! /^ /, '' + if arg == "neighbors/" + raise "pass =replace/, instead of =replace/neighbors/" + end + + expand_when_done = nil + + if arg == "" + bounds = Tree.sibling_bounds + elsif arg =~ /^siblings\/(.*)\/?/ # =replace/siblings/, or =replace/siblings/2/, or =replace/siblings/expand/ + arg = $1 == "" ? nil : $1 + if arg + number, expand_when_done = arg.split "/" + (number.to_i - 1).times{ Tree.to_parent } + # Why would you launch when there's a number > Doesn't it just go up that many ancestors? + # insert_options[:launch] = 1 + end + bounds = Tree.sibling_bounds :cross_blank_lines=>1 #, :children=>1 + elsif arg == "quoted/" + raise "implement quoted/!" + elsif arg == "line/" + bounds = Line.left, nil, nil, Line.right+1 # Just replace line + else # Just =replace/ ? + raise "not implemented" end + + old_txt = View.txt bounds[0], bounds[3] + indent = old_txt[/^ */] + View.delete bounds[0], bounds[3] + txt = "#{txt}".gsub(/^/, indent) + + Tree.output_and_search txt, insert_options.merge(:not_under=>1) + + return true + end + + if txt =~ /\A=open file\/(.*)/ + + filename = $1 + + # Best place for special handling? + + self.open_file filename, options #> | + + # Check preferences to see which editor to open it with... + return true + end - if txt =~ /\A@flash\/(.*)/ - View.flash $1 + # <* or =flash/, so flash message... + + if txt =~ /\A=(flash)\/(.*)/ || txt =~ /\A<(\*) ?(.*)/ + kind, txt = $1, $2 + txt = "- #{txt}" if kind == "!" && txt =~ /^[^-]/ # Add bullet if <* foo + if txt.blank? + Effects.glow :times=>1 + else + View.flash txt + end return true end - if txt =~ /\A@prompt\/(.*)/ + + # find equivalent for linux distros? + Shell.command %`qlmanage -p \"#{filename}\"` + return true # Tell caller to do nothing because we handled it + end + + false # Not a special file extension, so tell caller to handle it + end + + + def self.delete_option_item_siblings path=nil #, line=nil + + # Make options appearing and being deleted be part of the same undo chunk + # so one undo won't just show the options. + View.remove_last_undo_boundary + + line = Line.value + path ||= Tree.path rescue nil + + # Do nothing unless unless a parent line is ~... + + path = Path.split(path[-1]) rescue [] + + index = path.index{|o| o =~ /\A[~*^] / && o !~ /\n/} + + return if ! index # Do nothing if no ~... in path + + # Jump up to line that has ~... + ((path.length - 1) - index).times { Tree.to_parent } + + bounds = Tree.sibling_bounds(:cross_blank_lines=>1, :must_match=>"\\^ |\\* ") + + at_left_margin = Line =~ /^[~*^]/ + + # Also delete any non-tilde lines above, if not on blank line + if ! at_left_margin + # Maybe Find Better Way than calling .sibling_bounds twice + bounds_of_all = Tree.sibling_bounds(:cross_blank_lines=>1) + bounds[0] = bounds_of_all[0] + end + + + # Delete ~... siblings + + # raise "look for problem" + + + txt = View.delete bounds[0], bounds[3] + + + # At left margin + + if at_left_margin + + # At left margin, so delete linebreak after (if still blank) + cursor = View.cursor + if View.txt(cursor, cursor+1) == "\n" + View.delete(cursor, cursor+1) + end + + else + # Not at left margin, so move up to item option was nested under + Line.previous + end + + if @@added_option_item_padding_above + @@added_option_item_padding_above = nil + $el.backward_delete_char(1) if View.txt(cursor-2, cursor) == "\n\n" + end + + # Compare to see if it's back to unsaved state, and set unsaved if not + if View.file # Only do if > file exists + + diff = DiffLog.save_diffs(:just_return=>1) if View.file # Only do if > file exists + + # If diff shows no changes, mark as unsaved! + if ! diff || diff.count("\n") <= 2 + $el.set_buffer_modified_p nil + end + + end + + txt + + end + + + @@added_option_item_padding_above = nil + def self.added_option_item_padding_above= val + @@added_option_item_padding_above = val + end + + + def self.open_file txt, options={} + + # if |..., pull off line and go to it + quote = Path.extract_quote(txt) + + if line_number = Path.extract_line_number(txt) + line_number = line_number.to_i + + # Before we open, calculate difference between cursor's line and :... line number + if char = Line[/^ +\|([+-])/, 1] + siblings = Tree.siblings :before=>1 + line_number += siblings.select{|o| o =~ /^\[|:]#{Regexp.escape char}/}.length + end + end + + # Special handling for certain extensions like images > if no line number or quote + return "" if self.specal_handling_for_file_extension txt + + open_options = options[:prefix] == 0 ? {:same_view=>1} : {} # 0+ means use same view + + # Open with xsh, or editor specified in conf... + + conf_kind = options[:grab] ? "grab" : "expand" + + View.open txt, open_options + + # Check file prefs > if default editor set + + if line_number + View.line = line_number + elsif quote + View.to_snippet quote.sub(/./, '') # 1st char of quote is a redundant space or + or something else #> || + end + + end + + # Editor-only handling for <<, <=, and <@ bullets. + # For when launching <<... etc. lines, not for when output includes it. + # # Also when more <'s, like <<< and <<= etc. - def self.bullet_prefix_handling line - arrow_bullet = line[/^ +(<[<=@]+) /, 1] + def self.process_target_bullets_after line, txt, insert_options # , path, options + + arrow_bullet = line[/^ +(<[+:])/, 1] return nil if ! arrow_bullet # Not handled key = arrow_bullet.sub /<+/, '<' + indent = Line.indent line + + if key == "<+" + # <+ foo, so delete current line before inserting... + Line.delete + else + bounds = Tree.sibling_bounds(:cross_blank_lines=>1) + # <: foo, so delete siblings before inserting... + View.delete bounds[0], bounds[3] + end + + txt.gsub! /^/, indent + insert_options[:not_under] = 1 + + end + + + # How does this compare to > .process_output_bullets? + # - should they be merged? + def self.process_target_bullets_before line + + arrow_bullet = line[/^ +(<[<=]+)/, 1] + + return nil if ! arrow_bullet # Not handled + + key = arrow_bullet.sub /<+/, '<' + + case key when "<" - Menu.collapser_launcher - when "<=" - Menu.replacer_launcher - when "<@" - Menu.root_collapser_launcher + # << foo, so replace parent before expanding... + Command.launch_after_collapse + when /<=/ + # <= foo, so replace all parents up to "=" before expanding... + Command.launch_after_collapse_root end - true # We handled it end + def self.insert_menu + + # Text selected, so nest selected text under the command... + + if View.selection? + + # Get bookmark from user + + command = Keys.input "Command to nest this text under: " + + left, right = View.range + txt = View.delete left, right + + View.<< "#{command}/\n#{Tree.pipe txt, :indent=>" "}", :dont_move=>1 + + return + end + + line = Line.value + indent = Line.indent line + blank = Line.blank? + + prefix = Keys.prefix + + if prefix == :u # Indent 1 level less + indent.sub! " ", "" + end + + + # /foo/ + # @bar/ + if prefix == 2 + Line << "#{Keys.bookmark_as_path}" + Line << "\n @" + menu = Keys.input :timed=>1 + menu = "" if menu == " " + Line << "#{menu}" + Launcher.launch_or_collapse + return + end + + # /foo/ + # @ + if prefix == 8 + Line << "#{Keys.bookmark_as_path}" + Line << "\n @" + Launcher.launch_or_collapse + return + end + + # If line not blank, usually indent after + + Line.<<("\n#{indent} = ") if ! blank + + # If at end of line, and line not blank, go to next line + + # Todo: if dash+, do auto-complete even if exact match - how to implement? + + prompt = "Start typing a command ('a' for all): " + prompt.sub! ")", ", space for suggestions)" if ! blank + input = Keys.input(:timed=>true, :prompt=>prompt) + + # If space, they want to do just raw "@", which will suggest something based on parent + input = "" if input == " " + + View << input + + Launcher.launch + end + + + def self.open_topic options={} + insert = options[:insert] + bm = options[:bm] + + if ! insert + View.to_buffer View.unique_name("untitled.xiki") + Notes.mode + View >> "\n\n\n" + end + + if ! bm + View.flash(options[:as_command] ? "- Type a command quickly!" : "- Type a topic or command quickly!", :dont_nest=>1) + bm = Keys.timed_insert :prompt=>"", :delay=>0.40 + end + + # Insert command from bookmark + + file = Bookmarks["%#{bm}"] + + if ! Notes.in_home_xiki_dir?(file) + file = Notes.expand_link_file(file) + end + return if ! file + + topic = File.basename(file, ".*") + topic.gsub! "_", " " + Line.sub! /.*/, topic + Launcher.launch + + end + + def self.open_prompt + + prompt = Keys.prefix_u ? "% " : "$ " + + View.to_buffer View.unique_name("untitled.xiki") + Notes.mode + + View << "#{prompt}" + View >> "\n\n\n" + + end + + # Mapped to jump+command. + def self.open_nested_command + + dir = Keys.bookmark_as_path :prompt=>"Bookmark to run command under: " + + View.to_buffer View.unique_name("untitled.xiki") + Notes.mode + + View << "#{dir}\n = " + View >> "\n\n\n" + + ControlLock.disable + end + + def self.options_key + Keys.remember_key_for_repeat ["task"] + self.launch :task=>[] + end + + def self.tasks_menu_on_bookmark options={} + + file = Keys.bookmark_as_path options.merge(:include_file=>1) + + return View.message("This view doesn't have a file.") if ! file + + # If not a dir, indent file under + if file !~ /\/$/ + file.sub! /.+\//, "\\0\n - " # Indent before file + end + + View.new_file + View << file + + self.launch :task=>[] + + end + + def self.double_click + + # Line is heading, so show outline + if Line =~ /^> / + return FileTree.to_outline :not_topic_outline=>1 + end + + self.launch_or_collapse + end + + def self.right_click options={} + + path = Tree.path + + expand_options = {:task=>[]} + expand_options[:mouse] = 1 # unless options[:no_mouse] + + txt = Xiki.expand path, expand_options + + txt.unindent! + txt_with_stars = txt.dup + txt.gsub! /~ /, "" + + return if ! result + + self.launch :path_append=>"* #{result}" + + end + + # Jumps up to ^n and runs first "- )" line. + def self.do_task options={} + if Keys.prefix_u :clear=>1 + Code.load_this_file + end + + # Update all Ol's + Launcher.do_last_launch :nth=>1 #> ||||||||| + OlHelper.highlight_executed_lines + end + + # Grabs path at cursor, and opens it in a new view. + def self.expand_in_new_view + + path = Tree.path[-1] + Launcher.open path + + end + end end @@ -1591,7 +1542,7 @@ def require_menu file, options={} result = :not_found begin - result = Xiki::Menu.load_if_changed file + result = Xiki::Command.load_if_changed file rescue LoadError => e gem_name = Xiki::Requirer.extract_gem_from_exception e.to_s Xiki::Requirer.show "The file #{file} wants to use the '#{gem_name}' gem.\n% gem install #{gem_name}\n\n" @@ -1604,5 +1555,3 @@ def require_menu file, options={} Xiki::Launcher.add stem if ! Xiki::Launcher.menus[1][stem] end - -Xiki::Launcher.init_default_launchers diff --git a/lib/xiki/core/line.rb b/lib/xiki/core/line.rb index 4662ada9..f749198d 100644 --- a/lib/xiki/core/line.rb +++ b/lib/xiki/core/line.rb @@ -41,6 +41,7 @@ def self.value n=1, options={} eol = "(+ 1 #{eol})" if options[:include_linebreak] result = $el.el4r_lisp_eval("(buffer-substring (point-at-bol #{n}) #{eol})") + # result = $el.buffer_substring $el.point_at_bol #{n}) #{eol})") if options[:delete] $el.el4r_lisp_eval("(delete-region (point-at-bol #{n}) #{eol})") @@ -66,12 +67,16 @@ def self.next_matches pattern end def self.indent line=nil + + # If line is a number, get value + line = self.value(line) if line.is_a?(Fixnum) + line ||= self.value line[/^\s*/].gsub("\t", ' ') end - def self.right - $el.point_at_eol + def self.right down=1 + $el.point_at_eol down end def self.at_right? @@ -86,6 +91,10 @@ def self.left down=1 $el.point_at_bol down end + def self.rest + View.txt(View.cursor, self.right) + end + def self.at_left? $el.point_at_bol == $el.point end @@ -99,18 +108,39 @@ def self.txt self.without_indent end - def self.bounds - [$el.point_at_bol, $el.point_at_eol] + def self.bounds options={} + left, right = $el.point_at_bol, $el.point_at_eol + right += 1 if options[:linebreak] + [left, right] end def self.delete leave_linebreak=nil value = self.value - bol, eol = $el.point_at_bol, $el.point_at_eol - eol += 1 unless leave_linebreak - $el.delete_region(bol, eol) + left, right = Line.left, Line.right + right += 1 unless leave_linebreak + $el.delete_region(left, right) value end + def self.delete_line_key + prefix = Keys.prefix + lines = prefix.is_a?(Fixnum) ? prefix : 1 + left, right = Line.left, Line.left(lines+1) + + $el.delete_region(left, right) + end + + def self.kill_line + prefix = Keys.prefix + lines = prefix.is_a?(Fixnum) ? prefix : 1 + left, right = Line.left, Line.left(lines+1) + + # If no prefix, leave linebreak + right -= 1 if ! prefix + + $el.delete_region(left, right) + end + # Gets symbol at point def self.symbol options={} symbol = $el.thing_at_point(:symbol) @@ -177,18 +207,26 @@ def self.to_words end def self.to_beginning options={} - down = options[:down] + prefix = options[:prefix] || Keys.prefix down ||= prefix # If prefix go down n lines first - Line.next down if down.is_a? Fixnum + Line.next options[:down] if options[:down] Line.to_left + # If numeric prefix, move to nth word... + + if prefix.is_a? Fixnum + Line.next prefix + end + + # Otherwise, just move past indent... + prefix == :u || options[:quote]? $el.skip_chars_forward("[^ \t]") : - $el.skip_chars_forward("[^ \t|]") # If quoted, skip quote unless :u + $el.skip_chars_forward("[^ \t|:!]") # If quoted, skip quote unless :u nil end @@ -203,11 +241,10 @@ def self.label line=nil def self.without_label options={} line = options.has_key?(:line) ? options[:line] : self.value - return nil if line.nil? # Delete comment (parenthesis) - line = line.sub /^(\s*)(?:[+-]|<+) [^\n\(]+\) (.*)/, "\\1\\2" + line = line.sub /^(\s*)(?:[+-]|<+) [^\n\(]*\) (.*)/, "\\1\\2" # If paren at end of line, delete label line.sub! /^(\s*)(?:[+-]|<+) [^\n\(]+?\)$/, "\\1" @@ -239,63 +276,128 @@ def self.is_bullet? line=nil # Line.number 20 # => 3 # Line.number View.bottom # => 439 def self.number pos=nil - $el.xiki_line_number pos || $el.point + # Was this better in some way? Maybe re hidden lines? + $el.line_number_at_pos pos || $el.point end def self.to_blank $el.re_search_forward "^[ \t]*$" end - def self.duplicate_line + def self.move direction - prefix = Keys.prefix + selection = View.selection - # If in file tree, actually duplicate file - if prefix == :u && FileTree.handles? - Location.as_spot - FileTree.copy_to :prefix=>1 + # >... line, so move as block... + + if Line =~ /^>( |$)/ && ! selection + if direction == :previous + Keys.remember_key_for_repeat(proc { Notes.move_block(:up=>1) }, :movement=>1) + Notes.move_block(:up=>1) + else + Keys.remember_key_for_repeat(proc { Notes.move_block }, :movement=>1) + Notes.move_block + end return end + column = View.column + many = Keys.prefix_times - line = "#{Line.value}\n" - Line.to_left - Code.comment(:line) if prefix == :u - times = if prefix.nil? - 1 - elsif prefix == :u - 1 # Put commented line after - # 0 # Put commented line before - elsif prefix == 0 - 0 - elsif prefix > 0 - prefix + 1 - elsif prefix < 0 - prefix - end + many = (0 - many) if direction == :previous - Line.next times - View.insert line - Line.previous + range = View.range + + txt = selection ? + View.delete(*range) : + Line.value(1, :include_linebreak=>1, :delete=>1) # No selection, so cut line + + Line.to_next many + + View.insert txt, :dont_move=>1 + + if selection + # Selection existed, so restore it + cursor = View.cursor + View.selection = cursor, cursor+selection.length + else + # No selection existed, so keep cursor on line + View.column = column + end - View.column = column end - def self.move direction - column = View.column + + + def self.move_word direction + + # column = View.column many = Keys.prefix_times - many = (0 - many) if direction == :previous + many = (0 - many) if direction == :backward - line = Line.value 1, :include_linebreak=>true, :delete=>true # Get line - Line.to_next many - View.insert line + selection = View.selection + + # Try > if selection, move one char at a time + if selection + txt = View.delete(*View.range) + $el.forward_char many + View.insert txt + cursor = View.cursor + View.selection = [cursor, cursor-txt.length] + return + end + + + # No selection, so move word + if selection + # Cut it + txt = View.delete(*View.range) + + # Strip selection + txt.strip! + else + # Selection, so move selected + + # Cut the word + txt = View.delete(*$el.bounds_of_thing_at_point(:word)) + + end + + # Delete char we're on if it's a space + + cursor = View.cursor + + + # Delete space if before or after the cursor + if View.txt(cursor-1, cursor) =~ /^[ ]$/ # =~ /^[ _-]$/ + deleted = View.delete(cursor-1, cursor) + elsif View.txt(cursor, cursor+1) =~ /^[ ]$/ # =~ /^[ _-]$/ + deleted = View.delete(cursor, cursor+1) + end + + + $el.forward_word many + + if direction == :backward + left = View.cursor + View.insert txt + View.insert(" ", :dont_move=>1) + else + txt View.insert, :dont_move=>1 + View.insert " " + left = View.cursor + end + right = left + txt.length + + if selection + View.selection = [left, right] + end - Line.previous - View.column = column end + def self.sub! from, to orig = Location.new value = Line.value @@ -329,25 +431,24 @@ def self.< txt View.insert txt end - # # Add slash to end of line, and optionally a string as well # # Line.add_slash # Line.add_slash "hi" - # def self.add_slash options={} line = Line.value + orig = View.cursor + line =~ /\/$/ || line.blank? ? Move.to_end : Line << "/" txt = options.is_a?(String) ? options : options[:txt] - orig = View.cursor Line << txt if txt - View.cursor = orig if options[:left] + View.cursor = orig if options[:no_move] nil end @@ -360,6 +461,12 @@ def self.do_lines_sort $el.elvar.sort_fold_case = true $el.sort_lines(nil, $el.region_beginning, $el.region_end) $el.elvar.sort_fold_case = old + + return if $el.region_beginning != View.cursor # Can't delete blanks if not at beginning + + # Delete any blank lines (they'd be sorted to the top) + Deletes.forward while View.char == "\n" + end def self.do_lines_toggle @@ -388,16 +495,23 @@ def self.init return if ! $el # Do nothing if not running under el4r - # Define lisp function to get list of displayed lines - # In case something has been done to change them - $el.el4r_lisp_eval %q` - (defun xiki-line-number (pos) - (save-excursion - (goto-char pos) - (forward-line 0) - (1+ (count-lines 1 (point)))) - ) - ` + # This seems to do the same this as line-number-at-pos (pos). + # I think maybe it deals with hidden overlays better, but + # we don't really use those any more. + # + # Is it any faster when near bottom of large files? + # + # Old comment: + # # # Define lisp function to get list of displayed lines + # # # In case something has been done to change them + # $el.el4r_lisp_eval %q` + # (defun xiki-line-number (pos) + # (save-excursion + # (goto-char pos) + # (forward-line 0) + # (1+ (count-lines 1 (point)))) + # ) + # ` end def self.enter_docs @@ -431,7 +545,94 @@ def self.enter_docs end Line.add_slash :txt=>"docs/", :unless_blank=>1 - Launcher.launch_unified + Launcher.launch + end + + def self.length + self.value.length + end + + def self.duplicate + + # If a file path, up+ will duplicate actual file, with a .1 suffix. + # Inserts a duplicate of the line underneath. + + prefix = Keys.prefix :clear=>1 + + # If in file tree and up+, actually duplicate file... + + if prefix == :u && FileTree.handles? + Location.as_spot + source = Tree.construct_path + dest = Files.unique_name source + + command = "cp -r \"#{source}\" \"#{dest}\"" + + Shell.run command, :sync=>true + + column = View.column + Line << "\n#{Line[/[ +-]+/]}#{File.basename dest}" + View.column = column + + return + end + + column = View.column + + line = "#{Line.value}\n" + Line.to_left + + if prefix == :u + if line =~ /^ *:([+ -])/ # Toggle between :+... and :-... + char = $1 + # Or, between :... and :+... + line.sub!(/:([+ -])/){ ":#{$1 == "-" ? "+" : "-"}" } + else # Or just comment or uncomment + Code.comment(:line) + end + end + + times = if prefix.nil? + 1 + elsif prefix == :u + 1 # Put commented line after + elsif prefix == 0 + 0 + elsif prefix > 0 + prefix + 1 + elsif prefix < 0 + prefix + end + + Line.next times if ! char # If was : ... + View.insert line + Line.previous + + Line.next if char + View.column = column + + end + + def self.insert_date + prefix = Keys.prefix :clear=>1 + + return View.<<(Time.now.strftime("%Y-%m-%d %I:%M:%S%p").sub(' 0', ' ').downcase) if prefix == :u + return View.<<(Time.now.strftime("%I:%M:%S%p").sub(/^0/, '').downcase) if prefix == :- + + View.<<(Time.now.strftime("%Y-%m-%d")) + end + + def self.insert_time + prefix = Keys.prefix :clear=>1 + + # up+, so insert time and date... + + return View.<<(Time.now.strftime("%Y-%m-%d %I:%M:%S%p").sub(' 0', ' ').downcase) if prefix == :u + + # No prefix, so insert just time... + + View.<<(Time.now.strftime("%I:%M:%S%p").sub(/^0/, '').downcase) + end end diff --git a/lib/xiki/core/links.rb b/lib/xiki/core/links.rb index 14768a03..94e3688f 100644 --- a/lib/xiki/core/links.rb +++ b/lib/xiki/core/links.rb @@ -4,5 +4,20 @@ def self.open_first url = View.txt[/http:\/\/.+/] $el.browse_url url end + + def self.to_nth_file n + items = Xiki["files/", :go=>1].split("\n") + item = items[n-1].sub(/^\+ /, '') + Xiki["files", [item]] + end + + def self.show_in_nav target_file, target_quote + View.open "%links" + View.to_top + Search.forward $el.regexp_quote(target_file) + if target_quote + Search.forward $el.regexp_quote(target_quote), :beginning=>1 + end + end end end diff --git a/lib/xiki/core/location.rb b/lib/xiki/core/location.rb index 3535c10f..5397696b 100644 --- a/lib/xiki/core/location.rb +++ b/lib/xiki/core/location.rb @@ -41,7 +41,7 @@ def initialize *args @file = $el.buffer_file_name @buffer = View.name @line = Line.number - @column = $el.point - $el.point_at_bol + @column = ($el.point - $el.point_at_bol) + 1 # Get buffer if no file @buffer = $el.buffer_name unless @file @@ -53,30 +53,28 @@ def initialize *args # If nothing passed, prompt user. If string assume it's a path. # If symbol, assume it's a bookmark. def self.go path=nil, options={} + # If no param passed, get key from user and go to corresponding location if path.nil? - loc = Keys.input(:optional => true) + loc = Keys.input(:optional=>true) loc ||= "0" loc = "_#{loc}" - View.open("$#{loc}") + View.open(":#{loc}") # Optionally go to point $el.bookmark_jump(loc) unless $el.elvar.current_prefix_arg + return # If symbol, look up location in map and go to it elsif path.class == Symbol - # @@hash[path.to_s].go - View.open("$#{path.to_s}") - $el.bookmark_jump(path.to_s) + View.open(":#{path.to_s}") + @@spots[path.to_s].go + return # If string, look up location in map and go to it elsif path.class == String and path[/^\$./] - View.open(path, :goto_point => true) - - # Jump to specific boomkark point - redundant?? - # Is this even a bookmark? - $el.bookmark_jump(path.sub(/^\$/, "")) + View.open(path, :goto_point=>true) return end @@ -87,6 +85,7 @@ def self.go path=nil, options={} # Goes to location, with whatever information we have. Note that if # file is already shown, we just move to its window. def go options={} + if ! options[:assume_file] if @file View.open(@file, options) @@ -101,8 +100,8 @@ def go options={} return unless @column # If enough space, just move to column - if $el.point + @column <= $el.point_at_eol - $el.forward_char @column + if $el.point + (@column-1) <= $el.point_at_eol + $el.forward_char (@column-1) # Otherwise, move to end else $el.end_of_line @@ -113,64 +112,46 @@ def go options={} # Saves a generic location based on user input def self.save name=nil + # Use string if user types it quickly (or arg, if passed) name ||= Keys.input(:prompt => "Save this spot as (pause when done): ", :optional => true) name ||= "0" - #path = path.to_s unless path.class == String name = "_#{name}" # Remove beginning $ (it might have been passed in with the arg) name.sub!(/^\$/, "") # Save location in corresponding register - # @@hash[name] = Location.new - $el.bookmark_set(name) + @@spots[name] = Location.new end - def self.jump path=nil - path ||= "0" - path = "_#{path}" - #path = path.to_s unless path.class == String - View.open("$#{path}") - # Optionally go to point - $el.bookmark_jump(path) #unless $el.elvar.current_prefix_arg + def self.as_spot key='0' + + # Remember window (in case buffer in 2 windows) + @@spot_index = View.index if key == '0' + + @@spots[key] = Location.new + end - # Enter selected text at spot - def self.enter_at_spot + def self.hop_remembered + txt = View.selection - txt = FileTree.snippet if Keys.prefix_u? - Location.jump("0") - View.set_mark - $el.insert txt - end + self.to_spot - def self.as_spot key='0' + # They'd selected nothing, so hopping is all we needed to do... - # Remember window (in case buffer in 2 windows) - @@spot_index = View.index if key == '0' + return if ! txt - if View.file # If buffer has a file, just save in location - Location.save(key) - return @@spots[key] = nil - end + # They'd selected something, so put it here... - # Must be a buffer - @@spots[key] = [$el.buffer_name(View.buffer), $el.point] + View.insert txt, :dont_move=>1 end - def self.to_spot key='0' - if @@spots[key] - View.to_buffer @@spots[key][0] - View.to @@spots[key][1] - else # If file, just jump - # If original window/buffer still there, go back - if key == '0' - if $el.buffer_file_name($el.window_buffer(View.nth(@@spot_index))) == Bookmarks["$_0"] - View.to_nth @@spot_index - end - end - Location.jump(key) - end + def self.to_spot key='0', options={} + + loc = @@spots[key] + return if ! loc + loc.go end end diff --git a/lib/xiki/core/macros.rb b/lib/xiki/core/macros.rb index 5eb87279..9e7f7f93 100644 --- a/lib/xiki/core/macros.rb +++ b/lib/xiki/core/macros.rb @@ -45,6 +45,11 @@ def self.run # Run it prefix times $el.call_last_kbd_macro $el.elvar.current_prefix_arg || 1 + + # Make C-. run macro again (macros might have changed this) + + Keys.remember_key_for_repeat(proc {Macros.run}) + end end end diff --git a/lib/xiki/core/menu.rb b/lib/xiki/core/menu.rb index 815d9e45..7ef6161f 100644 --- a/lib/xiki/core/menu.rb +++ b/lib/xiki/core/menu.rb @@ -1,1175 +1,17 @@ module Xiki module Menu - def self.menu - ' - - history/ - - @log/ - - @last/ - - @all/ - - .create/ - - here/ - - class/ - - .install/ - - gem/ - - .setup/ - - @~/menu/ - - @$x/menu/ - - .reload_menus/ - - .api/ - > Summary - | How to use ruby code to define menus. - | - | You can create sophisticated menus backed by classes, or by using other - | simple means: - - .classes/ - - .simple class/ - - .menu with method/ - - .menu with two methods/ - - other/ - - With a string/ - | - | Menu.fish :menu=>"- salmon/\n- tuna/\n - yellow fin/" - | - Try it out by typing 1 do_ruby (C-1 Ctrl-d Ctrl-r) while on it, then - double-clicking on this menu to see what happens: - | - @fish/ - | - - Delegating to an existing menu/ - | - | Menu.critters :menu=>"foo/animals" - | - @critters/ - | - - Using a block/ - | - | Menu.foo do - | "hey/" - | end - | - The block can optionally take a |path| param to handle multiple levels - of nesting. - | - | Menu.foo do |path| - | "hey/#{path}" - | end - | - - Extract menu text from somewhere/ - | Tree.children just expects text that is in the form of a menu (lines with - | 2-space indenting for nesting). So, the text can be pulled from - | anywhere, such as a part of a larger file: - | - | Menu.lawn do |path| - | menu = Notes.read_block("/tmp/garage.notes", "> Lawn") - | Tree.children menu, Tree.rootless(path) - | end - | - | - | If you want to create a very simple menu you can do so without code, - | by just putting the menu in a file such as ~/menu/foo.menu. See: - | - << docs/creating/ - - .docs/ - - .using/ - - .creating/ - ' - end - - def self.install *args - Xiki.dont_search - ' - > TODO - | implement this. - - - Look in gem dirs for installed gems with this name - - Google search for xiki menu on web - @google/"foo.menu" - ' - end - - def self.simple_class *args - root = 'foo' - trunk = Xiki.trunk - root = TextUtil.snake_case(trunk[-2][/^[\w -]+/]) if trunk.length > 1 # If nested path (due to @), grab root of parent - - %` - - @~/menu/ - - #{root}.rb - | class #{TextUtil.camel_case(root)} - | def self.menu *args - | "- sample item/\\n- Args passed: \#{args.inspect}\\n- Customize me in) @~/menu/#{menu}.rb" - | end - | end - ` - end - - def self.menu_with_method *args - root = 'foo' - trunk = Xiki.trunk - root = TextUtil.snake_case(trunk[-2][/^[\w -]+/]) if trunk.length > 1 # If nested path (due to @), grab root of parent - - %` - - @~/menu/ - - #{root}.rb - | class #{TextUtil.camel_case(root)} - | def self.menu - | " - | - cake/ - | - chocolate/ - | - .pie/ - | " - | end - | - | def self.pie - | "- apple/" - | end - | end - ` - end - - def self.menu_with_two_methods *args - root = 'foo' - trunk = Xiki.trunk - root = TextUtil.snake_case(trunk[-2][/^[\w -]+/]) if trunk.length > 1 # If nested path (due to @), grab root of parent - - %` - - @~/menu/ - - #{root}.rb - | class #{TextUtil.camel_case(root)} - | def self.menu - | " - | - sammiches/ - | - ham/ - | - .buy/ - | - tofu/ - | - .buy/ - | - .checkout/ - | - cash/ - | - credit/ - | " - | end - | def self.buy category, item - | "- buying \#{item} \#{category}" - | end - | def self.checkout kind - | "- checking out as \#{kind}..." - | end - | end - | - ` - end - - def self.using *args - txt = %` - > Summary - | How to use Xiki menus. Xiki menus are menus you type, not menu bar. - | - | All xiki menus can be used the same way. Just type something and - | double-click on it (or type Ctrl-enter while the cursor is on the line) - | to open the menu. - | - - example/ - | 1: Type "sammiches" on a line (the "@" isn't necessary when the line - | isn't indented) - @sammiches - | - | 2: Double-click (or Ctrl-enter) on it to open it. You can try it on - | the line above. It will look like this: - | @sammiches/ - | + meat/ - | + veggie/ - | + checkout/ - | - | 3: Double-click to open items. It will look like this: - | @sammiches/ - | + meat/ - | + veggie/ - | + cucumber/ - | + bark/ - | + checkout/ - | - - mouse/ - | You can click on the "bullets" (the - and + at the beginnings of lines) - | to expand and collapse. You can also double-click to expand and - | collapse. - | - - keyboard/ - | You can do everything with the keyboard that you can do with the mouse. - | Type Ctrl-enter while your cursor is on the same line as a menu or menu - | item to open it. Or, if it already has things under it, it will collapse. - | - | Right after you open a menu, some keys you type have special meaning. This - | is the case whether you you used the mouse or keyboard to open the menu. - | The cursor turns into a block to indicate this. - | - | Special keys right after opening: - - search to narrow down/ - | If you type numbers and letters, it incrementally narrows down the list. - - opening/ - | Return - opens menu item cursor is on - | Tab - opens, hiding the other items at that level - | Ctrl-/ - opens, moving the item onto the same line as its parent - | Ctrl-1, Ctrl-2, Ctr-3, etc. - opens the nth item - | Ctrl-G: stops searching - | - ` - - Tree.children txt, args - end - - def self.creating *args - txt = %q` - > Summary - | How to make your own Xiki menus. This is strongly encouraged! Make - | a bunch of menus for all the various things you do, if you're so - | inclined. You can make menus for from simple notes, to powerful - | CRUD interfaces etc. for controlling external tools. - | - - Pretend they exist/ - | The easiest way to create new menus is to type them and open them as - | though they exist. Xiki will then guide you through turning them - | into actual menus. - | - | The simplest option is the first one it shows you. You can just type - | menu items inline to create the menu. For example, you could type - | this: - | - | milkshakes/ - | - chocolate/ - | - vanilla/ - | - | And then with your cursor on one of these lines you could type - | Ctrl-a Ctrl-m (for "as menu") to turn it into an actual menu. Then, - | the next time you type "milkshakes" and open it, it would show the - | menu items. For this menu to be useful, you'll probably want to add - | more items underneath them, or open the items to be prompted to - | create a ruby class to run when they're opened in the future. - | - - Screencasts/ - | Check out these two points in one of the xiki screencast to see - | creating menus in action: - | - @http://www.youtube.com/watch?v=bUR_eUVcABg#t=1m30s - @http://www.youtube.com/watch?v=bUR_eUVcABg#t=1m57s - | - - Creating .menu files/ - | You can make menus without code, by just putting "whatever.menu" files - | in the "menu" dir in your home dir. - | - | For example you could create a "drinks.menu" file, like the following. - | (The "|" characters aren't actually in the file). - | - | ~/menu/ - | - drinks.menu - | | - fruit/ - | | - lemonade/ - | | - smoothie/ - | | - caffeinated/ - | | - coffee/ - | | - tea/ - | - | Then when you type drinks on a line and open it, it will look like - | this: - | - | drinks/ - | + fruit/ - | + caffeinated/ - | - - Delegating/ - | You can make simple menus that delegate to other things, using the - | "@" character. For example: - | - | ~/menu/ - | - foo.menu - | | - @other menu/ - | | - @MyClass.my_method - | | @$ shell command - | - - Wiki elements/ - | You can put wiki elements in menus, like headings, paragraphs and - | bullet points. Thus you can make a menu just to store notes: - | - | shopping list/ - | > Grocery - | - Eggs - | - Vodka - | - | > Pet store - | Not sure yet. Maybe just a bunch of snakes. - | - - Creating .rb files/ - | Create ruby files in ~/menu/ to make dynamic menus. The .menu class - | method will be invoked. Example: - | - | ~/menu/ - | - pie.rb - | | class Pie - | | def self.menu *args - | | " - | | - fruit/ - | | - apple/ - | | - pumpkin/ - | | - nuts/ - | | " - | | end - | | end - | - | To make a menu run a method, put a dot in front of it: - | - | ~/menu/ - | - pie.rb - | | class Pie - | | def self.menu *args - | | " - | | - fruit/ - | | - apple/ - | | - pumpkin/ - | | - .nuts/ - | | " - | | end - | | def self.nuts *args - | | # Put any code here. The string you return will be inserted - | | # into the tree. - | | "- pecan/\n- pecan/" - | | end - | | end - | - | - > For more info, see: - @menu/api/ - | - ` - - Tree.children(txt, args.join('/')) - end - - def self.reload_menus - Launcher.reload_menu_dirs - View.flash - nil - end - - # Eval the menu path, and return the output (delegates to .call) - # Examples: - # @menu/api/other/With a string/ - def self.[] path - path, rest = path.split '/', 2 - - self.call path, rest - end - - def self.call root, rest=nil - root = root.gsub /[ +-]/, '_' - menus = Launcher.menus - block = menus[0][root] || menus[1][root] - return if block.nil? - Tree.output_and_search block, :line=>"#{root}/#{rest}", :just_return=>1 - end - - # TODO: After Unified, don't use this any more for defining methods. - def self.method_missing *args, &block - Launcher.method_missing *args, &block - "- defined!" - end - - # Deprecated after Unified refactor. - # Superceded by Path.split - # - # Menu.split("a/b").should == ["a", "b"] - # Menu.split("aa/|b/b/|c/c").should == ["aa", "|b/b", "|c/c"] - # Menu.split("aa/|b/b/|c/c", :return_path=>1) - # Menu.split("a/b/@c/d/", :outer=>1).should == ["a/b/", "c/d/"] - def self.split path, options={} - - # :outer means split based on @'s... - - if options[:outer] - - # If /|... quote, pull it off during split, so quoted "@" won't mean ancestor - # Deal with quoted | a/@b - split off \| before and add back to last after!"] - quoted = nil - if found = path =~ /\/\|/ - path = path.dup - quoted = path.slice!(found..-1) - end - - path = path.split /\/@ ?/ - (path.length-1).times {|i| path[i].sub! /$/, '/'} # Add slashes back - - path[-1] << quoted if quoted - - return path - end - - # Split based on /... - - path = path.sub /\/$/, '' - path = Tree.rootless path if options[:return_path] - - return [] if path.empty? - - groups = path.split '/|', -1 - - result = groups[0] =~ /^\|/ ? - [groups[0]] : - groups[0].split('/', -1) - - result += groups[1..-1].map{|o| "|#{o}"} - end - - # Probably make this supercede to_menu. - # Note this works for patterns as well as menus. - def self.to_menu - - return self.to_menu_old if Keys.prefix_u - - item = Line.without_label - item = Path.split(item)[-1] # Grab last item (in case multiple items on the same line separated by slashes) - - path = Tree.path_unified - options = Expander.expanders path - - source = Tree.source options - - return View.flash "- no source found!" if ! source - - # If was a string, show tree in new view... - - if source.is_a?(String) - Launcher.open(source, :no_launch=>1) - Launcher.enter_all # Show dir recursively, or file contents in tree - return - end - - # Must be [file, line_number], so open and jump to line... - - file, line_number = source - - View.open file - return View.line = line_number if line_number - - # Try to find string we were on when key was pressed - if item - orig = View.cursor - View.to_highest - item = Search.quote_elisp_regex item - item.sub! /^(- |\\\+ )/, "[+-] \\.?" # match if + instead of -, or dot after bullet - found = Search.forward item - Move.to_axis - View.cursor = orig if ! found - end - end - - def self.to_menu_old - - line = Line.without_label - - # Get root... - - # Take best guess, by looking through dirs for root - trunk = Xiki.path - return View.<<("- You weren't on a menu\n | To jump to a menu's implementation, put your cursor on it\n | (or type it on a blank line) and then do as+menu (ctrl-a ctrl-m)\n | Or, look in one of these dirs:\n - ~/menu/\n - $xiki/menu/") if trunk[-1].blank? - root = trunk[0][/^[\w _-]+/] - - # Go to ancestor menu, or leaf menu?... - - # Go to leaf menu if cursor is on it, otherwise go to root - root = trunk[-1][/^[\w _-]+/] if line =~ /^@/ - - root.gsub!(/[ -]/, '_') if root - root.downcase! - - # Try looking in tools dir (formerly menu3)!... - - dir = "#{Xiki.dir}lib/xiki/tools" - - file = Dir["#{dir}/#{root}.*"] - if file.any? - View.open file[0] - - if line # Found, so try to find line we were on - cursor = View.cursor - View.to_highest - line = Search.quote_elisp_regex line - line.sub! /^(- |\\\+ )/, "[+-] \\.?" # match if + instead of -, or dot after bullet - found = Search.forward line - Line.to_beginning - View.cursor = cursor if ! found - end - return - end - - # Try dynamically-loaded procs!... - - # message = " - # - No menu found: - # | No \"#{root}\" menu or class file found in these dirs: - # @ ~/menu/ - # @ $x/menu/ - # ".unindent - - # Should be able to get it right from proc - - proc = Launcher.menus[1][root] - - return View.flash "- Menu 'root' doesn't exist!", :times=>4 if ! proc - - location = proc.source_location # ["./firefox.rb", 739] - - # location[0].sub! /^\.\//, Xiki.dir - View.open location[0].sub(/^\.\//, Xiki.dir) - View.line = location[1] - - end - - - # Deprecated? - def self.external menu, options={} - - View.message "" - - View.wrap :off - - # IF nothing passed, must want to do tiny search box - if menu.empty? - Launcher.open "" - View.message "" - View.prompt "Type anything", :timed=>1, :times=>2 #, :color=>:rainbow - - Launcher.launch_unified - else - Launcher.open menu, options - end - end - - def self.as_menu - orig = View.cursor - - Tree.to_root - - root, left = Line.value, View.cursor - root = Line.without_label :line=>root - - root = TextUtil.snake_case(root).sub(/^_+/, '') - - Tree.after_children - right = View.cursor - View.cursor = left - - # Go until end of paragraph (simple for now) - Effects.blink :left=>left, :right=>right - txt = View.txt left, right - txt.sub! /.+\n/, '' - txt.gsub! /^ /, '' - - # Remove help text that prompts you to create the menu with the items (if exists) - txt.sub! /^ +> Make this into a menu\?\n +\| Create the .+\n.+\n/, '' - # Remove help text that prompts you to update menus - txt.sub! /^ +> Update this menu\?\n +\| Save changes.+\n.+\n.+\n/, '' - - return Tree << "| You must supply something to put under the '#{root}' menu.\n| First, add some lines here, such as these:\n- line/\n- another line/\n" if txt.empty? - - menu_dir = File.expand_path "~/menu" - path = File.expand_path "#{menu_dir}/#{root}.menu" - - file_existed = File.exists? path - - if file_existed - treeb = File.read path - txt = Tree.restore txt, treeb - - DiffLog.save_diffs :patha=>path, :textb=>txt - end - - txt = txt.unindent - - Dir.mkdir menu_dir if ! File.exists? menu_dir - File.open(path, "w") { |f| f << txt } - - View.cursor = orig if orig - - View.flash "- #{file_existed ? 'Updated' : 'Created'} ~/menu/#{root}.menu", :times=>3 - nil - end - - @@loaded_already = {} - - def self.load_if_changed file - return :not_found if ! File.exists?(file) - previous = @@loaded_already[file] - recent = File.mtime(file) - - if previous == nil - load file - @@loaded_already[file] = recent - return - end - - return if recent <= previous - - load file - @@loaded_already[file] = recent - end - - - # Collapse tree one level. Assumes line has arrows - def self.collapser_launcher - line = Line.value - arrows = line[/<+/].length - arrows -= 1 if arrows > 1 # Make "<<" go back just 1, etc. - - line = Line.without_label :line=>line - - skip = line.empty? && arrows - 1 - - Line.sub! /^( +)<+ .+/, "\\1- " # Delete after bullet to prepare for loop - - arrows.times do |i| - - # If no items left on current line, jump to parent and delete - if Line =~ /^[ +-]+$/ - Tree.to_parent - Tree.kill_under - Move.to_end - end - - unless i == skip # Remove last item, or after bullet if no items - Line.sub!(/\/[^\/]+\/$/, '/') || Line.sub!(/^([ @+-]*).*/, "\\1") - end - end - - if Line.indent.blank? - line.sub! /^@ ?/, '' - Line.sub! /^@ ?/, '' - end - - Line << line unless skip - Launcher.launch_unified - end - - def self.root_collapser_launcher - - View.cursor - - - # Grab line - line = Line.value - - arrows = line[/<+/].length - - line.sub!(/ *<+@ /, '') - - # Go up to root, and kill under - arrows.times { Tree.to_root } - Tree.kill_under - - # Insert line, and launch - old = Line.delete :leave_linebreak - old.sub! /^( *).+/, "\\1" - old << "@" if old =~ /^ / # If any indent, @ is needed - View << "#{old}#{line}" - - Launcher.launch_unified - end - - def self.replacer_launcher - Line.sub! /^( +)<+= /, "\\1+ " - - # Run in place, grab output, then move higher and show output - - orig = View.line - Launcher.launch_unified :no_search=>1 - - # If didn't move line, assume it had no output, and it's collapse things itself - return if orig == View.line - - # If it inserted something - # output = Tree.siblings :everything=>1 - output = Tree.siblings :cross_blank_lines=>1, :everything=>1 - - # Shouldn't this be looping like self.collapser_launcher ? - Tree.to_parent - Tree.to_parent - Tree.kill_under :no_plus=>1 - Tree << output - end - - def self.menu_to_hash txt - txt = File.read txt if txt =~ /\A\/.+\z/ # If 1 line and starts with slash, read file - - txt.gsub(/^\| /, '').split("\n").inject({}) do |o, txt| - txt = txt.split(/ : /) - o[txt[0]] = txt[1] - o - end - - end - - # def self.config txt, *args - - # # TODO: implement - # # Args look like sample invocation below - # # If not there, create it first, using supplied default - # # Insert quoted file contents to be edited - - # # Sample invocation - # # Menu.config " - # # - @ ~/xiki_config/browser.notes - # # | - default browser: - # # | - Firefox - # # | - others: - # # | - Safari - # # | - Chrome - # # ", *args - - # "TODO" - # end - - # Moves item to root of tree (replacing tree), then launches (if appropriate). - def self.do_as_menu - prefix = Keys.prefix :clear=>1 # Check for numeric prefix - launch = false - line = Line.value - - - txt = - if line =~ /^ +\| +def (self\.)?(\w+)/ # If on a quoted method, construct Foo.bar - Ruby.quote_to_method_invocation - elsif line =~ /^ *\|/ # If on quoted line, will grab all quoted siblings and unquote - Tree.siblings :string=>1 - elsif line =~ /^[ +-]*@/ && Tree.has_child? # If on ^@... line and there's child on next line... - # Will grab the whole tree and move it up - Tree.subtree.unindent.sub(/^[ @+-]+/, '') - else - launch = true - Tree.path.last - end - - Tree.to_root(:highest=>1) - - # Keys.prefix_u ? Tree.to_root : Tree.to_root(:highest=>1) - launch = false if prefix == :u - - Tree.kill_under - - Line.sub! /^([ @]*).+/, "\\1#{txt}" - - return if ! launch - - Launcher.launch_unified - end - - - # The following 3 methods are for the menu bar - # - a different use of the "Menu" class - # TODO move them into menu_bar.rb ? - - def self.add_menubar_menu *name - menu_spaces = name.join(' ').downcase - menu_dashes = name.join('-').downcase - name = name[-1] - - lisp = %Q< - (define-key global-map - [menu-bar #{menu_spaces}] - (cons "#{name}" (make-sparse-keymap "#{menu_dashes}"))) - > - $el.el4r_lisp_eval lisp - - menu = $el.elvar.menu_bar_final_items.to_a - $el.elvar.menu_bar_final_items = menu.push(name.downcase.to_sym) - end - - def self.add_menubar_item menu, name, function - - menu_spaces = menu.join(' ').downcase - lisp = " - (define-key global-map - [menu-bar #{menu_spaces} #{function}] - '(\"#{name}\" . #{function})) - " - $el.el4r_lisp_eval lisp - end - - ROOT_MENU = 'Keys' - def self.init return if ! $el Mode.define(:menu, ".menu") do - Notes.mode + Xiki::Notes.mode end - - add_menubar_menu ROOT_MENU - - menus = [ - [ROOT_MENU, 'To'], - [ROOT_MENU, 'Open'], - [ROOT_MENU, 'Layout'], - [ROOT_MENU, 'As'], - [ROOT_MENU, 'Enter'], - [ROOT_MENU, 'Do'], - [ROOT_MENU, 'Search'] - ] - menus.reverse.each do |tuple| - add_menubar_menu tuple[0], tuple[1] + Mode.define(:xiki, ".xiki") do + Xiki::Notes.mode end - end - - # - # Whether line exists in menu - # - # p Menu.line_exists? "menu name", /^- text to add$/ - # p Menu.line_exists? "menu name", /^- text to (.+)$/ - # - def self.line_exists? name, pattern #, options={} - name.gsub! /\W/, '_' - dir = File.expand_path "~/menu3" - file = File.expand_path "#{dir}/#{name}.menu" - txt = File.read(file) rescue "" - txt =~ pattern ? ($1 || $&) : nil # Return whole string or group - end - - # - # Create simple .menu file if it doesn't exist, otherwise add line to it. - # - # Menu.append_line "menu name", "- text to add" - # - def self.append_line name, addition #, options={} - - name.gsub! /\W/, '_' - - # Default to ~/menu - # If menu there, create, otherwise append - - # Get existing - dir = File.expand_path "~/menu" - Dir.mkdir dir if ! File.exists? dir - - file = File.expand_path "#{dir}/#{name}.menu" - txt = File.read(file) rescue "" - - if txt =~ /^#{Regexp.escape addition}$/ - return ".flash - was already there!" - end - - # Append to end (might be blank) - - txt << "#{addition}\n" - - # Save - File.open(file, "w") { |f| f << txt } - - "- saved setting!" - end - - - - - - - # Unified refactor from here on down... - # TODO: Probably extract from here on down into MenuExpander! - - - - - - # Examples: "ip"=>, "tables"=>"/etc/menus/tables" - @@defs ||= {} - - # def self.menu - # " - # > TODO: add docs here - # - todo - # > See - # << menu path/ - # << defs/ - # " - # end - - # Adds to :expanders if :name is backed by a menu. - def self.expands? options - - # If no name, maybe an inline menufied path, so set sources... - - if ! options[:name] - if options[:menufied] - self.root_sources_from_dir options - (options[:expanders] ||= []).push self - return - end - return # Can't handle if wasn't inline and no :name - end - - # See if name is directly defined... - - if implementation = @@defs[options[:name]] - if implementation.is_a? String - implementation = Bookmarks[implementation] - # If file, remove any extension - if File.file? implementation - implementation.sub! /\.\w+$/, '' - end - - options[:menufied] = implementation - - self.root_sources_from_dir options - return (options[:expanders] ||= []).push self - end - - kind = - if implementation.is_a? Proc - :proc - elsif implementation.is_a? Class - :class - end - raise "Don't know how to deal with: #{implementation.inspect}" if ! kind - - options[kind] = implementation - return (options[:expanders] ||= []).push self - end - - # Try to look name up in PATH env... - - (options[:expanders] ||= []).push(Menu) if self.root_sources_from_path_env(options)[:sources] # Found it if we created :sources - - - # - # > Discussion of future caching - # TODO: cache output of .root_sources_from_path_env outputs whet it starts to take up time - # - clear cache when updated by guard - think through guard strategy - probably gurad just builds one big file upon updates, and xiki checks only that file's mod date, and reloads (if Xiki.caching = :optimized - # caching - punt for now - # - if :all, store hash with output of .root_sources_from_path_env for each name - # - if :optimized, check date of /tmp/xiki_path_env_dir_cache - # - if :off (maybe rails dev mode - not worth setting up guard) - # - - nil # We couldn't find anything, so continue onto patterns - - end - - # Expand thing (represented by the hash) like a menu. Could be a block, or - # menufied (has sources). - def self.expand options - - # Get simple invocations out of the way (block or class)... - - # If it's a proc, just call - return options[:output] = options[:proc].call(options[:items] || [], options) if options[:proc] - - # If it's a class, just call (or, wait, need to include .menu file - probably no) - raise "TODO: implement dealing with a class" if options[:class] - - if ! options[:menufied] # Assume Menu.expands? should have pulled this out for now - raise "Can't do anything if no menufied and no name: #{options.inspect}" if ! options[:name] - - implementation = @@defs[options[:name]] - - raise "Can't do anything if no implentation: #{options.inspect}" if ! implentation - raise "Don't know how to expand #{implementation}." if ! implementation.is_a? String - end - - - # It's a string... - - # Must be either - # menufied path? (starts with slash) - # menu name - # either: could have items - - return self.expand_menufied options - end - - - # Does actual invocation. Finds all sources and delegates to handlers. - def self.expand_menufied options - - # Probably do caching here, when we get to that point - - # Update :sources to have rest of sources from :containing_dir - self.climb_sources options - - # Delegates to handlers according to source extensions - self.handle options - end - - - # Climbs down menu source dir according to path, to find source files - # eligible to handle the path. - # - # Menu.climb_sources(:menufied=>"/tmp/foo/a/b", :items=>["a"]) - # => sources: [["foo/", "foo.rb"], ["a/", "a.rb"]] - def self.climb_sources options - - path, items, menufied, sources = options[:path], options[:items], options[:menufied], options[:sources] - - sources.pop # Remove :incomplete, since we're going to grab them all for this path - - # For each item... - - climbed_path = "#{menufied}" - (items||[]).each do |item| - - break if sources[-1][0] !~ /\/$/ # If last source climbed doesn't have a dir - - # If there is a dir for it, grab files and dir that match - - climbed_path << "/#{item}" - found = self.source_glob climbed_path - - break if ! found # Stop if none found - sources << found - end - - options[:last_source_dir] = Menu.source_path options - - # Create :args, having :items that weren't sources - if items - args = items[sources.length-1..-1] - options[:args] = args if args.any? - end - options - end - - @@handlers = nil - @@handlers_order = nil - def self.handlers - @@handlers ||= { - "*"=>[ConfLoadingHandler], # This should always run - "conf"=>ConfHandler, # This should always run - "rb"=>RubyHandler, - "menu"=>MenuHandler, - "notes"=>NotesHandler, - "html"=>HtmlHandler, - "markdown"=>MarkdownHandler, - "bootstrap"=>BootstrapHandler, - "txt"=>TxtHandler, - "py"=>PythonHandler, - "js"=>JavascriptHandler, - "coffee"=>CoffeeHandler, - "/"=>DirHandler, - } - end - - def self.handlers_order - @@handlers_order ||= self.handlers.inject({}){|hash, kv| hash[kv[0]] = hash.length; hash} - end - - # Go through :sources and call appropriate handler for each - def self.handle options - sources = options[:sources][-1] - - raise "no sources?" if ! sources - - # Make 'ex' map from extensions to source files - - options[:ex] = ex = {} # {"/"=>"a/", "rb"=>"a.rb"} - sources.each_with_index do |source, i| - options[:source_index] = i - key = source[/\w+$|\/$/] - ex[key] = source if key - end - - # TODO: Optimize this - use hash lookup for each extension - # Wait until we figure out final-ish way to register handlers - # Somehow sort keys based on below order - - self.handlers_order # Ensure @@ vars are set - - # Always run "*" handlers - @@handlers["*"].each do |handler| - handler.handle options - end - - extensions = ex.keys.sort{|a, b| (@@handlers_order[a]||100000) <=> (@@handlers_order[b]||100000)} # Sort by order in hash (it has the correct priority) - - extensions.each do |o| - handler = @@handlers[o] - next if ! handler - handler.handle options - end - - return options[:output] if options[:output] || options[:halt] - - if options[:client] =~ /^editor\b/ && sources.find{|o| o =~ /\.menu$/} - options[:output] = " - > Update this menu? - | Save changes you made to this menu? Alternately you can type - | as+menu as a shortcut (meaning type Ctrl+a Ctrl+m). - @as menu/ - " - options[:no_slash] = 1 - end - - nil - end - - def self.defs - @@defs - end - - def self.root_sources_from_dir options - found = self.source_glob options[:menufied] - - raise "Couldn't find source for: #{options}" if ! found - - options[:sources] = [found, :incomplete] - end - - - # Finds the first dir in MENU_PATH that has this menu. - # - # Menu.root_sources_from_path_env(:name=>"dd")[:sources] - # Menu.root_sources_from_path_env(:name=>"drdr")[:sources] # multi-level - # Menu.root_sources_from_path_env(:name=>"red")[:sources] # none - def self.root_sources_from_path_env options - - name = options[:name] - - # For each dir in path env... - - Xiki.menu_path_dirs.each do |dir| - # Grab sources if they exist... - - menufied = "#{dir}/#{name}" - found = self.source_glob menufied - - next if ! found - - options[:sources] = [found, :incomplete] - options[:menufied] = menufied - - return options - - end - options - end - - def self.source_glob dir - list = Dir.glob ["#{dir}/", "#{dir}.*", "#{dir}/index.*"] - return nil if list.empty? - - containing_dir_length = dir[/.*\//].length - list.each{|o| o.slice! 0..containing_dir_length-1} # Chop off paths - list - end - - # Constructs path for nth source in :sources. - # Menu.source_path :menufied=>"/tmp/drdr", :sources=>[["drdr/", "drdr.rb"]] - # Menu.source_path :menufied=>"/tmp/drdr", :sources=>[["drdr/", "drdr.rb"], ["a/", "a.rb"]] - def self.source_path options, nth=-2 # Default to last - menufied = options[:menufied] - - path = "#{File.dirname menufied}/#{options[:sources][0..nth].map{|o| o[0]}.join("")}" - - path.sub /^\/\/+/, "/" # Extraneous leading slash can be added when at root (File.dirname adds one, etc.) - end - def self.format_name name - name.gsub(/[ -]/, '_').downcase end end diff --git a/lib/xiki/core/menu_suggester.rb b/lib/xiki/core/menu_suggester.rb deleted file mode 100644 index de1402ce..00000000 --- a/lib/xiki/core/menu_suggester.rb +++ /dev/null @@ -1,97 +0,0 @@ -module Xiki - class MenuSuggester - - def self.expands? options - - # Only interject if no launcher or just pre launcher - return if options[:expanders].any? || options[:expanders] == [PrePattern] - - return if ! options[:name] # If menu name, will either auto-complete or suggest creating - - # Add ourself as handler if it looks like a menu. When Menu.expand - # tries to run, we'll know whether it actually is a menu. - - (options[:expanders] ||= []).push(self) - end - - def self.expand options - return if options[:output] || options[:halt] - - name = options[:name] - - return nil if ! name # Don't suggest if it's not menu-like - - # Don't try to complete if line doesn't end with a slash... - - if options[:path] !~ /\/$/ - - # If found any completions, return them... - - if(list = self.completions(name)).any? - if list.length == 1 - options[:output] = "<<< #{list[0]}/" - return - end - - Ol["TODO: If editor, add '...' at the end of line!"] - # if editor, add "..." at the end of the line - if options[:client] =~ /^editor\b/ - Line << "..." - end - - options[:no_slash] = 1 # If auto-completed, do no slash - options[:output] = list.sort.uniq.map{|o| "<< #{o}/\n"}.join("") - return - end - end - - # No completions or existing menu, so suggest creating via samples (@sample_menus)... - - txt = Expander.expand "sample menus", options[:items] # Will handle if no items or a sample menu item - - if txt - txt.gsub! "", name - txt.gsub! "", TextUtil.camel_case(name) - options[:output] = txt - return - end - - # User had items that weren't in @sample - - # Non-existant items were created, so suggest making a new menu out of them... - if options[:client] =~ /^editor\b/ - options[:output] = " - > Make this into a menu? - | Create a new '#{options[:name]}' menu with these items? - @as menu/ - " - end - - # Shelved for now - # return "@back up/1/#{name}...\n#{completions}" - - end - - - def self.completions name - - # Check defined menus... - - result = [] - Menu.defs.keys.each do |key| - result << key.gsub("_", ' ') if key =~ /^#{name}/ - end - - # Check MENU_PATH menus... - - Xiki.menu_path_dirs.each do |dir| - start = "#{dir}/#{name}*" - Dir.glob(start).each do |match| - result << File.basename(match, ".*").gsub("_", ' ') - end - end - result - end - - end -end diff --git a/lib/xiki/core/merb.rb b/lib/xiki/core/merb.rb index 66bda599..90a51faf 100644 --- a/lib/xiki/core/merb.rb +++ b/lib/xiki/core/merb.rb @@ -50,7 +50,7 @@ def self.generate dir, port=nil def self.rake dir, port, task=nil unless task #puts self.path_from_dir(dir) - out = Console.run "rake -T", :dir => self.path_from_dir(dir), :sync => true + out = Shell.run "rake -T", :dir => self.path_from_dir(dir), :sync => true # Pull out tasks out.scan(/^rake ([\w:]+)/) do |m| puts "- #{m[0]}" @@ -58,12 +58,12 @@ def self.rake dir, port, task=nil return end - Console.run "rake #{task}", :dir => self.path_from_dir(dir), :buffer => "*rake #{dir}" + Shell.run "rake #{task}", :dir => self.path_from_dir(dir), :buffer => "*rake #{dir}" end def self.merb_gen command, dir, port=nil - Console.run "merb-gen #{command} --no-color", :dir => self.path_from_dir(dir), :buffer => "*merb-gen #{dir}" + Shell.run "merb-gen #{command} --no-color", :dir => self.path_from_dir(dir), :buffer => "*merb-gen #{dir}" end def self.links @@ -93,16 +93,15 @@ def self.create dir, port=nil # Split into dir and name dir = self.path_from_dir(dir) path, name = dir.match(/(.+)\/(.+)/)[1..2] - Console.run "merb-gen app -f --no-color #{name}", :dir=>path, :buffer=>"*merb-gen app #{name}" #, :sync=>true + Shell.run "merb-gen app -f --no-color #{name}", :dir=>path, :buffer=>"*merb-gen app #{name}" #, :sync=>true end def self.start dir, port - Console.run "merb -a thin -p #{port}", :dir=>self.path_from_dir(dir), :buffer=>"*merb #{dir}" - # Console.run "merb -p #{port}", :dir=>self.path_from_dir(dir), :buffer=>"*merb #{dir}" + Shell.run "merb -a thin -p #{port}", :dir=>self.path_from_dir(dir), :buffer=>"*merb #{dir}" end def self.shell dir, port - Console.run "", :dir=>self.path_from_dir(dir)#, :buffer=>"*merb #{dir}" + Shell.run "", :dir=>self.path_from_dir(dir)#, :buffer=>"*merb #{dir}" # buffer = "*" + self.name_from_dir(dir) + " merb shell" @@ -115,7 +114,7 @@ def self.shell dir, port # return # end - # Console.run nil, :path => path, :buffer => buffer + # Shell.run nil, :path => path, :buffer => buffer end # def self.url path='/', dir=nil, port @@ -133,7 +132,7 @@ def self.console dir=nil, port=nil View.handle_bar View.to_buffer b else - Console.run "merb -i", :dir => dir, :buffer => b + Shell.run "merb -i", :dir => dir, :buffer => b end nil end @@ -151,7 +150,7 @@ def self.migrate dir=nil, port=nil View.to_buffer(b) $el.insert("repository.auto_migrate!") #$el.insert("DataMapper::Base.auto_migrate!") - Console.enter + Shell.enter # TODO: item for migrating test # - always take env as param? @@ -167,13 +166,13 @@ def self.name_from_dir dir def self.path_from_dir dir # If ., grab path of window after bar dir = View.dir_of_after_bar if dir == "." - Bookmarks.expand(dir, :absolute => true) + View.expand_path dir end def self.models model=nil # If no model specified, show all unless model - Dir.foreach(Bookmarks['$mo']) { |m| + Dir.foreach(Bookmarks['%mo']) { |m| next unless m =~ /(.+)\.rb$/ puts "+ #{TextUtil.camel_case($1)}/" } @@ -230,7 +229,7 @@ def self.recent *args end def self.version - Console.run('merb --version', :sync=>true) + Shell.run('merb --version', :sync=>true) end def self.launch_merb_log_line line @@ -239,7 +238,7 @@ def self.launch_merb_log_line line action = line[/"action"=>"(.+?)"/, 1] controller = line[/"controller"=>"(.+?)"/, 1] # Open controller - View.open "$co/#{controller}.rb" + View.open "%co/#{controller}.rb" # Jump to method View.to_highest Search.forward "^\\s-+def #{action}[^a-z_]" @@ -249,7 +248,7 @@ def self.launch_merb_log_line line def self.init # Jump to controller from line in merb log - Launcher.add(/^.* ~ Routed to: \{/) do |line| + Launcher.add(/^.* * Routed to: \{/) do |line| Merb.launch_merb_log_line line end end diff --git a/lib/xiki/core/meths.rb b/lib/xiki/core/meths.rb index 76f5bf67..6e3f3c0f 100644 --- a/lib/xiki/core/meths.rb +++ b/lib/xiki/core/meths.rb @@ -1,30 +1,11 @@ # Is this still being used? -gem 'method_source' -require 'method_source' - # Adds .drill method to Object and Class # For: # my_instace.drill class ::Object - # def drill method=nil - - # # If no method, show all - # if method.nil? - # result = "" - # methods = self.methods - Object.methods - # cmethods = self.class.methods - Class.methods - # return methods.sort.map{|o| "#{o}/"} + cmethods.sort.map{|o| "self.#{o}/"} - # end - - # # Method passed - - # method - - # # CodeTree.tree_search_option + result - # end def self.meths method=nil Class.meths_internal self, method @@ -35,21 +16,48 @@ def meths method=nil end def self.meths_internal clazz, method=nil - # If no method, show all + Meths.list clazz, method + end +end + +class Meths + + def self.list clazz, method=nil + + clazz = Xiki.const_get(clazz) if clazz.is_a?(String) + + # Foo., so list all methods... + if method.nil? result = "" methods = clazz.instance_methods - Object.methods cmethods = clazz.methods - Class.methods - return cmethods.sort.map{|o| ".#{o}"} + methods.sort.map{|o| "##{o}"} + return cmethods.sort.map{|o| "#{o}"} + methods.sort.map{|o| "##{o}"} end - # Method passed + # Foo.bar, so run or navigate... method.sub! /\/$/, '' - if method =~ /\.(.+)/ - return "- " + clazz.method($1).source_location.join(':') + + if method =~ /#(.+)/ # If instance method, always navigate + return "" end - "- " + clazz.instance_method(method).source_location.join(':') + + # If as+open, jump to it... + + if Xiki::Keys.prefix == "open" + file, line = clazz.method(method).source_location + Xiki::View.open file, :to=>line + return "" + end + + # Run it... + + returned, out, exception = Xiki::Code.eval "#{clazz}.#{method}", Xiki::View.file, Xiki::View.line, :pretty_exception=>1 + return exception if exception + returned ||= out # Use output if nothing returned + returned = returned.to_s if returned + returned end diff --git a/lib/xiki/core/move.rb b/lib/xiki/core/move.rb index 8cdbb602..ccf45109 100644 --- a/lib/xiki/core/move.rb +++ b/lib/xiki/core/move.rb @@ -7,7 +7,7 @@ class Move # Go to last line having indent def self.to_indent direction_down = true # Assume down - prefix = Keys.prefix + prefix = Keys.prefix :clear=>1 line = Line.value if prefix.is_a? Fixnum # If U, reverse @@ -38,7 +38,7 @@ def self.to_indent success = Search.forward "^ \\{#{indent}\\}[^ \t\n]" end - Move.to_column prefix.is_a?(Fixnum) ? indent : column + Move.to_column prefix.is_a?(Fixnum) ? indent+1 : column unless success View.beep @@ -47,8 +47,16 @@ def self.to_indent end def self.to_next_paragraph options={} - prefix = Keys.prefix :clear=>1 + prefix = options[:prefix] || Keys.prefix(:clear=>1) + if prefix == :u || prefix == :uu # If C-u, just go to end + column = View.column + + # hop+last and next line is blank, so move down first + if prefix == :uu && Line.value(2) == "" + Move.down + end + if Line.blank? Line.value(2) =~ /./ ? # If on blank line but next line has stuff, just move down Line.next : @@ -57,7 +65,10 @@ def self.to_next_paragraph options={} Search.forward "^[ \t]*$", :go_anyway=>1 - Line.previous if prefix == :uu + if prefix == :uu # hop+last + Line.previous + return View.column = column + end Move.to_axis return @@ -72,7 +83,8 @@ def self.to_next_paragraph options={} $el.beginning_of_line end - def self.to_previous_paragraph + def self.to_previous_paragraph options={} + prefix = Keys.prefix :clear=>1 if prefix == :u # If C-u, just go to end @@ -86,6 +98,8 @@ def self.to_previous_paragraph prefix = prefix.is_a?(Fixnum) ? prefix : 1 + Move.to_axis if options[:skip_if_top] + prefix.times do $el.skip_chars_backward "\n " $el.re_search_backward "\n[ \t]*\\(\n+[ \t]*\\)+", nil, 1 @@ -124,20 +138,27 @@ def self.to_line n=nil def self.to_column n=nil prefix = Keys.prefix :clear=>1 + + if prefix == :u + Move.to_end + return Move.backward 1 + end + # If dash+, go to specific char if prefix == :- && ! n # Don't prompt if called with a param return View.cursor = Keys.input(:prompt=>"Point to go to: ", :timed=>1).to_i end n = n || prefix || Keys.input(:prompt=>"column to go to: ").to_i - if n < 0 + + if n <= 0 Move.to_end n = $el.abs(n) n > length = Line.txt.length and n = length Move.backward n return end - $el.move_to_column n# - 1 + $el.move_to_column n - 1 end # Go to opposite bracket @@ -161,32 +182,98 @@ def self.to_other_bracket end end + def self.next options={} + if options.is_a?(Fixnum) + prefix = options + else + prefix = options[:prefix] || Keys.prefix + end + + if prefix + Keys.remember_key_for_repeat(proc {Xiki::Move.next :prefix=>prefix}, :movement=>1) + end + prefix = 7 if prefix == :u + prefix = 20 if prefix == :uu + $el.next_line prefix + end + + def self.previous options={} + + if options.is_a?(Fixnum) + prefix = options + else + prefix = options[:prefix] || Keys.prefix + end + + if prefix + Keys.remember_key_for_repeat(proc {Xiki::Move.previous :prefix=>prefix}, :movement=>1) + end + prefix = 7 if prefix == :u + prefix = 20 if prefix == :uu + + # If at top line, move to beginning (checking approximate cursor position to optimize) + if View.cursor < 200 && View.line == 1 + return Line.to_left + end + + prefix ||= 1 + + $el.previous_line prefix + end + + def self.up options={} + self.previous options + end + + def self.down options={} + self.next options + end + + + def self.backward_key count=nil + Keys.remember_key_for_repeat ["backward"] + count ||= Keys.prefix# :clear => true + + return $el.backward_word(1) if count == :u + + count ||= 1 + $el.backward_char count + end + + def self.forward_key count=nil + Keys.remember_key_for_repeat ["forward"] + count ||= Keys.prefix# :clear => true + + return $el.forward_word(1) if count == :u + + count ||= 1 + $el.forward_char count + end + def self.backward count=nil count ||= Keys.prefix# :clear => true + + return $el.backward_word(1) if count == :u + count ||= 1 - case count - when :u; $el.backward_word 1 - when :uu; $el.backward_word 2 - when :uuu; $el.backward_word 3 - else - $el.backward_char count - end + $el.backward_char count end def self.forward count=nil - count ||= Keys.prefix# :clear => true + + return $el.forward_word(1) if count == :u + count ||= 1 - case count - when :u - $el.forward_word 1 - when :uu - $el.forward_word 2 - when :uuu - $el.forward_word 3 - else - $el.forward_char(count) rescue nil # In case tried to move past end - end + $el.forward_char count + end + + def self.left count=nil + self.backward count + end + + def self.right count=nil + self.forward count end def self.top @@ -206,12 +293,16 @@ def self.to_quote options={} Keys.clear_prefix elsif prefix.is_a? Fixnum times = prefix - View.to_relative + View.to_relative :line=>1 end - patterns = options[:pipes] ? - [/^ *\|/, "^ *|."] : + patterns = if options[:pipes] + [/^ *\|/, "^ +|."] + elsif options[:colons] + [/^ *:/, "^ +:."] + else [/^ *(\||:[ +-])/, "^ *\\(|.\\|:[ +-]\\)"] + end found = nil (times||1).times do @@ -228,7 +319,23 @@ def self.to_quote options={} # Move to file in tree (not dir) ? def self.to_junior - Keys.prefix_times.times do + + prefix = Keys.prefix :clear=>true + if prefix.nil? + + # No prefix, so go to one after cursor... + + times = 1 + Keys.clear_prefix + elsif prefix.is_a? Fixnum + + # Numeric prefix, so go to nth after top of this view... + + times = prefix + View.to_relative :line=>1 + end + + times.times do # Move to line without / at end Line.next if Line.matches(/^ +[+-]? ?[a-zA-Z_-].+[^\/\n]$/) $el.re_search_forward "^ +[+-]? ?[a-zA-Z_-].+[^\/\n]$" @@ -243,19 +350,84 @@ def self.to_junior # Move.to_axis # Move.to_axiss def self.to_axis - n = Keys.prefix_n # Check for numeric prefix - Line.next(n) if n.is_a? Fixnum # If there, move down Line.to_left end + def self.hop_right_key + prefix = Keys.prefix :clear=>1 + return View.column = -1 if prefix == :u + + if prefix.is_a? Fixnum + Line.next prefix + end + + Line.to_right + + end + + def self.hop_left_key + prefix = Keys.prefix :clear=>1 + + if prefix == :u + Line.to_beginning + Move.forward 2 + return + end + + if prefix.is_a? Fixnum + Line.next prefix + end + + Line.to_left + + end + # Moves cursor to left of line: # # Move.to_end + def self.to_beginning + Line.to_left + end + def self.to_end n=nil - n ||= Keys.prefix_n # Check for numeric prefix Line.next(n) if n.is_a? Fixnum # If there, move down Line.to_right end + def self.backward_word + $el.backward_word + end + def self.backward_word_key + self.backward_word + Keys.remember_key_for_repeat(proc {Xiki::Move.backward_word_key}, :movement=>1) + end + + def self.forward_word + $el.forward_word + end + def self.forward_word_key + self.forward_word + Keys.remember_key_for_repeat(proc {Xiki::Move.forward_word_key}, :movement=>1) + end + + def self.backward_delete_word + + cursor = View.cursor + $el.backward_word + View.delete cursor, View.cursor + + Keys.remember_key_for_repeat(proc {Xiki::Move.backward_delete_word}) + end + + def self.forward_delete_word + + # $el.forward_kill_word 1 + cursor = View.cursor + $el.forward_word + View.delete cursor, View.cursor + + Keys.remember_key_for_repeat(proc {Xiki::Move.forward_delete_word}) + end + end end diff --git a/lib/xiki/core/notes.rb b/lib/xiki/core/notes.rb index 5a25d4f9..39a3eeb3 100644 --- a/lib/xiki/core/notes.rb +++ b/lib/xiki/core/notes.rb @@ -8,84 +8,16 @@ module Xiki class Notes - LABEL_REGEX = /(?:[a-zA-Z0-9 _-]+\) )?/ - - def self.menu_before *args - - # If @notes is nested under a menu, use that menu as the .notes filename... - - # 3 main scenarios - # foo/@notes/ - .menu_before assumes ~/note/foo.notes is the file - # foo/@notes/list/ - just let .menu handle it - # notes/list/ - just let .menu handle it - - path = Xiki.path rescue [] - - # Assume menu handled it if - - # Take over and assume ~/notes/ancestor.notes if we're nested, and no args or 1st arg is a quoted heading - take_over = path.length >= 2 && !args[0] || args[0] =~ /^>/ - return if ! take_over - - # Pull out file name and path from "foo/@notes/bar" - root = path[-2] - root.sub! /\/$/, '' - - notes_dir = File.expand_path("~/notes") - - # Can we just pass Notes.drill the stem? Is it smart enough to look for index, etc? - - notes_path = - if File.exists?(found = "#{notes_dir}/#{root}/index.notes") - found - elsif File.exists?(found = "#{notes_dir}/#{root}/#{root}.notes") - found - elsif File.exists?(found = "#{notes_dir}/#{root}.notes") - found # Internally, .drill will ask them to create it, I think - end - - Notes.drill found, *args - end - - def self.menu - %` - - .list/ - - api/ - - misc methods/ - > Turn notes wiki text into html - @Notes.to_html "> Heading\\njust text\\n" - - docs/ - - nesting/ - - intro/ - | You can nest @notes under other menus, like so: - @foo/ - @notes/ - - | It will use ~/notes/foo.notes. This is a convenience mechanism - | for you to create notes that are associated with menus. - - example/ - > 1. if you have a file like this - @~/notes/foo.notes - | > Bar - | Some notes about bar. - | - | > Baz - | Some notes about baz. - - > 2. When you expand, it will look like this - @foo/ - @notes/ - > Bar - > Baz - - | Then you can expand the headings and edit inline. - ` - end + # Marker regex + MARKER_REGEX = /(?:[a-zA-Z0-9 '"!?*_-]+\) )?/ def self.list *args - Notes.drill "~/notes/", *args + self.drill "~/xiki/", *args end + # Returns contents block where cursor is. + # Blocks are delimited by >... headings. + # Notes.block def self.block regex="^> " left, after_header, right = View.block_positions regex View.txt after_header, right @@ -98,7 +30,7 @@ def self.narrow_block options={} # If nothing hidden, hide all but current if $el.point_min == 1 && ($el.buffer_size + 1 == $el.point_max) left, after_header, right = View.block_positions "^#{delimiter}\\( \\|$\\)" - $el.narrow_to_region left, right + Hide.hide left, right return end # Otherwise, expand all, go to next heading, hide all but current @@ -109,22 +41,22 @@ def self.narrow_block options={} end def self.archive - block = self.current_section + block = self.current_section_object block.archive end def self.show_text - block = self.current_section + block = self.current_section_object block.show_text end def self.hide_text - block = self.current_section + block = self.current_section_object block.hide_text end - def self.to_block up=false - prefix = Keys.prefix :clear=>1 + def self.to_block options={} + prefix = options[:prefix] || Keys.prefix #:clear=>1 kind = :h1 kind = :h0 if prefix == :u @@ -132,18 +64,30 @@ def self.to_block up=false times = prefix.is_a?(Fixnum) ? prefix : 1 - if up + if options[:up] + + return Ruby.custom_previous if View.mode == :ruby_mode + times.times do Line.to_left Search.backward regex end else + + return Ruby.custom_next if View.mode == :ruby_mode + times.times do Line.next if $el.string_match regex, Line.value # Use elisp matcher Search.forward regex Line.to_left end end + + # Run from the key shortcut, so remember it for C-, + if options[:key] + prock = options[:up] ? proc {Xiki::Notes.to_block :up=>1} : proc {Xiki::Notes.to_block} + Keys.remember_key_for_repeat(prock, :movement=>1) + end end # @@ -171,20 +115,23 @@ def self.heading_regex kind=nil, options={} kind = self.heading_kind kind if kind == :h1 options[:match_only_self] ? - "^[>=]\\( .*[^\n:]$\\| ?$\\)" : - "^[>=]\\( \\|$\\)" + "^>\\( .*[^\n:]$\\| ?$\\)" : + "^>\\( \\|$\\)" elsif kind == :h2 - "^[>=]\\{1,2\\}\\( \\|$\\)" + "^>\\{1,2\\}\\( \\|$\\)" elsif kind == :h0 "^> .*:$" end end - def self.move_block up=false - prefix = Keys.prefix :clear=>1 + def self.move_block options={} + + Keys.remember_key_for_repeat(proc {Notes.move_block options}) + + prefix = options[:prefix] || Keys.prefix(:clear=>1) kind = self.heading_kind times = Keys.prefix_times prefix - regex = self.heading_regex kind#, :match_only_self=>1 + regex = self.heading_regex kind #, :match_only_self=>1 if kind == :h0 regex = self.heading_regex :h0 @@ -192,16 +139,17 @@ def self.move_block up=false end orig = Location.new - block = self.current_section prefix - block.blink if kind == :h0 || kind == :h2 - block.delete_content + section = self.current_section_object prefix + + section.delete - if up + if options[:up] times.times do Search.backward regex, :go_anyway=>true end - $el.insert block.content + + View.insert section.content Search.backward regex else @@ -212,59 +160,105 @@ def self.move_block up=false Search.forward move_regex, :go_anyway=>true end Move.to_axis - View.insert block.content + + View.insert section.content Search.backward move_regex end + times == 1 ? - self.current_section(regex).fade_in : + self.current_section_object(regex).fade_in : orig.go + end def self.insert_heading options={} + + Keys.remember_key_for_repeat(proc {Xiki::Notes.insert_heading}) + Line.start orig = Line.value - prefix = Keys.prefix times = Keys.prefix_n || 1 - times.times { $el.insert ">" } - View.insert " " # unless times > 1 + prefix = options[:prefix] || Keys.prefix(:clear=>1) + times = 1 if times > 5 + + # Put comment above line + Move.to_axis + View.insert "#{">"*times} " + + linebreaks = 4 + linebreaks = 1 if orig.any? && orig !~ /^>/ # If on non-blank line, and not heading, don't put space after heading + + if prefix == :u || prefix == :- + View << ":"#, :dont_move=>1 + + linebreaks = 1 if orig =~ /^>(.*[^:\n]|)$/ # If on ">..." line, don't add space after this ">...:" heading + + View.insert "\n"*linebreaks, :dont_move=>1 + View.column = -1 + return + end + + if prefix == nil + View.insert "\n"*linebreaks, :dont_move=>1 + end + + if prefix == 1 + View << "!:"#, :dont_move=>1 + View.insert "\n"*4, :dont_move=>1 + View.column = -2 + return + end - if options[:extra_space] || prefix == :u # If up+, create blank lines. + if prefix == 8 View.insert("\n"*4, :dont_move=>1) + View << "today > :"#, :dont_move=>1 + Move.to_column -1 + ControlLock.disable return end - if options[:extra_space] || prefix == :- + if prefix == 9 View.insert("\n"*2, :dont_move=>1) - View.<< "!:", :dont_move=>1 + View << "feature > :"#, :dont_move=>1 + Move.to_column -1 + ControlLock.disable return end - View.insert("\n", :dont_move=>1) if orig != "" + if prefix == :- + View.insert("\n", :dont_move=>1) if orig != "" + end + + raise "- Don't understand the prefix: #{prefix.inspect}" if prefix + end def self.cut_block no_clipboard=false - block = self.current_section Keys.prefix - block.blink + Keys.remember_key_for_repeat(proc {Xiki::Notes.cut_block}) + + block = self.current_section_object Keys.prefix + + # block.blink unless no_clipboard Clipboard.set("0", block.content) end - block.delete_content + block.delete end def self.copy_block prefix = Keys.prefix - block = self.current_section prefix == :u ? 2 : 1 - block.blink + block = self.current_section_object prefix == :u ? 2 : 1 Clipboard.set("0", Keys.prefix_u ? block.text : block.content) end - def self.move_block_to_top no_clipboard=false + # def self.move_block_to_top no_clipboard=false + def self.move_block_to_top options={} prefix_u = Keys.prefix_u :clear=>true - block = self.current_section + block = self.current_section_object if prefix_u line = View.line_number @@ -272,9 +266,11 @@ def self.move_block_to_top no_clipboard=false orig_right = block.right end - block.fade_out unless prefix_u + # Do fade while moving if > no hyphen prefix and no :no_fade option + + do_fade = ! prefix_u && ! options[:no_fade] - block.delete_content + block.delete $el.beginning_of_buffer $el.insert block.content @@ -288,44 +284,16 @@ def self.move_block_to_top no_clipboard=false View.to_line 1 end - moved_section = self.current_section + moved_section = self.current_section_object - moved_section.fade_in unless prefix_u + moved_section.fade_in if do_fade end def self.keys - return if ! $el - # Get reference to map if already there (don't mess with existing buffers) $el.elvar.notes_mode_map = $el.make_sparse_keymap unless $el.boundp :notes_mode_map - Keys.custom_archive(:notes_mode_map) { Notes.archive } - Keys.custom_back(:notes_mode_map) { Notes.move_block :backwards } # Move block up to before next block - Keys.custom_clipboard(:notes_mode_map) { Notes.copy_block } # block -> clipboard - Keys.custom_delete(:notes_mode_map) { Notes.cut_block :backwards } # block -> clear - # Keys.custom_expand(:notes_mode_map) { Notes.narrow_block } # Show just block - Keys.custom_forward(:notes_mode_map) { Notes.move_block } # Move block down to after next block - Keys.custom_heading(:notes_mode_map) { Notes.insert_heading } # Insert |... etc. heading - Keys.custom_item(:notes_mode_map) { Agenda.quick_add_line } - # j - Keys.custom_kill(:notes_mode_map) { Notes.cut_block } # block -> cut - # l - Keys.custom_mask(:notes_mode_map) { Notes.hide_text } # block -> hide - Keys.custom_next(:notes_mode_map) { Notes.to_block } # Go to block after next block - Keys.custom_open(:notes_mode_map) { Notes.show_text } # block -> reveal - Keys.custom_previous(:notes_mode_map) { Notes.to_block :backwards } # Go to block before next block - # q - # r - Keys.custom_stamp(:notes_mode_map) { $el.insert Time.now.strftime("- %Y-%m-%d %I:%M%p: ").downcase.sub(' 0', ' ') } - Keys.custom_top(:notes_mode_map) { Notes.move_block_to_top } # block -> top - # u - # v - # w - Keys.custom_x(:notes_mode_map) { Notes.cut_block } # block -> cut - # y - # z - $el.define_key(:notes_mode_map, $el.kbd(""), :notes_mouse_double_click) $el.define_key(:notes_mode_map, $el.kbd(""), :notes_mouse_toggle) @@ -333,273 +301,267 @@ def self.keys $el.define_key(:notes_mode_map, $el.kbd(""), :notes_mouse_double_click) $el.define_key(:notes_mode_map, $el.kbd(""), :notes_mouse_double_click) + # custom+N, to jump to nth visible label + # custom+1, custom+2, etc + (1..9).each do |n| + $el.define_key(:notes_mode_map, $el.kbd("C-c C-#{n}")){ Launcher.do_last_launch :nth=>n, :here=>1, :dont_launch=>1 } + end + + $el.define_key(:notes_mode_map, $el.kbd("C-i")) { Notes.tab_key :flashing_ok=>1 } + $el.define_key(:notes_mode_map, $el.kbd("")) { Notes.tab_key :prefix=>:u, :flashing_ok=>1 } + + $el.define_key(:notes_mode_map, $el.kbd("C-m")) { Notes.return_wrapper } # Return key + + # For now, don't over-ride return > it's messing up pasting in the terminal + # $el.define_key(:notes_mode_map, $el.kbd("C-m")) { View.insert_line } - $el.define_key(:notes_mode_map, $el.kbd("C-i")) { Notes.tab_key } end def self.define_styles - return if ! $el - - # - foo (r): - Styles.define :notes_light_gray, :fg => "bbb" - - # - foo (r): - Styles.define :variable, :face => 'verdana' - - # - foo (r): - Styles.define :notes_label_parens, - :fg => "bbb", - :size => "-2", - :face => 'arial' - - # >... - h1_size = "+3" - - # Colors of "| ..." headings - if Styles.dark_bg? # If black bg - @@h1_styles = { - :notes_h1 =>"333", - :notes_h1r=>"611", # | r This will be red - :notes_h1o=>"841", # | o This will be orange - :notes_h1y=>"871", - :notes_h1e=>"363", - :notes_h1g=>"363", - :notes_h1b=>"678", - :notes_h1p=>"636", - :notes_h1m=>"622", - :notes_h1x=>"345", - :notes_h1t=>"055", - } - else - # Colors of headings - @@h1_styles = { - :notes_h1 =>"909090", - :notes_h1r=>"b66", # | r This will be red - :notes_h1o=>"b83", # | o This will be orange - :notes_h1y=>"bb3", - :notes_h1e=>"363", - :notes_h1g=>"7b6", - :notes_h1b=>"678", - :notes_h1p=>"b8b", - :notes_h1m=>"944", - :notes_h1x=>"678", - :notes_h1t=>"055", - } - end - - @@h1_styles.each do |k, v| - pipe = v.gsub(/./) {|c| - hex = c.to_i(16) + 3 - hex = 15 if hex > 15 # In case it's aleady really light - hex.to_s(16) - } - label = v.gsub(/./) {|c| - hex = c.to_i(16) + 6 - hex = 15 if hex > 15 # In case it's aleady really light - hex.to_s(16) - } + Code.cache(:notes_define_styles) do - # label = "eee" if label == "fff" + # - foo (r): + Styles.define :variable, :face => 'verdana' - Styles.define k, - :face=>'arial', :size=>h1_size, :fg=>'ffffff', :bg=>v, :bold=> true - Styles.define "#{k}_pipe".to_sym, :face=>'arial', :size=>h1_size, :fg=>pipe, :bg=>v, :bold=>true - Styles.define "#{k}_label".to_sym, :face=>'arial', :size=>h1_size, :fg=>label, :bg=>v, :bold=>true - end + # >... + + h1_size = "+3" + bg = "555" + + pipe = "777" + label = "bbb" - Styles.define :notes_h1_agenda_pipe, :face => 'arial', :size => h1_size, :fg => '88cc88', :bg => '336633', :bold => true - Styles.define :notes_h1_agenda, :face => 'arial', :size => h1_size, :fg => 'ffffff', :bg => '336633', :bold => true + Styles.define :notes_h1, :face=>'arial', :size=>h1_size, :fg=>'ccc', :bg=>bg, :bold=>nil + Styles.define :notes_h1_pipe, :face=>'arial', :size=>h1_size, :fg=>pipe, :bg=>bg, :bold=>true - # >>>... - Styles.define :notes_h3, - :face => 'arial', :size => "-1", - :fg => '999',#, :bg => "9999cc", - :bold => true - Styles.define :notes_h3_pipe, - :face => 'arial', :size => "-1", - :fg => '333' + Styles.define :notes_h1_agenda_pipe, :face => 'arial', :size => h1_size, :fg => '88cc88', :bg => '336633', :bold => true + Styles.define :notes_h1_agenda, :face => 'arial', :size => h1_size, :fg => 'ffffff', :bg => '336633', :bold => true - # >>>>... - Styles.define :notes_h4, - :face => 'arial', :size => "-3", - :fg => '55b', - :bold => true - Styles.define :notes_h4_pipe, - :face => 'arial', :size => "-3", - :fg => '224' + # >>>... + Styles.define :notes_h3, + :face => 'arial', :size => "-1", + :fg => '999',#, :bg => "9999cc", + :bold => true + Styles.define :notes_h3_pipe, + :face => 'arial', :size => "-1", + :fg => '333' + # >>>>... + Styles.define :notes_h4, + :face => 'arial', :size => "-3", + :fg => '55b', + :bold => true + Styles.define :notes_h4_pipe, + :face => 'arial', :size => "-3", + :fg => '224' - if Styles.dark_bg? # If black and white label_color = "e70" - else - label_color = "f70" - end - - # Labels, emphasis - Styles.define :notes_label, - :face=>'arial black', :size=>"0", # Mac - #:face=>'courier', :size=>"0", # Mac - :fg=>label_color, :bold=>true - - Styles.define :notes_bullet_parens, - :face => 'arial', :size => "-2", - :fg => "ee7700", :bold => true - - # Strikethrough - Styles.define(:strike, :strike=>true) - - # - (r): foo - Styles.define :notes_label_link, - :face => 'verdana', :size => "-1", - :fg => "66f", - :bold => true, :underline => true - - Styles.define :notes_g, :fg=>"6cf", :face=>'arial black', :size=>"0", :bold=>true - Styles.define :notes_blue, :fg=>"69f", :face=>'arial black', :size=>"0", :bold=>true - Styles.define :notes_red, :fg=>"c55", :face=>'arial black', :size=>"0", :bold=>true - Styles.define :notes_yellow, :fg=>"CC0", :face=>'arial black', :size=>"0", :bold=>true - Styles.define :notes_green, :fg=>"3c3", :face=>'arial black', :size=>"0", :bold=>true - - if Styles.dark_bg? # If black bg - Styles.define :notes_h2, :face=>'arial', :size=>"-1", :fg=>'fff', :bg=>"333", :bold=>true - Styles.define :notes_h2_pipe, :face=>'arial', :size=>"-1", :fg=>'555555', :bg=>"333333", :bold=> true - Styles.define :notes_h0_pipe, :face=>'arial', :size=>"+8", :fg=>'666666', :bg=>"333333", :bold=> true - Styles.define :notes_h0, :fg=>"fff", :bg=>"333", :face=>'arial', :size=>"+8", :bold=>true - Styles.define :notes_h0_green, :fg=>"8f4", :bg=>"333", :face=>'arial', :size=>"+8", :bold=>true - Styles.define :notes_h0_green_pipe, :fg=>"555", :bg=>"333", :face=>'arial', :size=>"+8", :bold=>true - Styles.define :notes_h1_green, :fg=>"8f4", :bg=>"333", :face=>'arial', :size=>"+3", :bold=>true - Styles.define :notes_h1_green_pipe, :fg=>"555", :bg=>"333", :face=>'arial', :size=>"+3", :bold=>true - else # If white bg - Styles.define :notes_h2, :face=>'arial', :size=>"-1", :fg=>'fff', :bg=>"909090", :bold=>true - Styles.define :notes_h2_pipe, :face=>'arial', :size=>"-1", :fg=>'bbb', :bg=>"909090", :bold=>true - Styles.define :notes_h0_pipe, :face=>'arial', :size=>"+8", :fg=>'bbb', :bg=>"909090", :bold=>true - Styles.define :notes_h0, :fg=>"fff", :bg=>"909090", :face=>'arial', :size=>"+8", :bold=>true - Styles.define :notes_h0_green, :fg=>"cf7", :bg=>"909090", :face=>'arial', :size=>"+8", :bold=>true - Styles.define :notes_h0_green_pipe, :fg=>"bbb", :bg=>"909090", :face=>'arial', :size=>"+8", :bold=>true - Styles.define :notes_h1_green, :fg=>"cf7", :bg=>"909090", :face=>'arial', :size=>"+3", :bold=>true - Styles.define :notes_h1_green_pipe, :fg=>"bbb", :bg=>"909090", :face=>'arial', :size=>"+3", :bold=>true - end - - if Styles.dark_bg? # If black bg + + + # Labels, emphasis + Styles.define :notes_label, + :face=>'arial black', :size=>"0", # Mac + :fg=>label_color, :bold=>true + + Styles.define :notes_bullet_parens, + :face => 'arial', :size => "-2", + :fg => "ee7700", :bold => true + + Styles.define :hidden, :fg=>"000", :bg=>"000" + + Styles.define :notes_g, :fg=>"6cf", :face=>'arial black', :size=>"0", :bold=>true + Styles.define :notes_blue, :fg=>"69f", :face=>'arial black', :size=>"0", :bold=>true + Styles.define :notes_red, :fg=>"c55", :face=>'arial black', :size=>"0", :bold=>true + Styles.define :notes_yellow, :fg=>"cb0", :face=>'arial black', :size=>"0", :bold=>true + Styles.define :notes_green, :fg=>"3c3", :face=>'arial black', :size=>"0", :bold=>true + + bg_color = Styles.attribute(:default, :background) + + + Styles.define :notes_h2, :face=>'arial', :size=>"-1", :fg=>'999' + Styles.define :notes_h2_pipe, :face=>'arial', :size=>"-1", :fg=>pipe + Styles.define :notes_h0_pipe, :face=>'arial', :size=>"+8", :fg=>pipe, :bg=>bg, :bold=> true + Styles.define :notes_h0, :fg=>"fff", :bg=>bg, :face=>'arial', :size=>"+8", :bold=>true + Styles.define :notes_h0_green, :fg=>"8f4", :bg=>bg, :face=>'arial', :size=>"+8", :bold=>true + Styles.define :notes_h1_green, :fg=>"9d4", :bg=>bg, :face=>'arial', :size=>"+3" + + Styles.define :notes_h0_yellow, :fg=>"ff0", :bg=>bg, :face=>'arial', :size=>"+8", :bold=>true + Styles.define :notes_h1_yellow, :fg=>"dd0", :bg=>bg, :face=>'arial', :size=>"+3" + Styles.define :notes_h0_red, :fg=>"d00", :bg=>bg, :face=>'arial', :size=>"+8", :bold=>true + Styles.define :notes_h1_red, :fg=>"d00", :bg=>bg, :face=>'arial', :size=>"+3" + + Styles.define :escape_glyph, :fg=>"666" # Red special char was too ugly + Styles.define :trailing_whitespace, :bg=>'555' + + Styles.define :quote_hidden, :fg=>bg_color + Styles.dotted :bg=>'080808', :fg=>'111', :strike=>nil, :underline=>nil, :border=>['111', -1] - else - Styles.dotted :bg=>'eee', :fg=>'ddd', :strike=>nil, :underline=>nil, :border=>['ddd', -1] - end - notes_exclamation_color = Styles.dark_bg? ? "7c4" : "5a0" + exclamation_color = Styles.dark_bg? ? "4c4" : "5a0" + + Styles.define :notes_exclamation, # Green bold text + :face=>'arial black', :size=>"0", + :fg=>exclamation_color, :bold=>true - Styles.define :notes_exclamation, # Green bold text - :face=>'arial black', :size=>"0", - :fg=>notes_exclamation_color, :bold=>true + Styles.define :notes_double_exclamation, # Purple bold text + :face=>'arial black', :size=>"0", + :fg=>"aa0", :bold=>true - Styles.notes_link :fg=>(Styles.dark_bg? ? "9ce" : "08f") + Styles.define :notes_triple_exclamation, # Purple bold text + :face=>'arial black', :size=>"0", + :fg=>"d22", :bold=>true - Styles.shell_prompt :fg=>'#888', :bold=>1 + # Styles.define :notes_link, :fg=>(Styles.dark_bg? ? "9ad" : "08f"), :bold=>false + Styles.define :notes_link, :fg=>(Styles.dark_bg? ? "aaa" : "08f"), :bold=>1 - bg_color = Styles.attribute(:default, :background) - Styles.define :quote_hidden, :fg=>bg_color #, :size=>"0" + Styles.define :shell_prompt, :fg=>'#888', :bold=>1 + + # Green filter highlight + Styles.define :filter_highlight, :fg=>"8ac" + + end end def self.apply_styles + # Don't format quotes (it overrides the following formatting) - Styles.clear - # >... lines (headings) - Styles.apply("^\\(>\\)\\(.*\n\\)", nil, :notes_h1_pipe, :notes_h1) + Code.cache(:notes_apply_styles) do + Styles.clear - Styles.apply("^\\(> \\)\\(.*\n\\)", nil, :notes_h1_pipe, :notes_h1) - Styles.apply("^\\(>> \\)\\(.*\n\\)", nil, :notes_h2_pipe, :notes_h2) + # >... lines (headings) + Styles.apply("^\\(>\\)\\(.*\n\\)", nil, :notes_h1_pipe, :notes_h1) - # > Green! - Styles.apply("^\\(> \\)\\(.*!\\)\\(\n\\)", nil, :notes_h1_green_pipe, :notes_h1_green, :notes_h1_green) + Styles.apply("^\\(> \\)\\(.*\n\\)", nil, :notes_h1_pipe, :notes_h1) + Styles.apply("^\\(>> \\)\\(.*\n\\)", nil, :notes_h2_pipe, :notes_h2) - # > Large: - Styles.apply("^\\(> \\)\\(.*:\\)\\(\n\\)", nil, :notes_h0_pipe, :notes_h0, :notes_h1) + # # ... lines (headings) + Styles.apply("^\\(# \\)\\(.+\n\\)", nil, :notes_h1_pipe, :notes_h1) - Styles.apply("^\\(> \\)\\(.*!:\\)\\(\n\\)", nil, :notes_h0_green_pipe, :notes_h0_green, :notes_h0_green) + # > Green! + Styles.apply("^\\(> \\)\\(.*!\\)\\(\n\\)", nil, :notes_h1_pipe, :notes_h1_green, :notes_h1_green) + Styles.apply("^\\(> \\)\\(.*!!\\)\\(\n\\)", nil, :notes_h1_pipe, :notes_h1_yellow, :notes_h1_yellow) - Styles.apply("^\\(>\\)\\( .+?: \\)\\(.+\n\\)", nil, :notes_h1_pipe, :notes_h1_label, :notes_h1) + # > Large heading: + Styles.apply("^\\(> \\)\\(.*:\\)\\(\n\\)", nil, :notes_h0_pipe, :notes_h0, :notes_h0) - Styles.apply("^\\(> 20[0-9][0-9]-[0-9][0-9]-[0-9][0-9].*:\\)\\(.*\n\\)", nil, :notes_h1_agenda_pipe, :notes_h1_agenda) + Styles.apply("^\\(> \\)\\(.*!:\\)\\(\n\\)", nil, :notes_h0_pipe, :notes_h0_green, :notes_h0_green) + Styles.apply("^\\(> \\)\\(.*!!:\\)\\(\n\\)", nil, :notes_h0_pipe, :notes_h0_yellow, :notes_h0_yellow) + Styles.apply("^\\(> \\)\\(.*!!!:\\)\\(\n\\)", nil, :notes_h0_pipe, :notes_h0_red, :notes_h0_red) - @@h1_styles.each do |k, v| - l = k.to_s[/_..(.)$/, 1] - next unless l - Styles.apply("^\\(> #{l}\\)\\(\n\\| .*\n\\)", nil, "#{k}_pipe".to_sym, k) - Styles.apply("^\\(>\\)\\( #{l} .+: \\)\\(.*\n\\)", nil, "#{k}_pipe".to_sym, "#{k}_label".to_sym, k) - end + # >>... lines + Styles.apply("^\\(>>\\)\\(.*\n\\)", nil, :notes_h2_pipe, :notes_h2) + + # Commented + Styles.apply("^\\(>> .+?: \\)\\(.+\n\\)", nil, :notes_h2_pipe, :notes_h2) + + # >>>... lines + Styles.apply("^\\(>>>\\)\\(.*\n\\)", nil, :notes_h3_pipe, :notes_h3) + + # >>>... lines + Styles.apply("^\\(>>>>\\)\\(.*\n\\)", nil, :notes_h4_pipe, :notes_h4) + + # foo: > style like dir + Styles.apply("\\(^[A-Za-z0-9 ][A-Za-z0-9 !-$'_.,>+?+-]*[\A-Za-z0-9.?]:\\)\\($\\| \\)", nil, :ls_dir) # foo: + Styles.apply("\\(^[\A-Za-z0-9 ]:\\)\\($\\| \\)", nil, :ls_dir) # f: < just 1 letter + + # >... headings (indented) + Styles.apply("^ +\\(> ?\\)\\(.*\n\\)", nil, :quote_heading_bracket, :quote_heading_h1) + Styles.apply("^ +\\(> ?\\)\\(.*!:?\n\\)", nil, :quote_heading_bracket, :quote_heading_h1_green) + + Styles.apply("^ +\\(>>\\)\\(.*\n\\)", nil, :quote_heading_bracket, nil) + + + # - bullets with labels and comments + + + # Todo > instead of having both here > make one syntax that catches them both + # - only match when: 1st char after space can't be [>|] + # - probably > 1st char after space has to be alphanumeric + + # Orange now, not gray + Styles.apply("^[ \t]*\\([<+-][<+=-]*\\) \\([^/:\n]+:\\) ", nil, :ls_bullet, :notes_label) # - foo: bar - # >>... lines - # Styles.apply("^\\(>>\\)\\(.*\\)", nil, :notes_h2_pipe, :notes_h2) - Styles.apply("^\\(>>\\)\\(.*\n\\)", nil, :notes_h2_pipe, :notes_h2) + Styles.apply("^[ \t]*\\([0-9]+\\.\\)\\( \\|$\\)", nil, :ls_bullet, :notes_label) # 1. foo + Styles.apply("^[ \t]*\\([<+-]\\) \\([0-9]+\\.\\)\\( \\|$\\)", nil, :ls_bullet, :notes_label) # - 1. foo - # Commented - Styles.apply("^\\(>> .+?: \\)\\(.+\n\\)", nil, :notes_h2_pipe, :notes_h2) + Styles.apply("^[ \t]*\\([<+-][<+=-]*\\) \\([^(\n]*?)\\)\\( \\|$\\)", nil, :ls_bullet, :notes_label) # - foo) bar + Styles.apply("^[ \t]*\\([<+-][<+=]*\\) \\(.*:\\)$", nil, :ls_bullet, :notes_label) # - foo: - # >>>... lines - Styles.apply("^\\(>>>\\)\\(.*\n\\)", nil, :notes_h3_pipe, :notes_h3) - # >>>... lines - Styles.apply("^\\(>>>>\\)\\(.*\n\\)", nil, :notes_h4_pipe, :notes_h4) + Styles.apply("^\\([ \t]*\\)\\([<+-]\\) \\(.+?:\\) +\\(|.*\n\\)", nil, :default, :ls_bullet, :notes_label, :ls_quote) + Styles.apply("^\\([ \t]*\\)\\([<+-]\\) \\([^(\n]+?)\\) +\\(|.*\n\\)", nil, :default, :ls_bullet, :notes_label, :ls_quote) - # - bullets with labels and comments - Styles.apply("^[ \t]*\\([<+-][<+=-]*\\) \\([^/:\n]+:\\) ", nil, :ls_bullet, :notes_label) # - hey: you - Styles.apply("^[ \t]*\\([<+-][<+=-]*\\) \\([^(\n]+?)\\)\\( \\|$\\)", nil, :ls_bullet, :notes_label) # - hey) you + # exclamation! / todo + Styles.apply("^[ \t]*\\([<+-]\\) \\(.*!\\)$", nil, :ls_bullet, :notes_exclamation) # - foo! + Styles.apply("^[ \t]*\\([<+-]\\) \\(.*!!\\)$", nil, :ls_bullet, :notes_double_exclamation) # - foo!! + Styles.apply("^[ \t]*\\([<+-]\\) \\(.*!!!\\)$", nil, :ls_bullet, :notes_triple_exclamation) # - foo!! - Styles.apply("^[ \t]*\\([<+-][<+=-]*\\) \\(.+:\\)$", nil, :ls_bullet, :notes_label) # - hey) - Styles.apply("^\\([ \t]*\\)\\([<+-]\\) \\(.+?:\\) +\\(|.*\n\\)", nil, :default, :ls_bullet, :notes_label, :ls_quote) - Styles.apply("^\\([ \t]*\\)\\([<+-]\\) \\([^(\n]+?)\\) +\\(|.*\n\\)", nil, :default, :ls_bullet, :notes_label, :ls_quote) + Styles.apply("^[ \t]*\\([0-9]+\\.\\) .+!$", :notes_exclamation) # 1. foo! + # Leave number orange > looks wierd - Styles.apply("^ *\\(!.*\n\\)", nil, :ls_quote) # ^!... for code + Styles.apply("^ *\\(!\\+.*\n\\)", nil, :diff_green) # !-Foo + Styles.apply("^ *\\(!-.*\n\\)", nil, :diff_red) # !+Foo - # exclamation! / todo - Styles.apply("^[ \t]*\\([<+-]\\) \\(.*!\\)$", nil, :ls_bullet, :notes_exclamation) - Styles.apply("^ +\\(!\\+.*\n\\)", nil, :diff_green) # Whole lines - Styles.apply("^ +\\(!-.*\n\\)", nil, :diff_red) + Styles.apply("^ *\\(!\\)\\(\\.\\)\\(.*\\)\n", nil, :quote_heading_pipe, :notes_red, :ls_quote) # !.Ruby code - Styles.apply("\\(\(-\\)\\(.+?\\)\\(-\)\\)", nil, :diff_small, :diff_red, :diff_small) - Styles.apply("\\(\(\\+\\)\\(.+?\\)\\(\\+\)\\)", nil, :diff_small, :diff_green, :diff_small) + Styles.apply("\\(\(-\\)\\(.+?\\)\\(-\)\\)", nil, :diff_small, :diff_red, :diff_small) + Styles.apply("\\(\(\\+\\)\\(.+?\\)\\(\\+\)\\)", nil, :diff_small, :diff_green, :diff_small) - # google/ - Styles.apply "^ *\\(-?\\) ?\\(@?\\)\\(g\\)\\(o\\)\\(o\\)\\(g\\)\\(l\\)\\(e\\)\\(/\\)", nil, :ls_bullet, :ls_dir, - :notes_blue, :notes_red, :notes_yellow, :notes_blue, :notes_green, :notes_red, - :ls_dir + Styles.apply "^ *\\(-?\\) ?\\([@=]? ?\\)\\(g\\)\\(o\\)\\(o\\)\\(g\\)\\(l\\)\\(e\\)\\(/\\|\n\\)", nil, :ls_bullet, :ls_dir, # google + :notes_blue, :notes_red, :notes_yellow, :notes_blue, :notes_green, :notes_red, + :ls_dir - Styles.apply "^hint/.+", :fade6 + Styles.apply "^hint/.+", :fade6 - Styles.apply "^[< ]*@? ?\\([%$&]\\) ", nil, :shell_prompt # Colorize shell prompts + # $..., %..., etc. + Styles.apply "^[< ]*[@=]? ?\\([%$&#]\\)\\( \\|$\\)", nil, :shell_prompt # "$ foo", "% foo", "& foo", and "# foo" - # Make |~... lines be Dotsies - Styles.apply("^ *\\(|~\\)\\([^\n~]+\\)\\(~?\\)", nil, :quote_heading_pipe, :dotsies, :quote_heading_pipe) - # Styles.apply("\\(^\\| \\)\\(|~\\)\\([^\n~]+\\)\\(~?\\)", nil, :quote_heading_pipe, :quote_heading_pipe, :dotsies, :quote_heading_pipe) + # $$... + Styles.apply "^[< ]*[@=]? ?\\(\\$\\$\\)", nil, :shell_prompt # Colorize shell prompts after "@" - # |... invisible - Styles.apply("^ *\\(|\\.\\.\\.\\)\\(.*\n\\)", nil, :quote_heading_pipe, :quote_hidden) + # Make |~... lines be Dotsies + Styles.apply("^ *\\(|~\\)\\([^\n~]+\\)\\(~?\\)", nil, :quote_heading_pipe, :dotsies, :quote_heading_pipe) + + # |#... invisible + Styles.apply("^ *\\(|[#*].*\n\\)", nil, :hidden) + + Styles.apply "^ *|\\^.*\n", :quote_medium + + Styles.apply("^ *\\(<\\) \\(.*\n\\)", nil, :ls_bullet, :ls_quote) # <... + + # Experimental + Styles.apply("^ *\\(<[a-z]+\\) \\(.*\n\\)", nil, :ls_bullet, :ls_quote) # <... + + + Styles.apply "^\\(Upvote\\) \\(for @[a-z]+\\.\\)$", nil, :notes_green, :ls_quote + Styles.apply "^\\(Upvote\\) \\(and comment for @[a-z]+:\\)$", nil, :notes_green, :ls_quote + Styles.apply "^Comment for @[a-z]+:$", :ls_quote + + end - Styles.apply "^ *|\\^.*\n", :quote_medium end # Startup def self.init - return if ! $el - $el.defun(:notes_mouse_meta_click, :interactive => "e") do |e| $el.mouse_set_point(e) View.insert "hey" end $el.defun(:notes_mouse_double_click, :interactive => "e") do |e| - next Launcher.insert "h" if Line =~ /^$/ # If blank line, launch history - Launcher.go_unified + Launcher.double_click end $el.defun(:notes_mouse_toggle, :interactive => "e") do |e| $el.mouse_set_point(e) - Notes.mouse_toggle + Notes.mouse_click + + false end $el.defun(:notes_mode, :interactive => "", :docstring => "Apply notes styles, etc") {# |point| @@ -608,18 +570,26 @@ def self.init FileTree.apply_styles Notes.apply_styles FileTree.apply_styles_at_end + $el.use_local_map $el.elvar.notes_mode_map + Xsh.chdir_when_xsh_session + View.tab_width 12 } $el.el4r_lisp_eval %q< (progn (add-to-list 'auto-mode-alist '("\\\\.notes\\\\'" . notes-mode)) + (add-to-list 'auto-mode-alist '("\\\\.md\\\\'" . notes-mode)) (add-to-list 'auto-mode-alist '("\\\\.xik\\\\'" . notes-mode)) - (add-to-list 'auto-mode-alist '("\\\\.wik\\\\'" . notes-mode))) + (add-to-list 'auto-mode-alist '("/conf/.*\\\\.conf\\\\'" . notes-mode)) + ) > end - def self.mode + def self.mode options={} $el.notes_mode + + View.wrap(:off) if options[:wrap] == false + nil end def self.enter_label_bullet @@ -630,20 +600,37 @@ def self.enter_label_bullet def self.enter_junior + prefix = Keys.prefix :clear=>1 + Move.to_end if Line.before_cursor =~ /^ +$/ # If at awkward position, move cursor = View.cursor line = Line.value indent = Line.indent line - pipe = line =~ /^ *([|#])/ ? $1 : "" + + prepend = line[/^ *([!|:#] +)/, 1] + prepend ||= "" + if Line.left == cursor || Line.right == cursor # If beginning or end, leave current line alone Move.to_end else # In middle of line Deletes.delete_whitespace end - return View.<< "\n#{line[/^[ |#]*/]} " if pipe - View << "\n#{indent}#{pipe} " + if prepend =~ /^[!|:] / + prefix = prefix == :u ? nil : :u + end + + prepend << " " if prefix == :u + prepend = " #{prepend[/. /]}" if prefix == nil # up+, so make quote char be indented + + prepend = " " if prefix == 0 # up+ means to not carry over prepends etc. + prepend = " | " if prefix == 1 + prepend = " : " if prefix == 2 + prepend = " # " if prefix == 3 + prepend = " - " if prefix == :- + + View << "\n#{indent}#{prepend}" end def self.bullet bullet_text="- " @@ -651,11 +638,11 @@ def self.bullet bullet_text="- " if prefix == :u Move.forward if Line.at_right? - return Tree.collapse + return Tree.collapse_upward end if prefix == :uu - return Tree.collapse :replace_parent=>1 + return Tree.collapse_upward :replace_parent=>1 end line = Line.value @@ -664,7 +651,7 @@ def self.bullet bullet_text="- " if line.present? # If non-blank line column = View.column - Move.to_end if line =~ /^ / && column <= indent.length # If just entered a bullet, go to end first + Move.to_end if line =~ /^ / && (column-1) <= indent.length # If just entered a bullet, go to end first Move.to_end if line =~ /^>/ || Line.at_left? # If line is heading, or if at beginning of the line if View.cursor != Line.right @@ -673,7 +660,7 @@ def self.bullet bullet_text="- " View.insert "\n" # Do simple case if quoted - return View.<<("#{line[/^[ |#]*/]} - ") if line =~ /^ *[|#]/ + return View.<<("#{line[/^[ :|#]*/]} - ") if line =~ /^ *[:|#]/ # Do simple case if on heading return View.<<("- ") if line =~ /^>/ @@ -717,33 +704,45 @@ def self.bullet bullet_text="- " end - def self.mouse_toggle + def self.mouse_click + + # Clicked "+" or "-" on a "+ foo" line, so expand or collapse... - # If next line is indented more, kill children - # If starts with plus or minus, and on plus or minus, launch if Line.matches(/^\s*[+-]/) and View.char =~ /[+-]/ - plus_or_minus = Tree.toggle_plus_and_minus - if ! Tree.children? - if FileTree.dir? or ! FileTree.handles? # If on a dir or code_tree - Launcher.launch_unified - else # If on a file in a FileTree - FileTree.enter_lines - end + children = Tree.children? + + # No childen and on file, so do "* outline"... - else # If -, kill under - Tree.kill_under - Line.to_beginning + if FileTree.handles? && !FileTree.dir? + return FileTree.enter_lines end + + # Childen, so handle just like double-click... + + return Launcher.launch_or_collapse # if Tree.children? + + end + + # Clicked at same point as last click, so treat as double-click... + + @@last_click_position ||= nil # Undefined when the 1st time run + @@last_click_time ||= 0 # Undefined when the 1st time run + if @@last_click_position == View.cursor && (Time.now.to_f - @@last_click_time < 0.7) + @@last_click_position, @@last_click_time = nil, 0 + Launcher.double_click + else + + # Store position of last click + + @@last_click_position, @@last_click_time = View.cursor, Time.now.to_f end + end - # - # Returns an instance of Section representing the block the point - # is currently in. - # - # def self.current_section regex="^[|>]\\( \\|$\\)" - def self.current_section regex=nil + # Remove this > useless creation of objects. + # Use View.block_positions instead, and just pass array of the 3 positions. + def self.current_section_object regex=nil regex = self.heading_regex if ! regex || ! regex.is_a?(String) left, after_header, right = View.block_positions regex @@ -751,59 +750,56 @@ def self.current_section regex=nil end def self.to_html txt + raise "Todo > use Html.to_html instead?!" txt = txt. gsub(/^> (.+)/, "

\\1

"). gsub(/(^|[^\n>])$/, "\\0
") end - def self.as_nav + def self.as_file options={} prefix = Keys.prefix :clear=>true - txt = "" - if prefix == :u || prefix == :uu - txt = Code.grab_containing_method - end label = nil - if prefix == 9 - label = Keys.input :prompt=>"label: ", :timed=>1 + + Effects.blink(:what=>:line) + + was_visible = View.file_visible? Bookmarks['%links'] + + if prefix == :u || options[:prompt_for_label] + label = Keys.input :prompt=>"label: " label = "do" if label.blank? label = Notes.expand_if_action_abbrev(label) || label + label.strip! + + # Only add ":" at end of label if not "!" at end + label = "#{label}:" if label !~ /[:!)]$/ prefix = nil end - if prefix == :uu # up+up means add function and line - txt << "\n#{Line.value}" - elsif prefix != :u - txt = View.txt_per_prefix prefix, :selection=>1, :default_is_line=>1, :just_txt=>1 - end + txt = View.txt_per_prefix prefix, :selection=>1, :default_is_line=>1, :just_txt=>1 + + orig = Location.new # If file has bullet or ends with slash, grab path + options = {} + keep_tweeking = true - if ! prefix && FileTree.handles? # Grab tree - txt = Tree.ancestors_indented :just_sub_tree=>1 - txt.sub! /^ /, ' - ' - keep_tweeking = false - end file = View.file - orig = Location.new - if keep_tweeking - if Search.fit_in_snippet(txt) # Insert it in existing tree if there - View << " - #{label}:\n" if label - return orig.go - end - else - View.layout_files :no_blink=>1 + if Search.try_merging_link(txt, :label=>label) # Insert it in existing tree if there + + orig.go if was_visible # Go to original place, if :n was already visible + return end # Make it quoted, unless already a quote if keep_tweeking && (txt !~ /\A([+-] |\/)/ || txt !~ /^ +/) # If txt isn't already a tree, make it one txt = FileTree.snippet :txt=>txt, :file=>file - txt.sub! /^ /, " - #{label}:\n " if label + txt.sub! /^ /, " - #{label}\n " if label end # Else, add it to top... @@ -811,7 +807,7 @@ def self.as_nav View.to_highest if prefix == 6 # Only move under existing >...: header if 6+ - Line.next if Line =~ /^> .*:$/ # If at >...: line, move after it + Line.next if Line =~ /^> .*:$/ # If at >...: line, move after it end if prefix == 8 @@ -823,35 +819,121 @@ def self.as_nav result = ">\n#{txt}\n" end + modified = View.modified? + View.<< result, :dont_move=>1 + Line.next 3 - orig.go + # If wasn't modified, save > since we may do ^; or other key that looks at :n on disk + + DiffLog.save :no_diffs=>1 if ! modified + + orig.go if was_visible # Go to original place, if :n was already visible end - def self.as_todo + def self.as_task options={} + prefix = Keys.prefix :clear=>1 + # Nothing was selected... + txt = nil + # Something was selected, so use it... + + selection = View.selection + # If method, make it Foo.bar method call line = Line.value - if View.file =~ /_spec.rb/ && line =~ /^ *(it|describe) / + if ! selection && View.file =~ /_spec.rb/ && line =~ /^ *(it|describe) / return Specs.enter_as_rspec end buffer_name = $el.buffer_name - file_name = View.file_name - path = Xiki.path rescue nil + file, file_name = View.file, View.file_name + path = Tree.path rescue nil + + + if options[:link] # || prefix == :u # up+, so add as quoted, with "- todo!" + + # N+as+path, so just navigate... - if prefix.nil? # So 1+ or numeric prefix just grab normally - if buffer_name == "*ol" # Make it into "foo = bar" format + if prefix.is_a?(Fixnum) + + # Go to ^n + View.open "%n" + # Jump to nth quote + View.to_top + prefix.times do + Search.forward "^ +:" + end + + # Expand + Effects.blink(:what=>:line) + Launcher.launch + + return "" + end + + + # No prefix, so prompt for label and add to ^n... + + txt = FileTree.snippet :txt=>Line.value, :file=>file + txt.sub! /^/, ">\n" + + + label = Keys.input :prompt=>"Enter label: " + label = Notes.expand_if_action_abbrev(label) || label + label = "#{label}:" if label =~ /\w$/ + + # Only add ":" at end of label if not "!" at end + txt.sub!(/:/, "- #{label}\n :") if label.any? + + elsif ! selection && prefix.nil? # No 1+ or numeric prefix just grab normally + + if buffer_name == "ol" # Make it into "foo = bar" format txt = line[/\) (.+)/, 1] txt.sub!(": ", " = ") if txt txt ||= line[/ *- (.+?) /, 1] - elsif path && path.last =~ /(\w+)\.rb\/\| *def ([\w\.?]+)/ + elsif file =~ /\.xiki$/ + + # File is in notes, or is linked to from notes, so use 'topic > Heading' syntax" + + if File.dirname(file) == File.expand_path("~/xiki") || self.expand_link_file(file) + + # On note "> Heading", so add as "foo > bar" + + # Only do something if > no ancestors + if path.length == 1 + + path = Path.split path[0] + + if path.length == 1 && path[0] =~ /^>/ + + # "> Foo", so add "filename > Foo" + topic = File.basename(file, ".*").gsub("_", " ") + heading = line + + # "> .Foo" > so convert to path + if heading =~ /^> \./ + heading = Notes.heading_to_path heading + txt = "#{topic}/#{heading}" + else + txt = "#{topic} #{heading}" + end + + elsif path.length == 2 && path[1] =~ /^>/ + txt = "#{path[0].sub(/^-/, "")} #{path[1]}" + end + + end + end + + + elsif path && path.last =~ /(\w+)\.rb\/: *def ([\w\.?]+)/ clazz = $1 method = $2 clazz = TextUtil.camel_case clazz if method.slice! /^self\./ @@ -864,20 +946,37 @@ def self.as_todo txt = "#{clazz}.#{method}" - elsif line =~ /^ *\|/ # Make it into Foo.bar format - txt = line.sub /^ *\| ?/, '' + elsif line =~ /^ *[|:]/ # Make it into Foo.bar format + txt = line.sub /^ *[|:][+-]? ?/, '' elsif FileTree.handles? txt = Tree.dir elsif line =~ /(^ *[+-] |\/$)/ # Make it into Foo.bar format - txt = Xiki.path.last + txt = Tree.path.last + elsif line =~ /^ *Ol.>> (.+?) #> (.+)/ # Make it into Foo = bar format + txt = "#{$1} = #{$2}" + elsif line =~ /^ *Ol.+, (.+?) #> (.+)/ # Make it into foo = bar format + txt = "#{$1} = #{$2}" end + + + elsif selection && prefix == 1 + # 1+as+task, so make lines ":- ..." + txt = selection.gsub(/^/, ":-") + + elsif selection && prefix == 2 + # 2+as+task, so make lines ":+ ..." + txt = selection.gsub(/^/, ":+") + end + # No text yet, so maybe there's a selection, or a numeric prefix... + + txt ||= selection txt ||= View.txt_per_prefix(prefix, :selection=>1, :just_txt=>1, :default_is_line=>1) + txt.strip! if txt =~ /\A.+\n\z/ # Strip when only 1 linebreak - options = prefix == :uu ? {:append=>1} : {} - Search.move_to "$t", txt, options + Search.move_to "%n", txt, options end @@ -916,7 +1015,7 @@ def fade_in Effects.glow :fade_in=>1, :what=>[left, right] end - def delete_content + def delete $el.delete_region left, right end @@ -938,10 +1037,10 @@ def hide_text @body_overlay.invisible = true end - # cuts the block, and stores it in archive.file.notes - # example: ruby.notes -> archive.ruby.notes + # cuts the block, and stores it in archive.file.xiki + # example: ruby.xiki -> archive.ruby.xiki def archive - delete_content + delete filename = 'archive.' + $el.file_name_nondirectory(buffer_file_name) timestamp = "--- archived on #{Time.now.strftime('%Y-%m-%d at %H:%M')} --- \n" $el.append_to_file timestamp, nil, filename @@ -949,7 +1048,8 @@ def archive end end - def self.enter_note + def self.enter_note options={} + # If on blank line, just insert it indent = "" if ! Line.blank? @@ -960,22 +1060,25 @@ def self.enter_note $el.open_line 1 end - Line << "#{indent}- !" + Line << "#{indent}- )" Move.backward - txt = Keys.input :timed=>1, :prompt=>'Enter a character: ' + txt = options[:txt] || Keys.input(:prompt=>'Your note (or a letter as a shortcut): ') + txt.strip! - expanded = Notes.expand_if_action_abbrev txt + expanded = self.expand_if_action_abbrev txt - if ["borrow"].member?(expanded) # If "borrow", change ! to : + # If ends in colon, remove ")" + + if expanded =~ /[!:]$/ View.delete :char - View.<< ":", :dont_move=>1 end View << (expanded || txt) - # If wasn't expanded prepare to edit - ControlLock.disable if expanded == txt + if txt =~ /^[0-9]$/ + Line << " " + end if expanded # Do nothing @@ -988,249 +1091,1870 @@ def self.enter_note nil end - def self.drill_split args - items = [[], [], []] # file, heading, content + # Split into sections + def self.split txt - items_index = 0 # Increase it as we move through - args.each do |arg| - # If content, they'll always be content - next items[2] << arg if items_index == 2 + # Replace in special char to be marker + # Might cause problem with utf strings, if this character is found? > Maybe mak sure they're utf encoded? + txt = txt.gsub(/^>($| )/, "\c4\\0") + sections = txt.split("\c4") - items_index = 1 if arg =~ /^> / # If >..., bump up to headings - items_index = 2 if arg =~ /^\|/ # If |..., bump up to content + # Remove blank string at beginning + sections.shift + sections - items[items_index] << arg - end - - items end - # - # Makes .notes file navigable as tree. - # - # Example usage: - # - # - /projects/xiki/menu/ - # - accounts.rb - # | class Accounts - # | def self.menu *args - # | Notes.drill '$accounts', *args - # | end - # | end - # + # 2015-07-12 > Todo > probably rename this to .expand?! + def self.drill file, *args + # Pull off options, to get prefix + options = args[-1].is_a?(Hash) ? args.pop : {} + prefix = Keys.prefix :clear=>true - file = Bookmarks[file] + option_item = options[:task] - # *args examples: - # args = ["> Arrays", "| [1, 2]"] - # args = ["tech", "ruby", "> Arrays", "| [1, 2]"] + username = XikihubClient.username - # Divide up path. Could look like... - # dir/dir/> heading/| contents + line_orig = $el ? Line.value : nil - # Pull off contents if any (quoted lines) + # Text passed instead of the filename, so set the name + if ! file + file_exists = nil - # This file_items stuff might not be necessary after the unified refactor. Delegating to the expander should take care of dirs. + elsif file =~ /\n/ + file_exists = true + txt = file + name = options[:name] - file_items, heading, content = Notes.drill_split args + else - heading = heading.any? ? heading.join("/") : nil - content = content.any? ? content.join("/") : nil - file << file_items.join("/")+"/" if file_items.any? + # File is a word, so add default path for them + if file =~ /^[a-z]/i + # "* share", so use xikihub path + file = option_item == ["share"] ? + "~/xiki/hub/#{username}/#{file}.xiki" : + "~/xiki/#{file}.xiki" + end + name = file[/(\w+)\.xiki$/, 1] - # Check for whether foo.notes or foo/foo.notes exists for this path... + file = Bookmarks[file] + file = File.expand_path file + # .link file, so follow link (use path inside it) + file = Notes.expand_if_link file - if file =~ /\/$/ && File.exists?(found = file.sub(/\/$/, ".notes")) - file = found - end + if file =~ /\/$/ && File.exists?(found = file.sub(/\/$/, ".xiki")) + file = found + end - if file =~ /\/$/ && File.exists?(found = file.sub(/\/$/, "/index.notes")) - file = found - end + if file =~ /\/$/ && File.exists?(found = file.sub(/\/$/, "/menu.xiki")) + file = found + end - # Somewhere in here, make it look for index.html files as well - if file_items.any? && File.exists?(found = "#{file}#{file_items[-1]}.notes") - file = found - end - # If it's a dir, show dirs inside... + # Somewhere in here, make it look for menu.html files as well - if File.directory? file + # If it's a dir, show dirs inside... - # If no topic, just show all dirs + if File.directory? file - entries = Dir.new(file).entries - entries = entries.select{|o| o =~ /^\w/} - entries.each{|o| o.sub! /\..+/, ''} - return entries.map{|o| "#{o}/"} - end + # If no topic, just show all dirs + entries = Dir.new(file).entries + entries = entries.select{|o| o =~ /^\w/} + entries.each{|o| o.sub! /\..+/, ''} + return entries.map{|o| "#{o}/"} + end - # If a file... - if file =~ /^\$(\w+)/ # If bookmark that wasn't found, complain - bm = $1 - return "| Set the following bookmark first. Then you'll be able to use this menu to\n| browse the file. The file should have '> ...' headings.\n\n@ $#{bm}\n" - end + # If a file... - # docs/, so output docs string... + if file =~ /^\$(\w+)/ # If bookmark that wasn't found, complain + bm = $1 + return "| Set the following bookmark first. Then you'll be able to use this menu to\n| browse the file. The file should have '> ...' headings.\n\n@ :#{bm}\n" + end - if heading == "docs" - message = " - > Summary - | Convenient way for browsing the headings in this file: - | - - @ #{file} - ".unindent - return message - end + file_exists = File.exists? file - if ! File.exists? file - return " - | File doesn't exist yet, do as+update to create it: - @#{file} - | > Heading - | Stuff - " + txt = file_exists ? File.open(file, 'rb') {|f| f.read} : nil end - txt = File.open(file, 'rb') {|f| f.read} - # /, so show just headings... + # if ! heading && ! content + if args.length == 0 - if ! heading - return View.open file if prefix == "open" # If as+open, just jump there + # Todo later > deal with this: these could be called multiple times for topic - txt = txt.split("\n") - txt = txt.grep /^\>( .+)/ - return "| This file has no '>...' headings:\n@ #{file}" if txt.empty? - return txt.join("\n") #.gsub /^> /, '| ' - end + # ^O on foo, and notes exist, so show option items + return " + * add note + * view source + * web search + " if option_item == [] - heading.sub!(/^\| /, '> ') - escaped_heading = Regexp.escape heading + # /~ or / (and no notes exist), so show 1 option item - # /> Heading, so show text under heading... + if option_item == ["add note"] + return self.add_note_task options + end - if ! content - if prefix == :u || prefix == "open" # If C-u on a heading, just jump there + if option_item == ["view source"] #|| options[:go] # ~ navigate View.open file - View.to_highest - Search.forward "^#{$el.regexp_quote heading}$", :beginning=>1 - View.recenter_top - return + return "" end - txt = self.extract_block txt, heading - ENV['no_slash'] = "1" - return txt.gsub(/^/, '| ').gsub(/^\| $/, '|') - end + if option_item == ["web search"] # ~ navigate + txt = "#{name}".gsub(" > ", " ") + Xiki::Google.search txt, :via_os=>1 + return "" + end - # /> Heading/| content, so navigate or save... + # Render actions as paths - # If C-4, grab text and save it to file / update - if prefix == "update" - # Extract parts from the file that won't change - index = txt.index /^#{escaped_heading}$/ - index += heading.length + headings = self.extract_headings_and_actions txt - before = txt[0..index] + # No headings, so show all content + return Tree.quote txt if headings.empty? - after = txt[index..-1] # Part we're replacing and everything afterward - heading_after = after =~ /^> / - after = heading_after ? after[heading_after..-1] : "" # If no heading, we replace all + return headings + end - # Find heading, if there is one + # Commented out > Don't know when heading would have been ": foo" - content = Tree.siblings :string=>1 # Content we're saving - txt = "#{before}#{content}#{after}" + # Figure out whether args has "> Heading", "content...", etc - DiffLog.save_diffs :patha=>file, :textb=>txt + raise "Notes.drill doesn't know how to deal with being passed content without a heading first" if args.length == 1 && args[0] =~ /\n/ #> "load" + heading = args[0] # Heading might also be just a path + content = args[-1] if args[-1] =~ /\n/ #> nil - # return - File.open(file, "w") { |f| f << txt } + if options[:change_to_heading] || options[:change_to_heading_and_launch] #> continue here!! + # Just change line and return + heading = self.find_heading_from_path txt, args[0] #> ||| - # Revert file if it's open? + Ol "heading", heading #> "> @boo .brown background" - View.flash "- Saved!" - return + # Remove username if any + heading.sub!(/^> \@\w* /, "> ") + + Line.sub!(/.+/, Line.value.gsub(/( *)[+-] (.+)/){"#{$1}#{heading}"}) + Launcher.launch if options[:change_to_heading_and_launch] + + return "" end - # navigate to heading and put cursor on line... - # return - View.open file - View.to_highest - Search.forward "^#{$el.regexp_quote heading}$" - View.recenter_top - Search.forward "^#{$el.regexp_quote content.sub(/^\| /, '')}" - Move.to_axis - nil - end - def self.extract_block txt, heading - txt = txt.sub /.*^#{Regexp.escape heading}\n/m, '' # Delete before block - txt.sub! /^>( |$).*/m, '' # Delete after block - txt # = txt.gsub(/^.?/, "|\\1") # Escape with pipes - end + # /> Heading (or path and possible other paths), so show note content (or navigate to it)... - def self.read_block file, heading - self.extract_block File.read(file), heading - end + # Todo > Hmm > If option_item passed, just delegate on to the action - def self.tab_key - indent = Line.indent(Line.value 0) - Line.sub! /^ */, indent - Line.to_beginning - end + is_heading = args[0] =~ /^> / - @@single_letter_abbrev = { - "b"=>"borrow", - "c"=>"create", - "d"=>"do", - "de"=>"delete", - "e"=>"extract", - "er"=>"error", - "f"=>"fix", - "i"=>"implement", - "p"=>"pass", - "po"=>"port", - "r"=>"rename", - "t"=>"todo", - "u"=>"update", - } + if args.length == 1 || ! is_heading - # If the string is "t" or "i", or a few others, return "todo" or "imprement" etc. respectively. - def self.expand_if_action_abbrev txt - @@single_letter_abbrev[txt] || txt - end + # Heading might be "> Foo" or "foo" + option_item = ["view source"] if options[:go] - def self.do_as_quote - # Make this add or remove quotes - end + # ^O on "> Foo", so show options - def self.from_markdown_format txt - txt = txt.gsub(/^#+/){"#{'>' * $&.length}"} - end + if is_heading + return " + * top + * view source + * web search + ".unindent if option_item == [] - # - # Returns whether notes mode is enabled. - # - def self.enabled? + if option_item == ["make action"] + # Passed programatically by ^G > change but don't run + Line.sub!(/.+/, Line.value.gsub(/( *)> \.?(.+)/){"#{$1}+ #{Notes.heading_to_path $2}"}) + return "" + elsif option_item == ["as action"] + Line.sub!(/.+/, Line.value.gsub(/( *)> \.?(.+)/){"#{$1}+ #{Notes.heading_to_path $2}"}) + Launcher.launch + return "" + elsif option_item == ["top"] + return "- todo > move to top!" + elsif option_item == ['web search'] + txt = "#{name} #{heading}".gsub(" >", "").downcase + Xiki::Google.search txt, :via_os=>1 + return "" + end + end + + # No '~ view source' option_item, so always expand + + # No option item, or option item on "+ action", so expand... + + if (option_item == ["view source"] || options[:go]) && heading =~ /^(@\w* )?[a-z]/ + + heading = self.find_heading_from_path txt, heading + + elsif ! option_item || heading =~ /^(@\w* )?[a-z]/ + + # "> Heading" without option, or "+ action", so extract section + + return if txt.blank? # File doesn't exist, return nil so it'll tell them to type ^O + + txt = self.extract_section txt, heading, options + + return nil if ! txt + + txt.sub! /\n\z/, '' + + # No text for this heading, so assume it wasn't there, so prompt them to enter it... + + # There was some output, so clean it up and return + + if txt.any? + + txt = Tree.pipe "#{txt}\n" + + # Todo > make $ at margins be unquoted + # Expose dollar prompts > and percent > unquote shell prompts + self.expose_some_prompts txt + + return txt + end + + # No output, so continue on and navigate + + end + + # No content, so just navigate... + + View.open file + View.to_highest + + if heading !~ /^>/ + # It's an action, so turn path into a heading + heading = self.find_heading_from_path txt, heading + end + + Search.forward "^#{$el.regexp_quote heading}$", :beginning=>1 + View.recenter_top + return "" + + end + + heading_exists = + if ! txt + false # Contents would have existed if file existed + else + section = self.extract_section txt, heading #> ||| + section.any? + end + + # /> Heading/content, so navigate or save... + + # ~, so show options... + if option_item + + # If more than one item, error out! + + items = options[:items] + return "<* - Collapse this first!" if items && items.length > 2 # They expanded the 'notes don't exist' message + + menu = self.option_items(options.merge( + :context=>:expanded, + :heading_exists=>heading_exists, + :name=>name, + :heading=>heading, + )) + + xik = Xik.new(menu) + + option_item = OptionItems.prepend_asterisk option_item + + + # These'll be needed in case it's "* save" + options[:save_args] = { + :txt=>txt, + :file=>file, + :heading=>heading, + :content=>content, + } + + return xik[option_item, :eval=>options] + end + + # File doesn't exist, so suggest ^n to save + + options[:line_found], options[:no_search] = 9, 1 + return " + - Did you mean Ctrl+T?: + | + | These notes don't exist yet. + | + | You can't navigate to notes that don't exist. + | If you are trying to create a note, you should type + | Ctrl+T and select 'save' instead of expanding. + | + = ok/ + " if ! file_exists + + + # No tasks, so navigate to heading (and move cursor to it)... + + + # In xikihub, so send them contents or checksum... + + orig = View.file + + View.open file #> |||| + + View.to_highest + + found = self.jump_to_heading heading + + # Not found, so show message... + + if heading && ! found + + if options[:inline_warning_messages] + View.open orig + options[:line_found], options[:no_search] = 9, 1 + return " + - Did you mean Ctrl+T?: + | + | This heading doesn't exist yet. + | + | You can't navigate to a heading that doesn't exist. + | If you are trying to create a note, you should type + | Ctrl+T and select 'save' instead of expanding. + | + = ok/ + " + end + return "<*- Heading doesn't exist yet!" + + end + + # Jump to text underneath heading... + + self.jump_to_text_under_heading line_orig + + return "" + + end + + def self.expose_some_prompts txt + + txt.gsub! /^\| ([$%&] )/, "\\1" + txt.gsub! /^\| (<+=? )/, "\\1" + + end + + + def self.jump_to_heading heading + return if ! heading + View.to_highest + found = Search.forward "^#{$el.regexp_quote heading}$" if heading + end + + def self.jump_to_text_under_heading line + View.recenter_top + if line + line.sub!(/^ *[|:].?/, '') + found = Search.forward "^#{$el.regexp_quote line}" if line.any? + Move.to_axis + end + end + + def self.add_note_task options + + self.add_note_prompt #:single_word_topic=>1 + return "" + + end + + + def self.find_heading_from_path txt, target, options={} + + # Extract all "> .foo" headings + + lines = txt.scan(/^> .+/) + + # Find one who's path version matches the target + + found = [] + + target = target.downcase + lines.each do |heading| + + # Ignore "(file option)" at beginning of heading #> "> .test" + path = heading + + + path = self.remove_ignorable_heading_parens(path)#.downcase + + if options[:find_all_usernames] + path = self.heading_to_path path, :remove_username=>1 + + if path == target + found << heading + end + else + path = self.heading_to_path path + if path == target + return heading + end + + # Find one, so check return single exact match #> "load" + + end + + end + + return nil if found == [] + + found + + end + + + def self.remove_ignorable_heading_parens path + return nil if ! path + path.sub(/^> \(file option\) /, '> ') #> "set desktop image" + end + + def self.extract_section txt, target, options={} + + # Heading isn't "> ...", so get heading that corresponds to this path #> "@ private and shared" + if target !~ /^> / #> ||||- + target = self.find_heading_from_path txt, target #> ||| + end + + return nil if ! target + + # Delete everything before and after the target section #> nil + + txt = txt.dup + before = txt.slice!(/.*^#{Regexp.escape target}\n/m) # Delete before block + + # Section doesn't exist > so return nil to indicate that + return nil if ! before + + # Todo > set :heading_line_number + # Pass in options, so we can set it there? + options[:heading_line_number] = before.scan(/\n/).length #> 4 + + options[:heading_found] = target + + txt.sub! /^>( |$).*/m, '' # Delete after block + txt + end + + def self.read_block file, heading + self.extract_section File.read(file), heading + end + + def self.tab_key options={} + + Keys.remember_key_for_repeat(proc {Notes.tab_key}) + + # Selection is active, so just indent it... + + return Code.indent_to(options) if View.selection? + + path = Tree.path + first = Path.split path[0] + last = Path.split path[-1] + line = Line.value + + # Blank line or just whitespace, so just make indent like previous line + + if line =~ /^ *$/ + + indent = Line.indent + indent_prev = Line.indent(Line.value 0) + + # Indented lower, so clear indent + if indent.length > indent_prev.length + Line.sub! /.*/, '' + + # Indented the same, so indent 2 lower + elsif indent.length == indent_prev.length + Line.sub! /^/, ' ' + + # Indented less, indent same + else + Line.sub! /.*/, indent_prev + end + + Line.to_right + + return + end + + + + + # $... path, so collapse up to parent shell command, if there is a parent... + + if path[-1] =~ /^ *\$( |$)/ + + if path.length > 1 + line = Line.value + Launcher.collapse_items_up_to_dollar line.sub(/^ */, '')#, :or_root=>1 + end + + return + end + + + + # |... quote under topic, so make quote replace topic + + if last[-1] =~ /\n/ + quote = last[-1] + return self.zoom_to_quote #quote + end + + # item under "f..." (auto-complete), so collapse upward and replace parent... + + if last.length == 2 && last[0] =~ /\A[a-z0-9][a-z0-9 ]*\.\.\.\z/ + # foo..., so collapse to parent + bounds = Tree.sibling_bounds(:must_match=>"[a-z]") + View.delete bounds[0], bounds[-1] + Move.up + Line.gsub! /.+/, last[-1] + return + end + + # Topic, so show completions or collapse in correct way... + + if Topic.matches_topic_syntax?(last[0]) + + # Tab on " = foo", so remove equals and replace parent + if line =~ /^ +=/ + Tree.collapse_upward :replace_root=>1 + return Launcher.launch + end + + # A single topic, so do tab completion + return Topic.tab_key(:flashing_ok=>1) if last.length == 1 + + # Tab on "topic\n > heading", so collapse into parent adding space + if last.length == 2 && last[-1] =~ /^>/ + Tree.collapse_upward :add_between=>" " + return Launcher.launch + end + + # commands/foo, so just collapse and don't launch... + + if last[0] == "xiki" + Tree.collapse_upward :replace_root=>1 + return + end + + # foo/bar, so collapse up to parent and run... + + if last.length > 1 && Topic.matches_topic_word?(last[1]) + + # Add tab at end if not one there + + Tree.collapse_upward :slash_separator=>1 + + return Launcher.launch + end + + + # Probably a topic under a topic, so just launch it to do what tab completion does + + return Launcher.launch(:tab=>1) #> | + + end + + # "$ command" nested under topic... + + if path.length == 2 && Topic.matches_topic_syntax?(first[0]) && last[0] =~ /^\$ / + + # Tab on "topic\n $ command", so collapse into parent... + + if first.length == 1 + Tree.collapse_upward :replace_parent=>1 + return Launcher.launch + end + + # Tab on "topic\n > Heading\n $ command", so collapse into parent... + + if first.length == 2 && first[1] =~ /^> / + Tree.collapse_upward :replace_parent=>1 + Line.to_left + Tree.collapse_upward :replace_parent=>1 + return Launcher.launch + end + + end + + # Something on the line besides whitespace... + + at_right = Line.at_right? + + # Cursor at right, so just expand... + + return Launcher.launch(:tab=>1) if at_right + + indent = Line.indent line + + # No indent, so do normal expand... + + return Launcher.launch if ! indent.any? + + + # Cursor not at right and line indented, so collapse into parent and expand... + + Tree.collapse_upward + + # Original line was "=...", so replace parent + + if line =~ /^ *=/ + line.sub!(/^ /, '') # Indent one line less + line.sub!(/^= ?/, '') # Remove "= " if at left margin + Line.sub!(/.*/, line) + end + + Launcher.launch + + # It's a =... line, so delete parent + + end + + + # If the string is "t" or "i", or a few others, return "todo" or "imprement" etc. respectively. + def self.zoom_to_quote #quote=nil + + quote = Tree.siblings + quote = quote.join("\n") + + quote.gsub!(/^\| ?/, '') # Remove quotes + + # grab_target_view var set, so jump back to where ^n was done and insert... + + return if Grab.to_grab_target_view quote + + # Collapse up to left margin or "="... + Tree.to_root + + Tree.collapse + Line.sub! /.*/, quote + "" + end + + def self.expand_if_action_abbrev txt + @@single_letter_abbrev[txt] || txt + end + + @@single_letter_abbrev = { + " "=>"", + "p"=>")", + "b"=>"borrow:", + "o"=>"original:", + "m"=>"mock:", + "g"=>"given:", + "re"=>"reshuffle:", + "w"=>"works:", + + "t"=>"try", + "tt"=>"try > ", + "ss"=>"should > ", + + "s"=>"save!", + "e"=>"!", + "a"=>"add!", + "ch"=>"check!", + "cr"=>"create!", + "d"=>"do!", + "de"=>"delete!", + "er"=>"error!", + "f"=>"fix!", + "fa"=>"favorite!", + "fi"=>"finish!", + "i"=>"implement!", + "r"=>"rename!", + "to"=>"todo!", + "te"=>"test!", + "tem"=>"temp!!!!!!!!!!!!!", + + "pr"=>"problem here!!!", + + "c"=>"continue here", + "c!"=>"continue here!", + "cc"=>"continue here > ", + "u"=>"update!", + "1"=>"1", + "2"=>"2", + "3"=>"3", + "4"=>"4", + "5"=>"5", + } + + def self.do_as_quote + # Make this add or remove quotes + end + + def self.from_markdown_format txt + txt = txt.gsub(/^[#]+/){"#{'>' * $&.length}"} + end + + # Returns whether notes mode is enabled. + def self.enabled? $el.current_local_map == $el.elvar.notes_mode_map end + # Mapped to open+note. + # Prompts for some chars, and opens "@notes/foo" where "foo" + # matches your chars. Matches are determined by Keys.fuzzy_filter(). + def self.open_note options={} + + open_or_insert = options[:insert] ? Launcher.method(:insert) : Launcher.method(:open) + + # Get user input... + + keys = Keys.input :optional=>1, :message=>"Which note? (Type a key or two): " + return open_or_insert.call "notes/" if ! keys + + # Get possible matches - files in ~/notes without extensions... + + files = Dir.new(File.expand_path("~/xiki/")).entries.grep /\A[^.]/ + files.map!{|o| o.sub /\..+/, ''} + + # First do a couple hard-coded mappings + if shortcuts = {"r"=>"ruby", "e"=>"elisp", "j"=>"javascript"}[keys] + found = shortcuts if files.member?(shortcuts) # Only use if it's in the list of notes we actually have + end + + # Narrow down by input + + found ||= Keys.fuzzy_filter files, keys + + return open_or_insert.call "notes/" if ! found + + open_or_insert.call ":#{found}" + + nil + end + + def self.open_todo options={} + + prefix = Keys.prefix :clear=>1 + + file = Bookmarks[options[:bookmark] || "%n"] + + FileUtils.mkdir_p File.dirname(file) + + View.open file, :stay_in_bar=>1 + + # up+, so make heading... + + if prefix == :u + View.to_highest + Notes.insert_heading :prefix=>:u + return + end + + end + + def self.append_to_section txt + + orig = View.cursor + Search.forward "^>", :beginning=>true + View << ">>\ntxt\n" + View.cursor = orig + + end + + # Delegate to View. Should probably move .block_positions into notes.rb. + def self.block_positions *args + View.block_positions *args + end + + def self.next_marker options={} + + times = options[:nth] || Keys.prefix_n(:clear=>1) + + # Numeric prefix, so go to top, then run that many times + + View.to_top if times + times ||= 1 + + found = nil + times.times do + # Move forward if at beginning of line, so we don't find the one on the current line + Move.forward if Line.at_left? && ! View.at_top? + # found = Search.forward "^ *[+-] [a-zA-Z0-9 +'.>_-]*)\\($\\| \\)", :beginning=>true + # New behavior > only go to ones on line by themselves + # Marker regex + found = Search.forward "^ *[+-] [a-zA-Z0-9 +'\"!?*~^.,>_-]*)$", :beginning=>true + end + + return Move.backward if ! found + + # Label for the next line, so move to next line + self.next_line_when_marker + end + + def self.next_line_when_marker + # Marker regex + Line.to_next if Line[/^ *[+-] [a-zA-Z0-9 +'"!?*~^.,>_-]*\)$/] # ' + end + + def self.extract_markers options={} + + txt = "" + + file, limit, indent = options[:file], options[:limit], options[:indent] + + path = View.as_file_or_temp_file(:file=>file) + extract_from_next_line = nil + label = nil + + IO.foreach(path, *Files.encoding_binary) do |line| + break if limit <= 0 + + # The previous line was a blank label, ... + + if label == "" + label = line[/\w.*/] + next if ! label + label.sub! /\/$/, '' # No slashes at end + limit -= 1 + txt << "#{indent}+ #{label}\n" + label = nil + next + end + + # Marker regex > Label regex + label = line[/^ *[+-] ([a-zA-Z0-9 +'"!?*~^.,>_-]*)\)$/, 1] + next if ! label + + if label == "" + label = line[/[.]?\w.+/] # - ) foo, so use 1st word as label... + label.sub! /\/$/, '' if label # No slashes at end + next label = "" if ! label + end + + txt << "#{indent}+ #{label}\n" + limit -= 1 + end + + txt + + end + + + def self.zoom + ignore, left, right = View.block_positions "^>" # Regex works with >\n lines + $el.narrow_to_region left, right + end + + + def self.heading + # Move to heading if there + cursor = View.cursor + found = Search.backward "^>" + return if ! found + + # Return heading and move back + line = Line.value + View.cursor = cursor + line + end + + + # Grabs the command out of our heading, if its format is "> foo/" or "> foo/ > something". + def self.command_heading options={} + + heading = nil + + # :check_current_line, so use current line if it's a heading... + + if options[:check_current_line] + line = Line.value + heading = line if line =~ /^>/ + end + + # Grab heading from above + + heading ||= self.heading + if heading =~ /^> (.+?)\/:?($| > )/ # Heading above, so return it + return $1 + end + + if heading =~ /^> =(.+?):?($| > )/ # Heading above, so return it + return $1 + end + + nil + end + + + # Usually just inserts linebreak, but does extra stuff when return on shell prompt + # line for the first time, or when typing on quotes with a quoted line following + def self.return_wrapper + + # Text selected, so delete it and insert return... + + if selection = View.selection + View.delete *View.range + View << "\n" + return + end + + line = Line.value + + # Cursor on shell prompt line, after the prompt, so maybe show message + if line =~ /^ *\$ / && View.column > line.index("$") + # Show tip about Ctrl+X vs Ctrl+G for shell prompt, unless already shown + return if Shell.maybe_show_shell_prompt_keys_tip + end + + Keys.remember_key_for_repeat(proc {Notes.return_wrapper}) + + # Add a linebreak + + # Remove trailing space if any after pipe + Line.sub!(/ $/, '') if line =~ /^ *\| +/ + + + next_line = Line.value 2 + indent = Line.indent next_line + + Keys.prefix_times do + View << "\n" + end + + + # Current and next line are " | ...", so add quote to this line + return View << "#{indent}| " if Line.at_right? && line =~ /^*\|/ && next_line =~ /^*\|/ + + end + + + # Adds section to .xiki file on disk. + # Default is at the top + def self.add_section filename, section + + # File exists, so prepend... + + txt = File.open(filename, 'rb') {|f| f.read} rescue "" + txt = "#{section.strip}\n\n#{txt.strip}\n\n" + File.open(filename, "w") { |f| f << txt } + end + + def self.extract_links options={} + + source_file = options[:file] || Bookmarks["%links"] + + # limit = 45 + # limit = 200 + # limit = options[:limit] || 1000 + limit = options[:limit] || 2000 + + # limit = 5 + result, dir, filename, filename_indent = [], [], nil, nil + heading = nil + + txt = options[:txt] || File.read(source_file) + + txt.split("\n").each do |line| + + indent = Tree.indent_size line + + next if line.blank? + line.sub! "\n", '' + + # .../, so append as dir... + if line =~ /^[^:|#]*\/$/ + dir = dir[0..indent] + next dir[indent] = line + end + + # Filename found, so add to path if it's ours... + + if like_filename = line[/^ *[+-] (.+\w)$/, 1] + + # Construct full path + full = dir.compact + full.map!{|o| Line.without_label :line=>o.strip} + full = full[0..indent] + full[indent] = like_filename + full = full.join + full = Bookmarks[full] + + # Not target file, so clear out filename and skip + + filename = full + filename_indent = line[/^ */] + + + # Heading existed, so add it before file path + if heading && ! options[:filter] + result << [heading] + heading = nil + end + + next + + end + + # Heading, so remember it + if line =~ /^>/ + heading = line + heading = nil if heading =~ /^> ?:?$/ + next + end + + next if ! filename # Can't do anything if no filename yet + + next if options[:filter] && line !~ options[:filter] # Ignore stuff that doesn't match :filter, if passed + + + next if line !~ /^ / # Ignore stuff at beginning of line that isn't a dir + + # It's a quote or bullet, so add it to result... + + # Subtract off of indenting the amount the file was indented + line.sub!(/^#{filename_indent} /, '') + + result << [filename, line] + + limit -= 1 # Todo > when finished > move down to where > we find quotes + break if limit < 1 + end + result + + end + + def self.jump_to_label name + label = Emacs.regexp_quote name + Search.forward "^ *[+-] \\(#{label})\\|)\n[^a-zA-Z_]*#{label}/?$\\)", :beginning=>1 + end + + # Notes.heading_to_path "> Foo bar > bah" + # "foo bar bah" + def self.heading_to_path heading, options={} + + path = heading.sub(/^> /, '') + path.downcase! + + # Remove username + + user = path.slice! /^\@\w* / + + path.gsub!(/['"()]/i, '') # Remove apostrophes and certain other characters + path.gsub!(/[^a-z0-9]/i, ' ') # Turn other characters into space + path.gsub!(/ +/, ' ') # Collapse subsequent punctuation into one space + path.sub!(/ +$/, '') # Remove spaces from end + + path.strip! + + # :url, so use dashes instead of spaces + path.gsub!(" ", "-") if options[:url] + + # Put @user back on if was there + if user && ! options[:remove_username] + path = "#{user}#{path}" + end + + path + end + + + def self.add_note_prompt options={} + + txt = %` + > My note + | Type Ctrl+T after typing or pasting your note here. + | (Optionally change "My note" first) + | + `.unindent + + # Add "+ save" and "+ share" if in noob mode + + # :shell, command passed, so use it in note... + + top_lines = "" + top_lines_length = (top_lines.scan(/\n/) || "").length + 1 + + insert_options = {:no_search=>1} + insert_options[:following] = 1 if options[:no_indent] + + Tree.<< txt, insert_options + + Line.next; Line.to_beginning + # Make green background for hints > "Note heading" and "note text" + cursor = View.cursor + Overlay.face :diff_green, :left=>View.cursor, :right=>Line.right + Line.next; Line.to_beginning + Overlay.face :diff_green, :left=>View.cursor, :right=>Line.right + View.cursor = cursor + + # Pause until they type any key + key = Keys.press_any_key :message=>"Type any key...." + + # Delete green stuff and add spaces + + View.delete View.cursor, Line.right + Line.next; Line.to_beginning; Move.left + View.delete View.cursor, Line.right + View.cursor = cursor + View.<<(key[0].chr) if key.length == 1 && key[0] >= 32 && key[0] <= 126 + + end + + + # Run when "* add note". + def self.add_note_prompt_shell command + + topic = Topic.shell_command_to_topic command + topic.sub!(/ .*/, '') # Only use first word + + # Open the topic file, and insert there at top + View.open File.expand_path("~/xiki/#{topic}.xiki") + + txt = "> Note name here\n$ #{command}\n\nOptional description here.\n\n\n" + + Move.top + + # Get topic name from command + View >> txt + + # Make green background for > "Optional description" + View.line = 4 + Overlay.face :diff_green, :left=>View.cursor, :right=>Line.right + + # Make green background for > "Note heading" + View.line, View.column = 1, 3 + Overlay.face :diff_green, :left=>View.cursor, :right=>Line.right + + # Pause until they type any key + key = Keys.press_any_key :message=>"Type any key....." + + # Delete green stuff and add spaces + View.line = 4 + Line.delete :leave_linebreak + View.line, View.column = 1, 3 + View.delete View.cursor, Line.right + View.<<(key[0].chr) if key.length == 1 && key[0] >= 32 && key[0] <= 126 + + end + + + def self.save_on_blank options + + task = options[:task] + + # "* save" and was saved already, so just resave + + if task == ["save"] && View.file + return DiffLog.save + end + + # "* share" and was saved already, so just __ + + if task == ["share"] && View.file + return View.flash "Already shared" + end + + + # No username, so say they need to create a username... + + user = XikihubClient.username + if ! user && task == ["share"] + options[:dont_nest] = 1 + View.<< "* share\n :-You need to set up a username before you can share!\n = ok/\n\n", :dont_move=>1 + Line.next 2 + View.column = 3 + + return "" + end + + + # ~ save/topic, so add to file, save, and show message... + + if task.length == 2 && task == ["save"] + + topic = task[1].sub(/^: /, '').downcase + + # Weird characters in topic name, so remove and show validation messages + + if topic !~ /^[a-z][a-z0-9 ]*$/ + topic.gsub! /[^a-z0-9 ]/, "" + View.<< "* save\n |-Topics can only contain letters and numbers. Type Ctrl+O when finished.\n : #{topic}\n\n", :dont_move=>1 + Line.next 2 + Line.to_right + + return "" + end + + topic.gsub!(" ", "_") + + if topic == "" + options[:nest] = 1 + # Fix this > it moves down > use buffer local variable to remember blank lines to put back in + return "<* You must provide a name" + end + + txt = View.txt + txt = "#{txt.strip}\n\n\n" + orig = View.name + topic_file = "#{Dirs.commands}/#{topic}.xiki" + + view_orig, file_orig = View.name, View.file + View.open topic_file + + # Error out if view has unsaved changes... + + if View.modified? + View.open(:txt=> " + > Couldn't save, because #{topic}.xiki has unsaved changes + views/ + - 1. Save your unsaved changes first: + #{topic}.xiki + + - 2. Then go back and try to save again: + #{orig} + + ".unindent, :line_found=>2) + return "" + end + + # Prepend generic heading > if none exists + txt = "> My note\n#{txt}" if txt !~ /\A> ./ + + # Prepend to actual file, and revert to show new addition + View.to_top + all_txt = File.read(topic_file) rescue "" + File.open(topic_file, "w") { |f| f << "#{txt}#{all_txt}" } + Files.revert + View.message " " + + # Delete original view if it was temporary + $el.kill_buffer(view_orig) if ! file_orig + + return "" + end + + + # * share, so save as session and pop up browser... + + if task[0] == "share" + + heading = DiffLog.save_xsh_session :shared=>1 + heading = Notes.heading_to_path heading.sub(/^@ /, ''), :url=>1 + + # Save to remote + XikihubClient.delegate_to_save_async(File.expand_path "~/xiki/sessions.xiki") + + # Open in browser + + # Go to specific note + url = "#{XikihubClient.url}/@#{XikihubClient.username}/sessions/#{heading}" + # Just show in browser + Browser.url url, :os_open=>1 + + return + end + + + # * save, so prompt them to enter command name... + + options[:dont_nest] = 1 + + message = self.share_or_save_prompt_text + + # Add linebreak before if non-blank line above + + txt = "* #{task[0]}\n | #{message}\n : \n\n" + + # Not at top line, and line before is non-blank so add \n before + if View.line > 1 && ! Line.value(0).blank? + View.<< "\n" + Launcher.added_option_item_padding_above = 1 # So it deletes it again + end + + + View.<< txt, :dont_move=>1 + Line.next 2 + View.column = 5 + + View.message " " + + "" + + end + + def self.share_or_save_prompt_text + "Type a topic name, then type Ctrl+O" + end + + def self.save_note_or_share_items options, key=:args + save_note = options[key][-1] == "save" + save_note ? View.delete(Line.left, Line.left(3)) : View.delete(Line.left(0), Line.left(2)) + Move.previous + options[:task] = save_note ? ["save"] : ["share"] + txt = Tree.siblings :string=>1 + options[key][-1] = txt + end + + def self.option_items options + + topic_vs_search = ! options[:delegated_from_search] #> false + + heading = options[:heading] + your_username = XikihubClient.username + + # Context is expanded note + + if options[:context] == :expanded + + save_or_create = "save" + name = options[:name] + name_hyphens = name.gsub('_', '-') + + txt = %` + * #{save_or_create} + ! Notes.save options + * share_placeholder + * web + ! url = "#{XikihubClient.url}/@#{your_username}/#{name_hyphens}/#{Notes.heading_to_path heading, :url=>1, :remove_username=>1}" + ! Browser.url url, :os_open=>1 + ! "" + `.unindent + + # In the future, when no username show "go here to join private beta" teaser + txt.sub!(/^\* web\n.+/m, '') if ! XikihubClient.username + + # Show '~ share' if > new > or not '> @ ...' heading... + # Show '* share' when > My note > or existing and your private or shared... + + # In the future, when no username show "go here to join private beta" teaser + if XikihubClient.username && ( ! options[:heading_exists] || options[:heading] !~ /^> @\w/ ) + txt.sub! "* share_placeholder\n", " + * share + ! Notes.save options + ! "" + ".unindent + else + txt.sub! "* share_placeholder\n", "" + end + + return txt + + end + + if options[:context] == :blank_line + + txt = %` + * save + ! #DiffLog.save + ! Notes.save_on_blank options + ! "" + * share + ! Notes.save_on_blank options + * as xiki topic + ! # "- Todo > Implement > maybe add '* save' at top? > like content+save in untitled view?!" + ! # Notes.as_xiki_file options + ! DiffLog.save_newly_created + ! #"* share\\n | #{Notes.share_or_save_prompt_text}\\n : " + ! #"" + * close + ! View.kill + ! "" + `.unindent + + # We're in a topic file, so suppress "* as topic" + txt.sub!(/^\* as xiki topic\n.+?\n\* /m, '* ') if ! View.file || View.topic_file? + + # We're not in an untitled view or no username, so suppress share + # In the future, when no username show "go here to join private beta" teaser + txt.sub!(/^\* share\n.+\n/, '') if View.file || ! XikihubClient.username + + txt + + end + + end + + + def self.save options={} + + save_args = options[:save_args] + content, txt, heading, file, name = save_args[:content], save_args[:txt], save_args[:heading], save_args[:file], save_args[:name] + + items, task = options[:items], options[:task] + + # Add @ to heading if > '> ~ share' > and > not already there + + if task == ["share"] && heading !~ /^> @/ + heading.sub! /^> /, '> @ ' + end + + # "* save", so replace or add... + + txt_old = (txt||"").dup + + # Replace existing or prepend to top... + + content = Tree.siblings :children=>1 + content.gsub! /^:.*\n/, '' # Don't save :... lines #> "| + Animalzz\n|\n: Your shared version\n" + content.gsub! /^[=|:] ?/, '' + + txt ||= "" # In case new + + self.replace_section txt, heading, content + + diffs = DiffLog.save_diffs :patha=>file, :textb=>txt + + # Save to xikihub (if were editing shared) + if items[0] =~ /^> @ / + XikihubClient.delegate_to_save_async(file) + end + + # Create dir if it doesn't exist + FileUtils.mkdir_p File.dirname(file) + + # Save the actual file + File.open(file, "w") { |f| f << txt } + + return task == ["share"] ? "<* - shared!" : "<* - saved!" + + end + + + # If there's a ~/xiki/foo.link for this file, update its timestamp + # as well (so it appears at the top of list+topics). + def self.update_link_timestamp_if_any filename + + # In the ~/xiki dir, so do nothing + return if filename.start_with? File.expand_path("~/xiki")+"/" + + # Not a .xiki or .xiki file, so do nothing + extension = filename.slice(/\.(notes|xiki)$/) + return if ! extension + + link_filename = self.expand_link_file filename + return if ! link_filename + + # Update timestamp of the .link file + FileUtils.touch link_filename + nil + + end + + + + # Returns path to ^n/foo.link file that links to filename, if there is one. + def self.expand_link_file file + + # Directory, so look for .xiki file in it + if Dir.exists? file + if File.exists?("#{file}menu.xiki") + file = "#{file}menu.xiki" + else + basename = File.basename file + if File.exists?("#{file}#{basename}_menu.xiki") + file = "#{file}#{basename}_menu.xiki" + else + # No matching file in dir + return + end + # Todo > check for dir_name.xiki & dir_name_menu.xiki also + + end + end + + # Look for corresponding .link file (assume it has the same name) + base = File.basename file, ".*" + + link_file = File.expand_path "~/xiki/#{base}.link" + + # Link exists, so return it... + + if File.exists? link_file + txt = File.expand_path File.read(link_file).strip + return link_file if file == txt + end + + # Link exists when no "_menu", so return it... + + link_file_without_menu = link_file.sub(/_menu\.link$/, '.link') + if link_file_without_menu != link_file && File.exists?(link_file_without_menu) + txt = File.expand_path File.read(link_file_without_menu).strip + return link_file_without_menu if file == txt + end + + # Check for .link files named similar to file or dir + + pattern = base + if pattern == "menu" + pattern = File.basename(File.dirname(file)) + Dir[File.expand_path "~/xiki/#{pattern}*.link"].each do |candidate| + txt = File.expand_path(File.read(candidate)).strip + return candidate if file == txt + end + end + + nil + + end + + def self.expand_if_link file + + if file =~ /\.link$/ + file = File.read(file).strip + file = File.expand_path file + end + file + end + + + # Looks at quoted lines where cursor is, and shows green "| > Add heading here" prompt, + # if the first line isn't "| > ...". + def self.prompt_for_heading_if_missing + + indent = Line.indent + + orig = View.cursor # Remember where we started, so we can just come back here if > it's already there + # Don't need > ## Line.previous while Line =~ /^ *\|/ # Move back until |... line + Line.previous while Line.value(0) =~ /^ *\|/ # Move back until above line isn't |... line + + # It's already ">...", so jump back and do nothing + if Line =~ /^ *\| > ./ + View.cursor = orig + return + end + + # Add "^ save\n^ share" back + top = View.cursor + View.cursor = orig + Line << "\n#{indent}^ save\n#{indent}^ share" + + + View.cursor = top + Line.to_left + View.<< "#{indent}| > Type a task name\n", :dont_move=>1 + Line.to_beginning + Move.right 2 + + Overlay.face :diff_green, :left=>View.cursor, :right=>Line.right + + # Pause until they type any key + key = Keys.press_any_key :message=>"Type any key......." + + # Delete green stuff and add spaces + View.delete View.cursor, Line.right + View.<<(key[0].chr) if key.length == 1 && key[0] >= 32 && key[0] <= 126 + + # Indicate that we prompted them (caller will do nothing, so they can type the heading) + return true + + end + + def self.indent_note_under_heading + + # Fake heading + indent = Line.indent + orig = View.line # Remember where we started, so we can just come back here if > it's already there + + # Move up to 1st quoted line + Line.previous while Line.value(0) =~ /^ *\|/ + # Delete quote before heading + Line.to_beginning + $el.delete_backward_char 2 + + # Indent the rest of the lines + Line.next + left = View.cursor + Line.next while Line.value =~ /^ *\|/ + txt = View.delete(left, View.cursor) + View << txt.gsub(/^/, " ") + + View.to_line orig + + end + + + def self.enter_task + path = Clipboard.get("=") + + # Extract topic + topic = path[/^ [+-] ([\w]+)/, 1] + topic.gsub! /_/, ' ' + # Extract task + task = path[/^ : (> .*)/, 1] + Line << "<- #{topic} #{task}" + + end + + # For now, only extracts non-blank ones + def self.extract_headings txt, options={} + + if options[:exclude_just_upvotes] + # Have to read whole file into hash, and look at each body + + hash = Notes.wiki_headings_to_hash(txt) + + # Remove ones that are just upvotes... + + label_regex = XikihubClient.comment_or_upvote_label_regex "\\w+" + hash.delete_if do |k, v| + v =~ /\A\n*#{label_regex}\n*\z/ #> nil + end + + return hash.keys.map{|o| o.sub(/^> /, '')} + end + + result = txt.scan(/^> (.+)/).flatten + + # :paren, so filter out only items with text in parens #> "> Private and shared\nThe shared one\n\n\n> One shared\n+ Animalzz\n\n> Shared and installed\nShared\n\n\n> Private, shared, installed\nShared\n\n\n" + if paren = options[:paren] #> "> Private and shared\nThe shared one\n\n\n> One shared\n+ Animalzz\n\n> Shared and installed\nShared\n\n\n> Private, shared, installed\nShared\n\n\n" + result = result.select{|o| o =~ /^\(#{paren}\) /} + end + + result + end + + def self.diff_headings before, after + + before = Notes.extract_headings before, :exclude_just_upvotes=>1 + after = Notes.extract_headings after, :exclude_just_upvotes=>1 + + create = after - before + delete = before - after + + [create, delete] + end + + + + def self.in_home_xiki_dir? filename + filename.start_with? File.expand_path("~/xiki")+"/" + end + + def self.replace_section txt, heading, section_txt + + # Add linebreaks to end if not enough + section_txt = "#{section_txt.sub(/\n+\z/, '')}\n\n\n" if section_txt[/(\n*)\z/].length < 3 + + # Get location of heading + index = txt ? txt.index(/^#{Regexp.escape heading}$/) : nil + + # If doesn't exist yet, add to top + if ! index + return txt.replace "#{heading}\n#{section_txt}#{txt}" + end + + + index += heading.length + + before = txt[0..index] + after = txt[index..-1] # Part we're replacing and everything afterward + heading_after = after =~ /^>( |$)/ # Get location of heading of following section + # Text to grab after section begins at next heading (if there is one) #> "New\nsection added!" + after = heading_after ? after[heading_after..-1] : "" # If no heading, we replace all #> 0 + + txt.replace "#{before}#{section_txt}#{after}" + end + + + def self.prepend_section txt, section + # Add linebreaks to end if not enough + section = "#{section.sub(/\n+\z/, '')}\n\n\n" if section[/(\n*)\z/].length < 3 + txt.replace "#{section}#{txt}" + end + + + def self.delete_section txt, heading + + # Look for heading + index = txt ? txt.index(/^#{Regexp.escape heading}$/) : nil + + # If doesn't exist, do nothing + return nil if ! index + + before = txt[0..index] + # Intentionally include 1st char of match (for weirdness when [0..0]) then chop off + before.sub! /.\z/, '' #> "" + + after = txt[index+1..-1] # Part we're replacing and everything afterward #> " One\nHey.\n\n> Two\nHey hey.\n" + heading_after = after =~ /^>($| )/ + + # Text to grab after section begins at next heading (if there is one) #> "New\nsection added!" + after = heading_after ? after[heading_after..-1] : "" # If no heading, we replace all #> 0 + + txt.replace "#{before}#{after}" + + end + + + def self.fix_numbers + section = View.block_positions + txt = View.txt section[1], section[2] + i = 0 + txt.gsub!(/^( *)[0-9]+\.( |\n)/) do |match| + i += 1 + "#{$1}#{i}.#{$2}" + end + + orig = Location.new + View.delete section[1], section[2] + View << txt + orig.go + Move.to_end + + end + + + def self.wiki_headings_to_hash txt, options={} + + filter = options[:filter] + + # Reading the file and extract headings and txt's + + chunks = txt.split(/(^>(?:| .*)\n)/)[1..-1] + + # None found (maybe blank file) + return {} if ! chunks + + hash, previous_heading = {}, nil + # Store last heading + + chunks.each do |chunk| + # Heading, so remember and go next + if chunk =~ /\A>(| .*)\n\z/ + previous_heading = chunk.strip + next + end + + # It's a section body > so add to hash using last heading + + # Ignore if blank + next if previous_heading !~ /\w/ + + # Only add if matches the filter + + if filter + next if previous_heading !~ filter + end + + # Ad to hash if doesn't exist yet + hash[previous_heading] ||= chunk + + end + + hash + end + + + def self.blank_out_wrapper_patterns txt + # |:... under shell commands + # |:... by itself + + # txt.gsub!(/t/, "--") + # txt.gsub!(/^[^ \n].*\n \|:.+\n+/, "") + txt.gsub!(/^[^ \n].*\n \|:.+\n/, "\n\n") + + txt.gsub!(/^ *\|:.+\n/, "\n") + + # txt.sub!(/(^[$%&] .+\n \|:.+\n+)+/, '') + + end + + + def self.extract_upvotes command, txt + + headings = Notes.wiki_headings_to_hash txt, :filter=>/\A> [^@\n]/ + + label_regex = XikihubClient.comment_or_upvote_label_regex "\\w+" + + upvotes = [] + headings.each do |heading, txt| + + matches = txt.scan(/#{label_regex}/) + matches.flatten.each do |match| + user = match[/@(\w+)/, 1] + path = Notes.heading_to_path heading.sub(/@ /, ''), :url=>1 + key = "@#{user}/#{command.gsub(" ", "_")}/#{path}" + upvotes << key + end + + end + + upvotes + end + + + # def self.extract_upvoted_headings txt + def self.extract_upvoted_headings section_hash + + label_regex = XikihubClient.comment_or_upvote_label_regex "\\w+" + + headings = [] + section_hash.each do |heading, txt| + next if heading !~ /^> @ / + next if txt !~ /#{label_regex}/ + + headings << heading + end + + headings + end + + + + def self.extract_headings_and_actions txt + + hash = Notes.wiki_headings_to_hash(txt) + headings = hash.map do |heading, txt| + + match = heading.match(/> (@\w* )?(\..+)/) + + next heading if ! match + user, words = match[1..2] + + # Catch case where just upvotes + next heading if Notes.blank_out_upvotes_and_comments(txt) !~ /./ + + + "+ #{user}#{Notes.heading_to_path(words).downcase}" + end + + headings.delete_if{|o| o =~ /^> - /} + + headings.join("\n") + + end + + def self.blank_out_upvotes_and_comments txt + + upvote_regex = XikihubClient.comment_or_upvote_label_regex "\\w+", :upvotes=>1 + comment_regex = XikihubClient.comment_or_upvote_label_regex "\\w+", :comments=>1 + + txt = txt.gsub /#{upvote_regex}/, "" + txt = txt.gsub(/#{comment_regex}\n.*?(\n\n\n|\z)/m) do + "\n" * $&.scan("\n").size + end + + txt + end + + end - Notes.define_styles - Notes.init - Notes.keys # Define local keys + # This only runs in emacs standalone mode + + if $el + Notes.define_styles + Notes.init + Notes.keys # Define local keys + end # TODO - how to turn these on conditionally? # What's the best approach for presentations? @@ -1239,3 +2963,4 @@ def self.enabled? # require 'deck' # Deck.keys :notes_mode_map # Temporary - only when doing presentations end + diff --git a/lib/xiki/core/ol.rb b/lib/xiki/core/ol.rb index 4f7077a5..b040875a 100644 --- a/lib/xiki/core/ol.rb +++ b/lib/xiki/core/ol.rb @@ -1,8 +1,11 @@ # Meant to log short succinct messages to help with troubleshooting # while coding. Log statements hyperlink back to the line that logged it. + +require 'fileutils' + class Ol @@last = [Time.now - 1000] - @@timed_last = Time.now + @@timed_last = {""=>Time.now} @@roots = [] def self.menu @@ -83,7 +86,7 @@ def self.menu - example/ @/tmp/ - nested.rb - | require '/projects/xiki/lib/xiki/ol.rb' + | require '/projects/xiki/lib/xiki/core/ol.rb' | def a | Ol.root | b @@ -103,32 +106,24 @@ def self.menu ` end - # For when the caller constructs what to log on its own. - # Is this being used anywhere? - def self.log_directly txt, line, name=nil - path = name ? "/tmp/#{name}_ol.notes" : self.file_path - self.write_to_file path, txt - self.write_to_file_lines path, line - end - # Called by .line, to do the work of writing to the file # # Ol.log "hey" - def self.log txt, l=nil, name=nil, time=nil, options=nil + def self.log txt, l, options=nil - path = name ? "/tmp/#{name}_ol.notes" : self.file_path + path = self.file_path l_raw = l.dup # If l is array (it's the whole trace), use only first for now l = l[0].dup if l.is_a?(Array) - if l.nil? # If Ol.log "foo" called directly (probably never happens + if l.nil? # If Ol.log "foo" called directly (probably never happens) return self.line(txt, caller(0)[1]) end heading = nil - if self.pause_since_last? time + if self.pause_since_last? # time # If n seconds passed since last call heading = "\n>\n" @@ -146,7 +141,7 @@ def self.log txt, l=nil, name=nil, time=nil, options=nil l_raw : caller(0)[3..-1] # Omit Ol calls - root_offset = self.nesting_match trace + root_offset = options[:indent] || self.nesting_match(trace) root_offset_indent = " " * (root_offset||0) # Add this to @@roots if none found @@ -235,27 +230,47 @@ def self.[] *args self.line txt, caller(0)[1] end + # Deprecated in favor of a def self.ap *args + self.a *args + end + + def self.b *args + args << {:truncate=>1} + self.a *args#, :truncate=>1 + end + + def self.a *args + options = args.pop if args.length > 1 && args[-1].is_a?(Hash) + options ||= {} + caption = args.shift if args.length > 1 # If 2 args, 1st is caption txt = args.shift if txt.is_a?(String) # If string, show as multi-line (not inspect) txt = txt.dup - else + elsif txt.respond_to?("ai") txt = txt.ai - txt.gsub!(/^ /, "") + txt.gsub!(/^ /, "") if txt =~ /\A / if txt.count("\n") == 2 # If only one item in hash or array - txt.gsub! "\n", "" # Leave brackets, so it's obvious what it is + txt.gsub! /\n */, "" # Leave brackets, so it's obvious what it is else txt.sub!(/\A[\[{]\n/, '') txt.sub!(/\n[\]}]\z/, '') end + else # .ai not defined, so just inspect and add message + txt = "Do 'gem install awesome_print' for Ol.a to work properly. In the mean time, using .inspect:\n#{txt.inspect}" end - self.line txt, caller(0)[1], nil, nil, nil, :caption=>caption - end + line = options[:stack_line] || caller(0)[1] + if options[:truncate] + txt = "#{txt[0..100]}#{txt.length > 100 ? "\n[...]" : ''}" + end + + self.line txt, line, :caption=>caption + end def self.yaml *args txt = args.shift @@ -264,7 +279,7 @@ def self.yaml *args txt.sub! /.+\n/, '' # Remove 1st line - it's ---... txt.gsub!(/[:-] $/, "") - self.line txt, caller(0)[1] #, nil, nil, nil, :caption=>caption + self.line txt, caller(0)[1] end @@ -272,25 +287,38 @@ def self.xi *args txt = args.shift txt = Xi txt - self.line txt, caller(0)[1] #, nil, nil, nil, :caption=>caption + self.line txt, caller(0)[1] end - def self.time nth=1 + def self.i txt + self.line txt.inspect, caller(0)[1] + end + + + # The 'tag' param lets you have separate timers + def self.time tag="" #nth=1 + + @@timed_last[tag] ||= Time.now + now = Time.now - elapsed = self.pause_since_last? ? nil : (now - @@timed_last) + elapsed = self.pause_since_last? ? nil : (now - @@timed_last[tag]) + @@timed_last[tag] = now + + tag = "(#{tag}) " if tag != "" - self.line "#{elapsed ? "(#{elapsed}) " : ''}#{now.strftime('%I:%M:%S').sub(/^0/, '')}:#{now.usec.to_s.rjust(6, '0')}", caller(0)[nth] - @@timed_last = now + self.line "time #{tag}> #{elapsed ? "(#{elapsed}) " : ''}#{now.strftime('%I:%M:%S').sub(/^0/, '')}:#{now.usec.to_s.rjust(6, '0')}", caller(0)[1] + # self.line "time > #{elapsed ? "(#{elapsed}) " : ''}#{now.strftime('%I:%M:%S').sub(/^0/, '')}:#{now.usec.to_s.rjust(6, '0')}", caller(0)[1] end # The primary method of this file - def self.line txt=nil, l=nil, indent="", name=nil, time=nil, options=nil + def self.line txt=nil, l=nil, options=nil l ||= caller(0)[1] - l_raw = l.dup # If l is array (it's the whole trace), use only first here + return if l == [] # Guard against error when stack is too deep? + l = l[0].dup if l.is_a?(Array) l.sub! /^\(eval\)/, 'eval' # So "(" doesn't mess up the label @@ -298,9 +326,11 @@ def self.line txt=nil, l=nil, indent="", name=nil, time=nil, options=nil h = self.parse_line(l) + return if @filter && l !~ /^#{Regexp.quote @filter}/ + options ||= {} if h[:clazz] - self.log txt.to_s, l_raw, name, time, options.merge(:label=>self.extract_label(h)) + self.log txt.to_s, l_raw, options.merge(:label=>self.extract_label(h)) else display = l.sub(/_html_haml'$/, '') display.sub! /.*\//, '' # Chop off path (if evalled) @@ -309,7 +339,8 @@ def self.line txt=nil, l=nil, indent="", name=nil, time=nil, options=nil label = "- #{display})" label << " #{options[:caption]}" if options[:caption] - self.log txt.to_s, l_raw, name, time, options.merge(:label=>label) + + self.log txt.to_s, l_raw, options.merge(:label=>label) end nil @@ -321,7 +352,7 @@ def self.extract_label h def self.parse_line path method = path[/`(.+)'/, 1] # ` - path, l = path.match(/(.+):(\d+)/)[1..2] + path, l = path.match(/(.+):(\d+)/)[1..2] rescue ['not found', '1'] path = File.expand_path path clazz = path[/.+\/(.+)\.rb/, 1] clazz = self.camel_case(clazz) if clazz @@ -329,7 +360,13 @@ def self.parse_line path end def self.file_path - "/tmp/out_ol.notes" + return @file_path if @file_path + + # File not set yet, so create dir if necessary + dir = File.expand_path "~/.xiki/misc/ol/" + FileUtils.mkdir_p dir + @file_path = "#{dir}/out_ol.xiki" + end def self.camel_case s @@ -338,45 +375,58 @@ def self.camel_case s # Logs short succinct stack trace def self.ancestors n=6, nth=1 - ls ||= caller(0)[nth..(n+nth)] + + ls = caller(0)[nth..(n+nth)] self.line "ancestors...", ls.shift, "" ls.each_with_index do |l, i| - self.line((i+1).to_s, l, " ", nil, nil, :leave_root=>1) + self.line((i+1).to_s, l, :leave_root=>1, :indent=>2) end nil end + # If stack is very deep, stop execution + # Ol.limit 200 # Does nothing because stack smaller than 200 + # Ol.limit 4 # Raises because stack deeper than 4 + def self.limit n=50 + + stack = caller(0) + + # Too deep... + + if stack.length > n + self.stack n, 1, :caller=>stack #[n..-1] + raise "Ol.limit raised, because stack exceeded #{n} levels deep" + end + + # Not too deep, so show "less than N"... + + self.line "stack limit less than #{n}", stack[1..-1] + + nil + end + + # Logs stack with indenting, like: + # Ol.stack # Foo.a) # Boo.b) # Coo.c) ...stack - def self.stack n=3, nth=1 - ls ||= caller(0)[nth..-1] + def self.stack n=4, nth=1, options={} + ls = options[:caller] || caller(0)[nth..-1] + + n -= 1 - # dots = nil n.downto(1) do |i| - # dots = "." * (i+1) - # dots = "|" * (i+1) - i == n ? - # self.line(dots, ls[i..-1], nil, nil, nil, :leave_root=>1) : - # self.line(dots, ls[i..-1]) - # self.line("...", ls[i..-1], nil, nil, nil, :leave_root=>1) : - # self.line("...", ls[i..-1]) - # self.line(nil, ls[i..-1], nil, nil, nil, :leave_root=>1) : - # self.line(nil, ls[i..-1]) - - self.line("|||", ls[i..-1], nil, nil, nil, :leave_root=>1) : - self.line("|||", ls[i..-1]) + self.line("|"*(n-i+1), ls[i..-1]) end - # self.line "|", ls[0..-1], nil, nil, nil, :leave_root=>1 - # self.line ".", ls[0..-1], nil, nil, nil, :leave_root=>1 - # self.line "...stack", ls[0..-1], nil, nil, nil, :leave_root=>1 + last = options[:exclamation] ? "!" : "-" - self.line "|||", ls[0..-1], nil, nil, nil, :leave_root=>1 + pipes = "|"*n+"|#{last}" + self.line pipes, ls, :leave_root=>1 nil end @@ -487,7 +537,7 @@ def self.should label, value end def self.extract_test - log = Buffers.txt "*ol" + log = Buffers.txt "ol" log.sub! /.+\n\n/m, '' txt = "" @@ -501,18 +551,26 @@ def self.grab_value value value.sub! /.+?\) ?/, '' # Remove ...) value.sub! /.+?: /, '' # Remove ...: if there - # Clear value unless it looks like a literal: "foo", 1.1, [1, 2], true, nil, etc. - value = "" if value !~ %r'^[\["{]' && value !~ /^true|false|nil|[0-9][0-9.]*$/ + # Why were we clearing these > leave them off for a while? + # Clear value unless it looks like a literal: "foo", 1.1, [1, 2], true, nil, etc. + # value = "" if value !~ %r'^[\["{]' && value !~ /^true|false|nil|[0-9][0-9.]*$/ + value end def self.update_value_comment value - value = " # => #{value}" - Line =~ / # / ? - Line.sub!(/(.*) # .*/, "\\1#{value}") : # Line could be commented, so don't replace after first "#" - Line.<<(value) - Move.to_axis + column = Xiki::View.column + value = " #> #{value}" + Xiki::Line =~ / #> / ? + Xiki::Line.sub!(/(.*) #> .*/, "\\1#{value}") : # Line could be commented, so don't replace after first "#" + Xiki::Line.<<(value) + Xiki::View.column = column end + + class << self + attr_accessor :filter + end + end def Ol *args @@ -520,7 +578,7 @@ def Ol *args if args == [] nil elsif args.length == 1 # Just text - args[0].inspect + args[0].is_a?(String) ? args[0] : args[0].inspect else # Label and text "#{args[0]}: #{args[1].inspect}" end diff --git a/lib/xiki/core/ol_helper.rb b/lib/xiki/core/ol_helper.rb index 134cc833..8ebbe844 100644 --- a/lib/xiki/core/ol_helper.rb +++ b/lib/xiki/core/ol_helper.rb @@ -1,15 +1,17 @@ module Xiki class OlHelper - # Finds last instance of file:line in /tmp/out_ol.notes.lines + # Finds last instance of file:line in /tmp/out_ol.xiki.lines, + # and returns how far from the bottom of out_ol.xiki.lines it is. + # OlHelper.source_to_output "/projects/foo/bah.rb", 6 + # 17 def self.source_to_output file, line target = "#{file}:#{line}\n" - lines = IO.readlines("/tmp/out_ol.notes.lines").reverse - - lines_total = lines.length + lines = IO.readlines("#{Ol.file_path}.lines").reverse # Get how far from bottom + lines_total = lines.length distance, found = 0, nil while distance < lines_total do break found = distance if lines[distance] == target @@ -19,13 +21,14 @@ def self.source_to_output file, line found end + # Expand while in ol buffer, so figure out where to navigate to def self.launch prefix = Keys.prefix :clear=>1 # Get path from end - path = Ol.file_path #[/\/.+/] + path = Ol.file_path - # TODO: get total_lines - current_line + # Get total_lines - current_line distance_to_end = Line.number(View.bottom) - Line.number # Go to log.lines and get n from end @@ -34,7 +37,6 @@ def self.launch path, line = line.split(':') - if ! File.exists? path View.flash "- file \"#{path}\" not found!" raise "- file \"#{path}\" not found!" # This throws an error, but at least blocks it from opening a blank file @@ -60,7 +62,87 @@ def self.open_last_outlog Line.previous # <= 1 end - Launcher.launch_unified + Launcher.launch end -end; end + def self.highlight_executed_lines + + prefix = Keys.prefix :clear=>1 + + # 1. Get contents from Ol view + orig = View.name + View.to_buffer "ol" + txt = View.txt.split("\n") + View.to_buffer orig + return if txt.length == 0 + + # 2. Get equal number of lines from ....lines file + count = txt.length + paths = IO.readlines("#{Ol.file_path}.lines") + paths = paths[-(count)..-1] + + + + # Dash+do+highlight, so jump to "continue here" line first + + if prefix == :u + + found = txt.index{|o| o =~ /\bcontinue here\b/} + if found + found = paths[found] + path, number = found.strip.split(":") #> 3 + View.open path + View.line = number + View.recenter + end + end + + # Clear existing highlights + Color.clear_light + + + # 3. For each line + # target, orig_line = View.file, View.line + target, orig_location = View.file, Location.new + + paths.each_with_index do |path, i| + path, number = path.strip.split(":") + next if path != target + # Path matches current file + + # Ignore |... lines (only top line for each Ol.a output starts with a label) + next if txt[i] =~ /^ *\|/ + + View.open path + View.line = number + Color.mark "gray" + + value = txt[i].sub(/.+?\) ?/, "") + + # No useful value returned, so don't update comment + # Nothing after "label)", so skip + next if value.blank? + + # Literal 'Ol "..."' line without "#{", so skip + line_value = Line.value + next if line_value =~ /^[ |]*Ol "[^"]+"$/ && line_value !~ /#\{/ + + # remove ") " + # return nothing if > no ": " + # remove "foo: " + value.sub! /.+?: /, '' + + Ol.update_value_comment value + # Jump to line and highlight + # Also paste new value + + end + + View.open target + orig_location.go + + end + + + end +end diff --git a/lib/xiki/core/option_items.rb b/lib/xiki/core/option_items.rb new file mode 100644 index 00000000..7f06600d --- /dev/null +++ b/lib/xiki/core/option_items.rb @@ -0,0 +1,17 @@ +module Xiki + class OptionItems + + # Makes copy of items, with tilde prepended (used for routing) + def self.prepend_asterisk option_item + + # Put tilda before 1st item + if option_item[0] + option_item = option_item.dup # Don't affect original option + option_item[0] = "* #{option_item[0]}" + end + + option_item + + end + end +end diff --git a/lib/xiki/core/options.rb b/lib/xiki/core/options.rb new file mode 100644 index 00000000..9384172d --- /dev/null +++ b/lib/xiki/core/options.rb @@ -0,0 +1,102 @@ +module Xiki + class Options + + def self.propagate_some_outward options, options_out + + # Pass options from child up the parent stack + + keys_out = self.important_options_out + + keys_out.each do |key| + options_out[key] = options[key] if options[key] + end + + end + + def self.propagate_some_inward options, options_in + + # Pass options from here to the child call + + options.each{|k, v| options_in[k] = v if [ + :task, + ].include?(k)} + + end + + + # + # xiki api/propagate only certain important options + # + def self.propagate_important_options options_outer + + # important_options = [:ctrlx, :task] + options_in = self.important_options_in + options_out = self.important_options_out + + options = options_outer.select{|key, value| important_options_in.include?(key)} + + result = yield(options) + + important_options_out.each do |key| + options_outer[key] = options[key] if options[key] + end + + result + + end + + def self.important_options + [ + :nest, + :no_task, + :no_slash, + :no_search, + :line_found, + :column_found, + :hotkey, + :omit_slashes, + :error, + # :filter_not_recursive, + :filter_dont_collapse, + :filter_number_counts_only_quotes, + :leave_trailing, + :digit_means_replace_parent, + :ctrlx, + :task, + # :go, + ] + end + + def self.important_options_in + [ + :task, + :ctrlx, + # :go, + ] + end + + def self.important_options_out + [ + :nest, + :no_task, + :no_slash, + :no_search, + :line_found, + :column_found, + :hotkey, + :omit_slashes, + :error, + # :filter_not_recursive, + :filter_dont_collapse, + :filter_number_counts_only_quotes, + :leave_trailing, + :digit_means_replace_parent, + :ctrlx, + :task, + :returned_file_contents, + # :go, + ] + end + + end +end diff --git a/lib/xiki/core/path.rb b/lib/xiki/core/path.rb index 02789ecd..253c18c0 100644 --- a/lib/xiki/core/path.rb +++ b/lib/xiki/core/path.rb @@ -1,19 +1,25 @@ module Xiki class Path + # xiki api/extract quote from file path + # Removes |... from path and returns it # Only reliable when one quote. # # p Path.extract_quote "/projects/foo/bar.rb/| quoted" # " quoted" def self.extract_quote path - found = path =~ %r"/\|(.+)" # Pipes followed by anything are quotes - found ||= path =~ %r"/:([ +-].*)" # Colons followed by only certain chars are quotes + # Look for both at the same time + + found = path =~ %r"/\|(.+)$|/:([ +-].*|)$" # Pipes followed by anything are quotes + return if ! found - path = path.slice!(found..-1).sub(/../, '') + path = path.slice!(found..-1).sub(/./, '') # Remove leading slash - path.sub! %r".+/\|", "" # Remove ancestor quotes if multiple + # Remove ancestor quotes if multiple + split = Path.split path + path = split[-1].sub(/./, "") # Use only leaf path, with leading colon removed path end @@ -27,16 +33,18 @@ def self.extract_line_number path def self.escape! item item.gsub! ";", ";;" + + # Todo > Maybe more solid way of handling this > Don't ;-escape lines before passing to pattern matchers + # - Then the below won't be necessary (patterns can only match when no linebreaks) + + # Escape leading dollars etc, but only if linebreaks (mean was quote rather than literal shell command line) + item.sub!(/^[$%&=>]/, ";\\0") if item =~ /\n/ + item.gsub! "/", ";/" - item.sub! /^@/, ";@" # At's only need escaping at the beginning - item.gsub! "\n", ";0" - item # Just in case they look at return val - end - def self.escape item - item = item.dup - self.escape! item - item + item.gsub! "\n", ";l" + item.gsub! /\A\| =/, "| ;=" + item # Just in case they look at return val end def self.unescape! item @@ -46,7 +54,7 @@ def self.unescape! item last_was_escape = false item.length.times do |i| c = item[i] - if c == "0" && last_was_escape + if c == "l" && last_was_escape result.<< "\n" elsif c == ";" result.<< last_was_escape ? ";" : "" @@ -61,6 +69,14 @@ def self.unescape! item result end + # Path.escape "Hey\nyou" + # Hey;0you + def self.escape item + item = item.dup + self.escape! item + item + end + def self.unescape item item = item.dup self.unescape! item @@ -78,10 +94,10 @@ def self.split path, options={} result, last_was_escape = [""], false - # If :outer, split based on "/@"... + # If :outer, split based on "/="... if options[:outer] - return self.split_outer path, options + return self.split_outer path #, options end # Else, split based on "/"... @@ -93,8 +109,13 @@ def self.split path, options={} if c == "/" && ! last_was_escape is_last_char = i+1 == path.length result << "" if ! is_last_char || options[:trailing] + elsif c == "l" && last_was_escape + result[-1] << "\n" + + # Todo > remove this > ;l is the new way elsif c == "0" && last_was_escape result[-1] << "\n" + elsif c == ";" # This does escaping - should we do this only if an option?? # It doesn't do escaping of \n etc @@ -119,6 +140,10 @@ def self.split path, options={} end + # Splits apart path based on "/=" tokens, properly handling ";" escaping. + # A newer feature is to also split on "/$". + # Path.split.split_outer(["a/=c"]) + # ["a/b/", "c"] def self.split_outer path, options={} result, last_was_escape = [""], false @@ -127,12 +152,28 @@ def self.split_outer path, options={} while i < path.length c = path[i] cc = path[i, 2] + ccc = path[i, 3] - # If /@ and not escaped - if cc == "/@" && ! last_was_escape + # If /= and not escaped + if cc =~ /\A\/=$/ && ! last_was_escape result[-1] << "/" result << "" i += 1 + # If /$ and not escaped + elsif ccc =~ %r"\A/\$[ /]?$" && ! last_was_escape + result[-1] << "/" + result << "$" + i += 1 + # If /% and not escaped + elsif ccc =~ %r"\A/\%[ /]?$" && ! last_was_escape + result[-1] << "/" + result << "%" + i += 1 + # If /& and not escaped + elsif ccc =~ %r"\A/\&[ /]?$" && ! last_was_escape + result[-1] << "/" + result << "&" + i += 1 else result[-1] << c end @@ -140,13 +181,17 @@ def self.split_outer path, options={} i += 1 end - result + # Remove = from beginning if there is one + result[0].sub!(/\A= ?/, '') if result[0] + + result.each{|o| o.sub! /^ +/, ''} + result end # Joins path array into a string, being sure to re-escape slashes def self.join array - array.map{|o| Path.escape o}.join "/" + array.map{|o| self.escape o}.join "/" end end diff --git a/lib/xiki/core/pattern.rb b/lib/xiki/core/pattern.rb index eccc9b26..5fb4b9ca 100644 --- a/lib/xiki/core/pattern.rb +++ b/lib/xiki/core/pattern.rb @@ -42,7 +42,7 @@ def self.expands? options, defs_override=nil # - the tree will be climbed if keys/vals at any level are keys/vals in target_hash # - example: # - Xi.dig hash, target - # - Xi.dig {:word=>{"select"=>{:extension=>{"notes"=>'$'}}}}, {:extension=>{"notes"}, :word=>"select"} + # - Xi.dig {:word=>{"select"=>{:target_extension=>{"notes"=>'$'}}}}, {:target_extension=>{"notes"}, :word=>"select"} # - returns => array with "$" in it # # - Step through tree while.. @@ -55,7 +55,7 @@ def self.expands? options, defs_override=nil # - do recursively? # - probably # - or: store a stack of .keys output and current index for each level - # | stack => [[:target_view, :global], [/foo/, :extension]] + # | stack => [[:target_view, :global], [/foo/, :target_extension]] # | stack_indexs = [0, 1] # - Treat :global differently @@ -63,7 +63,7 @@ def self.expands? options, defs_override=nil # | :target_view => { # | "*ol" => { # | /foo/ => #, - # | :extension => { + # | :target_extension => { # | "rb" => { # | // => # # | }}}}, @@ -90,15 +90,19 @@ def self.add_expander_maybe regex, implementation, options def self.expand options expander = options[:expanders][options[:expanders_index]] - options[:no_slash] = 1 # For menus, no slash by default, though they can remove the option + options[:no_slash] = 1 # For patterns, no slash by default, though they can remove the option - if options[:path] && expander[:proc] + prock = expander[:proc] + + if options[:path] && prock begin output = expander[:proc].call(options[:path], options) options[:output] = output if output return rescue Exception=>e - return options[:output] = CodeTree.draw_exception(e, expander[:proc].source) + source = prock.respond_to?("source") ? + prock.source : "do 'gem install sourcify' and then we can show you the source of the errors" + return options[:output] = CodeTree.draw_exception(e, source) end end diff --git a/lib/xiki/core/php.rb b/lib/xiki/core/php.rb index ba9595b2..1ee319cb 100644 --- a/lib/xiki/core/php.rb +++ b/lib/xiki/core/php.rb @@ -17,7 +17,7 @@ def self.run_internal txt # Write to temp file File.open("/tmp/tmp.php", "w") { |f| f << "\n" } # Call js - Console.run "php -f /tmp/tmp.php", :sync=>true + Shell.run "php -f /tmp/tmp.php", :sync=>true end end diff --git a/lib/xiki/core/projects.rb b/lib/xiki/core/projects.rb index 5ba7eabd..203baff9 100644 --- a/lib/xiki/core/projects.rb +++ b/lib/xiki/core/projects.rb @@ -17,11 +17,11 @@ def self.menu def self.default # If parent is dir, return it, else return first project - dir = FileTree.handles?(Xiki.trunk[-2]) ? "#{Dir.pwd}/" : self.default_project + dir = FileTree.handles?(Tree.path[-2]) ? "#{Dir.pwd}/" : self.default_project end def self.default_project - txt = File.read(File.expand_path "~/menu/projects.menu") rescue nil + txt = File.read(File.expand_path "~/.xiki/roots/projects.menu") rescue nil return nil if ! txt Line.without_label(:line=>txt[/.+/]) diff --git a/lib/xiki/core/remote.rb b/lib/xiki/core/remote.rb index 693ef6e7..776032fe 100644 --- a/lib/xiki/core/remote.rb +++ b/lib/xiki/core/remote.rb @@ -1,6 +1,3 @@ -gem 'net-ssh' -require 'net/ssh' -require 'net/sftp' require 'timeout' require 'xiki/core/ol' @@ -25,7 +22,7 @@ def self.menu def self.file_contents whole_path user, server, port, path = self.split_root(whole_path) connection = self.connection whole_path - connection.sftp.download!(path) + connection.scp.download!(path) end def self.dir root, *path_append @@ -34,9 +31,6 @@ def self.dir root, *path_append user, server, port, path = self.split_root(root) - # Add slash to path if none there - path << "/" unless path =~ /\/$/ - path_passed = path_append.size > 0 if path_passed # If anything in array @@ -46,6 +40,7 @@ def self.dir root, *path_append timeout(15) do if path =~ /\/$/ # If a dir + path << "/" unless path =~ /\/$/ out = connection.exec!("ls -pa #{path}") out ||= "" out = out.split("\n").grep(/^[^#]+$/).join("\n") # Weed out #...# @@ -65,7 +60,7 @@ def self.dir root, *path_append # Download if not open already unless was_open begin - connection.sftp.download!(path, local_path) + connection.scp.download!(path, local_path) rescue Exception=>e # If doesn't exist, we'll just create end @@ -76,7 +71,7 @@ def self.dir root, *path_append # TODO: save root path as var in buffer $el.make_local_variable :remote_rb_server_root - server_root = "/#{user}@#{server}#{port ? ":#{port}" : ""}/" + server_root = "/#{user}@#{server}#{port ? ":#{port}" : ""}/" # " $el.elvar.remote_rb_server_root = server_root # TODO save timestamp in buffer var @@ -86,28 +81,18 @@ def self.dir root, *path_append end end - def self.command root #, *path_append - - the_command = root[-1][/\$ ?(.+)/, 1] - - # Pull off command - while(root.last =~ /^\$/) do # Remove all !foo lines from root - root.pop - end - root = root.join('') + # Handles only async? + def self.command root, command #, *path_append connection = self.connection root user, server, port, path = self.split_root(root) - path << "/" unless path =~ /\/$/ # Add slash to path if none there - timeout(6) do - out = connection.exec!("cd \"#{path}\" && #{the_command}") - # out = connection.exec!("cd \"#{path}\"; #{the_command}") + out = connection.exec!("cd \"#{path}\" && #{command}") out ||= "" - Tree.under out, :escape=>'| ', :no_slash=>1 + out end end @@ -116,6 +101,8 @@ def self.connections end def self.connection root + require 'net/ssh' + require 'net/scp' user, server, port, path = self.split_root root address = "#{user}@#{server}:#{port}" @@connections[address] ||= self.new_connection user, server, port.to_i @@ -132,12 +119,10 @@ def self.new_connection user, server, port end end - def self.split_root root # Splits name@server:port/path root = root.dup # Append / at end if no / exists - root << "/" unless root =~ /\/$/ - user, server_port, path = root.match(/^(.+?)@(.+?)(\/.*?)\/?$/)[1..3] + user, server_port, path = root.match(/^(.+?)@([^\/]+)(.*)/)[1..3] if(server_port =~ /(.+?):(.+)/) server, port = $1, $2 @@ -177,7 +162,8 @@ def self.save_file remote_path = self.calculate_remote_path local_path begin # Do save connection = self.connection $el.elvar.remote_rb_server_root - connection.sftp.upload!(local_path, remote_path) + # connection.sftp.upload!(local_path, remote_path) + connection.scp.upload!(local_path, remote_path) View.message "successfully saved remotely!" rescue Exception => e View.message "- error: #{e.message}" @@ -197,12 +183,91 @@ def self.init # TODO remove this end - end + def self.remote_files_in_dir dir + txt = self.dir(dir) + return txt if txt.is_a? String # If it's a file - Keys.do_as_remote do - Remote.save_file - end + txt.map!{|i| "#{dir}#{i}"} + [txt.select{|i| i =~ /\/$/}.map{|i| i.sub(/\/$/, '')}, txt.select{|i| i !~ /\/$/}] + end + + def self.remote_file_contents file + path, file = file.match(/(.+\/)(.+)/)[1..2] + self.dir path, file # Delegate to Remote.dir + end + + # Handles if $... or %... + def self.expand_command path, options + + # Not $... or %..., so we don't handle it... + + command = options[:command] + + path = Path.split path + + # Fall back to extracting from path for %... prompts for now + if path[-1] =~ /^[$%] / + command = path.pop + end + + return if ! command + + shell_prompt, command = /(.).(.+)/.match(command)[1..2] + + # Get rid of any other nested + path.pop while(path.last =~ /^[$%] /) + path = path.join('/') + + # $, so sync command... + + return Remote.command path, command if shell_prompt == "$" + + # %, so async command... + view_orig = View.name + + Shell.to_shell_buffer path, :cd_and_wait=>true + + View.insert command + Shell.enter + + View.to_buffer view_orig if View.buffer_visible? view_orig + + "" # Handled it + + end + + def self.expand path, options + + # $ foo, so run as a shell command... + + txt = self.expand_command path, options + + if txt + return Tree.quote(txt) if txt.any? + return "" # Async returns blank string so don't quote + end + + # It's a dir (or file?)... + + dirs, files = self.remote_files_in_dir path + + return if dirs.is_a? String + + # If empty, say so... + + return "<* dir is empty!" if files.empty? && dirs.empty? + + indent = "#{Line.indent} " + + # Change path to proper indent + dirs.collect!{|i| i.sub(/.*\/(.+)/, "#{indent}+ \\1/")} + files.collect!{|i| i.sub(/.*\/(.+)/, "#{indent}+ \\1")} + + both = dirs + files + both.join("\n") + "\n" + + end + end - # Remote.init end diff --git a/lib/xiki/core/requirer.rb b/lib/xiki/core/requirer.rb index f7956410..d5252872 100644 --- a/lib/xiki/core/requirer.rb +++ b/lib/xiki/core/requirer.rb @@ -5,7 +5,7 @@ class Requirer def self.show txt if ! $el return if txt !~ /(^Xiki requires|exception:$)/ - return puts "#{txt}" + return puts txt end $el.switch_to_buffer "Issues Loading Xiki" @@ -34,7 +34,7 @@ def self.extract_gem_from_exception txt def self.require_classes files files.each do |l| - # Ol "l", l + begin require l rescue LoadError => e diff --git a/lib/xiki/core/rest_tree.rb b/lib/xiki/core/rest_tree.rb index 058d6327..b4e8ef32 100644 --- a/lib/xiki/core/rest_tree.rb +++ b/lib/xiki/core/rest_tree.rb @@ -1,3 +1,5 @@ +require 'net/http' + module Xiki class RestTree @@ -29,25 +31,6 @@ def self.launch_inner path, children [verb || root_verb, path.join(''), body] end - def self.launch options={} - - Tree.plus_to_minus_maybe - verb, url, body = self.launch_inner options[:path], Tree.children - url = "http://#{url}" unless url =~ %r"http://" - - result = self.request verb, url, body - result = "#{result}\n" unless result =~ /\n\z/ - result.gsub! "\cm", '' - - # Quote unless begins with "|" - result.gsub! /^/, "| " - result.gsub! /^\| ( *[-+] )/, "\\1" - - Tree.<< result, :escape=>'', :no_slash=>1 - - nil - end - # Tell Launcher whether we're in a rest tree def self.handles? list list.index{|i| i =~ /^(GET|PUT|POST|DELETE)/} @@ -74,21 +57,142 @@ def self.remove_before_root list list end - def self.request verb, url, body=nil + def self.post url, options={} + options[:verb] = 'POST' + self.request url, options + end + def self.get url, options={} + self.request url, options #.merge(:verb=>'GET') + end + + # Examples: + # RestTree.request "http://localhost:4717", :verb=>'POST', :body=>"heyy" + # You posted: heyy + def self.request url, options={} + verb = options[:verb] || "GET" + begin net_http_class = Net::HTTP.const_get(verb.capitalize) url.gsub!('"', '%22') uri = URI.parse(url) req = net_http_class.new(uri.request_uri) + + body = options[:body] + + # :body is a hash, so convert to json! + if body.is_a?(Hash) + body = JSON.pretty_generate body + end + req.body = body - res = Net::HTTP.start(uri.host, uri.port) {|http| + + # Set header vars + (options[:headers]||{}).each do |key, val| + req[key] = val + end + + request_options = uri.port == 443 ? {:use_ssl=>true} : {} + res = Net::HTTP.start(uri.host, uri.port, request_options) {|http| http.request(req) } - (res.code == '200' ? '' : "#{res.code} ") + res.body + + options[:code] = res.code + options[:response] = res + + body = res.body + + body + rescue Exception=>e + + raise e if options[:raise] e.message end + end + + def self.xiki_url url, options={} + + request_options = {:headers => {"User-Agent"=>"Xsh"}} + + items = options[:items] + + # "url/| Quote", so pull off last item as text to post + + post_txt = nil + + split = Path.split options[:path] + + if split[-1] =~ /\n/ + post_txt = split[-1] + url = Path.join split[0..-2] + end + + post_txt ||= options[:post] + + url.sub! /^xiki:\/\//, 'http://' + url.gsub! ' ', '+' + url = "http://#{url}" if url !~ /\Ahttp:\/\// + + if post_txt + txt = RestTree.post url, request_options.merge(:body=>post_txt) + else + txt = self.get url, request_options + end + + + # If 404, grab root + if options[:code] == "404" + root_url = url[%r`.+?//.+?/`] + txt = self.request(root_url, request_options) if root_url + end + + # If options[:code] == "" + # TODO: This is assuming we're at the root + # rewrite so it starts at lowest path and climbs until it's not 301 + + path = url[%r`.+?//.+?/(.+)`, 1] || "" + + if txt =~ /\A\s*{/ || txt =~ /\A\s*<(\w|!--|!DOCTYPE)/ || txt =~ /\A.*\n\s*<(\w|!--|!DOCTYPE)/ + # Html, so quote + txt = Tree.pipe txt + else + # Don't quote + + # Remove consecutive groups of blank lines + txt.gsub! /\n\n\n+/, "\n\n" + + end + + txt.sub!(/\A\| 301 (.+)\.\.\.$/){ "\n<@ #{$1.sub(/^http/, 'xiki')}" } + + txt + end + + # Called by .xiki_url. If found, grabs menus from + # comments like the following and routes (calls + # Tree.children). + # + # /m] + return nil if ! comment # Return existing if there's no tree + txt = comment.sub(/ */, '') + txt.unindent! + end + + path.gsub! "+", ' ' + + Tree.children txt, path + + # Returns nothing, so uses whole tree if no match found end diff --git a/lib/xiki/core/ruby.rb b/lib/xiki/core/ruby.rb index 66619859..3a339f03 100644 --- a/lib/xiki/core/ruby.rb +++ b/lib/xiki/core/ruby.rb @@ -53,20 +53,61 @@ def self.classes clazz=nil, method=nil # To use them, add this line: # ~/.el4r/init.rb # | Ruby.keys + def self.custom_next + + column = View.column + Move.to_end + Search.forward "^ *\\(def\\|it\\) ", :beginning=>1, :go_anyway=>1 + View.column = column + + Keys.remember_key_for_repeat(proc {Ruby.custom_next}, :movement=>1) + end + + def self.custom_previous + column = View.column + Move.to_axis + Search.backward "^ *\\(def\\|it\\) ", :go_anyway=>1 + View.column = column + + Keys.remember_key_for_repeat(proc {Ruby.custom_previous}, :movement=>1) + end + + def self.init_in_client + + # Call it when ruby-mode-map + + $el.el4r_lisp_eval %` + (progn + (defun xiki-ruby-mode-keys () + (el4r-ruby-eval "Xiki::Ruby.keys") + ) + (add-hook 'ruby-mode-hook 'xiki-ruby-mode-keys) + + ; Highlight "#+ Comments" indent as white (it wasn't possible to highlight the comment itself) + (font-lock-add-keywords 'ruby-mode '( + ("\\\\( *\\\\)#\\\\+" (0 nil) (1 'color-rb-white)) + )) + + ) + ` + + self.keys # Call it now, in case ruby-mode-map already loaded (it probably is if reloading after a .rb file was visited) + + end + def self.keys - Keys.custom_next(:ruby_mode_map) { - column = View.column - Move.to_end - Search.forward "^ *\\(def\\|it\\) ", :beginning=>1, :go_anyway=>1 - View.column = column - } - - Keys.custom_previous(:ruby_mode_map) { - column = View.column - Move.to_axis - Search.backward "^ *\\(def\\|it\\) ", :go_anyway=>1 - View.column = column - } + if $el.boundp :ruby_mode_map + + $el.define_key(:ruby_mode_map, $el.kbd("C-c C-n")){ Ruby.custom_next } # custom+next + $el.define_key(:ruby_mode_map, $el.kbd("C-c C-p")){ Ruby.custom_previous } # custom+previous + + $el.define_key :ruby_mode_map, $el.kbd("C-c C-e") do + Xiki::View.insert "end" + $el.ruby_indent_line + end + + end + end # Makes "Foo.bar" string from quoted method line. diff --git a/lib/xiki/core/ruby_console.rb b/lib/xiki/core/ruby_console.rb index b07b2010..527a888b 100644 --- a/lib/xiki/core/ruby_console.rb +++ b/lib/xiki/core/ruby_console.rb @@ -1,6 +1,3 @@ -gem 'net-ssh' -require 'net/ssh' - module Xiki # Wraps around a local or remote irb or merb (and probably rails) # console, passing commands to it and returning their stdout output. diff --git a/lib/xiki/core/search.rb b/lib/xiki/core/search.rb index b19d000e..0aba922c 100644 --- a/lib/xiki/core/search.rb +++ b/lib/xiki/core/search.rb @@ -6,7 +6,22 @@ module Xiki class Search - SPECIAL_ORDER = "X!='\"[]{}<>_-+*@#\\/!$X" + # Used by search+just+Plus and search+just+Minus + SPECIAL_ORDER = "X!='\"[]{}<>?-+ +*@#\\/!$X:|" + # These override it + SPECIAL_ORDER_MINUS = { + " "=>"-", + "+"=>"-", + "-"=>"?", + "?"=>"-", + } + SPECIAL_ORDER_PLUS = { + " "=>"+", + "-"=>"+", + "+"=>" ", + "?"=>"+", + "@"=>"=", + } @@case_options = nil @@ -15,10 +30,9 @@ class Search def self.outline_goto_once; @@outline_goto_once; end def self.outline_goto_once= txt; @@outline_goto_once = txt; end - @@log = File.expand_path("~/.emacs.d/search_log.notes") + @@log = File.expand_path("~/.xiki/misc/logs/search_log.xiki") - def self.menu - ' + MENU = ' - .history/ - .log/ - .launched/ @@ -46,8 +60,8 @@ def self.menu | search_value: Insert found where search began | search_delete: Delete found | search_diffs (without searching): Search in diffs - | search_todo: Search in $t bookmark - | search_files: Search in $f bookmark + | search_todo: Search in :t bookmark + | search_files: Search in :n bookmark | search_paths: Search history of menus - miscellaneous/ | search_search: Re-do the last search @@ -56,12 +70,11 @@ def self.menu | search_usurp: Suck the next expression in | | For more details about Xiki keyboard shortcuts, see: - - @keys/docs/ + + =keys/docs/ | - see/ - <@ next/ + <= next/ ' - end def self.case_options return @@case_options if @@case_options @@ -70,6 +83,7 @@ def self.case_options self.add_case_option 'lower', proc {|txt| txt.downcase} self.add_case_option 'camel', proc {|txt| TextUtil.camel_case(txt)} self.add_case_option 'snake', proc {|txt| TextUtil.snake_case(txt)} + self.add_case_option 'whitespace', proc {|txt| TextUtil.whitespace_case(txt)} @@case_options end @@ -97,25 +111,30 @@ def self.insert_tree_at_spot $el.insert txt end - def self.insert_at_search_start + def self.insert_at_search_start options={} + was_reverse = self.was_reverse match = self.stop - if match.nil? # If nothing searched for yet, search difflog - loc = Keys.input(:chars=>1, :prompt=>"Enter one char to search for corresponding string: ") - loc = loc.to_s - - txt = Clipboard.hash[loc.to_s] || Clipboard.hash_by_first_letter[loc.to_s] - txt ||= self.searches.find{|o| o =~ /^#{loc}/i} - - return View.message("Nothing to search for matching '#{loc}'.", :beep=>1) if txt.nil? - self.isearch txt, :reverse=>was_reverse + # Nothing searched for yet, so do search+value (search for what's in the clipboard)... - return + if match.nil? + return self.isearch Clipboard[0] end + # Pull match back to search start... + self.to_start # Go back to start - $el.insert match + + match = "#{options[:prepend]}#{match}" + + View.insert match #, :dont_move=>1 + View.message "" + end + + def self.isearch_have_wikipedia + term = self.stop + Wikipedia.wp term end def self.isearch_have_within @@ -146,6 +165,7 @@ def self.move_to_search_start match def self.insert_var_at_search_start match = self.stop self.to_start # Go back to start + match.strip! $el.insert "\#{#{match}}" end @@ -162,35 +182,74 @@ def self.isearch_select_inner Effects.blink :what=>:region end - def self.isearch_delete + def self.isearch_diffs + + was_reverse = self.was_reverse match = self.stop - # If nothing searched for, go to spot of last delete - if match.nil? # If nothing searched for yet, search difflog + # Nothing searched for yet, so search difflog... + + if match.nil? + Location.as_spot DiffLog.open View.to_bottom Search.isearch nil, :reverse=>true + return + end + + # Match found, so behave like search+dir... + + # Search for filenames in dir + + dir = Keys.bookmark_as_path :prompt=>"Enter bookmark to look in (or space for recently edited): " + + # "dir/file.txt", so just open path + if match =~ /\// + View.open "#{dir}#{match}" + return + end + + + return View.message("Use space!", :beep=>1) if dir == :comma + + return self.open_file_and_method(match) if dir == :space # If key is comma, treat as last edited + + TextUtil.snake_case! match if match =~ /[a-z][A-Z]/ # If camel case, file is probably snake + FileTree.grep_with_hashes dir, match, '**' # Open buffer and search + + + + end + + # Used by search+add and search+just+2 etc. + def self.isearch_clear + match = self.stop + + if match.nil? # Nothing searched for yet, so go to spot of last delete + Location.to_spot('killed') else - View.delete(Search.left, Search.right) + View.delete Search.left, Search.right Location.as_spot('killed') end end - def self.enter txt=nil + def self.isearch_go match = self.stop - txt ||= Clipboard[0] - if match.nil? # If nothing searched for yet, do search_edits - bm = Keys.input :timed=>true, :prompt=>"Enter a bookmark to search edits: " + Grab.go_key - return Launcher.open("diff log/diffs/") if bm == "8" || bm == " " + end - path = bm == "." ? View.file : "$#{bm}/" - return Launcher.open("- #{path}\n @edits/") + def self.enter txt=nil + match = self.stop + txt ||= Clipboard[0] + + if match.nil? # Nothing searched for yet, so show search history + return Launcher.open "searching/history/", :bar_is_fine=>1 end View.delete(Search.left, Search.right) - View.insert txt + View.insert txt, :dont_move=>1 end def self.copy_and_comment @@ -218,15 +277,21 @@ def self.just_increment options={} position = SPECIAL_ORDER.index match + decrement = options[:decrement] + # If one of certain chars, use custom order result = - if position # Change '[' to ']', etc + if ! decrement && found = SPECIAL_ORDER_PLUS[match] + found + elsif decrement && found = SPECIAL_ORDER_MINUS[match] + found + elsif position # Change '[' to ']', etc - increment_or_decrement = options[:decrement] ? -1 : 1 + increment_or_decrement = decrement ? -1 : 1 SPECIAL_ORDER[position+increment_or_decrement].chr else - if options[:decrement] + if decrement match =~ /[a-z]/i ? (match[0] - 1) : (match.to_i - 1).to_s @@ -261,30 +326,15 @@ def self.just_edits def self.copy match Clipboard[0] = match - $el.set_register ?X, match - $el.x_select_text match - Clipboard.save_by_first_letter match # Store for retrieval with enter_yank - end - - def self.cut - match = self.stop - - # If nothing searched for, go to spot of last delete - if match.nil? # If nothing searched for yet - Location.to_spot('killed') - else - Clipboard.set(0, match) - $el.set_register ?X, match - View.delete self.left, self.right - Location.as_spot('killed') - end + $el.x_select_text(match) if Environment.gui_emacs end def self.go_to_end match = self.stop if match.nil? # If nothing searched for yet - Search.isearch_restart "$f", :restart=>true + Location.as_spot + Search.isearch_restart "%links", :restart=>true return end @@ -294,14 +344,23 @@ def self.go_to_end # Clears the isearch, allowing for inserting, or whatever else def self.stop options={} - left, right = self.left, self.right + txt = self.match + + # TODO > restore or fix! + + # Maybe use (isearch-abort) instead?, and then move cursor to line? + # might result in weird scrolling - txt = self.match(left, right) + # Or, try one of these functions? + # isearch-exit + # isearch-done + # isearch-complete1 + # isearch-complete + # isearch-cancel # Make it do special clear if nothing found (to avoid weird isearch error) if txt.nil? if $el.elvar.isearch_success # || Search.left == View.bottom - # Done so isearch_done won't error $el.isearch_resume "[^`]", true, nil, true, "", true View.message "" else @@ -314,7 +373,8 @@ def self.stop options={} $el.isearch_clean_overlays $el.isearch_done - txt == :not_found ? Search.last_search : txt + # Why doing this? > self.last_search + txt == :not_found ? self.last_search : txt end def self.match left=nil, right=nil @@ -333,58 +393,57 @@ def self.to_start end # Do query replace depending on what they type + # def self.query_replace s1=nil, s2=nil def self.query_replace s1=nil, s2=nil - if s1 && s2 # If manually passed in - $el.query_replace_regexp($el.regexp_quote(s1 || ""), s2 || "") - return - end - first = Keys.input(:timed=>true) - # If they typed 'o', use clipboard 1 and clipboard 2 - if first == "o" || first == "1" - $el.query_replace_regexp($el.regexp_quote(Clipboard.get("1")), Clipboard.get("2")) - # If they typed 't', use clipboard 2 and clipboard 1 - elsif first == "t" || first == "2" - $el.query_replace_regexp(Clipboard.get("2"), Clipboard.get("1")) - # If 'q', prompt for both args - elsif first == "q" - $el.query_replace_regexp( - Keys.input(:prompt=>"Replace: "), - Keys.input(:prompt=>"With: ")) - # If they typed 'c', use clipboard and prompt for 2nd arg - elsif first == "c" - $el.query_replace_regexp(Clipboard.get, Keys.input(:prompt=>"Replace with (pause when done): ", :timed=>true)) - # If they typed 'c', use clipboard and prompt for 2nd arg - elsif first == "l" - $el.query_replace_regexp(@@query_from, @@query_to) - # Otherwise, just get more input and use it + # If no params, prompt for them... + + if ! s1 + s1 = Keys.input :prompt=>"Replace from: " + s2 = Keys.input :prompt=>"Replace to: " else - $el.query_replace_regexp(first, Keys.input(:timed=>true)) + s1 = self.quote_elisp_regex s1 + end + + $el.query_replace_regexp s1, s2 + nil end - def self.isearch_query_replace after=nil - match = self.stop# + def self.isearch_query_replace # after=nil + match = self.stop + + # No match, so prompt for before... + + if ! match + no_match_existed = true + match = Keys.input(:prompt=>"Exchange occurrences of what?: ") + end + was_upper = match =~ /[A-Z]/ match.downcase! left, right = Search.left, Search.right before = $el.regexp_quote(match) # Always start with isearch match - # If before not there or is :match, prompt for input - if after.nil? || after == :match - initial_input = after == :match ? before : '' - after = Keys.input(:prompt=>"Change instances of '#{before}' to: ", :initial_input=>initial_input) - @@query_from, @@query_to = before, after - end + # Prompt for after... - View.delete left, right - View.insert was_upper ? - TextUtil.title_case(after) : - after + initial_input = '' + after = Keys.input(:prompt=>"Exchange occurrences of '#{before}' with: ", :initial_input=>initial_input) + @@query_from, @@query_to = before, after + + # We're replacing a match right here, so do it before doing query replace... + + if ! no_match_existed + View.delete left, right + View.insert was_upper ? + TextUtil.title_case(after) : + after + end $el.query_replace_regexp before, after + nil end def self.grep @@ -396,6 +455,8 @@ def self.tree_grep # prefix=nil prefix = Keys.isearch_prefix path = Keys.bookmark_as_path :include_file=>1 # Get path (from bookmark) + return if ! path + # If C-u, just jump to bookmark and search from the top if prefix == :u View.open path @@ -450,9 +511,9 @@ def self.kill_search(left, right) def self.search_in_bookmark match - bm = Keys.bookmark_as_path :include_file=>1 + path = Keys.bookmark_as_path :include_file=>1 - if bm == :slash # If space, go back to root and search + if path == :slash # If space, go back to root and search # Make match be orange Overlay.face(:ls_search, :left=>self.left, :right=>self.right) self.search_at_root match @@ -461,16 +522,15 @@ def self.search_in_bookmark match View.to_after_bar if View.in_bar? - if bm == :space # If space, search in buffers + if path == :space # If space, search in buffers self.find_in_buffers match return end match.gsub!(/([#()*+?^$\[\]\/|.])/, "\\\\\\1") - # match.gsub! "\\+", "." # Change + to . to help when searching for key shortcuts # Search in bookmark - FileTree.grep_with_hashes bm, match + FileTree.grep_with_hashes path, match end @@ -516,7 +576,7 @@ def self.launched *arg txt = txt.select{|o| o =~ regex} end - result = "- @#{txt.join("- @")}" + result = "=#{txt.join("=")}" result end @@ -537,11 +597,11 @@ def self.find_in_buffers string, options={} string.gsub!('"', '\\"') new_args = "\"#{string}\"" new_options = {} - new_options[:buffer] = View.buffer_name if options[:current_only] + new_options[:buffer] = View.name if options[:current_only] new_args << ", #{new_options.inspect[1..-2]}" unless new_options.empty? View.bar if options[:in_bar] - $el.switch_to_buffer "*tree find in buffers" + $el.switch_to_buffer "find in buffers/" Notes.mode $el.erase_buffer @@ -551,7 +611,7 @@ def self.find_in_buffers string, options={} if new_options[:buffer] # Goto first match $el.goto_line 4 Line.to_words - Tree.search(:recursive=>false, :left=>Line.left, :right=>View.bottom) + Tree.filter(:recursive=>false, :left=>Line.left, :right=>View.bottom) else # Goto first match in 2nd file $el.goto_line 2 $el.re_search_forward "^ -", nil, true @@ -566,7 +626,12 @@ def self.just_marker $el.highlight_regexp(Regexp.quote(match), :notes_label) end - def self.highlight_found + def self.isearch_highlight_match + match = self.stop + View.selection = [self.right, self.left] + end + + def self.highlight_all_found match = self.stop $el.highlight_regexp(Regexp.quote(match), :hi_yellow) @@ -582,9 +647,11 @@ def self.hide # Insert line at beginning of search def self.have_line self.stop + column = View.column line = Line.value(1, :include_linebreak=>true).sub("\n", "") self.to_start # Go back to start $el.insert line + View.column = column end # Insert line at beginning of search @@ -633,21 +700,22 @@ def self.isearch_pull_in_sexp end # During isearch, open most recently edited file with the search string in its name - def self.isearch_to + def self.isearch_options match = self.stop if match.nil? # If nothing searched for yet - Search.isearch_restart "$t", :restart=>true + + # Do something if nothing searched yet? + + Location.as_spot + Search.isearch_restart "%o", :restart=>true else - dir = Keys.bookmark_as_path :prompt=>"Enter bookmark to look in (or space for recently edited): " - return View.message("Use space!", :beep=>1) if dir == :comma + # Match found, so do task menu here... - return self.open_file_and_method(match) if dir == :space # If key is comma, treat as last edited + Launcher.options_key - TextUtil.snake_case! match if match =~ /[a-z][A-Z]/ # If camel case, file is probably snake - FileTree.grep_with_hashes dir, match, '**' # Open buffer and search end end @@ -678,10 +746,12 @@ def self.open_file_and_method match match = "#{match}." snake = "#{snake}." # For each file edited - found = (Files.edited_array + Files.history_array).find do |o| + found = DiffLog.file_list.find do |o| + next if ! o.is_a? String - next if o =~ /.notes$/ # Ignore notes files + next if o =~ /.xiki$/ # Ignore notes files next if o =~ /:/ # Ignore files with colons (tramp) + name = o[/.*\/(.*)/, 1] # Strip off path # Check for match if name =~ /^#{Regexp.quote(match)}/i || (snake && name =~ /^#{Regexp.quote(snake)}/i) @@ -703,7 +773,7 @@ def self.open_file_and_method match $el.recenter 0 dir, name = found.match(/(.+\/)(.+)/)[1..2] - Search.append_log dir, "- #{name}\n | #{Line.value}" + Search.append_log dir, "- #{name}\n : #{Line.value}" end else @@ -718,12 +788,25 @@ def self.isearch_or_copy name was_reverse = self.was_reverse match = self.stop + # Isearch for it, or remember what's already searched... + if match.nil? # If nothing searched for yet - self.isearch Clipboard[name].downcase, :reverse=>was_reverse - else # Else, if nothing searched for + target = Clipboard.register(name).downcase + self.isearch target, :reverse=>was_reverse + + # Make next "C-," repeat just jump to this search string... + + was_reverse ? + Keys.remember_key_for_repeat(proc {Search.backward target, :quote=>1}, :movement=>1) : + Keys.remember_key_for_repeat(proc {Search.forward target, :quote=>1, :beginning=>1}, :movement=>1) + + else # If found something, just remember it self.stop - Clipboard[name] = match + target = match + Clipboard.register(name, target) end + + end def self.isearch_copy_as name @@ -731,20 +814,48 @@ def self.isearch_copy_as name Clipboard.set(name, self.match) end + def self.kill_matching options={} + # TODO: Get options[:kill_matching]=>true to delete matching + # - and map to Keys.just_kill_matching + + # Prompt for string + filter = Keys.input :promp=>"Remove lines containing what? " + + Line.start + left = $el.point + $el.re_search_forward "^$", nil, 1 + right = $el.point + $el.goto_char left + + txt = View.delete left, right + txt = txt.split("\n").select{|o| o !~ /#{filter}/}.join("\n") + + View.<< "#{txt}\n", :dont_move=>1 + + # Delete non-matching lines + + + end + def self.kill_filter options={} # TODO: Get options[:kill_matching]=>true to delete matching - # - and map to Keys.do_kill_matching + # - and map to Keys.just_kill_matching Line.start left = $el.point $el.re_search_forward "^$", nil, 1 right = $el.point $el.goto_char left - Tree.search(:left=>left, :right=>right, :recursive=>true) + Tree.filter(:left=>left, :right=>right, :recursive=>true) end def self.cancel + + # Clear any subsequent keys, since it could be a mouse click + Keys.read_subsequent_chars + self.stop self.to_start # Go back to start + View.message "" end # def self.not_found? @@ -753,18 +864,39 @@ def self.cancel # ! elvar.isearch_success # end + # Search.forward "from" + # Search.forward "from", :dont_move=>1 # Just return location + # Search.forward "from", :beginning=>1 # Beginning of match def self.forward target, options={} View.to_highest if options[:from_top] orig = View.cursor - found = options[:not_regex] ? - $el.search_forward(target, nil, (options[:go_anyway] ? 1 : true)) : - $el.re_search_forward(target, nil, (options[:go_anyway] ? 1 : true)) + # :line, so make regex to find just the line... + + if options[:line] + target = "^#{Search.quote_elisp_regex target}$" + end + + target = self.quote_elisp_regex target if options[:quote] + + # Do actual search + + found = $el.re_search_forward(target, nil, (options[:go_anyway] ? 1 : true)) View.cursor = orig if options[:dont_move] - View.cursor = self.left if options[:beginning] && found && View.cursor != View.bottom + if options[:beginning] && found && View.cursor != View.bottom + left = self.left + # Before moving back to left side, if left of match is where we were already, search again + + if orig == left && left != 1 # Unless we're at the top + self.forward target, options + else + View.cursor = left + end + + end found end @@ -797,16 +929,13 @@ def self.isearch_google options={} term = self.stop if term term.gsub!(' ', '+') - term = "\"#{term}\"" if options[:quote] - term =~ /^https?:\/\// ? # If url, just browse $el.browse_url(term) : $el.browse_url("http://google.com/search?q=#{term}") return end - Firefox.log Search.isearch nil, :from_bottom=>true end @@ -825,14 +954,14 @@ def self.isearch_move_line View.delete $el.point_at_bol, $el.point_at_eol + 1 #self.to_start # Go back to start $el.exchange_point_and_mark - $el.insert line + View.insert line, :dont_move=>1 end def self.outline if Keys.prefix_u? - History.open_current :outline => true, :prompt_for_bookmark => true + History.open_current :outline=>true, :prompt_for_bookmark=>true else - History.open_current :outline => true + History.open_current :outline=>true end end @@ -857,18 +986,32 @@ def self.downcase def self.enter_like_edits Notes.enter_junior View << "@edits/" - Launcher.launch_unified + Launcher.launch end + # Inserts a "- ##foo/" string into the current view. def self.enter_search bm=nil, input=nil + # If line already has something, assume we'll add - ##foo/ to it if ! Line[/^ *$/] - input = Keys.prefix_u ? Clipboard.get : Keys.input(:prompt=>"Text to search for: ") + indent = Line.indent Line.to_right - View.insert "\n#{indent} - ###{input}/" - Launcher.launch_unified + + # up+, so just grab from clipboard... + + if Keys.prefix_u + input = Clipboard.get + View.insert "\n#{indent} - ###{input}/" + Launcher.launch + return + end + + # Insert ##/ under... + View << "\n#{indent} - ##/" + View.to(Line.right - 1) return + end bm ||= Keys.input(:timed=>true, :prompt=>"Enter bookmark in which to search: ") @@ -884,14 +1027,14 @@ def self.enter_search bm=nil, input=nil dir = nil end else - dir = Bookmarks.expand("$#{bm}") + dir = Bookmarks.expand("%#{bm}") end - View.insert("- #{dir}" || "") + View.insert("#{dir}" || "") indent = Line.indent Line.to_right View.insert("\n#{indent} - ###{input}/") - FileTree.launch + Launcher.launch end @@ -900,14 +1043,10 @@ def self.isearch_have_outlog options={} return self.isearch_have_outlog_javascript if View.extension == "js" match = self.stop + match.strip! self.to_start return View.insert("Ol.line") if match.nil? - if Tree.construct_path(:all=>1, :slashes=>1) =~ /" - elsif last == 'style' - Tree.quote Html.default_css :no_tags=>1 - elsif last == 'body' - ['div/'] - else - ['h1/Info', 'p/lorem ipsum...'] - end - - end - - def initialize txt - @txt = txt - end - - @@filler = { - "h1"=>"Info", - "title"=>"Welcome", - "p"=>"Lorem ipsum...", - } - - # Html.default_css - # Html.default_css :no_tags=>1 - def self.default_css options={} - txt = "" - txt.<< "\n" if ! options[:no_tags] - txt - end - - - end -end diff --git a/lib/xiki/tools/index.menu b/lib/xiki/tools/index.menu index 5137ef9e..83a669e9 100644 --- a/lib/xiki/tools/index.menu +++ b/lib/xiki/tools/index.menu @@ -1,4 +1,4 @@ > Summary | These are older classes that were menus before the Unified refactor. | Many of them should be ported and moved into the menu dir: -@$x/menu/ +=%s/roots/ diff --git a/lib/xiki/tools/ip.rb b/lib/xiki/tools/ip.rb deleted file mode 100644 index f239c76f..00000000 --- a/lib/xiki/tools/ip.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Xiki - class Ip - def self.menu - inet = `ifconfig`.split("\n").grep(/\binet\b/) - - return "| #{inet[0][/[\d.]+/]}" if inet.length < 2 - inet[1][/[\d.]+/] - end - end -end diff --git a/lib/xiki/tools/iterm.rb b/lib/xiki/tools/iterm.rb index 6ef56f9f..9d461496 100644 --- a/lib/xiki/tools/iterm.rb +++ b/lib/xiki/tools/iterm.rb @@ -15,14 +15,17 @@ def self.menu *args end def self.command txt - $el.do_applescript %` + Applescript.run %` #{txt} application "iTerm" ` return nil end def self.run txt, options={} - $el.do_applescript %` + txt.gsub! "\\", '\\\\\\' + txt.gsub! '"', '\"' + + Applescript.run %` tell application "iTerm" #{options[:activate] ? 'activate' : ''} tell the first terminal diff --git a/lib/xiki/tools/javascript.rb b/lib/xiki/tools/javascript.rb index b4d373b0..172c69d3 100644 --- a/lib/xiki/tools/javascript.rb +++ b/lib/xiki/tools/javascript.rb @@ -11,7 +11,6 @@ def self.menu def self.run Block.do_as_something do |txt| - txt << " function p(txt) { var json = JSON.stringify(txt); @@ -19,8 +18,9 @@ def self.run print(json); }"; - result = self.run_internal txt + self.run_internal txt end + end def self.run_internal txt @@ -28,7 +28,7 @@ def self.run_internal txt # Write to temp file File.open("/tmp/tmp.js", "w") { |f| f << txt } # Call js - Console.run "js /tmp/tmp.js", :sync=>true + Shell.run "js /tmp/tmp.js", :sync=>true end def self.launch @@ -55,20 +55,21 @@ def self.enter_as_jquery def self.wrap_jquery_load txt, url=nil url ||= "http://code.jquery.com/jquery.min.js" - # url ||= "http://xiki.loc/assets/js/jquery.js" + # Temp hard-code to local! + # url ||= "http://xiki.loc/ajax/libs/jquery/1.9.1/jquery.min.js" txt = " - var f = function(){ - #{txt} - }; if(typeof($) == 'undefined') { - var s = document.createElement('script'); - s.src = '#{url}'; - s.onload = f; - document.getElementsByTagName('head')[0].appendChild(s); + if(! document.getElementById('xiki_jquery_tag')) { + var s = document.createElement('script'); + s.src = '#{url}'; + s.id = 'xiki_jquery_tag'; + document.getElementsByTagName('head')[0].appendChild(s); + } + '- loading jquery!' }else{ - f(); + ".unindent+txt+" } ".unindent @@ -77,3 +78,4 @@ def self.wrap_jquery_load txt, url=nil end end + diff --git a/lib/xiki/tools/local_storage.rb b/lib/xiki/tools/local_storage.rb index 082922db..c0817cc2 100644 --- a/lib/xiki/tools/local_storage.rb +++ b/lib/xiki/tools/local_storage.rb @@ -42,11 +42,11 @@ def self.all key=nil, val=nil if Keys.delete? Firefox.exec "localStorage.removeItem(\"#{key}\")" Tree.to_parent if val - Tree.kill_under + Tree.collapse View.flash "- deleted!" # TODO: let this behave properly when returning with .flash: # Currently it ends up one line off? Because we're treating it like it returned nil - make it treat ^.flash as nill? - # return ".flash - deleted!" + # return "<* deleted!" Line.delete return end diff --git a/lib/xiki/tools/mac.rb b/lib/xiki/tools/mac.rb index a5764fa4..4aa88717 100644 --- a/lib/xiki/tools/mac.rb +++ b/lib/xiki/tools/mac.rb @@ -20,6 +20,7 @@ def self.menu end def self.define_keys + self.keys View.flash "- defined Command-C, Command-V, Command-Q, etc." end @@ -28,11 +29,12 @@ def self.define_keys # # Put this line in your init.rb file to use the below key mappings. # - # Mac.keys_for_aquamacs def self.keys_for_aquamacs # options={} return if ! $el.boundp(:osx_key_mode_map) + $el.define_key(:osx_key_mode_map, $el.kbd("A-r")){ Browser.reload } + # Don't do the rest by default, since it presumes that "Options > Appearance > Auto Faces" is off ... $el.define_key(:osx_key_mode_map, $el.kbd("A-0")) { Styles.font_size 110 } @@ -44,38 +46,15 @@ def self.keys_for_aquamacs # options={} # Changes aquamacs behavior so it opens in same frame. Is there # an aquamacs setting for this that could be used instead of redefining # key? - $el.define_key :osx_key_mode_map, $el.kbd("A-n") do - View.new_file - end - - end - - # Use this if you're not using aquamacs - # - # Put this line in your init.rb file to use the below key mappings. - # - # Mac.keys - def self.keys + $el.define_key(:osx_key_mode_map, $el.kbd("A-n")) { View.new_file } # Command+N - # def self.keys - Keys._Q { $el.save_buffers_kill_emacs } # Command-Q to quit - Keys._C { - left, right = View.range - Effects.blink :left=>left, :right=>right - $el.kill_ring_save left, right - } # Command-C - Keys._V { $el.yank } # Command-V - Keys._A { View.to_highest; Clipboard.copy_everything } # Command-V + $el.define_key(:osx_key_mode_map, $el.kbd("")) { Code.indent_to } # Command+right + $el.define_key(:osx_key_mode_map, $el.kbd("")) { Code.indent_to :left=>1 } # Command+left - Keys._X { - left, right = View.range - Effects.blink :left=>left, :right=>right - $el.kill_region left, right - } + # Command+Control+X + $el.define_key(:osx_key_mode_map, $el.kbd("C-A-x")) { Xiki::Command.external } - Keys._S { DiffLog.save } # save (or, with prefix, save as) ** - - $el.define_key :global_map, $el.kbd("M-X"), :execute_extended_command end + end end diff --git a/lib/xiki/tools/memcache.rb b/lib/xiki/tools/memcache.rb index 89f4ff0d..e6b85513 100644 --- a/lib/xiki/tools/memcache.rb +++ b/lib/xiki/tools/memcache.rb @@ -1,6 +1,6 @@ -gem 'memcached' -require 'memcached' -require 'net/telnet' +# gem 'memcached' +# require 'memcached' +# require 'net/telnet' module Xiki class Memcache @@ -20,7 +20,7 @@ def self.start View.flash "- '*memcached' is already open!", :times=>4 return self.server end - Console.run('memcached -vv -p 11211 -m 64', :buffer=>buffer) + Shell.run('memcached -vv -p 11211 -m 64', :buffer=>buffer) end def self.server @@ -47,7 +47,7 @@ def self.suggest_if_not_running e | > Other platforms and more info @http://code.google.com/p/memcached/wiki/NewStart | - " if `which memcached`.empty? + " if `which memcached` !~ /\A\// return " > Not running diff --git a/lib/xiki/tools/models.rb b/lib/xiki/tools/models.rb deleted file mode 100644 index a711be25..00000000 --- a/lib/xiki/tools/models.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Xiki - class Models - def self.menu - " - - .list/ - - .relationships/ - " - # - structure/ - # - diagram/ - end - - def self.list - dir = Projects.default - models_dir = "#{dir}app/models/" - models = Dir.new(models_dir).entries.select{|o| o !~ /^\./} - models.map{|o| "@r/#{o[/\w+/].camelize}.first"} - end - - def self.relationships - dir = Projects.default - models_dir = "#{dir}app/models/" - txt = " - @#{models_dir} - - ##^ *(has_|belongs_to )/ - " - Tree.<< txt, :no_search=>1 - Line.next - Launcher.launch - nil - end - - end -end diff --git a/lib/xiki/tools/mysql.rb b/lib/xiki/tools/mysql.rb index ad74dbcd..d2738297 100644 --- a/lib/xiki/tools/mysql.rb +++ b/lib/xiki/tools/mysql.rb @@ -54,7 +54,6 @@ def self.install def self.menu_after txt, *args return nil if txt - ENV['no_slash'] = "1" Tree.quote self.run(nil, ENV['txt']) end @@ -63,13 +62,13 @@ def self.default_db db end def self.start - Console.run "mysqld", :buffer=>"mysql", :dir=>"/tmp/" + Shell.run "mysqld", :buffer=>"mysql", :dir=>"/tmp/" View.to_buffer "mysql" nil end def self.start_in_background - Console.run "mysql.server start" + Shell.run "mysql.server start" end def self.tables *args @@ -130,7 +129,7 @@ def self.dbs db=nil, table=nil, row=nil self.save db, table, row - ".flash - saved record!" + "<* saved record!" end def self.dummy_row db=nil, table=nil @@ -161,7 +160,7 @@ def self.use kind=nil, db=nil end @default_db = db - ".flash - using db #{db}!" + "<* using db #{db}!" end def self.create what, name=nil, columns=nil @@ -171,8 +170,8 @@ def self.create what, name=nil, columns=nil end if what == "db" - txt = Console.run "mysqladmin -u root create #{name}", :sync=>true - return ".flash - created db!" + txt = Shell.run "mysqladmin -u root create #{name}", :sync=>true + return "<* created db!" end if columns.nil? @@ -193,7 +192,7 @@ def self.create what, name=nil, columns=nil out = self.run(@default_db, txt) - ".flash - created table!" + "<* created table!" end def self.drop what, name=nil @@ -202,20 +201,20 @@ def self.drop what, name=nil end if what == "db" - txt = Console.run "mysqladmin -u root drop #{name}" #, :sync=>true + txt = Shell.run "mysqladmin -u root drop #{name}" #, :sync=>true return end out = self.run(@default_db, "drop table #{name}") - ".flash - dropped table!" + "<* dropped table!" end def self.run db, sql db ||= @default_db File.open("/tmp/tmp.sql", "w") { |f| f << sql } - out = Console.run "mysql -u root #{db} < /tmp/tmp.sql", :sync=>true + out = Shell.run "mysql -u root #{db} < /tmp/tmp.sql", :sync=>true raise "> Mysql doesn't appear to be running. Start it?\n- @mysql/setup/start/" if out =~ /^ERROR.+Can't connect/ raise "| Select a db first:\n- @mysql/setup/db/use/" if out =~ /^ERROR.+: No database selected/ @@ -245,10 +244,9 @@ def self.select statement, row=nil # Row passed, so save it - ENV['no_slash'] = "1" self.save @default_db, table, row - ".flash - saved!" + "<* - saved!" end end end diff --git a/lib/xiki/tools/node.rb b/lib/xiki/tools/node.rb index bdfa2f46..6e26f475 100644 --- a/lib/xiki/tools/node.rb +++ b/lib/xiki/tools/node.rb @@ -1,88 +1,11 @@ module Xiki class Node - def self.menu - " - > Pass javascript to eval in node - | puts(1 + 2) - - .controller/ - - docs/ - - menu/ - > To try out controller code - | @node/controller/ - | | function - - node/ - > Snippets - @technologies/node_js/ - " - end - - def self.menu_after output, *args - return output if output - - if args.empty? - View.prompt("Enter some code to run in node.js") - return "| " - end - - return self.block if args == ['block'] - - txt = Tree.leaf args[0] - result = Tree.quote self.run(txt) - - result - end - - def self.wrap_controller code - - " - var http = require('http'); - http.createServer(function (req, res) { - - #{code} - }).listen(1338, '127.0.0.1'); - console.log('Server running at http://127.0.0.1:1338/'); - ".unindent - - end - - def self.controller *args - - return " - | // Sample controller code - | console.log('got a request'); - | res.end('Hello Node World!'); - " if args.empty? - - code = self.wrap_controller ENV['txt'] - self.run_controller code - end - - def self.run_controller code - File.open("/tmp/controller.js", "w") { |f| f << code } - - Buffers.delete "node" if View.buffer_open? "node" - - Console.run "node controller.js", :dir=>"/tmp/", :buffer=>"node" - $el.sit_for 0.2 - Firefox.url "http://localhost:1338" - ".flash - showing in browser!" - end - - def self.block - left = Line.right + 1 - ignore, ignore, right = View.block_positions "^>" - - txt = self.run View.txt(right, left) - Block >> txt - - nil - end def self.run txt file = "/tmp/nodejs.js" txt = "function puts (txt){ return console.log(txt) }\n\n#{txt}" File.open(file, "w") { |f| f << txt } - Console["node #{file}"] + Shell["node #{file}"] end end diff --git a/lib/xiki/tools/postgres.rb b/lib/xiki/tools/postgres.rb index 7038cb9e..4d5fc6fc 100644 --- a/lib/xiki/tools/postgres.rb +++ b/lib/xiki/tools/postgres.rb @@ -6,7 +6,7 @@ def self.menu db=nil, table=nil # If just /, show all db's... if db.nil? - txt = Console.run("psql -c '\\l'", :sync=>true) + txt = Shell.run("psql -c '\\l'", :sync=>true) txt = txt.scan(/^ (\w+)/).map{|i| "- #{i[0]}/"} return txt.join "\n" end @@ -15,7 +15,7 @@ def self.menu db=nil, table=nil if table.nil? - txt = Console.run("psql #{db} -c '\\d'", :sync=>true) + txt = Shell.run("psql #{db} -c '\\d'", :sync=>true) txt = txt.split "\n" txt = txt[3..-2] output = "" @@ -30,7 +30,7 @@ def self.menu db=nil, table=nil # If /mydb/mytable, show rows... - txt = Console.run("psql #{db} -c 'select * from #{table}'", :sync=>true) + txt = Shell.run("psql #{db} -c 'select * from #{table}'", :sync=>true) txt = txt.split "\n" header = txt.shift txt.shift @@ -42,16 +42,16 @@ def self.menu db=nil, table=nil end def self.create_db name - Console.run "createdb #{name}" + Shell.run "createdb #{name}" end def self.drop_db name - Console.run "dropdb #{name}" + Shell.run "dropdb #{name}" end def self.tables db db.sub! /\/$/, '' - txt = Console.run "psql #{db} -c '\\d'", :sync=>true + txt = Shell.run "psql #{db} -c '\\d'", :sync=>true end end diff --git a/lib/xiki/tools/python.rb b/lib/xiki/tools/python.rb index e9e3c862..5082945d 100644 --- a/lib/xiki/tools/python.rb +++ b/lib/xiki/tools/python.rb @@ -18,24 +18,17 @@ def self.menu_after txt, *args txt end - def self.run_block - # Get block contents - txt, left, right = View.txt_per_prefix #:prefix=>Keys.prefix - - result = self.run_internal txt - # Insert result at end of block - orig = Location.new - View.cursor = right - Line.to_left - View.insert result.gsub(/^/, ' ') - orig.go + def self.run + Block.do_as_something do |txt| + result = self.run_internal txt + end end def self.run_internal txt # Write to temp file File.open("/tmp/tmp.py", "w") { |f| f << txt } # Call js - Console.run "python /tmp/tmp.py", :sync=>true + Shell.run "python /tmp/tmp.py", :sync=>true end end end diff --git a/lib/xiki/tools/r.rb b/lib/xiki/tools/r.rb deleted file mode 100644 index 502bec92..00000000 --- a/lib/xiki/tools/r.rb +++ /dev/null @@ -1,37 +0,0 @@ -gem 'httparty'; require 'httparty' - -module Xiki - # - # Runs code in a rails app. Implants xiki_dev_controller.rb into the - # app, so it can pass messages to it to eval. - # - # For security it only works in dev mode, only accepts local requests, - # and reads input from a file on disk. - # - class R - def self.menu code=nil, yaml=nil - - # If nothing passed, show last r/... commands... - - if ! code - txt = Launcher.last "r", :exclude_path=>1 - txt.gsub! /^- (\| )?/, '| ' - txt = txt.split("\n").uniq.join("\n") - return txt - end - - ENV['no_slash'] = "1" - - if yaml - txt = ENV['txt'] - txt = Rails.run_in_app txt, :yaml=>1 - else - txt = Rails.run_in_app ENV['txt'] - txt = Tree.quote(txt) if txt !~ /^\s+(>|\|)/ - end - - txt - end - - end -end diff --git a/lib/xiki/tools/rails.rb b/lib/xiki/tools/rails.rb deleted file mode 100644 index 4fcb80f0..00000000 --- a/lib/xiki/tools/rails.rb +++ /dev/null @@ -1,248 +0,0 @@ -require 'xiki/core/ruby_console' - -module Xiki - class Rails - - CODE_SAMPLES = %q< - # Show options to help create new rails app - - Show options: Rails.menu - > - - def self.menu - " - - .start/ - - .generate/ - - app/ - - model/ - - resource/ - - controller/ - - scaffold/ - - .interact/ - - .rails console/ - - .sqlite console/ - - @models/ - - .setup/ - - .db/ - - .migrate/ - - .use rspec/ - - .rails version/ - " - # - .eval/ - end - - def self.menu_after txt, *args - txt - end - - def self.menu_before *path - dir = Projects.default # Returns dir in tree, or current project (top of projects.menu) - - # Don't intercede if already rails app or trying to generate - return nil if ["generate", "general"].member?(path[0]) || File.exists?("#{dir}app") - - # If not a rails dir, give option to generate - return " - > No rails app in #{dir} yet. Generate it? - - generate/app/ - - > Non project-specific options - - general/ - " - end - - def self.rails_version - "| #{`rails --version`}" - end - - def self.use_rspec - dir = Projects.default - - txt = " - @ #{dir} - - 1. Add these lines: - - Gemfile - |+group :development, :test do - |+ gem 'rspec-rails' - |+end - | - - 2. Run these commands: - % bundle - % rails g rspec:install - | - - 3. Delete the test/ dir: - % rm -r test/ - " - end - - def self.sqlite_console - Console.run "sqlite3 db/development.sqlite3", :dir=>Projects.default, :buffer=>"sqlite console" - ".flash - opened console!" - end - - def self.rails_console - Console.run "rails c", :dir=>Projects.default, :buffer=>"rails console" - ".flash - opened console!" - end - - def self.generate what, name=nil, detail=nil - - examples = " - > Example fields - | name:string - | details:text - | summary:text - | quantity:integer - | price:decimal - | delivery:boolean - | purchased_at:datetime - | user:references - ".unindent - - case what - when "app" - Console.run "rails new . --skip-bundle", :dir=>Projects.default - return "- generating rails app..." - when "model", "resource", "scaffold" - return View.prompt "Enter a name" if ! name - return examples if ! detail - fields = ENV['txt'].gsub("\n", ' ').strip - Console.run "rails g #{what} #{name} #{fields}", :dir=>Projects.default - return "- generating #{what}..." - when "controller" - return View.prompt "Enter a name" if ! name - return View.prompt "Enter an action" if ! detail - Console.run "rails g controller #{name} #{detail}", :dir=>Projects.default - return "- generating controller..." - end - - "- Don't know how to generate a '#{what}'!" - end - - def self.start *args - - # If 1st arg is number, assume it's the port - port = args[0] =~ /^\d+$/ ? args.shift : nil - - # If 'browse', just bring up in browser - if args == ['browse'] - Firefox.url "http://localhost:#{port || 3000}/" - return ".flash - opened in browser!" - end - - command = "rails s" - command << " -p #{port}" if port - - Console.run command, :dir=>Projects.default, :buffer=>"rails server" - - # Check whether it's already running - "| Rails app was already running\n- browse/" - "| Starting rails app...\n- browse/" - - end - - def self.command txt - Console.run txt, :dir=>Projects.default - end - - def self.migrate - self.command "rake db:migrate" - end - - def self.eval *args - - if args.blank? - return " - > Put some code here, to run it in the context of a controller - | request.methods - " - end - - # Text passed, so run put in controller method and call - - # Start server if necessary - # And install the dev controller? - - txt = ENV['txt'] - - "- TODO) implement calling dev_controller" - end - - def self.run_in_app txt, options={} - - # If just code passed, run it... - - if options[:yaml] - # If yaml passed, deduce code to save model, and run... - - txt = " - txt = #{txt.inspect} - mods = YAML::load(txt) - - mods = [mods] if ! mods.is_a?(Array) - mods.each do |mod| - mod.partial_updates = false - - existing = mod.class.where :id=>mod.id - mod.instance_variable_set('@new_record', true) if existing.empty? - - mod.save - end - " - end - - File.open("/tmp/rails_run_tmp.txt", "w") { |f| f << txt } - response = HTTParty.get("http://localhost:3000/xiki_dev") rescue :exception - - return "| The rails server doesn't appear to be running. Start default server?\n@rails/start/" if response == :exception - - return self.suggest_creating_controller if response.response.is_a?(Net::HTTPNotFound) - - return ".flash - saved!" if options[:yaml] - - txt = response.body - txt.gsub!(/ +$/, '') - - # If is error, delete html at top - txt.sub!(/.+?

/m, '

') if txt =~ /
/ && txt =~ /

/ - - # If file not found, suggest generating it - if txt =~ /uninitialized constant XikiDevController::(\w+)/ - clazz = $1 - return " - > Class '#{clazz}' doesn't exist. Generate it as a model? - @#{Projects.default} - @rails/generate/model/#{clazz} - " - - end - - txt - - end - - def self.suggest_creating_controller - - - %` - | This rails app may not have the xiki dev controller - | installed to let xiki evaluate code in it in dev mode. - | Create it? - @#{Projects.default} - - app/controllers/ - - xiki_dev_controller.rb - | class XikiDevController < ApplicationController - | def index - | return render(:text=>"Disabled unless development and called locally.") if ! Rails.env.development? || request.remote_ip != "127.0.0.1" - | code = File.read "/tmp/rails_run_tmp.txt" - | txt = eval code - | render :text=>txt.to_yaml - | end - | end - - config/routes.rb - |+ match 'xiki_dev' => 'xiki_dev#index' - | end - ` - end - - end -end diff --git a/lib/xiki/tools/rake.rb b/lib/xiki/tools/rake.rb index 56e1d686..d27603c0 100644 --- a/lib/xiki/tools/rake.rb +++ b/lib/xiki/tools/rake.rb @@ -3,12 +3,12 @@ class Rake def self.menu task=nil if task.nil? # If no task passed, show all - txt = Console.run 'rake -T', :sync=>true + txt = Shell.run 'rake -T', :sync=>true return "- Error: no rake file." if txt =~ /^No Rakefile found/ return txt.scan(/^rake ([\w:]+)/).join("\n") end - Console.run "rake #{task}" # If task passed, run it + Shell.run "rake #{task}" # If task passed, run it end end end diff --git a/lib/xiki/tools/random_menu.rb b/lib/xiki/tools/random_menu.rb index 0487c7ba..0f6bbf23 100644 --- a/lib/xiki/tools/random_menu.rb +++ b/lib/xiki/tools/random_menu.rb @@ -1,7 +1,7 @@ module Xiki class RandomMenu def self.menu *args - dirs = ["~/menu/", "$xiki/menu/"] + dirs = ["~/.xiki/roots/"] menus = [] dirs.each do |dir| menus += Dir.new(Bookmarks[dir]).entries.grep(/\A[^.]/) diff --git a/lib/xiki/tools/readme.rb b/lib/xiki/tools/readme.rb index 29c01125..365fe781 100644 --- a/lib/xiki/tools/readme.rb +++ b/lib/xiki/tools/readme.rb @@ -1,7 +1,7 @@ module Xiki class Readme def self.menu *args - txt = File.read "#{Bookmarks["$x"]}README.markdown" + txt = File.read "#{Bookmarks["%xiki"]}README.md" Notes.from_markdown_format txt end end diff --git a/lib/xiki/tools/redmine.rb b/lib/xiki/tools/redmine.rb index b6f2cc05..272a43c8 100644 --- a/lib/xiki/tools/redmine.rb +++ b/lib/xiki/tools/redmine.rb @@ -1,7 +1,7 @@ -require 'net/http' -require 'uri' -require 'timeout' -require 'xiki/core/keys' +# require 'net/http' +# require 'uri' +# require 'timeout' +# require 'xiki/core/keys' module Xiki class Redmine @@ -15,7 +15,6 @@ def self.menu + .pages/ - .start - local: http://localhost:3401/wiki/gateway - - remote: http://rlitio.chase.com:3401/wiki/1 " end @@ -85,7 +84,7 @@ def self.page_from_server name end def self.save - name, version = View.buffer_name.split('/')[1..2] + name, version = View.name.split('/')[1..2] txt = View.txt # Get text from buffer return puts("- Not a redmine page!") unless txt self.xiki_to_redmine txt @@ -131,7 +130,6 @@ def self.init # Inherit notes_mode_map! $el.set_keymap_parent $el.elvar.redmine_mode_map, $el.elvar.notes_mode_map end - Keys.XS(:redmine_mode_map) { Redmine.save } # Make C-. follow link Launcher.add(/\[\[.+\]\]/) do |line| # Redmine wiki links @@ -162,7 +160,7 @@ def self.apply_styles end def self.start - Rails.start '$o/redmine/trunk', @@url[/\d+/] + Rails.start '%o/redmine/trunk', @@url[/\d+/] end end diff --git a/lib/xiki/tools/riak_tree.rb b/lib/xiki/tools/riak_tree.rb index 806fd3d9..03cfa4e8 100644 --- a/lib/xiki/tools/riak_tree.rb +++ b/lib/xiki/tools/riak_tree.rb @@ -1,6 +1,6 @@ -gem 'httparty'; require 'httparty' -gem 'json'; require 'json' -gem 'riak-client'; require 'riak' +# gem 'httparty'; require 'httparty' +# gem 'json'; require 'json' +# gem 'riak-client'; require 'riak' module Riak @@ -47,7 +47,7 @@ def self.buckets bucket=nil, key=nil, txt=nil # Show contents of object if passed if key - Files.append "~/.emacs.d/riak_log.notes", "- Riak.buckets \"#{bucket}\", \"#{key}\"/" + Files.append "~/.emacs.d/riak_log.xiki", "- Riak.buckets \"#{bucket}\", \"#{key}\"/" key = "#{bucket}/#{key}" @@ -69,7 +69,7 @@ def self.filter bucket=nil, *args if args.any? args = args.join(', ').sub(/\/$/, '') - Files.append "~/.emacs.d/riak_log.notes", "- Riak.filter \"#{bucket}\", #{args}/" + Files.append "~/.emacs.d/riak_log.xiki", "- Riak.filter \"#{bucket}\", #{args}/" args = eval "[#{args}]" @@ -172,33 +172,33 @@ def self.help bucket=nil, key=nil end def self.ping - `#{Bookmarks['$riak']}/bin/riak ping` + `#{Bookmarks['%riak']}/bin/riak ping` end def self.log - txt = File.read File.expand_path("~/.emacs.d/riak_log.notes") + txt = File.read File.expand_path("~/.emacs.d/riak_log.xiki") txt = txt.split("\n").reverse.uniq.join("\n") end end -Keys.enter_list_riak do - Launcher.insert '- Riak.menu/' -end +# Keys.enter_list_riak do +# Launcher.insert '- Riak.menu/' +# end -Keys.enter_list_buckets do - Launcher.insert '- buckets/' -end +# Keys.enter_list_buckets do +# Launcher.insert '- buckets/' +# end -Launcher.add("riak") do |path| - " - - @buckets/ - " -end +# Launcher.add("riak") do |path| +# " +# - @buckets/ +# " +# end -Launcher.add "buckets" do |path| - args = path.split('/')[1..-1] - Riak.buckets(*args) -end +# Launcher.add "buckets" do |path| +# args = path.split('/')[1..-1] +# Riak.buckets(*args) +# end -CodeTree.add_menu "Riak" # Force it, since we're a module +# CodeTree.add_menu "Riak" # Force it, since we're a module diff --git a/lib/xiki/tools/rvm.menu b/lib/xiki/tools/rvm.menu deleted file mode 100644 index 4c684f99..00000000 --- a/lib/xiki/tools/rvm.menu +++ /dev/null @@ -1,6 +0,0 @@ -- list/ - @$ rvm list -- set default/ - @% rvm --default use system - @% rvm --default use 1.8.7 - @% rvm --default use 1.9.3 diff --git a/lib/xiki/tools/safari.rb b/lib/xiki/tools/safari.rb index 6d40e808..5f03f9aa 100644 --- a/lib/xiki/tools/safari.rb +++ b/lib/xiki/tools/safari.rb @@ -2,7 +2,7 @@ module Xiki class Safari def self.reload - $el.do_applescript %` + Applescript.run %` tell application "Safari" do JavaScript "window.location.reload();" in the first document end tell diff --git a/lib/xiki/tools/sass.rb b/lib/xiki/tools/sass.rb index c9a34fd8..4b337c82 100644 --- a/lib/xiki/tools/sass.rb +++ b/lib/xiki/tools/sass.rb @@ -11,7 +11,7 @@ def self.menu *args Firefox.exec code - ".flash - Loaded in browser!" + "<* Loaded in browser!" end end end diff --git a/lib/xiki/tools/say.rb b/lib/xiki/tools/say.rb index f7dd8674..212c5394 100644 --- a/lib/xiki/tools/say.rb +++ b/lib/xiki/tools/say.rb @@ -2,7 +2,7 @@ module Xiki class Say def self.menu *args txt = args.join('/').gsub('"', '\"') - $el.do_applescript "say \"#{txt}\"" + Applescript.run "say \"#{txt}\"" end end end diff --git a/lib/xiki/tools/selenium_wrapper.rb b/lib/xiki/tools/selenium_wrapper.rb new file mode 100644 index 00000000..baf7c727 --- /dev/null +++ b/lib/xiki/tools/selenium_wrapper.rb @@ -0,0 +1,231 @@ +# +# > .kill server +# ! result = SeleniumWrapper.quit +# ! result == "" ? "killed" : result +# +# > .check if server running +# ! RestTree.get("http://localhost:5313/ping", :raise=>1) rescue "not running" +# +# > .Run javascript in the browser +# ! SeleniumWrapper.js("return 1 + 2") +# +# > .Go to a url +# ! SeleniumWrapper.url("http://google.com") +# + +if ENV['XIKI_DIR'] + require "#{ENV['XIKI_DIR']}/lib/xiki/core/ol.rb" +end + +# +# How to run independently > for troubleshooting +# +# $ export GEM_HOME=~/.xiki/misc/gems/; ruby -e "load '/Users/craig/Dropbox/xiki/lib/xiki/tools/selenium_wrapper.rb'; Xiki::SeleniumWrapper.start_server" +# +module Xiki + class SeleniumWrapper + + @@driver = {} + + @@jquery_url = "http://code.jquery.com/jquery.min.js" + # @@jquery_url = "http://localhost:8162/js/jquery.min.js" + + # + # > .select current tab + # ! SeleniumWrapper.select_visible_tab + # + def self.select_visible_tab + + driver = self.driver + + # For each window + driver.window_handles.each do |window| + # Select to each window + driver.switch_to.window window + + # Hidden, so ignore + next if driver.execute_script("return document.hidden") + + # Visible, so stop looking and leave it selected + return + end + + end + + def self.driver + begin + require "selenium-webdriver" + rescue LoadError + puts 'Selenium gem not found. Install it with "xikigem install selenium"' + # Todo > tell users they need to install selenium + return nil # Callers shauld do something else if nil + end + + # kind = (Conf.get("driver", "driver") || "firefox").to_sym + # For now > hard-code to chrome + kind = "chrome".to_sym + + begin + + driver = @@driver[kind] + if ! driver + + driver = @@driver[kind] = Selenium::WebDriver.for kind + + # Set to the right half of the window + self.resize driver + end + + # Hangs when no window now? > check number of windows + # This seems to be necessary to make it not hang + if driver.window_handles.length == 0 + @@driver[kind].quit # Quit existing browser + raise "no window" + end + + # Do something trivial, so it'll error if not connected + driver.execute_script("return 1") + + rescue Exception=>e + + driver = @@driver[kind] = Selenium::WebDriver.for kind + + self.resize driver + + end + + @@driver[kind] + + end + + def self.resize driver + # driver.manage.window.move_to(720, 20) + # driver.manage.window.resize_to(720, 865) + driver.manage.window.move_to(750, 20) + driver.manage.window.resize_to(670, 855) + end + + def self.url txt + # Make sure server is running + self.ensure_server_running + + RestTree.post("http://localhost:5313/url", :raise=>1, :body=>txt) + end + + def self.url_internal txt + # Always go to the visible tab first + self.select_visible_tab + + self.driver.navigate.to txt + end + + + def self.start_server + + require 'webrick' + server = WEBrick::HTTPServer.new :Port=>5313 + trap('INT') { server.shutdown } + + server.mount_proc '/js' do |req, res| + txt = self.js_internal req.body + res.body = txt.to_s + end + server.mount_proc '/quit' do |req, res| + server.shutdown + end + server.mount_proc '/url' do |req, res| + txt = self.url_internal req.body + res.body = txt.to_s + end + + server.mount_proc '/ping' do |req, res| + res.body = "pong" + end + + server.start + + end + + def self.ensure_server_running + # See if we get a response! + txt = RestTree.get("http://localhost:5313/ping", :raise=>1) + rescue Exception=>e + + # Not running, so start + Shell.sync %`export GEM_HOME=~/.xiki/misc/gems/; ruby -e "load '#{XIKI_DIR}/lib/xiki/tools/selenium_wrapper.rb'; Xiki::SeleniumWrapper.start_server" &` + + # Give it time before we try to connect + sleep 0.5 + end + + # Main entry point for running JavaScript. It'll spin off a server + # in the background if One isn't running yet. It Delegates to + # .js_internal. + def self.js txt, options={} + + # Make sure server is running + self.ensure_server_running + + RestTree.post("http://localhost:5313/js", :raise=>1, :body=>txt) + end + + def self.quit + RestTree.post "http://localhost:5313/quit" #, :raise=>1 + end + + def self.js_internal txt, options={} + driver = self.driver + + raise " + > A gem is required + | The selenium-webdriver gem must be installed to send js + | to the browser. To jump back to your shell and install + | it as a xiki-local gem, type ^G on this line: + | + $ xikigem install selenium-webdriver + " if ! driver + + + # Always go to the visible tab first + self.select_visible_tab + + + txt = begin + driver.execute_script txt + rescue Exception=>e + + if e.message =~ /\Aunknown error: \$ is not defined\n/ + driver.execute_script " + var s=document.createElement('script'); + s.setAttribute('src', '#{@@jquery_url}'); s.setAttribute('id', 'jqid'); + document.getElementsByTagName('head')[0].appendChild(s); + + var s=document.createElement('script'); + s.setAttribute('src', 'http://xiki.org/javascripts/util.js'); + document.getElementsByTagName('head')[0].appendChild(s); + ".unindent + + wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds + wait.until { driver.execute_script("return typeof( $ )") != "undefined" } + + driver.execute_script txt + end + end + + # Selenium objects in result, so change to html + + if txt.class == Array + return txt.map do |o| + if o.class == Selenium::WebDriver::Element + next o.attribute("outerHTML") + end + o + end.join("\n") + end + + return txt + + end + + end +end diff --git a/lib/xiki/tools/serve.rb b/lib/xiki/tools/serve.rb index 8933f4e4..ef27e140 100644 --- a/lib/xiki/tools/serve.rb +++ b/lib/xiki/tools/serve.rb @@ -5,7 +5,7 @@ def self.menu *args children = Tree.children(:string=>1) children = children.unindent if children - trunk = Xiki.trunk + trunk = Tree.path # If no children and no parents, say how to use it. if children.blank? && trunk.length == 1 @@ -24,7 +24,7 @@ def self.menu *args if children.blank? - file = File.expand_path "~/menu/#{trunk[0]}.menu" + file = File.expand_path "~/.xiki/roots/#{trunk[0]}.menu" else file = "/tmp/tmp.menu" File.open(file, "w") { |f| f << children } @@ -33,13 +33,13 @@ def self.menu *args code = self.wrap_controller file Node.run_controller code - ".flash - showing in browser!" + "<* showing in browser!" end def self.wrap_controller file %` var http = require('http'); - var Xiki = require('#{Xiki.dir}etc/js/xiki.js'); + var Xiki = require('#{Xiki.dir}misc/js/xiki.js'); var fs = require('fs'); http.createServer(function(req, res) { @@ -50,7 +50,7 @@ def self.wrap_controller file // If requesting js, just return it if(url.match(/^\\/js\\//)){ res.writeHead(200, {'Content-Type': 'application/x-javascript'}); - var js = fs.readFileSync('#{Xiki.dir}etc/js/'+url.replace(/.+\\//, ''), 'utf8'); + var js = fs.readFileSync('#{Xiki.dir}misc/js/'+url.replace(/.+\\//, ''), 'utf8'); res.end(js); return; } @@ -71,8 +71,8 @@ def self.wrap_controller file } - }).listen(8161, '127.0.0.1'); - console.log('Server running at http://127.0.0.1:8161/'); + }).listen(8163, '127.0.0.1'); + console.log('Server running at http://127.0.0.1:8163/'); `.unindent end diff --git a/lib/xiki/tools/shoes.menu b/lib/xiki/tools/shoes.menu deleted file mode 100644 index 59f00ffc..00000000 --- a/lib/xiki/tools/shoes.menu +++ /dev/null @@ -1,1482 +0,0 @@ -> Type some shoes code here -| Shoes.app { button "Hello World" } -- examples/ - - button/ - @shoes/ - | Shoes.app :width=>104, :height=>40 do - | button("Click me!") { alert("Good job.") } - | end - - mask/ - @shoes/ - | Shoes.app do - | background black - | - | stack :top => 0.4, :left => 0.2 do - | @stripes = stack - | - | mask do - | title "Shoes", :weight => "bold", :size => 82 - | end - | end - | - | animate 10 do - | @stripes.clear do - | 20.times do |i| - | strokewidth 4 - | stroke rgb((0.0..0.5).rand, (0.0..1.0).rand, (0.0..0.3).rand) - | line 0, i * 5, 400, i * 8 - | end - | end - | end - | end - - dialogs/ - @shoes/ - | Shoes.app :width => 300, :height => 150, :margin => 10 do - | def answer(v) - | @answer.replace v.inspect - | end - | - | button "Ask" do - | answer ask("What is your name?") - | end - | button "Confirm" do - | answer confirm("Would you like to proceed?") - | end - | button "Open File..." do - | answer ask_open_file - | end - | button "Save File..." do - | answer ask_save_file - | end - | button "Open Folder..." do - | answer ask_open_folder - | end - | button "Save Folder..." do - | answer ask_save_folder - | end - | button "Color" do - | answer ask_color("Pick a Color") - | end - | - | @answer = para "Answers appear here" - | end - - clock/ - @shoes/ - | # - | # Shoes Clock by Thomas Bell - | # posted to the Shoes mailing list on 04 Dec 2007 - | # - | Shoes.app :height => 260, :width => 250 do - | @radius, @centerx, @centery = 90, 126, 140 - | animate(8) do - | @time = Time.now - | clear do - | draw_background - | stack do - | background black - | para @time.strftime("%a"), - | span(@time.strftime(" %b %d, %Y "), :stroke => "#ccc"), - | strong(@time.strftime("%I:%M"), :stroke => white), - | @time.strftime(".%S"), :align => "center", :stroke => "#666", - | :margin => 4 - | end - | clock_hand @time.sec + (@time.usec * 0.000001),2,30,red - | clock_hand @time.min + (@time.sec / 60.0),5 - | clock_hand @time.hour + (@time.min / 60.0),8,6 - | end - | end - | def draw_background - | background rgb(230, 240, 200) - | - | fill white - | stroke black - | strokewidth 4 - | oval @centerx - 102, @centery - 102, 204, 204 - | - | fill black - | nostroke - | oval @centerx - 5, @centery - 5, 10, 10 - | - | stroke black - | strokewidth 1 - | line(@centerx, @centery - 102, @centerx, @centery - 95) - | line(@centerx - 102, @centery, @centerx - 95, @centery) - | line(@centerx + 95, @centery, @centerx + 102, @centery) - | line(@centerx, @centery + 95, @centerx, @centery + 102) - | end - | def clock_hand(time, sw, unit=30, color=black) - | radius_local = unit == 30 ? @radius : @radius - 15 - | _x = radius_local * Math.sin( time * Math::PI / unit ) - | _y = radius_local * Math.cos( time * Math::PI / unit ) - | stroke color - | strokewidth sw - | line(@centerx, @centery, @centerx + _x, @centery - _y) - | end - | end - - follow/ - @shoes/ - | trails = [[0, 0]] * 60 - | Shoes.app :width => 200, :height => 200, :resizable => false do - | nostroke - | fill rgb(0x3, 0x1, 0x3, 0.6) - | - | # animation at 100 frames per second - | animate(60) do - | trails.shift - | trails << self.mouse[1, 2] - | - | clear do - | # change the background based on where the pointer is - | background rgb( - | 20 + (70 * (trails.last[0].to_f / self.width)).to_i, - | 20 + (70 * (trails.last[1].to_f / self.height)).to_i, - | 51) - | - | # draw circles progressively bigger - | trails.each_with_index do |(x, y), i| - | i += 1 - | oval :left => x, :top => y, :radius => (i*0.5), :center => true - | end - | end - | end - | - | end - - tank spank/ - @shoes/ - | # Tankspank - | # kevin conner - | # connerk@gmail.com - | # version 3, 13 March 2008 - | # this code is free, do what you like with it! - | - | $width, $height = 700, 500 - | $camera_tightness = 0.1 - | - | module Collisions - | def contains? x, y - | not (x < west or x > east or y < north or y > south) - | end - | - | def intersects? other - | not (other.east < west or other.west > east or - | other.south < north or other.north > south) - | end - | end - | - | class Building - | include Collisions - | - | attr_reader :west, :east, :north, :south - | - | def initialize(west, east, north, south) - | @west, @east, @north, @south = west, east, north, south - | @top, @bottom = 1.1 + rand(3) * 0.15, 1.0 - | - | color = (1..3).collect { 0.2 + 0.4 * rand } - | color << 0.9 - | @stroke = $app.rgb *color - | color[-1] = 0.3 - | @fill = $app.rgb *color - | end - | - | def draw - | $app.stroke @stroke - | $app.fill @fill - | Opp.draw_opp_box(@west, @east, @north, @south, @top, @bottom) - | end - | end - | - | module Guidance - | def guidance_system x, y, dest_x, dest_y, angle - | vx, vy = dest_x - x, dest_y - y - | if vx.abs < 0.1 and vy.abs <= 0.1 - | yield 0, 0 - | else - | length = Math.sqrt(vx * vx + vy * vy) - | vx /= length - | vy /= length - | ax, ay = Math.cos(angle), Math.sin(angle) - | cos_between = vx * ax + vy * ay - | sin_between = vx * -ay + vy * ax - | yield sin_between, cos_between - | end - | end - | end - | - | module Life - | attr_reader :health - | def dead? - | @health == 0 - | end - | def hurt damage - | @health = [@health - damage, 0].max - | end - | end - | - | class Tank - | include Collisions - | include Guidance - | include Life - | # ^ sounds like insurance - | - | @@collide_size = 15 - | def west; @x - @@collide_size; end - | def east; @x + @@collide_size; end - | def north; @y - @@collide_size; end - | def south; @y + @@collide_size; end - | - | attr_reader :x, :y - | - | def initialize - | @x, @y = 0, -125 - | @last_x, @last_y = @x, @y - | @tank_angle = 0.0 - | @dest_x, @dest_y = 0, 0 - | @acceleration = 0.0 - | @speed = 0.0 - | @moving = false - | - | @aim_angle = 0.0 - | @target_x, @target_y = 0, 0 - | @aimed = false - | - | @health = 100 - | end - | - | def set_destination - | @dest_x, @dest_y = @target_x, @target_y - | @moving = true - | end - | - | def fire - | Opp.add_shell Shell.new(@x + 30 * Math.cos(@aim_angle), - | @y + 30 * Math.sin(@aim_angle), @aim_angle) - | end - | - | def update button, mouse_x, mouse_y - | @target_x, @target_y = mouse_x, mouse_y - | - | if @moving - | guidance_system @x, @y, @dest_x, @dest_y, @tank_angle do |direction, on_target| - | turn direction - | @acceleration = on_target * 0.25 - | end - | - | distance = Math.sqrt((@dest_x - @x) ** 2 + (@dest_y - @y) ** 2) - | @moving = false if distance < 50 - | else - | @acceleration = 0.0 - | end - | - | guidance_system @x, @y, @target_x, @target_y, @aim_angle do |direction, on_target| - | aim direction - | @aimed = on_target > 0.98 - | end - | - | integrity = @health / 100.0 # the more hurt you are, the slower you go - | @speed = [[@speed + @acceleration, 5.0 * integrity].min, -3.0 * integrity].max - | @speed *= 0.9 if !@moving - | - | @last_x, @last_y = @x, @y - | @x += @speed * Math.cos(@tank_angle) - | @y += @speed * Math.sin(@tank_angle) - | end - | - | def collide_and_stop - | @x, @y = @last_x, @last_y - | hurt @speed.abs * 3 + 5 - | @speed = 0 - | @moving = false - | end - | - | def turn direction - | @tank_angle += [[-0.03, direction].max, 0.03].min - | end - | - | def aim direction - | @aim_angle += [[-0.1, direction].max, 0.1].min - | end - | - | def draw - | $app.stroke $app.blue - | $app.fill $app.blue(0.4) - | Opp.draw_opp_rect @x - 20, @x + 20, @y - 15, @y + 15, 1.05, @tank_angle - | #Opp.draw_opp_box @x - 20, @x + 20, @y - 20, @y + 20, 1.03, 1.0 - | Opp.draw_opp_rect @x - 10, @x + 10, @y - 7, @y + 7, 1.05, @aim_angle - | x, unused1, y, unused2 = Opp.project(@x, 0, @y, 0, 1.05) - | $app.line x, y, x + 25 * Math.cos(@aim_angle), y + 25 * Math.sin(@aim_angle) - | - | $app.stroke $app.red - | $app.fill $app.red(@aimed ? 0.4 : 0.1) - | Opp.draw_opp_oval @target_x - 10, @target_x + 10, @target_y - 10, @target_y + 10, 1.00 - | - | if @moving - | $app.stroke $app.green - | $app.fill $app.green(0.2) - | Opp.draw_opp_oval @dest_x - 20, @dest_x + 20, @dest_y - 20, @dest_y + 20, 1.00 - | end - | end - | end - | - | class Shell - | attr_reader :x, :y - | - | def initialize x, y, angle - | @x, @y, @angle = x, y, angle - | @speed = 10.0 - | end - | - | def update - | @x += @speed * Math.cos(@angle) - | @y += @speed * Math.sin(@angle) - | end - | - | def draw - | $app.stroke $app.red - | $app.fill $app.red(0.1) - | Opp.draw_opp_box @x - 2, @x + 2, @y - 2, @y + 2, 1.05, 1.04 - | end - | end - | - | class Opp - | def self.new_game - | @offset_x, @offset_y = 0, 0 - | @buildings = [ - | [-1000, -750, -750, -250], - | [-500, 250, -750, -250], - | [500, 1000, -750, -500], - | [750, 1250, -250, 0], - | [750, 1250, 250, 750], - | [250, 500, 0, 750], - | [-250, 0, 0, 500], - | [-500, 0, 750, 1000], - | [-1000, -500, 0, 500], - | [400, 600, -350, -150] - | ].collect { |p| Building.new *p } - | @shells = [] - | @boundary = [-1250, 1500, -1250, 1250] - | @tank = Tank.new - | @center_x, @center_y = $app.width / 2, $app.height / 2 - | end - | - | def self.tank - | @tank - | end - | - | def self.read_input - | @input = $app.mouse - | end - | - | def self.update_scene - | button, x, y = @input - | x += @offset_x - @center_x - | y += @offset_y - @center_y - | - | @tank.update(button, x, y) if !@tank.dead? - | @buildings.each do |b| - | @tank.collide_and_stop if b.intersects? @tank - | end - | - | @shells.each { |s| s.update } - | @buildings.each do |b| - | @shells.reject! do |s| - | b.contains?(s.x, s.y) - | end - | end - | #collide shells with tanks -- don't need this until there are enemy tanks - | #@shells.reject! do |s| - | # @tank.contains?(s.x, s.y) - | #end - | - | $app.clear do - | @offset_x += $camera_tightness * (@tank.x - @offset_x) - | @offset_y += $camera_tightness * (@tank.y - @offset_y) - | - | $app.background $app.black - | @center_x, @center_y = $app.width / 2, $app.height / 2 - | - | $app.stroke $app.red(0.9) - | $app.nofill - | draw_opp_box *(@boundary + [1.1, 1.0, false]) - | - | @tank.draw - | @shells.each { |s| s.draw } - | @buildings.each { |b| b.draw } - | end - | end - | - | def self.add_shell shell - | @shells << shell - | @shells.shift if @shells.size > 10 - | end - | - | def self.project left, right, top, bottom, depth - | [left, right].collect { |x| @center_x + depth * (x - @offset_x) } + - | [top, bottom].collect { |y| @center_y + depth * (y - @offset_y) } - | end - | - | # here "front" and "back" push the rect into and out of the window. - | # 1.0 means your x and y units are pixels on the surface. - | # greater than that brings the box closer. less pushes it back. 0.0 => infinity. - | # the front will be filled but the rest is wireframe only. - | def self.draw_opp_box left, right, top, bottom, front, back, occlude = true - | near_left, near_right, near_top, near_bottom = project(left, right, top, bottom, front) - | far_left, far_right, far_top, far_bottom = project(left, right, top, bottom, back) - | - | # determine which sides of the box are visible - | if occlude - | draw_left = @center_x < near_left - | draw_right = near_right < @center_x - | draw_top = @center_y < near_top - | draw_bottom = near_bottom < @center_y - | else - | draw_left, draw_right, draw_top, draw_bottom = [true] * 4 - | end - | - | # draw lines for the back edges - | $app.line far_left, far_top, far_right, far_top if draw_top - | $app.line far_left, far_bottom, far_right, far_bottom if draw_bottom - | $app.line far_left, far_top, far_left, far_bottom if draw_left - | $app.line far_right, far_top, far_right, far_bottom if draw_right - | - | # draw lines to connect the front and back - | $app.line near_left, near_top, far_left, far_top if draw_left or draw_top - | $app.line near_right, near_top, far_right, far_top if draw_right or draw_top - | $app.line near_left, near_bottom, far_left, far_bottom if draw_left or draw_bottom - | $app.line near_right, near_bottom, far_right, far_bottom if draw_right or draw_bottom - | - | # draw the front, filled - | $app.rect near_left, near_top, near_right - near_left, near_bottom - near_top - | end - | - | def self.draw_opp_rect left, right, top, bottom, depth, angle, with_x = false - | pl, pr, pt, pb = project(left, right, top, bottom, depth) - | cos = Math.cos(angle) - | sin = Math.sin(angle) - | cx, cy = (pr + pl) / 2.0, (pb + pt) / 2.0 - | points = [[pl, pt], [pr, pt], [pr, pb], [pl, pb]].collect do |x, y| - | [cx + (x - cx) * cos - (y - cy) * sin, - | cy + (x - cx) * sin + (y - cy) * cos] - | end - | - | $app.line *(points[0] + points[1]) - | $app.line *(points[1] + points[2]) - | $app.line *(points[2] + points[3]) - | $app.line *(points[3] + points[0]) - | end - | - | def self.draw_opp_oval left, right, top, bottom, depth - | pl, pr, pt, pb = project(left, right, top, bottom, depth) - | $app.oval(pl, pt, pr - pl, pb - pt) - | end - | - | def self.draw_opp_plane x1, y1, x2, y2, front, back, stroke_color - | near_x1, near_x2, near_y1, near_y2 = project(x1, x2, y1, y2, front) - | far_x1, far_x2, far_y1, far_y2 = project(x1, x2, y1, y2, back) - | - | $app.stroke stroke_color - | - | $app.line far_x1, far_y1, far_x2, far_y2 - | $app.line far_x1, far_y1, near_x1, near_y1 - | $app.line far_x2, far_y2, near_x2, near_y2 - | $app.line near_x1, near_y1, near_x2, near_y2 - | end - | end - | - | Shoes.app :width => $width, :height => $height do - | $app = self - | - | Opp.new_game - | @playing = true - | - | keypress do |key| - | if @playing - | if key == "1" or key == "z" - | Opp.tank.set_destination - | elsif key == "2" or key == "x" or key == " " - | Opp.tank.fire - | end - | else - | if key == "n" - | Opp.new_game - | @playing = true - | end - | end - | end - | - | click do |button, x, y| - | if @playing - | if button == 1 - | Opp.tank.set_destination - | else - | Opp.tank.fire - | end - | end - | end - | - | game_over_count = -1 - | animate(60) do - | Opp.read_input if @playing - | Opp.update_scene - | - | @playing = false if Opp.tank.dead? - | if !@playing - | stack do - | banner "Game Over", :stroke => white, :margin => 10 - | caption "learn to drive!", :stroke => white, :margin => 20 - | end - | end - | end - | end - - arc/ - @shoes/ - | # - | # a translation from a processing example - | # http://vormplus.be/weging/an-introduction-to-processing/ - | # - | Shoes.app :width => 420, :height => 420, :resizable => false do - | rotation = -(Shoes::HALF_PI / 3) - | step = 20 - | - | background gray(240) - | stroke gray(127) - | cap :curve - | nofill - | - | 10.times do |i| - | strokewidth i - | size = 200 + (step * i) - | shape do - | arc self.width / 2, self.height / 2, - | size, size, - | rotation * i, rotation * i + Shoes::TWO_PI - Shoes::HALF_PI - | end - | end - | end - - draw/ - @shoes/ - | Shoes.app do - | background "#999" - | stroke "#000" - | x, y = nil, nil - | motion do |_x, _y| - | if x and y and (x != _x or y != _y) - | append do - | line x, y, _x, _y - | end - | end - | x, y = _x, _y - | end - | end - - reminder/ - @shoes/ - | require 'yaml' - | - | Shoes.app :title => "A Gentle Reminder", - | :width => 370, :height => 560, :resizable => false do - | - | background white - | background tan, :height => 40 - | - | caption "A Gentle Reminder", :margin => 8, :stroke => white - | - | stack :margin => 10, :margin_top => 50 do - | para "You need to", :stroke => red, :fill => yellow - | - | stack :margin_left => 5, :margin_right => 10, :width => 1.0, :height => 200, :scroll => true do - | background white - | border white, :strokewidth => 3 - | @gui_todo = para - | end - | - | flow :margin_top => 10 do - | para "Remember to" - | @add = edit_line(:margin_left => 10, :width => 180) - | button("Add", :margin_left => 5) { add_todo(@add.text); @add.text = '' } - | end - | end - | - | stack :margin_top => 10 do - | background darkgray - | para strong('Completed'), :stroke => white - | end - | - | @gui_completed = stack :width => 1.0, :height => 207, :margin_right => 20 - | - | - | def data_path - | if RUBY_PLATFORM =~ /win32/ - | if ENV['USERPROFILE'] - | if File.exist?(File.join(File.expand_path(ENV['USERPROFILE']), "Application Data")) - | user_data_directory = File.join File.expand_path(ENV['USERPROFILE']), "Application Data", "GentleReminder" - | else - | user_data_directory = File.join File.expand_path(ENV['USERPROFILE']), "GentleReminder" - | end - | else - | user_data_directory = File.join File.expand_path(Dir.getwd), "data" - | end - | else - | user_data_directory = File.expand_path(File.join("~", ".gentlereminder")) - | end - | - | unless File.exist?(user_data_directory) - | Dir.mkdir(user_data_directory) - | end - | - | return File.join(user_data_directory, "data.yaml") - | end - | - | - | def refresh_todo - | @gui_todo.replace *( - | @todo.map { |item| - | [ item, ' ' ] + [ link('Done') { complete_todo item } ] + [ ' ' ] + - | [ link('Forget it') { forget_todo item } ] + [ "\n" ] - | }.flatten - | ) - | end - | - | - | def refresh - | refresh_todo - | - | @gui_completed.clear - | - | @gui_completed.append do - | background white - | - | @completed.keys.sort.reverse.each { |day| - | stack do - | background lightgrey - | para strong(Time.at(day).strftime('%B %d, %Y')), :stroke => white - | end - | - | stack do - | inscription *( - | @completed[day].map { |item| - | [ item ] + [ ' ' ] + [ link('Not Done') { undo_todo day, item } ] + - | (@completed[day].index(item) == @completed[day].length - 1 ? [ '' ] : [ "\n" ]) - | }.flatten - | ) - | end - | - | } - | end - | end - | - | - | def complete_todo(item) - | day = Time.today.to_i - | - | if @completed.keys.include? day - | @completed[day] << item - | else - | @completed[day] = [ item ] - | end - | - | @todo.delete(item) - | - | save - | - | refresh - | end - | - | - | def undo_todo(day, item) - | @completed[day].delete item - | - | @completed.delete(day) if @completed[day].empty? - | - | @todo << item unless @todo.include? item - | - | save - | - | refresh - | end - | - | - | def add_todo(item) - | item = item.strip - | - | return if item == '' - | - | if @todo.include? item - | alert('You have already added that to the list!') - | return - | end - | - | @todo << item - | - | save - | - | refresh_todo - | end - | - | - | def forget_todo(item) - | @todo.delete item - | - | save - | - | refresh_todo - | end - | - | - | def load - | if File.exist?(data_path) - | @todo, @completed = YAML::load(File.open(data_path, 'r')) - | else - | @todo = [] - | @completed = {} - | end - | - | refresh - | end - | - | - | def save - | File.open(data_path, 'w') { |f| - | f.write [ @todo, @completed ].to_yaml - | } - | end - | - | - | load - | - | end - - vjot/ - @shoes/ - | NOTES = [['Welcome to the vJot Clone', <<-'END']] - | This sample app is a notetaker, a clone of PJ Hyett's vjot.com. - | - | Creating - | ---------- - | Click "Add a New Note" and the jot will be loaded into the editor for reading or editing. - | - | Editing - | --------- - | Click a jot's title to load it. - | - | Saving - | -------- - | There is no save button, the jot is saved as you edit. - | - | END - | - | Shoes.app :title => "vJot", - | :width => 420, :height => 560, :resizable => false do - | - | @note = NOTES.first - | background "#C7EAFB" - | stack :width => 400, :margin => 20 do - | background "#eee", :curve => 12 - | border "#00D0FF", :strokewidth => 3, :curve => 12 - | stack :margin => 20 do - | caption "vJot" - | @title = edit_line @note[0], :width => 1.0 do - | @note[0] = @title.text - | load_list - | end - | stack :width => 1.0, :height => 200, :scroll => true do - | @list = para - | end - | @jot = edit_box @note[1], :width => 1.0, :height => 200, :margin_bottom => 20 do - | @note[1] = @jot.text - | end - | end - | end - | - | def load_list - | @list.replace *(NOTES.map { |note| - | [link(note.first) { @note = load_note(note); load_list }, "\n"] - | }.flatten + - | [link("+ Add a new Note") { NOTES << (@note = load_note); load_list }]) - | end - | - | def load_note(note = ['New Note', '']) - | @note = note - | @title.text = note[0] - | @jot.text = note[1] - | note - | end - | - | load_list - | end - - minesweeper/ - @shoes/ - | # - | # Shoes Minesweeper by que/varyform - | # - | LEVELS = { :beginner => [9, 9, 10], :intermediate => [16, 16, 40], :expert => [30, 16, 99] } - | - | class Field - | CELL_SIZE = 20 - | COLORS = %w(#00A #0A0 #A00 #004 #040 #400 #000) - | - | class Cell - | attr_accessor :flag - | def initialize(aflag = false) - | @flag = aflag - | end - | end - | - | class Bomb < Cell - | attr_accessor :exploded - | def initialize(exploded = false) - | @exploded = exploded - | end - | end - | - | class OpenCell < Cell - | attr_accessor :number - | def initialize(bombs_around = 0) - | @number = bombs_around - | end - | end - | - | class EmptyCell < Cell; end - | - | attr_reader :cell_size, :offset - | - | def initialize(app, level = :beginner) - | @app = app - | @field = [] - | @w, @h, @bombs = LEVELS[level][0], LEVELS[level][1], LEVELS[level][2] - | @h.times { @field << Array.new(@w) { EmptyCell.new } } - | @game_over = false - | @width, @height, @cell_size = @w * CELL_SIZE, @h * CELL_SIZE, CELL_SIZE - | @offset = [(@app.width - @width.to_i) / 2, (@app.height - @height.to_i) / 2] - | plant_bombs - | @start_time = Time.now - | end - | - | def total_time - | @latest_time = Time.now - @start_time unless game_over? || all_found? - | @latest_time - | end - | - | def click!(x, y) - | return unless cell_exists?(x, y) - | return if has_flag?(x, y) - | return die!(x, y) if bomb?(x, y) - | open(x, y) - | discover(x, y) if bombs_around(x, y) == 0 - | end - | - | def flag!(x, y) - | return unless cell_exists?(x, y) - | self[x, y].flag = !self[x, y].flag unless self[x, y].is_a?(OpenCell) - | end - | - | def game_over? - | @game_over - | end - | - | def render_cell(x, y, color = "#AAA", stroke = true) - | @app.stroke "#666" if stroke - | @app.fill color - | @app.rect x*cell_size, y*cell_size, cell_size-1, cell_size-1 - | @app.stroke "#BBB" if stroke - | @app.line x*cell_size+1, y*cell_size+1, x*cell_size+cell_size-1, y*cell_size - | @app.line x*cell_size+1, y*cell_size+1, x*cell_size, y*cell_size+cell_size-1 - | end - | - | def render_flag(x, y) - | @app.stroke "#000" - | @app.line(x*cell_size+cell_size / 4 + 1, y*cell_size + cell_size / 5, x*cell_size+cell_size / 4 + 1, y*cell_size+cell_size / 5 * 4) - | @app.fill "#A00" - | @app.rect(x*cell_size+cell_size / 4+2, y*cell_size + cell_size / 5, - | cell_size / 3, cell_size / 4) - | end - | - | def render_bomb(x, y) - | render_cell(x, y) - | if (game_over? or all_found?) then # draw bomb - | if self[x, y].exploded then - | render_cell(x, y, @app.rgb(0xFF, 0, 0, 0.5)) - | end - | @app.nostroke - | @app.fill @app.rgb(0, 0, 0, 0.8) - | @app.oval(x*cell_size+3, y*cell_size+3, 13) - | @app.fill "#333" - | @app.oval(x*cell_size+5, y*cell_size+5, 7) - | @app.fill "#AAA" - | @app.oval(x*cell_size+6, y*cell_size+6, 3) - | @app.fill @app.rgb(0, 0, 0, 0.8) - | @app.stroke "#222" - | @app.strokewidth 2 - | @app.oval(x*cell_size + cell_size / 2 + 2, y*cell_size + cell_size / 4 - 2, 2) - | @app.oval(x*cell_size + cell_size / 2 + 4, y*cell_size + cell_size / 4 - 2, 1) - | @app.strokewidth 1 - | end - | end - | - | def render_number(x, y) - | render_cell(x, y, "#999", false) - | if self[x, y].number != 0 then - | @app.nostroke - | @app.para self[x, y].number.to_s, :left => x*cell_size + 3, :top => y*cell_size - 2, - | :font => '13px', :stroke => COLORS[self[x, y].number - 1] - | end - | end - | - | def paint - | 0.upto @h-1 do |y| - | 0.upto @w-1 do |x| - | @app.nostroke - | case self[x, y] - | when EmptyCell then render_cell(x, y) - | when Bomb then render_bomb(x, y) - | when OpenCell then render_number(x, y) - | end - | render_flag(x, y) if has_flag?(x, y) && !(game_over? && bomb?(x, y)) - | end - | end - | end - | - | def bombs_left - | @bombs - @field.flatten.compact.reject {|e| !e.flag }.size - | end - | - | def all_found? - | @field.flatten.compact.reject {|e| !e.is_a?(OpenCell) }.size + @bombs == @w*@h - | end - | - | def reveal!(x, y) - | return unless cell_exists?(x, y) - | return unless self[x, y].is_a?(Field::OpenCell) - | if flags_around(x, y) >= self[x, y].number then - | (-1..1).each do |v| - | (-1..1).each { |h| click!(x+h, y+v) unless (v==0 && h==0) or has_flag?(x+h, y+v) } - | end - | end - | end - | - | private - | - | def cell_exists?(x, y) - | ((0...@w).include? x) && ((0...@h).include? y) - | end - | - | def has_flag?(x, y) - | return false unless cell_exists?(x, y) - | return self[x, y].flag - | end - | - | def bomb?(x, y) - | cell_exists?(x, y) && (self[x, y].is_a? Bomb) - | end - | - | def can_be_discovered?(x, y) - | return false unless cell_exists?(x, y) - | return false if self[x, y].flag - | cell_exists?(x, y) && (self[x, y].is_a? EmptyCell) && !bomb?(x, y) && (bombs_around(x, y) == 0) - | end - | - | def open(x, y) - | self[x, y] = OpenCell.new(bombs_around(x, y)) unless (self[x, y].is_a? OpenCell) or has_flag?(x, y) - | end - | - | def neighbors - | (-1..1).each do |col| - | (-1..1).each { |row| yield row, col unless col==0 && row == 0 } - | end - | end - | - | def discover(x, y) - | open(x, y) - | neighbors do |col, row| - | cx, cy = x+row, y+col - | next unless cell_exists?(cx, cy) - | discover(cx, cy) if can_be_discovered?(cx, cy) - | open(cx, cy) - | end - | end - | - | def count_neighbors - | return 0 unless block_given? - | count = 0 - | neighbors { |h, v| count += 1 if yield(h, v) } - | count - | end - | - | def bombs_around(x, y) - | count_neighbors { |v, h| bomb?(x+h, y+v) } - | end - | - | def flags_around(x, y) - | count_neighbors { |v, h| has_flag?(x+h, y+v) } - | end - | - | def die!(x, y) - | self[x, y].exploded = true - | @game_over = true - | end - | - | def plant_bomb(x, y) - | self[x, y].is_a?(EmptyCell) ? self[x, y] = Bomb.new : false - | end - | - | def plant_bombs - | @bombs.times { redo unless plant_bomb(rand(@w), rand(@h)) } - | end - | - | def [](*args) - | x, y = args - | raise "Cell #{x}:#{y} does not exists!" unless cell_exists?(x, y) - | @field[y][x] - | end - | - | def []=(*args) - | x, y, v = args - | cell_exists?(x, y) ? @field[y][x] = v : false - | end - | end - | - | Shoes.app :width => 730, :height => 450, :title => 'Minesweeper' do - | def render_field - | clear do - | background rgb(50, 50, 90, 0.7) - | flow :margin => 4 do - | button("Beginner") { new_game :beginner } - | button("Intermediate") { new_game :intermediate } - | button("Expert") { new_game :expert } - | end - | stack do @status = para :stroke => white end - | @field.paint - | para "Left click - open cell, right click - put flag, middle click - reveal empty cells", :top => 420, :left => 0, :stroke => white, :font => "11px" - | end - | end - | - | def new_game level - | @field = Field.new self, level - | translate -@old_offset.first, -@old_offset.last unless @old_offset.nil? - | translate @field.offset.first, @field.offset.last - | @old_offset = @field.offset - | render_field - | end - | - | new_game :beginner - | animate(5) { @status.replace "Time: #{@field.total_time.to_i} Bombs left: #{@field.bombs_left}" } - | - | click do |button, x, y| - | next if @field.game_over? || @field.all_found? - | fx, fy = ((x-@field.offset.first) / @field.cell_size).to_i, ((y-@field.offset.last) / @field.cell_size).to_i - | @field.click!(fx, fy) if button == 1 - | @field.flag!(fx, fy) if button == 2 - | @field.reveal!(fx, fy) if button == 3 - | - | render_field - | alert("Winner!\nTotal time: #{@field.total_time}") if @field.all_found? - | alert("Bang!\nYou loose.") if @field.game_over? - | end - | end - - animated shapes/ - @shoes/ - | Shoes.app do - | background rgb(0, 0, 0) - | fill rgb(255, 255, 255) - | rects = [ - | rect(0, 0, 50, 50), - | rect(0, 0, 100, 100), - | rect(0, 0, 75, 75) - | ] - | animate(24) do |i| - | rects.each do |r| - | r.move((0..400).rand, (0..400).rand) - | end - | end - | button "OK", :top => 0.5, :left => 0.5 do - | quit unless confirm "You ARE sure you're OK??" - | end - | end - - animated text/ - @shoes/ - | Shoes.app do - | stack :top => 0.5, :left => 0.5 do - | para "Counting up:" - | l = para "0" - | animate(24) do |i| - | f = ['Arial 14px', 'Serif 34px', 'Monospace 18px', 'Arial 48px'][rand(3)] - | l.replace "#{i}", :font => f - | end - | motion do |x, y| - | Shoes.p [x, y] - | end - | end - | end - - bounce/ - @shoes/ - | xspeed, yspeed = 8.4, 6.6 - | xdir, ydir = 1, 1 - | - | Shoes.app do - | background "#DFA" - | border black, :strokewidth => 6 - | - | nostroke - | @icon = image "#{DIR}/static/shoes-icon.png", :left => 100, :top => 100 do - | alert "You're soooo quick." - | end - | - | x, y = self.width / 2, self.height / 2 - | size = @icon.size - | animate(30) do - | x += xspeed * xdir - | y += yspeed * ydir - | - | xdir *= -1 if x > self.width - size[0] or x < 0 - | ydir *= -1 if y > self.height - size[1] or y < 0 - | - | @icon.move x.to_i, y.to_i - | end - | end - - calc/ - @shoes/ - | class Calc - | def initialize - | @number = 0 - | @previous = nil - | @op = nil - | end - | - | def to_s - | @number.to_s - | end - | - | (0..9).each do |n| - | define_method "press_#{n}" do - | @number = @number.to_i * 10 + n - | end - | end - | - | def press_clear - | @number = 0 - | end - | - | {'add' => '+', 'sub' => '-', 'times' => '*', 'div' => '/'}.each do |meth, op| - | define_method "press_#{meth}" do - | if @op - | press_equals - | end - | @op = op - | @previous, @number = @number, nil - | end - | end - | - | def press_equals - | @number = @previous.send(@op, @number.to_i) - | @op = nil - | end - | - | end - | - | number_field = nil - | number = Calc.new - | Shoes.app :height => 250, :width => 200, :resizable => false do - | background "#EEC".."#996", :curve => 5, :margin => 2 - | - | stack :margin => 2 do - | - | stack :margin => 8 do - | number_field = para strong(number) - | end - | - | flow :width => 218, :margin => 4 do - | %w(7 8 9 / 4 5 6 * 1 2 3 - 0 Clr = +).each do |btn| - | button btn, :width => 46, :height => 46 do - | method = case btn - | when /[0-9]/; 'press_'+btn - | when 'Clr'; 'press_clear' - | when '='; 'press_equals' - | when '+'; 'press_add' - | when '-'; 'press_sub' - | when '*'; 'press_times' - | when '/'; 'press_div' - | end - | - | number.send(method) - | number_field.replace strong(number) - | end - | end - | end - | end - | - | end - - control sizes/ - @shoes/ - | Shoes.app :width => 360, :height => 600, :resizable => false do - | stroke "#dde" - | background "#f1f5ff" - | 13.times { |x| line 20, 142 + (30 * x), 320, 142 + (30 * x) } - | 11.times { |x| line 20 + (30 * x), 142, 20 + (30 * x), 502 } - | - | stack :margin => 20 do - | title "Control Sizes", :size => 16 - | para "This app measures various controls against a grid of lines, to be sure they size appropriately despite the platform." - | stack :top => 122, :left => 40 do - | button "Standard" - | button "Margin: 2, Height: 28", :margin => 2, :height => 30 - | edit_line "Standard", :margin => 1 - | edit_line "Margin: 4, Height: 30", :height => 30, :margin => 4 - | list_box :items => ["Standard"], :choose => "Standard" - | list_box :items => ["Margin: 4, Height: 32"], - | :choose => "Margin: 4, Height: 32", - | :height => 32, :margin => 4 - | progress - | progress :height => 32, :margin => 4 - | edit_box - | end - | end - | end - - dictionary/ - @shoes/ - | Shoes.app :title => "Dictionary, powered by Definr", :width => 370, :height => 320 do - | stack do - | background red, :height => 60 - | flow :margin => 20 do - | caption "Define: ", :stroke => white - | @lookup = edit_line - | button "Go" do - | download "http://definr.com/definr/show/#{@lookup.text}" do |dl| - | doc = dl.response.body.gsub(' ', ' '). - | gsub(%r!(|
|)!, ''). - | gsub(%r!\(http://.+?\)!, '').strip - | title, doc = doc.split(/\n+/, 2) - | @deft.replace title - | @defn.replace doc - | end - | end - | end - | stack :margin => 20 do - | @deft = subtitle "", :margin => 10 - | @defn = para "" - | end - | end - | end - - pong/ - @shoes/ - | # - | # Pong in Shoes - | # a clone of http://billmill.org/pong.html - | # and shoes is at http://shoooes.net - | # - | # This is just for kicks -- I'm very fond of NodeBox as well. - | # - | # There's a slightly different approach in Shoes: rather than - | # redrawing the shapes, you can move the shapes around as objects. - | # Yeah, see, notice how @you, @comp and @ball are used. - | # - | Shoes.app :width => 400, :height => 400, :resizable => false do - | paddle_size = 75 - | ball_diameter = 20 - | vx, vy = [3, 4] - | compuspeed = 10 - | bounce = 1.2 - | - | # set up the playing board - | nostroke and background white - | @ball = oval 0, 0, ball_diameter, :fill => "#9B7" - | @you, @comp = [app.height-4, 0].map {|y| rect 0, y, paddle_size, 4, :curve => 2} - | - | # animates at 40 frames per second - | @anim = animate 40 do - | - | # check for game over - | if @ball.top + ball_diameter < 0 or @ball.top > app.height - | para strong("GAME OVER", :size => 32), "\n", - | @ball.top < 0 ? "You win!" : "Computer wins", :top => 140, :align => 'center' - | @ball.hide and @anim.stop - | end - | - | # move the @you paddle, following the mouse - | @you.left = mouse[1] - (paddle_size / 2) - | nx, ny = (@ball.left + vx).to_i, (@ball.top + vy).to_i - | - | # move the @comp paddle, speed based on `compuspeed` variable - | @comp.left += - | if nx + (ball_diameter / 2) > @comp.left + paddle_size; compuspeed - | elsif nx < @comp.left; -compuspeed - | else 0 end - | - | # if the @you paddle hits the ball - | if ny + ball_diameter > app.height and vy > 0 and - | (0..paddle_size).include? nx + (ball_diameter / 2) - @you.left - | vx, vy = (nx - @you.left - (paddle_size / 2)) * 0.25, -vy * bounce - | ny = app.height - ball_diameter - | end - | - | # if the @comp paddle hits the ball - | if ny < 0 and vy < 0 and - | (0..paddle_size).include? nx + (ball_diameter / 2) - @comp.left - | vx, vy = (nx - @comp.left - (paddle_size / 2)) * 0.25, -vy * bounce - | ny = 0 - | elsif nx + ball_diameter > app.width or nx < 0 - | vx = -vx - | end - | - | @ball.move nx, ny - | end - | end - - curve/ - @shoes/ - | # - | # based on the cairo curve_to example - | # http://www.cairographics.org/samples/curve_to/ - | # - | Shoes.app do - | x, y = 25.6, 128.0 - | x1 = 102.4; y1 = 230.4 - | x2 = 153.6; y2 = 25.6 - | x3 = 230.4; y3 = 128.0 - | - | nofill - | strokewidth 10.0 - | shape do - | move_to x, y - | curve_to x1, y1, x2, y2, x3, y3 - | end - | - | strokewidth 6.0 - | stroke rgb(1.0, 0.2, 0.2, 0.6) - | shape do - | move_to x, y - | line_to x1, y1 - | move_to x2, y2 - | line_to x3, y3 - | end - | end - - downloader/ - @shoes/ - | Shoes.app do - | background "#eee" - | @list = stack do - | para "Enter a URL to download:", :margin => [10, 8, 10, 0] - | flow :margin => 10 do - | @url = edit_line :width => -120 - | button "Download", :width => 120 do - | @list.append do - | stack do - | background "#eee".."#ccd" - | stack :margin => 10 do - | dl = nil - | para @url.text, " [", link("cancel") { dl.abort }, "]", :margin => 0 - | d = inscription "Beginning transfer.", :margin => 0 - | p = progress :width => 1.0, :height => 14 - | dl = download @url.text, :save => File.basename(@url.text), - | :progress => proc { |dl| - | d.text = "Transferred #{dl.transferred} of #{dl.length} bytes (#{dl.percent}%)" - | p.fraction = dl.percent * 0.01 }, - | :finish => proc { |dl| d.text = "Download completed" } - | end - | end - | end - | end - | end - | end - | end - - editor/ - @shoes/ - | str, t = "", nil - | Shoes.app :height => 500, :width => 450 do - | background rgb(77, 77, 77) - | stack :margin => 10 do - | para span("TEXT EDITOR", :stroke => red, :fill => white), " * USE ALT-Q TO QUIT", :stroke => white - | end - | stack :margin => 10 do - | t = para "", :font => "Monospace 12px", :stroke => white - | t.cursor = -1 - | end - | keypress do |k| - | case k - | when String - | str += k - | when :backspace - | str.slice!(-1) - | when :tab - | str += " " - | when :alt_q - | quit - | when :alt_c - | self.clipboard = str - | when :alt_v - | str += self.clipboard - | end - | t.replace str - | end - | end - - slide/ - @shoes/ - | # - | # mimicking the mootools demo for Fx.Slide - | # http://demos.mootools.net/Fx.Slide - | # - | Shoes.app do - | def stop_anim - | @anim.stop - | @anim = nil - | end - | def slide_anim &blk - | stop_anim if @anim - | @anim = animate 30, &blk - | end - | def slide_out slot - | slide_anim do |i| - | slot.height = 150 - (i * 3) - | slot.contents[0].top = -i * 3 - | if slot.height == 0 - | stop_anim - | slot.hide - | end - | end - | end - | def slide_in slot - | slot.show - | slide_anim do |i| - | slot.height = i * 6 - | slot.contents[0].top = slot.height - 150 - | stop_anim if slot.height == 150 - | end - | end - | - | background white - | stack :margin => 10 do - | para link("slide out") { slide_out @lipsum }, " | ", - | link("slide in") { slide_in @lipsum } - | @lipsum = stack :width => 1.0, :height => 150 do - | stack do - | background "#ddd" - | border "#eee", :strokewidth => 5 - | para "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", :margin => 10 - | end - | end - | end - | end - - timer/ - @shoes/ - | Shoes.app :height => 150, :width => 250 do - | background rgb(240, 250, 208) - | stack :margin => 10 do - | button "Start" do - | @time = Time.now - | @label.replace "Stop watch started at #@time" - | end - | button "Stop" do - | @label.replace "Stopped, ", strong("#{Time.now - @time}"), " seconds elapsed." - | end - | @label = para "Press ", strong("start"), " to begin timing." - | end - | end -- docs/ - - install shoes/ - - 1) Go here - @http://shoesrb.com/downloads - - - 2) Download the .dmg - | Currently this menu is mac-only, but it should be simple - | to update it to support other platforms. - - - 3) Drag Shoes.app to /Applications - - try it out/ - Run some of the examples in this menu. - <<< examples/ diff --git a/lib/xiki/tools/source.menu b/lib/xiki/tools/source.menu index cb6fd30e..11cad394 100644 --- a/lib/xiki/tools/source.menu +++ b/lib/xiki/tools/source.menu @@ -1,2 +1,2 @@ > Xiki source -<< $x/ +<< :xiki/ diff --git a/lib/xiki/tools/standalone.rb b/lib/xiki/tools/standalone.rb index 7999997a..e431865e 100644 --- a/lib/xiki/tools/standalone.rb +++ b/lib/xiki/tools/standalone.rb @@ -4,7 +4,7 @@ def self.menu *args children = Tree.children(:string=>1) children = children.unindent if children - trunk = Xiki.trunk + trunk = Tree.path # If no children and no parents, say how to use it. if children.blank? && trunk.length == 1 @@ -25,7 +25,7 @@ def self.menu *args menu = "untitled" if children.blank? - file = File.expand_path "~/menu/#{trunk[0]}.menu" + file = File.expand_path "~/.xiki/roots/#{trunk[0]}.menu" menu = trunk[0] children = File.read file end @@ -35,15 +35,15 @@ def self.menu *args Firefox.url "file:///tmp/untitled.html" - ".flash - showing in browser!" + "<* showing in browser!" end def self.wrap_standalone children %` - - + + diff --git a/lib/xiki/tools/steps.rb b/lib/xiki/tools/steps.rb new file mode 100644 index 00000000..4e32d7fc --- /dev/null +++ b/lib/xiki/tools/steps.rb @@ -0,0 +1,13 @@ +module Xiki + class Steps + + # Use notes styles for .steps files + def self.init + Mode.define(:steps, ".steps") do + Notes.mode + end + end + + end + Steps.init # Define mode +end diff --git a/lib/xiki/tools/svg.rb b/lib/xiki/tools/svg.rb deleted file mode 100644 index 139e5f89..00000000 --- a/lib/xiki/tools/svg.rb +++ /dev/null @@ -1,152 +0,0 @@ -module Xiki - class Svg - def self.menu - %` - > Pass svg xml here to show in browser - | - - .show/ - - api/ - > Render in browser - @Svg.render "" - - > Save as a .png file - @Svg.to_png "", "/tmp/line.png" - - examples/ - - circle/ - @svg/ - | - - rectangle/ - @svg/ - | - - triangle/ - @svg/ - | - - star/ - @svg/ - | - - text/ - @svg/ - | Hey - | Hey - | Hey - - gradient/ - @svg/ - | - | - | - | - | - | - | - - attributes instead of style/ - @svg/ - | - - transforms/ - - shrink it down/ - @svg/ - | - | - | - - inline rotate/ - | - - css/ - @svg/ - | - | - | - | Hey - - > See - @svg drawing tool/ - ` - end - - def self.menu_after output, *args - return output if output - - txt = ENV['txt'] - - Svg.render txt - end - - def self.show - txt = Tree.siblings.grep(/^\|/).join("\n") - txt = Tree.unquote txt - tmp_png = "/tmp/tmp.png" - self.to_png txt, tmp_png - Image >> tmp_png - end - - def self.to_png xml, file - - # Wrap in tags if doesn't have any - if xml !~ / - - #{xml} - - `.unindent - - end - - tmp_path = "/tmp/tmp.svg" - File.open(tmp_path, "w") { |f| f << xml } - - `convert #{tmp_path} "#{file}"` - - nil - end - - def self.render xml, options={} - Browser.html %` - - #{xml} - - - `.unindent - end - - def self.append xml - Browser.js %` - $("svg").append('#{xml}'); - $("body").html($("body").html()); - `.unindent - nil - end - - # Scale numeric values according to default font size - def self.scale xml, options={} - - scale = options[:scale] || Styles.get_font_size / 110.0 - - white_list = ["height", "width", "x", "y", "rx", "ry", "stroke-width"].inject({}){|o, i| o[i] = true; o} - - # foo="123"... - - xml.gsub!(/([-\w]+)="(\d+)"/) do |match| - key, val = $1, $2 - val = (val.to_i * scale).round if white_list[key] - "#{key}=\"#{val}\"" - end - - # 12,34... - - xml.gsub!(/(\d+),(\d+)/) do |match| - x, y = $1, $2 - "#{(x.to_i * scale).round},#{(y.to_i * scale).round}" - end - - # font-size:12... - - xml.gsub!(/font-size:(\d+)/) do |match| - "font-size:#{($1.to_i * scale).round}" - end - - xml - end - end -end diff --git a/lib/xiki/tools/tabs.rb b/lib/xiki/tools/tabs.rb deleted file mode 100644 index 78bfa089..00000000 --- a/lib/xiki/tools/tabs.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Xiki - class Tabs - def self.menu *args - - # If no args, show option that toggles... - - return $el.elvar.tabbar_mode ? "- hide tabs/" : "- show tabs/" if args.empty? - - # Option was passed in - - turn_on = args == ['show tabs'] - - $el.customize_set_variable :tabbar_mode, turn_on - - new_menu_item = turn_on ? "hide tabs/" : "show tabs/" - Tree.replace_item new_menu_item - Effects.glow :fade_in=>1 - - nil - end - end -end diff --git a/lib/xiki/tools/technologies.rb b/lib/xiki/tools/technologies.rb index 5cbb184c..1f9f649c 100644 --- a/lib/xiki/tools/technologies.rb +++ b/lib/xiki/tools/technologies.rb @@ -10,14 +10,14 @@ def self.menu topic=nil, *args # If no topic, just show all dirs if topic.nil? - entries = Dir.new(Bookmarks["$te"]).entries + entries = Dir.new(Bookmarks["%te"]).entries entries = entries.select{|o| o =~ /^\w/} return entries.map{|o| "#{o}/"} end # If just topic, list all headings - Notes.drill "$te/#{topic}/#{topic}.notes", heading, content + Notes.drill "%te/#{topic}/#{topic}.xiki", heading, content end end diff --git a/lib/xiki/tools/textile.rb b/lib/xiki/tools/textile.rb index 4faa3676..e7405282 100644 --- a/lib/xiki/tools/textile.rb +++ b/lib/xiki/tools/textile.rb @@ -1,5 +1,5 @@ -gem 'RedCloth' -require 'redcloth' +# gem 'RedCloth' +# require 'redcloth' module Xiki class Textile diff --git a/lib/xiki/tools/themes.rb b/lib/xiki/tools/themes.rb index b812e814..907671b9 100644 --- a/lib/xiki/tools/themes.rb +++ b/lib/xiki/tools/themes.rb @@ -3,19 +3,51 @@ module Xiki # Lists themes and run them. Make files here # to make more themes: # - # @$x/etc/themes/ + # =%xiki/misc/themes/ # class Themes - def self.use name - path = "#{Xiki.dir}etc/themes/#{name.gsub " ", "_"}.notes" + def self.use name # , options={} + + path = "#{Xiki.dir}misc/themes/#{name.gsub " ", "_"}.xiki" raise "- Theme doesn't exist!" if ! File.exists? path load path - Styles.reload_styles + if ControlLock.enabled? + $el.control_lock_apply_bar_color + end nil end + + def self.init_in_client + + $el.el4r_lisp_eval %` + (defun xiki-bar-add-padding (txt &optional space-face) + (let ((total (window-width)) left right) + (setq left (- total (length txt))) + (setq left (/ left 2)) + (when (> 2 left) (setq left 2)) + (setq right (- total (+ left (length txt)))) + (when (> 2 right) (setq right 2)) + (if space-face + (concat + (propertize (make-string left ? ) 'face space-face) ; Make string of spaces + txt + (propertize (make-string right ? ) 'face space-face) ; Make string of spaces + ) + (concat + (make-string left ? ) ; Make string of spaces + txt + (make-string right ? ) ; Make string of spaces + ) + ) + ) + ) + ` + + end + end end diff --git a/lib/xiki/tools/twitter.rb b/lib/xiki/tools/twitter.rb index 9d2136b6..7ef3be72 100644 --- a/lib/xiki/tools/twitter.rb +++ b/lib/xiki/tools/twitter.rb @@ -1,5 +1,5 @@ -require 'net/http' -require 'timeout' +# require 'net/http' +# require 'timeout' module Xiki class Twitter @@ -19,7 +19,7 @@ def self.post_or_list def self.list options={} View.handle_bar - Console.run "twitter timeline", :buffer => "*twitter timeline" + Shell.run "twitter timeline", :buffer => "*twitter timeline" Styles.define :twitter_name, :fg => '99f' Styles.define :twitter_date, :fg => 'ddd' Styles.apply '^\\(-- .+\\)\\( at .+\\)', nil, :twitter_name, :twitter_date diff --git a/lib/xiki/tools/urls.menu b/lib/xiki/tools/urls.menu index b7d4844d..bac0ced5 100644 --- a/lib/xiki/tools/urls.menu +++ b/lib/xiki/tools/urls.menu @@ -1 +1 @@ -<<< http/ +<< http/ diff --git a/lib/xiki/tools/wiki_syntax.menu b/lib/xiki/tools/wiki_syntax.menu index bb50d848..5b631d8b 100644 --- a/lib/xiki/tools/wiki_syntax.menu +++ b/lib/xiki/tools/wiki_syntax.menu @@ -7,8 +7,6 @@ - For bullets - begin with spaces and hyphens -For ~emphasis~ surround with tildas. - | For grayed text, begin with a pipe |+ For green diffs, pipe plus |- For red diffs, pipe minus diff --git a/lib/xiki/vim/line.rb b/lib/xiki/vim/line.rb index 663a6fe9..f93af5d7 100644 --- a/lib/xiki/vim/line.rb +++ b/lib/xiki/vim/line.rb @@ -3,8 +3,9 @@ class Line def self.number $curbuf.line_number end - def self.value - $curbuf.line + def self.value n=nil + n = self.number if ! n + $curbuf[n] end end end diff --git a/menu/as_menu.rb b/menu/as_menu.rb deleted file mode 100644 index 4d35021a..00000000 --- a/menu/as_menu.rb +++ /dev/null @@ -1,2 +0,0 @@ -Menu.as_menu -nil diff --git a/menu/ascii.menu b/menu/ascii.menu deleted file mode 100644 index 817b38e0..00000000 --- a/menu/ascii.menu +++ /dev/null @@ -1,429 +0,0 @@ -- table/ - - all/ - | Binary Dec:Oct Hex Abbr Name - | 000 0000 0:000 00 NUL ^@ \0 Null character - | 000 0001 1:001 01 SOH ^A Start of Header - | 000 0010 2:002 02 STX ^B Start of Text - | 000 0011 3:003 03 ETX ^C End of Text - | 000 0100 4:004 04 EOT ^D End of Transmission - | 000 0101 5:005 05 ENQ ^E Enquiry - | 000 0110 6:006 06 ACK ^F Acknowledgment - | 000 0111 7:007 07 BEL ^G \a Bell - | 000 1000 8:010 08 BS ^H \b Backspace[d][e] - | 000 1001 9:011 09 HT ^I \t Horizontal Tab[f] - | 000 1010 10:012 0A LF ^J \n Line feed - | 000 1011 11:013 0B VT ^K \v Vertical Tab - | 000 1100 12:014 0C FF ^L \f Form feed - | 000 1101 13:015 0D CR ^M \r Carriage return[g] - | 000 1110 14:016 0E SO ^N Shift Out - | 000 1111 15:017 0F SI ^O Shift In - - | 001 0000 16:020 10 DLE ^P Data Link Escape - | 001 0001 17:021 11 DC1 ^Q Device Control 1 (oft. XON) - | 001 0010 18:022 12 DC2 ^R Device Control 2 - | 001 0011 19:023 13 DC3 ^S Device Control 3 (oft. XOFF) - | 001 0100 20:024 14 DC4 ^T Device Control 4 - | 001 0101 21:025 15 NAK ^U Negative Acknowledgement - | 001 0110 22:026 16 SYN ^V Synchronous idle - | 001 0111 23:027 17 ETB ^W End of Transmission Block - | 001 1000 24:030 18 CAN ^X Cancel - | 001 1001 25:031 19 EM ^Y End of Medium - | 001 1010 26:032 1A SUB ^Z Substitute - | 001 1011 27:033 1B ESC ^[ \e[h] Escape[i] - | 001 1100 28:034 1C FS ^\ File Separator - | 001 1101 29:035 1D GS ^] Group Separator - | 001 1110 30:036 1E RS ^^[j] Record Separator - | 001 1111 31:037 1F US ^_ Unit Separator - - | 010 0000 32:040 20 Space - | 010 0001 33:041 21 ! - | 010 0010 34:042 22 " - | 010 0011 35:043 23 # - | 010 0100 36:044 24 $ - | 010 0101 37:045 25 % - | 010 0110 38:046 26 & - | 010 0111 39:047 27 ' - | 010 1000 40:050 28 ( - | 010 1001 41:051 29 ) - | 010 1010 42:052 2A * - | 010 1011 43:053 2B + - | 010 1100 44:054 2C , - | 010 1101 45:055 2D - - | 010 1110 46:056 2E . - | 010 1111 47:057 2F / - - | 011 0000 48:060 30 0 - | 011 0001 49:061 31 1 - | 011 0010 50:062 32 2 - | 011 0011 51:063 33 3 - | 011 0100 52:064 34 4 - | 011 0101 53:065 35 5 - | 011 0110 54:066 36 6 - | 011 0111 55:067 37 7 - | 011 1000 56:070 38 8 - | 011 1001 57:071 39 9 - | 011 1010 58:072 3A : - | 011 1011 59:073 3B ; - | 011 1100 60:074 3C < - | 011 1101 61:075 3D = - | 011 1110 62:076 3E > - | 011 1111 63:077 3F ? - - | 100 0000 64:100 40 @ - | 100 0001 65:101 41 A - | 100 0010 66:102 42 B - | 100 0011 67:103 43 C - | 100 0100 68:104 44 D - | 100 0101 69:105 45 E - | 100 0110 70:106 46 F - | 100 0111 71:107 47 G - | 100 1000 72:110 48 H - | 100 1001 73:111 49 I - | 100 1010 74:112 4A J - | 100 1011 75:113 4B K - | 100 1100 76:114 4C L - | 100 1101 77:115 4D M - | 100 1110 78:116 4E N - | 100 1111 79:117 4F O - - | 101 0000 80:120 50 P - | 101 0001 81:121 51 Q - | 101 0010 82:122 52 R - | 101 0011 83:123 53 S - | 101 0100 84:124 54 T - | 101 0101 85:125 55 U - | 101 0110 86:126 56 V - | 101 0111 87:127 57 W - | 101 1000 88:130 58 X - | 101 1001 89:131 59 Y - | 101 1010 90:132 5A Z - | 101 1011 91:133 5B [ - | 101 1100 92:134 5C \ - | 101 1101 93:135 5D ] - | 101 1110 94:136 5E ^ - | 101 1111 95:137 5F _ - - | 110 0000 96:140 60 ` - | 110 0001 97:141 61 a - | 110 0010 98:142 62 b - | 110 0011 99:143 63 c - | 110 0100 100:144 64 d - | 110 0101 101:145 65 e - | 110 0110 102:146 66 f - | 110 0111 103:147 67 g - | 110 1000 104:150 68 h - | 110 1001 105:151 69 i - | 110 1010 106:152 6A j - | 110 1011 107:153 6B k - | 110 1100 108:154 6C l - | 110 1101 109:155 6D m - | 110 1110 110:156 6E n - | 110 1111 111:157 6F o - - | 111 0000 112:160 70 p - | 111 0001 113:161 71 q - | 111 0010 114:162 72 r - | 111 0011 115:163 73 s - | 111 0100 116:164 74 t - | 111 0101 117:165 75 u - | 111 0110 118:166 76 v - | 111 0111 119:167 77 w - | 111 1000 120:170 78 x - | 111 1001 121:171 79 y - | 111 1010 122:172 7A z - | 111 1011 123:173 7B { - | 111 1100 124:174 7C | - | 111 1101 125:175 7D } - | 111 1110 126:176 7E ~ - - subsets/ - - control/ - | Binary Dec:Oct Hex Abbr Name - | 000 0000 0:000 00 NUL ^@ \0 Null character - | 000 0001 1:001 01 SOH ^A Start of Header - | 000 0010 2:002 02 STX ^B Start of Text - | 000 0011 3:003 03 ETX ^C End of Text - | 000 0100 4:004 04 EOT ^D End of Transmission - | 000 0101 5:005 05 ENQ ^E Enquiry - | 000 0110 6:006 06 ACK ^F Acknowledgment - | 000 0111 7:007 07 BEL ^G \a Bell - | 000 1000 8:010 08 BS ^H \b Backspace[d][e] - | 000 1001 9:011 09 HT ^I \t Horizontal Tab[f] - | 000 1010 10:012 0A LF ^J \n Line feed - | 000 1011 11:013 0B VT ^K \v Vertical Tab - | 000 1100 12:014 0C FF ^L \f Form feed - | 000 1101 13:015 0D CR ^M \r Carriage return[g] - | 000 1110 14:016 0E SO ^N Shift Out - | 000 1111 15:017 0F SI ^O Shift In - - | 001 0000 16:020 10 DLE ^P Data Link Escape - | 001 0001 17:021 11 DC1 ^Q Device Control 1 (oft. XON) - | 001 0010 18:022 12 DC2 ^R Device Control 2 - | 001 0011 19:023 13 DC3 ^S Device Control 3 (oft. XOFF) - | 001 0100 20:024 14 DC4 ^T Device Control 4 - | 001 0101 21:025 15 NAK ^U Negative Acknowledgement - | 001 0110 22:026 16 SYN ^V Synchronous idle - | 001 0111 23:027 17 ETB ^W End of Transmission Block - | 001 1000 24:030 18 CAN ^X Cancel - | 001 1001 25:031 19 EM ^Y End of Medium - | 001 1010 26:032 1A SUB ^Z Substitute - | 001 1011 27:033 1B ESC ^[ \e[h] Escape[i] - | 001 1100 28:034 1C FS ^\ File Separator - | 001 1101 29:035 1D GS ^] Group Separator - | 001 1110 30:036 1E RS ^^[j] Record Separator - | 001 1111 31:037 1F US ^_ Unit Separator - - punctuation/ - | Binary Dec:Oct Hex Glyph - | 010 0000 32:040 20 Space - | 010 0001 33:041 21 ! - | 010 0010 34:042 22 " - | 010 0011 35:043 23 # - | 010 0100 36:044 24 $ - | 010 0101 37:045 25 % - | 010 0110 38:046 26 & - | 010 0111 39:047 27 ' - | 010 1000 40:050 28 ( - | 010 1001 41:051 29 ) - | 010 1010 42:052 2A * - | 010 1011 43:053 2B + - | 010 1100 44:054 2C , - | 010 1101 45:055 2D - - | 010 1110 46:056 2E . - | 010 1111 47:057 2F / - - | 011 1010 58:072 3A : - | 011 1011 59:073 3B ; - | 011 1100 60:074 3C < - | 011 1101 61:075 3D = - | 011 1110 62:076 3E > - | 011 1111 63:077 3F ? - - | 100 0000 64:100 40 @ - - | 101 1011 91:133 5B [ - | 101 1100 92:134 5C \ - | 101 1101 93:135 5D ] - | 101 1110 94:136 5E ^ - | 101 1111 95:137 5F _ - - | 110 0000 96:140 60 ` - - | 111 1011 123:173 7B { - | 111 1100 124:174 7C | - | 111 1101 125:175 7D } - | 111 1110 126:176 7E ~ - - numbers/ - | Binary Dec:Oct Hex Glyph - | 011 0000 48:060 30 0 - | 011 0001 49:061 31 1 - | 011 0010 50:062 32 2 - | 011 0011 51:063 33 3 - | 011 0100 52:064 34 4 - | 011 0101 53:065 35 5 - | 011 0110 54:066 36 6 - | 011 0111 55:067 37 7 - | 011 1000 56:070 38 8 - | 011 1001 57:071 39 9 - - letters/ - | Binary Dec:Oct Hex Glyph - | 100 0001 65:101 41 A - | 100 0010 66:102 42 B - | 100 0011 67:103 43 C - | 100 0100 68:104 44 D - | 100 0101 69:105 45 E - | 100 0110 70:106 46 F - | 100 0111 71:107 47 G - | 100 1000 72:110 48 H - | 100 1001 73:111 49 I - | 100 1010 74:112 4A J - | 100 1011 75:113 4B K - | 100 1100 76:114 4C L - | 100 1101 77:115 4D M - | 100 1110 78:116 4E N - | 100 1111 79:117 4F O - | 101 0000 80:120 50 P - | 101 0001 81:121 51 Q - | 101 0010 82:122 52 R - | 101 0011 83:123 53 S - | 101 0100 84:124 54 T - | 101 0101 85:125 55 U - | 101 0110 86:126 56 V - | 101 0111 87:127 57 W - | 101 1000 88:130 58 X - | 101 1001 89:131 59 Y - | 101 1010 90:132 5A Z - - | 110 0001 97:141 61 a - | 110 0010 98:142 62 b - | 110 0011 99:143 63 c - | 110 0100 100:144 64 d - | 110 0101 101:145 65 e - | 110 0110 102:146 66 f - | 110 0111 103:147 67 g - | 110 1000 104:150 68 h - | 110 1001 105:151 69 i - | 110 1010 106:152 6A j - | 110 1011 107:153 6B k - | 110 1100 108:154 6C l - | 110 1101 109:155 6D m - | 110 1110 110:156 6E n - | 110 1111 111:157 6F o - | 111 0000 112:160 70 p - | 111 0001 113:161 71 q - | 111 0010 114:162 72 r - | 111 0011 115:163 73 s - | 111 0100 116:164 74 t - | 111 0101 117:165 75 u - | 111 0110 118:166 76 v - | 111 0111 119:167 77 w - | 111 1000 120:170 78 x - | 111 1001 121:171 79 y - | 111 1010 122:172 7A z - - groups/ - - control/ - | Binary Dec:Oct Hex Abbr Name - | 000 0000 0:000 00 NUL ^@ \0 Null character - | 000 0001 1:001 01 SOH ^A Start of Header - | 000 0010 2:002 02 STX ^B Start of Text - | 000 0011 3:003 03 ETX ^C End of Text - | 000 0100 4:004 04 EOT ^D End of Transmission - | 000 0101 5:005 05 ENQ ^E Enquiry - | 000 0110 6:006 06 ACK ^F Acknowledgment - | 000 0111 7:007 07 BEL ^G \a Bell - | 000 1000 8:010 08 BS ^H \b Backspace[d][e] - | 000 1001 9:011 09 HT ^I \t Horizontal Tab[f] - | 000 1010 10:012 0A LF ^J \n Line feed - | 000 1011 11:013 0B VT ^K \v Vertical Tab - | 000 1100 12:014 0C FF ^L \f Form feed - | 000 1101 13:015 0D CR ^M \r Carriage return[g] - | 000 1110 14:016 0E SO ^N Shift Out - | 000 1111 15:017 0F SI ^O Shift In - - | 001 0000 16:020 10 DLE ^P Data Link Escape - | 001 0001 17:021 11 DC1 ^Q Device Control 1 (oft. XON) - | 001 0010 18:022 12 DC2 ^R Device Control 2 - | 001 0011 19:023 13 DC3 ^S Device Control 3 (oft. XOFF) - | 001 0100 20:024 14 DC4 ^T Device Control 4 - | 001 0101 21:025 15 NAK ^U Negative Acknowledgement - | 001 0110 22:026 16 SYN ^V Synchronous idle - | 001 0111 23:027 17 ETB ^W End of Transmission Block - | 001 1000 24:030 18 CAN ^X Cancel - | 001 1001 25:031 19 EM ^Y End of Medium - | 001 1010 26:032 1A SUB ^Z Substitute - | 001 1011 27:033 1B ESC ^[ \e[h] Escape[i] - | 001 1100 28:034 1C FS ^\ File Separator - | 001 1101 29:035 1D GS ^] Group Separator - | 001 1110 30:036 1E RS ^^[j] Record Separator - | 001 1111 31:037 1F US ^_ Unit Separator - - numbers and most punctuation/ - | Binary Dec:Oct Hex Glyph - | 010 0000 32:040 20 Space - | 010 0001 33:041 21 ! - | 010 0010 34:042 22 " - | 010 0011 35:043 23 # - | 010 0100 36:044 24 $ - | 010 0101 37:045 25 % - | 010 0110 38:046 26 & - | 010 0111 39:047 27 ' - | 010 1000 40:050 28 ( - | 010 1001 41:051 29 ) - | 010 1010 42:052 2A * - | 010 1011 43:053 2B + - | 010 1100 44:054 2C , - | 010 1101 45:055 2D - - | 010 1110 46:056 2E . - | 010 1111 47:057 2F / - - | 011 0000 48:060 30 0 - | 011 0001 49:061 31 1 - | 011 0010 50:062 32 2 - | 011 0011 51:063 33 3 - | 011 0100 52:064 34 4 - | 011 0101 53:065 35 5 - | 011 0110 54:066 36 6 - | 011 0111 55:067 37 7 - | 011 1000 56:070 38 8 - | 011 1001 57:071 39 9 - | 011 1010 58:072 3A : - | 011 1011 59:073 3B ; - | 011 1100 60:074 3C < - | 011 1101 61:075 3D = - | 011 1110 62:076 3E > - | 011 1111 63:077 3F ? - - capital and misc/ - | Binary Dec:Oct Hex Glyph - | 100 0000 64:100 40 @ - | 100 0001 65:101 41 A - | 100 0010 66:102 42 B - | 100 0011 67:103 43 C - | 100 0100 68:104 44 D - | 100 0101 69:105 45 E - | 100 0110 70:106 46 F - | 100 0111 71:107 47 G - | 100 1000 72:110 48 H - | 100 1001 73:111 49 I - | 100 1010 74:112 4A J - | 100 1011 75:113 4B K - | 100 1100 76:114 4C L - | 100 1101 77:115 4D M - | 100 1110 78:116 4E N - | 100 1111 79:117 4F O - - | 101 0000 80:120 50 P - | 101 0001 81:121 51 Q - | 101 0010 82:122 52 R - | 101 0011 83:123 53 S - | 101 0100 84:124 54 T - | 101 0101 85:125 55 U - | 101 0110 86:126 56 V - | 101 0111 87:127 57 W - | 101 1000 88:130 58 X - | 101 1001 89:131 59 Y - | 101 1010 90:132 5A Z - | 101 1011 91:133 5B [ - | 101 1100 92:134 5C \ - | 101 1101 93:135 5D ] - | 101 1110 94:136 5E ^ - | 101 1111 95:137 5F _ - - lower case and misc/ - | Binary Dec:Oct Hex Glyph - | 110 0000 96:140 60 ` - | 110 0001 97:141 61 a - | 110 0010 98:142 62 b - | 110 0011 99:143 63 c - | 110 0100 100:144 64 d - | 110 0101 101:145 65 e - | 110 0110 102:146 66 f - | 110 0111 103:147 67 g - | 110 1000 104:150 68 h - | 110 1001 105:151 69 i - | 110 1010 106:152 6A j - | 110 1011 107:153 6B k - | 110 1100 108:154 6C l - | 110 1101 109:155 6D m - | 110 1110 110:156 6E n - | 110 1111 111:157 6F o - - | 111 0000 112:160 70 p - | 111 0001 113:161 71 q - | 111 0010 114:162 72 r - | 111 0011 115:163 73 s - | 111 0100 116:164 74 t - | 111 0101 117:165 75 u - | 111 0110 118:166 76 v - | 111 0111 119:167 77 w - | 111 1000 120:170 78 x - | 111 1001 121:171 79 y - | 111 1010 122:172 7A z - | 111 1011 123:173 7B { - | 111 1100 124:174 7C | - | 111 1101 125:175 7D } - | 111 1110 126:176 7E ~ - - escaping in urls and html/ - > Using caret as an example... - | Binary Dec:Oct Hex Glyph - | 101 1110 94:136 5E ^ - | in html: ^ - | in url: %5E -- see/ - > Show chars codes in a file - <@ specials/ diff --git a/menu/black.menu b/menu/black.menu deleted file mode 100644 index 053ad53b..00000000 --- a/menu/black.menu +++ /dev/null @@ -1 +0,0 @@ -<<< themes/list/Black BG/ diff --git a/menu/console.menu b/menu/console.menu deleted file mode 100644 index 8cc542b0..00000000 --- a/menu/console.menu +++ /dev/null @@ -1,4 +0,0 @@ -- tree/ - ! Console.tree *args -- history/ - ! Console.history *args diff --git a/menu/dbs.rb b/menu/dbs.rb deleted file mode 100644 index 895783bf..00000000 --- a/menu/dbs.rb +++ /dev/null @@ -1 +0,0 @@ -Xiki['mysql/dbs', args] diff --git a/menu/dimensions/index.menu b/menu/dimensions/index.menu deleted file mode 100644 index 9bcb6093..00000000 --- a/menu/dimensions/index.menu +++ /dev/null @@ -1,14 +0,0 @@ -- half/ - ! width = 166 - ! width += 3 if ! View.scroll_bars - ! View.dimensions_set 0, 902, width, 63 - ! Styles.default_font_size - ! Window.visibility('full') -- full/ - ! View.dimensions_full -- right/ - ! View.dimensions_set 573, 22, 119, 57 - ! Styles.default_font_size -- small/ - ! View.dimensions_set 100, 22, 50, 15 - ! Styles.default_font_size diff --git a/menu/dimensions/index.rb b/menu/dimensions/index.rb deleted file mode 100644 index c9eb3eb3..00000000 --- a/menu/dimensions/index.rb +++ /dev/null @@ -1,65 +0,0 @@ -class Dimensions - def self.menu_before item=nil - - return if ! item - - # An item exists, so eval it if it's in the users dimensions.conf... - - # Otherwise .expand would just look at index.menu - conf = yield[:conf] - txt = Tree.children conf, item - - return if ! txt - - # Eval output... - - code = txt.gsub /^! ?/, '' - returned, out, exception = Code.eval code # , source_file, 1 - - View.kill if View.name == "@dimensions/" - - "" - end - - def self.menu_after output, *args - - # /, so prepend user conf if there, and append @conf... - - if args.blank? - conf = yield[:conf] - if conf # If conf, add item names defined by user - conf.gsub! /^-/, '+' - items = conf.scan(/^[+-] .+/) # Just pull out items - items += output.split "\n" # Split into arrays and merge, so we can remove duplicates - items.uniq! - output = items.join "\n" - end - - return "#{output}\n@conf/" - end - - - # Close view if we opened it with a shortcut... - - # TODO: We can probably make this generic - # Know to close after the first launch (that returns nil?) when - # - Maybe always close when :letter param - # - maybe only close when :letter=>"close" - # - yes, probably do this - # - just make the code that got the letter input close the view - # - when the view name is @... - # - then there's no need for this file - # - or possibly: make name of view special - # - to indicate that it should be closed - # - probably isn't necessary, since :letter-related code will still be running - - # /projects/xiki/lib/xiki/ - # - key_bindings.rb - # | Xiki.def "layout+dimensions", "dimensions/", :letter=>1 - - View.kill if View.name == "@dimensions/" - - - nil - end -end diff --git a/menu/echo.rb b/menu/echo.rb deleted file mode 100644 index 0f1f76f8..00000000 --- a/menu/echo.rb +++ /dev/null @@ -1 +0,0 @@ -args.inspect diff --git a/menu/edited.rb b/menu/edited.rb deleted file mode 100644 index ab14b851..00000000 --- a/menu/edited.rb +++ /dev/null @@ -1,18 +0,0 @@ -class Edited - def self.menu option=nil - - paths = Files.edited_array[0..300] - - # If "tree" option, show in tree form - if option == "tree" - txt = Tree.paths_to_tree paths - txt.gsub! /^- \//, "- @/" - return txt - end - - # Else, show in array form - - paths.map!{|i| i.sub(/(.+\/)(.+)/, "- @\\1\n - \\2")} - paths.join("\n") - end -end diff --git a/menu/editor_setup.menu b/menu/editor_setup.menu deleted file mode 100644 index a2c1b5e2..00000000 --- a/menu/editor_setup.menu +++ /dev/null @@ -1,12 +0,0 @@ -> Directions for installing and Xiki-enable an editor -<< aquamacs setup/ -<< emacs setup/ -<< vim setup/ - -Suggest more by tweeting to @xiki. - -in the works/ - > These editors may be supported at some point - @sublime setup/ - @rubymine setup/ - | Tweet to @xiki if you know (or want to learn) how to extend these editors, to make a Xiki plugin. Let's remote-pair on it! diff --git a/menu/emacs_config_tweaks/control-lock.el b/menu/emacs_config_tweaks/control-lock.el deleted file mode 100644 index 40bdad01..00000000 --- a/menu/emacs_config_tweaks/control-lock.el +++ /dev/null @@ -1,149 +0,0 @@ -;;; control-lock.el --- Like caps-lock, but for your control key. Give your pinky a rest! - -;; Copyright (C) 2013 Craig Muth -;; Created 10 November 2007 -;; Version 1.1.3 -;; Version Keywords: control key lock caps-lock - -;;;; Commentary -;; Quick Start / installation: -;; 1. Download this file and put it next to other files emacs includes -;; 2. Add this to your .emacs file and restart emacs: -;; (require 'control-lock) -;; (control-lock-keys) -;; 3. Type C-, and follow the below steps -;; -;; Use Case: -;; - Type C-, -;; - The cursor changes to a an underscore, so you know control-lock is on -;; - Type n to go to the next line -;; - Type v to scroll down -;; - Type xf to open a file -;; - Type any other chars, and emacs will behave as though control is held down -;; - Type , to exit control-lock -;; - The cursor changes back, so you know control-lock is off -;; -;; Input from commands: -;; When in control lock and a command gets input from the minibuffer, control-lock -;; doesn't interfere (i.e. chars are temporarily not converted to control chars). -;; -;; Inserting literal char: -;; Pressing ' will temporarily turn control-lock off for the following key stroke. -;; This is useful, for example, for typing 's to sort in dired mode. -;; Holding down shift acts to temporarily disable control-lock - -(defun ol (txt &optional txt2) - (write-region (concat (prin1-to-string txt) (prin1-to-string txt2) "\n") nil "/tmp/eol.txt" t)) - -(defun control-lock-letter (l ch) - "Called when keys are pressed. If we deem control-lock to be enabled, it returns the control-version of the key. Otherwise it just returns the key." - ;(ol "control-lock-enabled-p: " (control-lock-enabled-p)) - ; (pp (control-lock-enabled-p)) - (if (control-lock-enabled-p) - ch l)) - -(defun control-lock-enabled-p () - "Returns whether control lock should be enabled at a given point" - ; (pp "control-lock-mode-p") - ; (pp control-lock-mode-p) - (and control-lock-mode-p - ; If not disable once (turning off if set) - (if control-lock-disable-once - (progn - (setq control-lock-disable-once nil) - nil ; Not enabled this time - ) - t ; It's enabled as far as we know here - ) - (not isearch-mode) - (not (string-match "\\*Minibuf" (buffer-name))))) - -; Make ctrl-lock be off by default -(setq control-lock-mode-p nil) - -(defun control-lock-quote (p) - "Make ' disable ctrl-lock for next key" - (if (control-lock-enabled-p) - (progn - ;(echo "xxxxxxx") - (setq control-lock-disable-once t) - "") - "\\")) -; p)) - -(setq control-lock-disable-once nil) -;(define-key key-translation-map "'" 'control-lock-quote) -(define-key key-translation-map "\\" 'control-lock-quote) - -(defun control-lock-map-key (l ch fun &optional shift) - "Makes function to handle one key, and maps it to that key" - (eval (read - (concat - "(progn " - "(defun control-lock-" fun " (p) " - "(setq control-lock-shift " (if shift "t" "nil") ")" - "(control-lock-letter (kbd \"" l "\") (kbd \"" ch "\"))" - ") " - "(define-key key-translation-map (kbd \"" l "\") 'control-lock-" fun ")" - ")" - )))) - -; Map lowercase keys -(let ((c ?a) s) - (while (<= c ?z) - (setq s (char-to-string c)) - (control-lock-map-key s (concat "C-" s) s) - (setq c (+ c 1)))) - -; Map uppercase keys to lowercase -(let ((c ?A) s) - (while (<= c ?Z) - (setq s (char-to-string c)) - (control-lock-map-key s (downcase s) s t) - (setq c (+ c 1)))) - -; Map numbers -(let ((c ?0) s) - (while (<= c ?9) - (setq s (char-to-string c)) - (control-lock-map-key s (concat "C-" s) s) - (setq c (+ c 1)))) - - -; Map misc keys -(control-lock-map-key "'" "C-'" "apostrophe") -(control-lock-map-key "," "C-," "comma") -(control-lock-map-key "`" "C-`" "backtick") - - -(control-lock-map-key "C-i" "C-" "tab") - -(control-lock-map-key "/" "C-/" "slash") -(control-lock-map-key "SPC" "C-@" "space") -(control-lock-map-key "[" "C-[" "lsqrbracket") -(control-lock-map-key "]" "C-]" "rsqrbracket") -(control-lock-map-key ";" "C-;" "semicolon") -(control-lock-map-key "." "C-." "period") -(control-lock-map-key "=" "C-=" "equals") -(control-lock-map-key "-" "C--" "dash") - - -(defun control-lock-keys () - "Sets default keys - C-z enables control lock." - ; (global-set-key (kbd "C-z") 'control-lock-enable) - (global-set-key (kbd "C-,") 'control-lock-enable) -) - -(defun control-lock-enable () (interactive) - "Enable control lock. This function should be mapped to the key the user uses to enable control-lock." - (if control-lock-mode-p ; If control lock was enabled, disable it - (progn - (setq control-lock-mode-p nil) - (customize-set-variable 'cursor-type '(bar . 3)) - (setq isearch-mode nil) ; Hack for control lock staying off - ) - (progn ; Else, enable it - (setq control-lock-mode-p t) - (customize-set-variable 'cursor-type '(hbar . 3))))) - -(provide 'control-lock) diff --git a/menu/emacs_setup/el4rrc_default.txt b/menu/emacs_setup/el4rrc_default.txt deleted file mode 100644 index 593de8e8..00000000 --- a/menu/emacs_setup/el4rrc_default.txt +++ /dev/null @@ -1,65 +0,0 @@ -@lisp_object_gc_trigger_count = 100 -@lisp_object_gc_trigger_increment = 100 -@ruby_gc_trigger_count = 100 -@ruby_gc_trigger_increment = 100 -@log_buffer = "*el4r:log*" -@output_buffer = "*el4r:output*" -@unittest_lisp_object_gc_trigger_count = 5000 -@unittest_lisp_object_gc_trigger_increment = 5000 -@unittest_ruby_gc_trigger_count = 5000 -@unittest_ruby_gc_trigger_increment = 5000 -@temp_file = "/var/folders/66/3h81t2y913vf344z6fb2cks00000gn/T/el4r-#{ENV['USER'] || ENV['USERNAME'] || 'me'}.tmp" - -# New paths -el4r_gem = "{{trogdoro_el4r_dir}}" -# el4r_gem = "/Users/craig/.rvm/gems/ruby-1.9.3-p194/gems/trogdoro-el4r-1.0.7" -@stdlib_dir = "#{el4r_gem}/lib/el4r/emacsruby" -@autoload_dir = "#{el4r_gem}/lib/el4r/emacsruby/autoload" -@el_program = "#{el4r_gem}/data/emacs/site-lisp/el4r.el" -@instance_program = "#{el4r_gem}/bin/el4r-instance" - -# $: << File.join(ENV['EL4R_ROOT'], "lib") -# This has the el4r dir -# Deleting this should be fine -# $: << "/Users/craig/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/site_ruby/1.9.1" - -### El4r bootstrap code -def __conf__ - if ENV['EL4R_ROOT'] - $: << File.join(ENV['EL4R_ROOT'], "lib") - end - require 'el4r/el4r-sub' - ConfigScript.new(__FILE__) -end - -def __elisp_init__ - $> << "(setq \n" - instance_variables.map{|iv| [iv[1..-1], instance_variable_get(iv)]}.each {|k,v| $> << "el4r-#{k.gsub(/_/,'-')} #{v.inspect}\n" if Numeric === v or String === v} - $> << ')' << " -" -end - -at_exit { __elisp_init__ if __FILE__==$0 } - -### Customizable variables -### You can override these variables in User-setting area. -# directory containing EmacsRuby scripts -@home_dir = ENV['EL4R_HOME'] || File.expand_path("~/.el4r") -# startup EmacsRuby script -@init_script = "init.rb" -# EmacsRuby search path -@el4r_load_path = [ @home_dir, @site_dir, @stdlib_dir, "." ] -# End of the el4r block. -# User-setting area is below this line. - -# Ruby interpreter name used by el4r - -@ruby_program = "ruby" -# Probably rvm ENV vars will handle this -# @ruby_program = "/Users/craig/.rvm/rubies/ruby-1.9.3-p194/bin/ruby" -# @ruby_program = "/Users/craig/.rvm/rubies/ruby-1.9.3-p327/bin/ruby" - -# Probably need to set this redundantly - -# Emacs program name used by el4r / el4r-runtest.rb -@emacs_program = "emacs" diff --git a/menu/emacs_setup/index.menu b/menu/emacs_setup/index.menu deleted file mode 100644 index 0761e451..00000000 --- a/menu/emacs_setup/index.menu +++ /dev/null @@ -1,44 +0,0 @@ -> Summary -| This menu updates config files etc. for Xiki-enabling emacs. -| Currently it requires a GUI version of emacs. It won't run -| in no-window mode. - -> 1. Check whether emacs is installed -check for app/ - ! # Probably just do `which emacs`? - ! "TODO: borrow from aquamacs setup" - -> 2. Update scripts etc -update emacs conf/ - ! el4r_gem_lib = Gem::Specification.find_by_name("trogdoro-el4r").gem_dir+"/" - ! el4r_gem_dir = "#{el4r_gem_lib}data/emacs/site-lisp/" - ! - ! txt = %` - ! ; Load el4r, which loads Xiki - ! (add-to-list 'load-path "#{el4r_gem_dir}") - ! (require 'el4r) - ! (el4r-boot) - ! `.unindent - ! - ! File.open(File.expand_path("~/.emacs"), "w") { |f| f << txt } - ! "- Created the ~/.emacs file." - ! - ! # Todo: warn this will update the .emacs file -update el4rrc/ - ! el4r_gem_lib = Gem::Specification.find_by_name("trogdoro-el4r").gem_dir+"/" - ! txt = File.read("#{Xiki.dir}menu/emacs_setup/el4rrc_default.txt") - ! txt.sub!("{{trogdoro_el4r_dir}}", el4r_gem_lib.sub(/\/$/, '')) - ! - ! File.open(File.expand_path("~/.el4rrc.rb"), "w") { |f| f << txt } - ! "- Created the ~/.el4rrc.rb file." -update initrb/ - ! txt = File.read("#{Xiki.dir}menu/emacs_setup/initrb_default.txt") - ! txt.sub!("{{xiki_dir}}", Xiki.dir) - ! - ! el4r_dir = File.expand_path("~/.el4r") - ! Dir.mkdir(el4r_dir) if ! File.exists?(el4r_dir) - ! File.open("#{el4r_dir}/init.rb", "w") { |f| f << txt } - ! "- Created the ~/.el4r/init.rb file." - -> 3. Optionally tweak your emacs config -Paste "emacs config tweaks" into emacs and double-click on it. You'll be able to preview each conf item before it's added to your .emacs. diff --git a/menu/emacs_setup/initrb_default.txt b/menu/emacs_setup/initrb_default.txt deleted file mode 100644 index 382a8719..00000000 --- a/menu/emacs_setup/initrb_default.txt +++ /dev/null @@ -1,6 +0,0 @@ -$LOAD_PATH << "{{xiki_dir}}lib/" -# $LOAD_PATH << "/projects/xiki/lib/" -require 'xiki' -Xiki.init -Xiki::KeyBindings.keys # Use default key bindings -Xiki::Themes.use "Default" # Use xiki theme diff --git a/menu/facts.rb b/menu/facts.rb deleted file mode 100644 index b91871e1..00000000 --- a/menu/facts.rb +++ /dev/null @@ -1 +0,0 @@ -Xiki[:memorize, args] diff --git a/menu/font.menu b/menu/font.menu deleted file mode 100644 index 9018ea85..00000000 --- a/menu/font.menu +++ /dev/null @@ -1 +0,0 @@ -<<< styles/ diff --git a/menu/gems.rb b/menu/gems.rb deleted file mode 100644 index 1a50880c..00000000 --- a/menu/gems.rb +++ /dev/null @@ -1,72 +0,0 @@ -class Gems - MENU = " - - .list/ - - */ - - */ - - .readme/ - - .source/ - - .uninstall/ - - .environment/ - " - - def self.list name=nil - - # /..., so list noames - - if name.nil? - gem_list = Console.sync("gem list") - return gem_list.gsub(/ \(.+/, "").gsub(/.+/, "- \\0/") - end - - # /fooo, so list versions... - - txt = Console.sync("gem list #{name}") - versions = txt[/\((.+)\)/, 1] - versions = versions.split ", " - - return versions.map{|o| "#{o}/"} - - end - - def self.uninstall name, version - txt = "sudo gem uninstall #{name} -v #{version}" - Console.run txt - - ".flash - Running gem uninstall command in shell..." - end - - def self.gem_dir name - # TODO: use .find_all_by_name instead, and grab the appropreate version - # Probably require version as a param to this method. - Gem::Specification.find_by_name(name).gem_dir+"/" - end - - def self.source name, version -Ol.>> name, version - dir = Gems.gem_dir name - - "@#{dir}" - end - - def self.readme name, version - - dir = Gems.gem_dir name - - entries = Dir.new(dir).entries - file = entries.find{|o| o =~ /^readme/i} - - return "| No readme file found in\n@#{dir}" if ! file - - path = "#{dir}#{file}" - - Tree << "@#{path}" - - Launcher.enter_all - nil - end - - def self.environment - Console.sync("gem environment").strip.gsub(/^/, '| ') - end - -end diff --git a/menu/google.rb b/menu/google.rb deleted file mode 100644 index bc6194de..00000000 --- a/menu/google.rb +++ /dev/null @@ -1,36 +0,0 @@ -require "cgi" - -module Xiki::Menu - class Google - def self.menu *args - - if args.empty? # If no path, pull from history - return "@prompt/Type search string" - end - - txt = args.join("/").strip - txt.gsub! "\n", ' ' - txt = CGI.escape txt - - url = "http://www.google.com/search?q=#{txt}" - - Keys.prefix_u ? - Browser.url(url): - $el.browse_url(url) - nil - end - - def self.search txt - txt = CGI.escape txt - Browser.url "http://www.google.com/search?q=#{txt}" - nil - end - - def self.maps txt - txt = CGI.escape txt - Browser.url "http://maps.google.com/maps?q=#{txt}" - nil - end - - end -end diff --git a/menu/gray.menu b/menu/gray.menu deleted file mode 100644 index 8b1e65df..00000000 --- a/menu/gray.menu +++ /dev/null @@ -1 +0,0 @@ -<<< themes/list/Light Gray BG/ diff --git a/menu/handlers.rb b/menu/handlers.rb deleted file mode 100644 index 652ff80c..00000000 --- a/menu/handlers.rb +++ /dev/null @@ -1,7 +0,0 @@ -dir = Bookmarks["$x/lib/xiki/"] - -if args.blank? - Dir["#{dir}*_handler.rb"].map {|f| "- #{f[/(\w+)_handler/, 1]}/"}.join("\n") -else - "@open file/#{dir}/#{args[0]}_handler.rb" -end diff --git a/menu/http.rb b/menu/http.rb deleted file mode 100644 index 886aa2d5..00000000 --- a/menu/http.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Http - def self.menu *args - yield[:dont_log] = 1 - url = args.blank? ? nil : args.join('/') - - # If as+open, just jump to the log - return View.open Launcher.log_file if Keys.open? - - # If no url's, list them all... - - if url.blank? - txt = Launcher.last "http", :exclude_path=>1 - txt.gsub! /^- /, '<< ' - return txt - end - - Keys.prefix == :u ? $el.browse_url(url) : Firefox.url(url) - nil - end -end diff --git a/menu/kill.rb b/menu/kill.rb deleted file mode 100644 index 50eff19e..00000000 --- a/menu/kill.rb +++ /dev/null @@ -1,48 +0,0 @@ -class Kill - # Called like this: - # @kill/ - # @kill/2.6 625 craig /Appli... - # @kill/xiki/2.6 625 craig /Appli... - - MENU = " - docs/ - > Summary - | Lists process, so you can click to kill them. - > Filter - | If you pass an item, it filters the which processes are displayed... - @kill/user/ - << ports/ - " - - def self.menu_after output, *args - - return if args.any? && output # If MENU sub-item that menu handled do nothing - - # Grab filter and process args - filter = args.shift if args[0] !~ /^\|/ # If 1st arg isn't pipe-quoted, it's a filter - process = args[0] - - # /, so list processes, then MENU contents... - - if ! process - txt = `ps -eo pcpu,pid,user,args | sort -k 1 -r` - header = txt.slice!(/^ *%.+\n/) - txt = "#{header}#{txt}" - txt.gsub! /^/, '| ' - - if filter - txt = txt.grep(/#{Regexp.quote filter}/).join('') - end - - txt = "| none found" if txt == "" - return "> Select a process to kill it\n#{txt.strip}\n#{output}" - end - - # /process, so kill it... - - pid = process.split(/ +/)[2] - output = `kill #{pid}` - - "- killed #{pid}!" - end -end diff --git a/menu/maps.rb b/menu/maps.rb deleted file mode 100644 index 41cf0b0d..00000000 --- a/menu/maps.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'xiki/core/view' -require 'xiki/core/effects' - -class Maps - def self.menu txt=nil - - # If no arg, prompt to type something - - return View.prompt "Type something to search on google maps" if ! txt - - # If arg, look it up - - Firefox.url "http://maps.google.com/maps?q=#{txt.gsub "\n", ", "}" - nil - end -end diff --git a/menu/mark.rb b/menu/mark.rb deleted file mode 100644 index 8536f76c..00000000 --- a/menu/mark.rb +++ /dev/null @@ -1,137 +0,0 @@ -class Mark - MENU = " - - .next/ - - .previous/ - - .outline/ - - .show/ - - light/ - - red/ - - orange/ - - yellow/ - - green/ - - blue/ - - purple/ - - white/ - - .delete/ - - .clear/ - " - - def self.menu_after output, *args - return if ! Color.colors_by_name[args[0]] # Only continue if arg is an existing color - - Xiki::Color.mark args[0] - "" - end - - def self.next - Xiki::Color.next - "" - end - def self.previous - Xiki::Color.previous - "" - end - - def self.outline - View.kill if View.name == "@mark/" - - txt = self.get_marked_lines - if txt.blank? - txt = " - no marked lines in this view!" - else - txt.gsub! /^/, " | " - end - - file = View.file - - path = file ? - "- #{File.expand_path(file).sub(/(.+)\//, "\\1/\n - ")}\n" : - "- buffer #{View.name}/\n" - - txt = "#{path}#{txt}" - - Launcher.open txt, :no_launch=>1 - - "" - end - - - def self.get_marked_lines label=nil - overlays = $el.overlays_in(View.top, View.bottom) # Get all overlays - txt = "" - overlays.to_a.reverse.each do |o| # Loop through and copy all - if label - next if $el.overlay_get(o, :face).to_s != label - end - line = View.txt($el.overlay_start(o), $el.overlay_end(o)) - line << "\n" unless line =~ /\n$/ - txt << line - end - txt - end - - def self.show - hash = Xiki::Color.all_marks_hash - - if hash.empty? # If no marks found, just say so - return "- no marks found!" - end - - keys = hash.keys.sort.reverse - - txt = "" - last_file = nil - keys.each do |key| - file, line = hash[key] - if last_file == file # If same file as last, just add line - txt << " | #{line}" - else # Else, show file and line - txt << "@#{file.sub /(.+\/)/, "\\1\n - "}\n | #{line}" - end - - last_file = file - end - - Tree.<< txt, :no_search=>1 - - # Apply colors... - - keys.each do |key| - Move.to_quote - over = $el.make_overlay(Line.left+Line.indent.length, Line.right+1) - $el.overlay_put over, :face, hash[key] - end - - Tree.to_parent :u - Move.to_quote - - "" - end - - def self.delete - View.kill if View.name == "@mark/" - - overlays = $el.overlays_at($el.next_overlay_change($el.point_at_bol - 1)) - return View.beep "- No highlights after cursor!" if ! overlays - options = yield - return $el.delete_overlay(overlays[0]) - end - - def self.clear - View.kill if View.name == "@mark/" - - if Keys.prefix_u # Don't delete map mark - return $el.remove_overlays - end - - overlays = $el.overlays_in(View.top, View.bottom) # Get all overlays - overlays.to_a.reverse.each do |o| # Loop through and copy all - if $el.overlay_get(o, :face).to_s != "color-rb-light" - $el.delete_overlay(o) - end - end - - "" - end -end - diff --git a/menu/memorize.rb b/menu/memorize.rb deleted file mode 100644 index 83c1ef22..00000000 --- a/menu/memorize.rb +++ /dev/null @@ -1,163 +0,0 @@ -class Memorize - MENU = " - > Example - | France : Paris - | England : London - | Japan : Tokyo - | Germany : Berlin - - - docs/ - | Add some facts like the example, and launch one of the - | example lines to start an interactive memorize process. - | In addition to helping you memorize, this is a low-stress - | way to digest and review facts. - | Double-click on one of to lines to begin. - " - - MENU_HIDDEN = " - - .show answer/ - - .I was wrong/ - - .I was right/ - " - - def self.i_was_right - - # Delete 2 controls... - - indent = Line.indent - - View.delete Line.left(0), Line.left(2) - - # If finished, just say so... - - if Line.blank? - Line << "#{indent}- you're finished!\n" - return - end - - upcoming = Line.value - upcoming.sub! "...", " " - upcoming.sub! /(.+) : (.+)/, "\\1 : ?" - - # Delete top one if it also exists below (don't leave there until all are correct)... - - correct = Line.value 0 - - left = View.cursor - - self.skip_past_hidden - - pending = View.txt left, View.cursor - pending = pending.gsub("...", " ").split("\n") - repeated = pending.member? correct - - View.cursor = left - - if repeated - View.delete Line.left(0), Line.left(1) - end - - View << "#{upcoming}\n#{indent}- show answer/\n" - Line.previous - nil - end - - def self.i_was_wrong - - # Delete question and 2 controls... - - indent = Line.indent - - Line.previous - wrong_line = Line.value - View.delete Line.left(1), Line.left(4) - left = View.cursor - - # If none left, just insert current one - if Line.blank? - wrong_line.sub!(/(.+) : (.+)/, "\\1 : ?") - View << "#{wrong_line}\n" - View.<< "#{indent}- show answer/\n" - wrong_line.sub! "| ", "|..." - View.<< "#{wrong_line}\n" - Line.previous 2 - return - end - wrong_line.sub! "| ", "|..." - - # Change next one to question mark... - - txt = Line.value - View << txt.sub(/(.+) : (.+)/, "\\1 : ?")+"\n" - - # Insert wrong answer 2 down... - - 2.times { Line.next if Line =~ /\.\.\./ } - View << "#{wrong_line}\n" - - # Insert wrong answer at end... - - self.skip_past_hidden - Line.to_left - View << "#{wrong_line}\n" - - View.cursor = left - - Line.sub! "...", " " - - Line.next - View.<< "#{indent}- show answer/\n", :dont_move=>1 - - nil - end - - def self.skip_past_hidden - Line.next while Line =~ /^ *\|\.\.\./ - end - - def self.show_answer - indent = Line.indent - Line.previous - View.delete Line.left(1), Line.left(3) # Delete question and control - - Line.sub! "...", " " # Expose current line - - Line.next - View << "#{indent}- I was wrong/\n#{indent}- I was right/\n" - Line.previous 2 - - nil - end - - def self.menu_after output, *path - - # If quoted line, they're starting out... - - starting_out = path[0] && path[0] =~ /^\|/ - return if ! starting_out - - indent = Line.indent - - Search.backward "^[^:]*$" # Find line above without a colon - - left = Line.left 2 - Line.next - Search.forward "^[^:\n]*$" # Find line below without a colon - right = View.cursor - - txt = View.delete left, right - txt = txt.split "\n" - txt = txt.sort_by{ rand } - first = txt[0] - first = first.sub /(.+) : (.+)/, "\\1 : ?" - pending = txt.join("\n")+"\n" - - pending.gsub!(/\| /, "|...") - View.<< "#{first}\n#{indent}- show answer/\n#{pending}", :dont_move=>1 - - Line.next - - nil - end - -end diff --git a/menu/menu_path.rb b/menu/menu_path.rb deleted file mode 100644 index 73e7bb7f..00000000 --- a/menu/menu_path.rb +++ /dev/null @@ -1,9 +0,0 @@ -class MenuPath - def self.menu *args - dirs = Xiki.menu_path_dirs - return dirs.map{|o| "+ @#{o.sub /\/*$/, '/'}"}.join("\n") - - menu_path = menu_path.split(":").map{|o| "+ @#{o.sub /\/*$/, '/'}"}.join("\n") - menu_path - end -end diff --git a/menu/mysql/default.conf b/menu/mysql/default.conf deleted file mode 100644 index 2c5d2348..00000000 --- a/menu/mysql/default.conf +++ /dev/null @@ -1,3 +0,0 @@ -> The db to use when none is specified -- default db/ - foo diff --git a/menu/not_saved.rb b/menu/not_saved.rb deleted file mode 100644 index 1741107e..00000000 --- a/menu/not_saved.rb +++ /dev/null @@ -1,33 +0,0 @@ -# Narrow down to modified buffer only -buffers = $el.buffer_list.to_a. - select{|b| $el.buffer_modified_p(b)}. - select{|b| $el.buffer_file_name(b)} - -if (buffers.size == 0) - return "- No files unsaved!\n" -end - -txt = "" - -buffers.each do |b| - path = $el.buffer_file_name(b) - $el.with(:save_excursion) do - $el.set_buffer b - - diffs = DiffLog.save_diffs :dont_log=>1 - - if ! diffs - diffs = " | File no longer exists?\n" if ! File.exists?(path) - diffs ||= " | no changes\n" - end - diffs = diffs.sub(/^.+\n.+\n/, '').gsub /^ /, '' - - txt << "@#{path}\n" - txt << diffs - - end -end - -txt = "#{txt}- keys/\n | Key shortcut: open+not+saved\n" - -txt diff --git a/menu/options.rb b/menu/options.rb deleted file mode 100644 index 6c5249a6..00000000 --- a/menu/options.rb +++ /dev/null @@ -1 +0,0 @@ -Tree.quote options.ai diff --git a/menu/parse.rb b/menu/parse.rb deleted file mode 100644 index 1711dc29..00000000 --- a/menu/parse.rb +++ /dev/null @@ -1,54 +0,0 @@ -class Parse - def self.menu *args - - # Grab parent subpath to parse and pre-expand - - path = yield[:ancestors] - - raise "> How to use\n| Nest this under a menu name\n" if ! path - - # .expanders calls .parse - options = Expander.expanders path - - if options[:menufied] - Menu.climb_sources options - end - - # /, so show options... - - if args.empty? - txt = self.ap options - txt.gsub! /^ /, "" - txt.sub! "[\n [", "[[" - txt = Tree.quote txt - txt << "\n<< source/" if options[:menufied] # Sources menu helps for menus - return txt - end - - # /|:def..., so jump to it... - - file, line = Tree.source options - - return ".flash - no source found!" if ! file - - View.open file - View.line = line if line - - nil - end - - def self.ap txt - txt = txt.ai - txt.sub! /\A{\n/, '' - txt.sub! /\n}\z/, '' - txt - end - - def self.yaml txt - txt = txt.to_yaml - txt.sub! /\A--- \n/, '' - txt.gsub!(/ $/, '') - txt - end - -end diff --git a/menu/path.rb b/menu/path.rb deleted file mode 100644 index a68a2dcb..00000000 --- a/menu/path.rb +++ /dev/null @@ -1,7 +0,0 @@ -%` -| Xiki's MENU_PATH environment variable. Dirs that Xiki looks for menus in. -<< menu path/ -#{ -ENV['PATH'].split(":").map{|o| "@#{FileTree.add_slash_maybe o}\n"}.join("") -} -` diff --git a/menu/patterns/core_patterns.rb b/menu/patterns/core_patterns.rb deleted file mode 100644 index 540537ac..00000000 --- a/menu/patterns/core_patterns.rb +++ /dev/null @@ -1,128 +0,0 @@ -load "#{Xiki.dir}menu/mysql/index.rb" # Necessary since it's lazy loaded -module Xiki - Menu::Mysql.def_patterns - - # $... shell command lines when not under dir - Xiki.def(/^([$%&]) (.+)/) do |path, options| - match = options[:expanders].find{|o| o[:match]}[:match] - prompt, command = match[1..2] - options[:command] = command - - Console.shell_command_per_prompt prompt, options - end - - # Url's - Xiki.def(%r"^(https?://[^/]|file://)") do |path, options| - - # Todo: make this work when no editor - - Launcher.append_log path - - prefix = Keys.prefix - Keys.clear_prefix - - url = path[/(http|file).?:\/\/.+/] - if prefix == "all" - txt = RestTree.request("GET", url) - txt = Tree.quote(txt) if txt =~ /\A<\w/ - Tree.under Tree.quote(txt), :no_slash=>1 - next - end - url.gsub! '%', '%25' - url.gsub! '"', '%22' - prefix == :u ? $el.browse_url(url) : Firefox.url(url) - nil - end - - # Special launching for "*ol" view - Xiki.def(//, :pre=>1, :target_view=>"*ol") do |path, options| - options[:halt] = 1 - OlHelper.launch - Effects.blink(:what=>:line) - nil - end - - - # !... lines (at left margin) - Xiki.def(/^! /) do - code = Tree.siblings(:all=>1).select{|o| o =~ /^! /}.join("\n") - code.gsub! /^! /, '' - - line_number = View.line - - lines_above = Tree.siblings(:before=>1).select{|o| o =~ /^! /} - line_number -= lines_above.length - - - returned, out, exception = Code.eval code, View.file, line_number - returned ||= out # Use output if nothing returned - returned = returned.to_s if returned - returned - end - - - # Various lines that mean run as ruby - # p ... - # puts ... - # etc. - - - Xiki.def(/^(p|y|pp|puts|Ol|Ol\.ap) /) do |line| - CodeTree.run line, :quote=>1 - end - - Xiki.def(/^(ap) /) do |line| - CodeTree.run line, :quote=>1 - end - - Xiki.def(/^ao /) do |line| # "awesome out" - line.inspect - line.sub! /^ao /, "Ol.ap " - CodeTree.run line, :quote=>1 - nil - end - - Xiki.def(/^print\(/) do |line| - Javascript.launch - end - - Xiki.def(/^pg /) do |line| - line.sub! /^pg /, '' - CodeTree.run "puts JSON.pretty_generate(#{line})" - end - - - # GET http://foo.com - # and - # GET /regex/ http://foo.com - Xiki.def(/^(- )?GET /) do |path, options| - url = path.sub(/^GET /, '') - - regex = url.slice! %r"^/.+?/ " - - url = "http://#{url}" unless url =~ %r"http://" - url.gsub! ' ', '+' - - html = RestTree.request "GET", url - - if regex - regex = regex[%r"/(.*)/ $", 1] # " - html = html.split("\n").grep(/#{regex}/i).join("\n") - end - - html = Tree.quote(html) if html !~ /^[>|] / && html !~ /\A.+\/$/ - html - end - - # http:/foo.com (only one slash means show result) - # Xiki.def(%r"^http:/.+") do |url, options| - Xiki.def(%r"^http:(/|///)[^/]") do |url, options| - url.sub! %r"/+", "//" - url.gsub! ' ', '+' - - html = RestTree.request "GET", url - - html = Tree.quote(html) if html !~ /^[>|] / && html !~ /\A.+\/$/ - html - end -end diff --git a/menu/processes.menu b/menu/processes.menu deleted file mode 100644 index 8347ca0f..00000000 --- a/menu/processes.menu +++ /dev/null @@ -1,8 +0,0 @@ -- all/ - @$ ps -eo pcpu,pid,user,args | sort -k 1 -r -- cpu hogs/ - @$ ps -eo pcpu,pid,user,args | sort -k 1 -r | head -> See -- Mac Activity Monitor/ - @app/Utilities/Activity Monitor -<< kill/ diff --git a/menu/questions.rb b/menu/questions.rb deleted file mode 100644 index b91871e1..00000000 --- a/menu/questions.rb +++ /dev/null @@ -1 +0,0 @@ -Xiki[:memorize, args] diff --git a/menu/replace.rb b/menu/replace.rb deleted file mode 100644 index d5d34dc9..00000000 --- a/menu/replace.rb +++ /dev/null @@ -1,32 +0,0 @@ -class Replace - MENU = %` - - docs/ - | Shortcuts for setting the "1" and "2" clipboards, so you can - | subsequently do do+1 to search and replace. - | - | Example: - @replace/ - | foo - | bar - ` - - # If store values - def self.menu_after output, txt=nil - - # /..., so prepend current clipboard values... - - if ! txt - return ": #{Clipboard[1]}\n: #{Clipboard[2]}\n#{output}" - end - - return if output # Only do something if .menu outputted nothing - - # | before..., so save into clipboards... - - a, b = txt.split "\n" - - Clipboard[1], Clipboard[2] = a, b - - ".flash - Now do do+1 to search and replace" - end -end diff --git a/menu/ruby/index.menu b/menu/ruby/index.menu deleted file mode 100644 index 4b4b9dfc..00000000 --- a/menu/ruby/index.menu +++ /dev/null @@ -1,3 +0,0 @@ -- .classes/ -- load path/ - ! $LOAD_PATH.map{|o| "@#{o}/\n"}.join("") diff --git a/menu/ruby/index.rb b/menu/ruby/index.rb deleted file mode 100644 index 18f2dde1..00000000 --- a/menu/ruby/index.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Xiki::Menu - class Ruby - - def self.classes clazz=nil, method=nil - # /classes/, so show list of classes... - - if clazz.nil? - result = [] - ObjectSpace.each_object(Class) do |c| - name = c.to_s - next if name =~ /^#/ - result << "- #{name}/\n" - end - return result.sort.join - end - - # /classes/Class, so show methods... - - if method.nil? - result = "" - result << Kernel.const_get(clazz).instance_methods(false).sort. - collect {|i| "- #{i}/" }.join("\n") - result << Kernel.const_get(clazz).methods(false).sort. - collect {|i| "- ::#{i}/" }.join("\n") - return result - end - - # /classes/Class/method, so lookup method's doc - - method = "##{method}" unless method =~ /^::/ - command = "ri --format=rdoc #{clazz}#{method}" - Console[command].gsub(/\C-[.+?m/, '').gsub(/^/, '| ').gsub(/^\| +$/, '|') - end - end -end diff --git a/menu/sample_menus.menu b/menu/sample_menus.menu deleted file mode 100644 index 94989420..00000000 --- a/menu/sample_menus.menu +++ /dev/null @@ -1,130 +0,0 @@ -> Menu doesn't exist yet -| Create it? Make your menu by creating one of these... -- text file/ - @~/menu/.txt - | Change this text to something useful. - | It will be the content of your menu. -- items/ - @~/menu/.menu - | - dogs/ - | - hot/ - | - cats/ - - with wiki elements/ - @~/menu/.menu - | > Heading - | - Bullet point - | @http://google.com/ - - .directions/ - - creating inline/ - | As a shortcut for creating or updating a menu, you can just - | type some items in place. For example, double-click on '' - | above to collapse, then add some items kind of like this: - | - | / - | - dogs/ - | - pugs/ - | - cats/ - | - | To save, simply double-click on one of the items and you'll be - | prompted to save. Or, with your cursor on the menu or one of - | the items, type as+menu (meaning Control+A, Control+M). -- notes/ - @~/menu/.notes - | > A heading - | Stuff under headings appears - | after you expand. - | - | > Another heading - | You can put anything under the headings. -- code/ - @~/menu/.rb - | # Change this code to something useful. - | a = "la" - | a * 3 - - ruby class/ - @~/menu/.rb - | class - | def self.menu - | " - | hello - | " - | end - | end - - .directions/ - - other languages/ - - python/ - @~/menu/.py - | # Change this code to something useful. - | a = "la" - | print(a) - - js/ - @~/menu/.js - | # Change this code to something useful. - | a = "la" - | print(a) - - coffeescript/ - @~/menu/.coffee - | # Change this code to something useful. - | a = "la" - | console.log(a) - - combinations/ - - ruby class with menu method/ - - ruby class with embedded menu/ - - menu file and menu file/ - - plain ruby object/ - | Will use class or instance methods. - - programatic/ - - block/ - - pattern/ - - load from disk/ - - menu with other stuff/ - - menu with nested code/ - @~/menu/.menu - | - add stuff/ - | ! 1 + 2 - | - add numbers from path/ - | ! raise "Pass 2 numbers in this path, like\n- 11/22/" - | ! args[0] + args[1] - - todo: fix bug where returning number makes it fail! - - todo: get the above param stuff to work! - - .directions/ -- more/ - - redirect/ - | Makes one menu redirect to another. Useful for creating menus - | that just serve as shortcuts. - @~/menu/.menu - | <<< foo/ - - wrap other menu/ - - by name/ - @~/menu/.rb - | Xiki["tables/foo//", args] - - by bookmark/ - @~/menu/.rb - | Xiki["$tm//", options[:items]] - - directories/ - - show a simple example (dir of dirs) - - other stuff/ - - show example with a few different types nested - - hey.rb - - hey.menu - - hey.notes - - explain that you can have anything nested! - - index files/ - - explain they work like index.html files, and do the work for their containing directory - - so you can optionally have a dir a menu that contains all the files - - show one example - - show example of 2 equivalent versions - - multiple files/ - - show 2 files with same name that work together - - don't need to be nested in a directory - - old/ - - is this still relevant? - > Docs - @menus/docs/creating/ - - todo/ - > Add these menus - - with menu_after - - with menu_before - - cooperating - - with MENU and methods - - with foo.menu and methods diff --git a/menu/sample_menus.rb b/menu/sample_menus.rb deleted file mode 100644 index cccbeb1f..00000000 --- a/menu/sample_menus.rb +++ /dev/null @@ -1,13 +0,0 @@ -class SampleMenus - def self.directions *args - %q` - | 1. Change the above text. - | 2. Double-click on the text to create the file, - | thus creating the menu. - | 3. Double-click on "/" above to collapse, - | then double-click again to expand your new menu. - | Now you can type "" on any blank line and - | double-click it! - ` - end -end diff --git a/menu/sinatra.menu b/menu/sinatra.menu deleted file mode 100644 index d01a4b50..00000000 --- a/menu/sinatra.menu +++ /dev/null @@ -1,31 +0,0 @@ -- hello world/ - @/tmp/ - - sinatra_hi.rb - | require 'sinatra' - | - | get '/' do - | "Hello Booze!" - | end - % ruby -rubygems sinatra_hi.rb - @http://localhost:4567/ -- hello world OO/ - @/tmp/ - % ruby -rubygems sinatra_oo.rb - - sinatra_oo.rb - | require "sinatra/base" - | - | class SinatraOo < Sinatra::Base - | def say_hello - | "Hello" - | end - | - | get "/hello" do - | say_hello - | end - | end - | - | SinatraOo.run! - @http://localhost:4567/hello -- docs/ - > Intro - @http://www.sinatrarb.com/intro diff --git a/menu/tables.rb b/menu/tables.rb deleted file mode 100644 index 74e07a4f..00000000 --- a/menu/tables.rb +++ /dev/null @@ -1 +0,0 @@ -Xiki['mysql/tables', args] diff --git a/menu/tests.menu b/menu/tests.menu deleted file mode 100644 index 2697b4c2..00000000 --- a/menu/tests.menu +++ /dev/null @@ -1 +0,0 @@ -<< xiki/tests/ diff --git a/menu/themes.rb b/menu/themes.rb deleted file mode 100644 index 473adb2f..00000000 --- a/menu/themes.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Xiki::Menu - class Themes - MENU = " - - .list/ - - dir/ - @$x/etc/themes/ - - docs/ - > What themes control - | The window dividers (mode line) or all fonts and bg colors. - | So, it can make sense to use multiple themes at once. - " - - def self.list name=nil - # /list/, so list themes... - - if ! name - path = "#{Xiki.dir}etc/themes/" - themes = Dir.new(path).entries.select{|o| o =~ /^\w/} - return themes.map{|o| "- #{o[/\w+/].gsub('_', ' ')}/\n"}.join() - end - - # /list/name, so use or navigate to theme... - - if Keys.open? # If as+open, just navigate to it - path = "#{Xiki.dir}etc/themes/#{TextUtil.title_case name, :underscores=>1}.notes" - View.open path - return - end - - ::Xiki::Themes.use name - ".flash - updated!" - end - -end; end diff --git a/menu/time.menu b/menu/time.menu deleted file mode 100644 index 1d6583d9..00000000 --- a/menu/time.menu +++ /dev/null @@ -1,13 +0,0 @@ -- local/ - ! Time.now.strftime("%l:%M:%S %p %Z") -- time zone/ - ! zone = args[0] - ! if ! zone - ! return ActiveSupport::TimeZone.us_zones.map(&:name).map{|o| "- #{o}/\n"}.join('') - ! end - ! - ! zone.sub! /^\| /, '' - ! t = Time.now.in_time_zone ActiveSupport::TimeZone.new(zone) - ! t.strftime("%l:%M:%S %p %Z") -- city/ - @google/what time is it in Chicago? diff --git a/menu/welcome.menu b/menu/welcome.menu deleted file mode 100644 index 0ebf7680..00000000 --- a/menu/welcome.menu +++ /dev/null @@ -1,18 +0,0 @@ -> Welcome to Xiki -| Here are some menus and links to get you started. Double-click -| on them or type control-enter (or command-enter) while the cursor -| is on them. - -+ @docs/ - -> Website and Screencasts -Check out the screencasts on xiki.org: -@http://xiki.org -@http://xiki.org/screencasts - -> Twitter and google group -@http://twitter.com/xiki -@http://groups.google.com/group/xiki/ - -> Stop showing this menu on startup -+ @xiki/setup/misc/dont show welcome/ diff --git a/menu/white.menu b/menu/white.menu deleted file mode 100644 index 0b9a159b..00000000 --- a/menu/white.menu +++ /dev/null @@ -1 +0,0 @@ -<<< themes/list/White BG/ diff --git a/menu/xiki.rb b/menu/xiki.rb deleted file mode 100644 index c5212dae..00000000 --- a/menu/xiki.rb +++ /dev/null @@ -1,92 +0,0 @@ -module Xiki - - MENU = %` - - .tests/ - - .github/ - > files - @http://github.com/trogdoro/xiki - > commits - @https://github.com/trogdoro/xiki/commits/master - - .setup/ - - install command/ - | Double-click on these lines to add the executable 'xiki' command to - | your path: - | - @#{self.dir}/ - $ ruby etc/command/copy_xiki_command_to.rb /usr/local/bin/xiki - | - | Then you can type 'xiki' on a command line outside of emacs as a - | shortcut to opening xiki and opening menus, like so: - | - | $ xiki computer - | - - .install icon/ - | Double-click on this line to make .xiki files have the xiki 'shark' - | icon: - | - - install/ - | - | When you right-click on a .xiki file and select "Open With" and - | choose emacs, the files will be assigned the xiki shark icon. - | - - install global shortcut/ - - 1) With cursor on the following, type open+in+os, then click "Install" - @ #{self.dir}etc/services/Xiki Menu.workflow - - - 2) In Mac OS, open - | System Preferences > Keyboard > Keyboard Shortcuts > Services - - - 3) Scroll down to the bottom and give "Xiki Menu" the shortcut - | Command+Control+X - - - 4) Try it out by typing Command+Control+X from any application - | It should prompt you to type a xiki menu - - .process/ - - status/ - - start/ - - stop/ - - restart/ - - log/ - - el4r/ - > Configure - @#{self.dir} - % sudo bash etc/install/el4r_setup.sh - - - docs/ - | This will create/update files in your home directory, which make el4r - | point to the version of ruby currently active. - | - | You can run this multiple times. - - .misc/ - - .dont show welcome/ - - key shortcuts/ - - enable all/ - | Add this line to enable all xiki keys: - ~/.el4r/init.rb - | KeyBindings.keys # Use default key bindings - - > Todo: show options for more limited key mappings as well? - | # Only enable Control-return in all files. - | KeyBindings.map_control_return - | # Only enable Control-return in .notes files. - | @define_key(:notes_mode_map, kbd("")) { Launcher.go } - - minimal/ - | Add this line to enable all xiki keys: - ~/.el4r/init.rb - | KeyBindings.minimal__ - @web/ - - api/ - > Summary - Here are some functions that will always be available to menu classes, - even external ones. - | - | Put pipes at beginning of lines (except bullets etc) - | p Xiki.quote "hey\\nyou" - | - | Return path to tree's root including current line, will be a list with 1 - | path unless nested. - | p Xiki.trunk - | - ` - -end diff --git a/etc/command/copy_xiki_command_to.rb b/misc/command/copy_xiki_command_to.rb similarity index 58% rename from etc/command/copy_xiki_command_to.rb rename to misc/command/copy_xiki_command_to.rb index 6964256f..c1e629a1 100644 --- a/etc/command/copy_xiki_command_to.rb +++ b/misc/command/copy_xiki_command_to.rb @@ -3,14 +3,21 @@ # /usr/local/bin/) when installing Xiki manually from source # rather than as a gem. # -dest = ARGV[0] || "/usr/local/bin/xiki" -puts "Putting the 'xiki' shell command at: +dest = ARGV[0] - #{dest} +if ! dest + puts "Usage (run it from the xiki project dir):\n\n $ etc/command/copy_xiki_command_to.rb /usr/bin/xiki" + exit +end -You can pass a different path if you prefer... +if dest !~ /xiki$/ + puts "The path you pass should end with 'xiki'. Such as:\n\n $ etc/command/copy_xiki_command_to.rb /usr/bin/xiki" + exit +end - $ xiki /bin/xiki +puts "Putting the 'xiki' shell command at: + + #{dest} " source = "etc/command/xiki_wrapper" diff --git a/misc/command/external_shell_examples.txt b/misc/command/external_shell_examples.txt new file mode 100644 index 00000000..51225847 --- /dev/null +++ b/misc/command/external_shell_examples.txt @@ -0,0 +1,53 @@ +Examples when using key shortcuts: + ls <^X> List directory, then you can type to filter down + or expand to navigate files and dirs + . <^X> Shows the current dir, + -f <^X> Show tree of files, then you can type to filter + . Shows the current dir, with actions to perform on it + :d <^X> Navigates the desktop dir + -b . <^X> Creates a bookmark for your current dir + :foo <^X> Navigates a "foo" dir you've created + -ip <^X> Show your ip (runs the "ip" xiki command) + -tables <^X> Navigate and update your mysql db tables + cd Shows the previous cd commands you've typed + git Shows the previous cd commands you've typed + ^html <^X> Shows headings for your notes about "html" + +Examples: + xsh + Starts up a xsh session in the current dir. You can then + type xiki commands or shell commands (precede with '$ '). + xsh ls + Then you can type to incrementally filter down the output. + And you can expand the directories by typing Ctrl+X. + Type Ctrl+Q when you're done. + xsh -ip + Run the 'ip' xiki command. Xiki commands are kind of like + shell commands, but are more interactive and friendly. + xsh . + Browse the dirs and files in the current directory. + xsh ~ + Browse the dirs and files in your home directory. + xsh :d + Browse desktop directory + xsh -s + Recursively show all files and their content. You can then + type to incrementally filter. + xsh -n + Recursively show all files. You can then type to + incrementally filter. + xsh -s foo + Search for 'foo' in all files, showing matching lines as + a tree. + xsh -n foo + Search for 'foo' in all filenames, showing matching lines + as a tree. + +Advanced Usage: + xsh + xsh ./ + xsh // + xsh + Including: urls, code, google searches, css selectors, + contacts, sql statements, git commit hashes + diff --git a/misc/command/external_shell_help.txt b/misc/command/external_shell_help.txt new file mode 100644 index 00000000..7a8b66b4 --- /dev/null +++ b/misc/command/external_shell_help.txt @@ -0,0 +1,50 @@ +Xiki Shell! + +Usage: + xsh + xsh + xsh - + xsh - + +Tutorial: + xsh --tutorial + +Sample calls: + $ xsh --samples + +Key shortcuts for your external shell (the default): + Ctrl+O Open xsh (when blank prompt). Or type a shell command + and Ctrl+O runs it in xsh. + Ctrl+G Go to previous spot in xsh (when blank prompt). Or type a + topic and ^G runs its go action. + Ctrl+T Show tasks for the current dir. Or type a shell command + and Ctrl+T shows tasks for it. + Ctrl+X List your Xiki files (when blank prompt). Or type a shell + command or topic and Ctrl+X lists its xiki notes and actions. + Ctrl+S Search notes (and actions) shared by others to XikiHub (xiki.com). + Alt+R External shell history, like Ctrl+R (type Option+R on the mac) + +Options: + -o Open xsh. + -o shell_command Runs shell_command in xsh. + -g Go to previous spot in xsh. + -g topic Go to topic file. + -t Show tasks for the current dir. + -t shell_command Show tasks for shell_command. + -x List your Xiki files + -x topic List xiki notes (and actions) for topic. + -s search_term Search notes (and actions) on XikiHub (xiki.com). + + -r Recent shell history, like Ctrl+R (for external shell) + -f Search filenames + -c Search file contents + -e Edited recently + -h History (internal shell commands) + -n Notes + -l List xsh sessions + -b Bookmarks + -i Recent shell interactions with xsh + -d Diffs + --d debug - Use this to debug xsh startup problems. Doesn't connect + to the cached xsh process for faster startup + diff --git a/misc/command/external_shell_tutorial.txt b/misc/command/external_shell_tutorial.txt new file mode 100644 index 00000000..f2c96e8a --- /dev/null +++ b/misc/command/external_shell_tutorial.txt @@ -0,0 +1,5 @@ + +1. Go to http://xiki.com/@xiki/tutorial in a web browser. +2. Type the commands into this terminal window. + + diff --git a/misc/command/shell_please_move.txt b/misc/command/shell_please_move.txt new file mode 100644 index 00000000..3c70721e --- /dev/null +++ b/misc/command/shell_please_move.txt @@ -0,0 +1,9 @@ +Please move or rename the ~/xiki dir first. Since +~/xiki will be the path of the dir where user +customizations are stored. + +You can move it pretty much anywhere. Or use one or +both of these to rename it: + + $ mv ~/xiki ~/xiki_src + $ mkdir ~/xiki mv ~/xiki_src ~/xiki/src diff --git a/etc/command/xiki_command.rb b/misc/command/xiki_command.rb similarity index 84% rename from etc/command/xiki_command.rb rename to misc/command/xiki_command.rb index a1d99cf9..c4548cba 100644 --- a/etc/command/xiki_command.rb +++ b/misc/command/xiki_command.rb @@ -1,4 +1,5 @@ require 'timeout' +require 'json' # require 'xiki/environment' # @@ -25,7 +26,8 @@ def self.run if argv.empty? puts "#{self.usage}\n" @@dont_show_output = true - argv = ['start'] # So it continues on and starts server + # Does this make any sense when client isn't web ? + argv = [''] # So it continues on and starts server elsif argv.length == 1 && ['status', 'stop', 'restart'].member?(argv[0]) return self.ctrl argv[0] end @@ -33,10 +35,30 @@ def self.run flags, path = argv.partition{|o| o =~ /^-/} path = path.join ' ' - return self.emacs path if flags.member?("-e") # If -p, just prompt user to type a menu name + # xiki -e + return self.emacs path if flags.member?("-e") # If -e, just prompt user to type a menu name - client = flags.find{|o| o =~ /^-c/} - path = "#{client} #{path}" + options = {} + options[:client] = 'web' if flags.member?("-cweb") || flags.member?("-web") + + if flags.member? '-' + + stdin = $stdin.read.split("\n") + + # If 1st line is @options/..., parse it + if stdin[0] =~ /@options\/(.+)/ + hash = JSON[$1] + hash = hash.reduce({}){ |acc, o| acc[o[0].to_sym] = o[1]; acc } + options.merge! hash + stdin.shift + end + + path = stdin[0].strip + end + + if options.any? + path = "@options/#{JSON[options]}\n#{path}" + end wasnt_running = false @@ -111,6 +133,10 @@ def self.run end raise SystemExit.new + + # rescue Exception=>e + # puts e.to_s + end def self.get_response @@ -159,7 +185,7 @@ def self.usage and then double-clicking on it (or typing control-enter or command-enter) to browse and update your mysql database. - See the README.markdown file in the Xiki dir for help setting up + See the README.md file in the Xiki dir for help setting up your editor. You can view it by typing this command or going to this url: @@ -219,12 +245,17 @@ def self.emacs menu # Bring emacs to front - `open "/Applications/Aquamacs Emacs.app"` + # `open "/Applications/Aquamacs Emacs.app"` + `open "/Applications/Aquamacs.app/"` + + ruby = %`Xiki::Command.external \\"#{menu}\\"` + ruby.<< %`, :dir=>\\"#{Dir.pwd}\\"` if menu =~ /^@/ + + command = %`/Applications/Aquamacs.app/Contents/MacOS/bin/emacsclient -n -e '(el4r-ruby-eval "#{ruby}")'` +Ol["Make it use relative path if above doesn't exist!"] + # command = %`emacsclient -n -e '(el4r-ruby-eval "#{ruby}")'` - ruby = %`Menu.external \\"#{menu}\\"` - ruby << %`, :dir=>\\"#{Dir.pwd}\\"` if menu =~ /^@/ - command = %`emacsclient -n -e '(el4r-ruby-eval "#{ruby}")'` `#{command}` nil @@ -241,7 +272,7 @@ def self.add_coloring txt "#{self.heading_bracket $1} #{self.path $2}" when /^ *https?:\/\/.+/ "#{self.url $&}" - when /^.+\/$/ + when /^[^|\n].+\/$/ "#{self.path $&}" when /^( *- )(.*!)$/ "#{self.bullet $1}#{self.exclamation $2}" diff --git a/etc/command/xiki_process.rb b/misc/command/xiki_process.rb similarity index 77% rename from etc/command/xiki_process.rb rename to misc/command/xiki_process.rb index 720f8187..dfe671f5 100644 --- a/etc/command/xiki_process.rb +++ b/misc/command/xiki_process.rb @@ -25,7 +25,18 @@ def self.run path.strip! options = {} - options[:client] = "web" if path.slice!(/^-cweb /) + + # If line is "@options/...", parse it and read path... + + if path =~ /^@options\/(.+)/ + options = JSON[$1] + + # Turn keys into symbols + options = options.reduce({}){ |acc, o| acc[o[0].to_sym] = o[1]; acc } + + path = f.gets + path.strip! + end # Invoke menu and send response... diff --git a/etc/command/xiki_wrapper b/misc/command/xiki_wrapper similarity index 100% rename from etc/command/xiki_wrapper rename to misc/command/xiki_wrapper diff --git a/misc/core_patterns.rb b/misc/core_patterns.rb new file mode 100644 index 00000000..13f18f87 --- /dev/null +++ b/misc/core_patterns.rb @@ -0,0 +1,939 @@ +# Defines default patterns + +load "#{Xiki.dir}roots/mysql/mysql_index.rb" + +module Xiki + # Menu::Mysql.def_patterns + + # $..., %... or &... shell commands... + + Xiki.def(/\A([$%&])( |$| ?\/).*\z/) do |path, options| + + ancestors, dir = options[:ancestors], options[:dir] + + if dir =~ /^\/\w+@/ + next Remote.expand dir, options.merge(:command=>path) + end + + # "* task" ancestor above this prompt, so collapse up parent command and replace it... + + if ancestors && (last = Path.split ancestors[-1]) && (last.find{|o| o =~ /^\* /}) + + # This block is for when you have something like: + # $ foo + # * recent/ + # $ foo bar + + # Grab this line, then collapse up to before the tasks + command = Line.without_label + + Tree.to_parent + Tree.collapse + + # Delete up until and including ~... level + Launcher.delete_option_item_siblings + + line = Line.without_label + + # Line is $..., so replace it + if line =~ /^\$( | ?$)/ + Line.sub! /( *).*/, "\\1#{command}" + else + # Line not $..., so insert $... underneath + Line.sub! /( *).*/, "\\0\n\\1 #{command}" + Line.next + end + + next Launcher.launch + + end + + + # Normal parent root, so put command under it... + + prompt = path[/^[$%&]/] + + command = path[/^..(.+)/, 1] + + if command + command.sub! /^\//, '' + command = Path.split command + options[:command] = command.shift + + options[:args] = command if command.any? + end + + # '% foo' with no children, so Change to '$ foo' And relaunch + if prompt == "%" && options[:args] == nil + Line.sub! "%", "$" + next Launcher.launch + end + + Shell.shell_command_per_prompt prompt, options #> |||||||||||||||||||| + end + + # http://foo.com... + + Xiki.def(%r"\A(https?://[^/]|file://)") do |path, options| + # "url/| Quote", so pull off last item as text to post + + list = Path.split path + if list[-1] =~ /\n/ + post_txt = list.pop + path = Path.join list + end + + task = options[:task] + + if task == [] + next " + * browser + * source + * no useragent + * pretty json + * text from page + " + end + + Launcher.append_log path + + # source/ item on separate line, so insert the source... + + prefix = Keys.prefix + Keys.clear_prefix + + url = path[/(http|file).?:\/\/.+/] + + + if task == ["text from page"] + txt = Shell.command "lynx --dump \"#{url}\"" + txt.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + next txt.snippet + end + + + # Make this look for "* source/" in path? + if post_txt || prefix == "all" || task == ["source"] || task == ["no useragent"] || task == ["pretty json"] + + options = {} + if ! task || task != ["no useragent"] + options[:headers] = {"User-Agent"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36"} + end + + if post_txt + options[:body] = post_txt + txt = RestTree.post url, options + else + txt = RestTree.get url, options + end + + if task == ["pretty json"] + txt = JSON[txt] + txt = JSON.pretty_generate txt + end + + txt = Tree.quote(txt) if txt =~ /\A<\w/ + next Tree.quote txt + end + + url.gsub! '%', '%25' + url.gsub! '"', '%22' + + Browser.url url, :os_open=>1 + + options[:halt] = 1 # Don't do any other expanders + + "<*" + end + + # Special launching for "ol" view > output log... + + Xiki.def(//, :pre=>1, :target_view=>"ol") do |path, options| + # bug > this doesn't get control when > root is '/foo' > passes control to FileTree + options[:halt] = 1 + OlHelper.launch + Effects.blink :what=>:line + nil + end + + # Comments in .rb files (often Foo.bar) + Xiki.def(//, :pre=>1, :target_extension=>".rb") do |path, options| + + line = Line.value + indent_orig = Line.indent line + + next unless line =~ /^ *#/ + + options[:halt] = 1 + + # Eval comment inline + line.sub! /^ *# */, '' + + txt = Xiki[line, options.select{|key, value| [:ctrlx, :task].include?(key)}] #> ||||||||||||| + + next nil if txt.blank? + + txt.gsub!(/^/, "#{indent_orig}# ") + txt.sub!(/\n+\Z/, "") + + Line.<< "\n#{txt}", :dont_move=>1 + + nil + end + + # !... lines (at left margin)... + + Xiki.def(/\A!( |$|\/|\.)/) do |path, options| + + path_split = Path.split(path) + + # No children, so just use siblings + if path_split.length == 1 + + code = Tree.siblings.select{|o| o =~ /^![. ]/}.join("\n") + else + + # Jump to !... ancestor + cursor = View.cursor + while Line =~ /^ *[^ !\n]/ + Tree.to_parent + end + + raise "Not under !... line" if Line !~ /^ *!/ + + code = Tree.siblings.grep(/^!/).join("\n") + + View.cursor = cursor + end + + if options[:client] =~ /^editor/ + # Calculate where we are in relation to the parent, for stack trace + line_number = View.line + lines_above = Tree.siblings(:before=>1).select{|o| o =~ /^!/} + line_number -= lines_above.length + else + line_number = 0 + end + + Code.eval_snippet code, View.file, line_number, {:pretty_exception=>1, :simple=>1}, {:args=>path_split[1..-1]} + + end + + # a b c, so treat as music notes... + + Xiki.def(/\A[a-z][ #]([a-z ][ #])+[a-z ]#?\z/i) do |path, options| + + options[:halt] = 1 + + Kernel.require "#{Xiki.dir}roots/piano" + + Piano.song path + + "" + end + + # Various lines that mean run as ruby + # p ... + # puts ... + # etc. + Xiki.def(/\A(p|pp|print|puts) /) do |line| + # Don't quote temporarily, for presentation + CodeTree.run line#, :quote=>1 + nil + end + + Xiki.def(/^ap /) do |line| + returned, txt, exception = Code.eval line, :pretty_exception=>1 + + txt.sub!(/\A{\n/, '') + txt.sub!(/\n}\n\z/, '') + Tree.pipe txt + end + + Xiki.def(/^print\(/) do |line| + Javascript.launch + end + + Xiki.def(/^pg /) do |line| + line.sub! /^pg /, '' + CodeTree.run "puts JSON.pretty_generate(#{line})" + end + + # GET http://foo.com + # and + # GET /regex/ http://foo.com + Xiki.def(/^(- )?GET /) do |path, options| + url = path.sub(/^GET /, '') + regex = url.slice! %r"^/.+?/ " + + url = "http://#{url}" unless url =~ %r"http://" + url.gsub! ' ', '+' + + html = RestTree.request "GET", url + + if regex + regex = regex[%r"/(.*)/ $", 1] # " + html = html.split("\n").grep(/#{regex}/i).join("\n") + end + + html = Tree.quote(html) if html !~ /^[>|] / && html !~ /\A.+\/$/ + html + end + + Xiki.def(%r"^source://") do |path, options| + path.sub! /^source/, 'http' + path.gsub! ' ', '+' + Tree.quote `curl -A "Mozilla/5.0" #{path}` + end + + # xiki://... url... + + Xiki.def(%r"^xiki://") do |path, options| + + path = URI.encode(path.gsub(" ", '-')) #> "http://xiki.loc/nova/> Bang/Here are some elements;lfor you.;l;l;l" + + + RestTree.xiki_url path, options + end + + # http//foo.com, so show result... + + # (only one slash means show result) + # Xiki.def(%r"^http:/.+") do |url, options| + Xiki.def(%r"\Ahttp:(/|///)[^/]") do |url, options| + url.sub! %r"/+", "//" + url.gsub! ' ', '+' + + html = RestTree.request "GET", url + + html = Tree.quote(html) if html !~ /^[>|] / && html !~ /\A.+\/$/ + html + end + + # # comment... + + Xiki.def(%r"\A# (\w+)") do |path, options| + + split = Path.split(path) + + X"%xiki/###{path}/", :client=>"editor" + + end + + # #css_id... + + Xiki.def(%r"\A#($|\w+)") do |path, options| + next Xiki["ids/"].gsub(/^\+/, "<<") if path == "#" + Xiki["ids/#{path}", options] + end + + # Foo.bar, so invoke method on class... + Xiki.def(%r'\AX[" ]|\A[A-Z][:A-Za-z]+\.[a-z]') do |path, options| + + # Eval comment inline + path.sub! /^ *# */, '' + txt, out, exception = Code.eval path, View.file, Line.number, :pretty_exception=>1 + txt = CodeTree.returned_to_s txt + + next if ! txt && out == "" && ! exception # Do nothing if no return value or output + + txt ||= out + txt = exception ? exception.strip : txt.to_s + txt + end + + # Foo. ... + Xiki.def(%r"\A[A-Z][A-Za-z:]+\.(\/|\z)") do |path, options| + + path = path.split '/' + + path[0].sub!(/\.$/, '') + + txt = Meths.list *path + txt = txt.map{|o| "- #{o}\n"}.join('') if txt.is_a?(Array) + txt + end + + # Matches lines from el4r log like: + # 23:25:55:095 Error: + # from /Users/steve/.rvm/gems/ruby-1.9.3-p327@xiki/gems/trogdoro-el4r-1.0.7/bin/el4r-instance:847:in `instance_eval' + + Xiki.def(%r"^\d\d:\d\d:\d\d:\d\d\d .+/from (/.+):(\d+):") do |path, options| + match = options[:expanders][0][:match] + file, line = match[1..2] + + View.open file + View.line = line + nil + end + + # foo+bar ? ... + + Xiki.def(/^[a-z]+\+[a-z.+]*.?(\/|$)/) do |path, options| + Keys.expand_plus_syntax Path.split(path), options + end + + # foo.com > urls... + + Xiki.def(/\A[a-z][a-z0-9.-]+\.(org|com|edu|net|co|cc|in|de|loc|us)(:\d+)?(\/|\z)/) do |path, options| + + + split = Path.split path + + options.delete :no_slash + url = "xiki://#{path}" + if items = options[:items] + url = FileTree.add_slash_maybe url + url += items.join("/") + end + + Xiki.expand url#, :post=>post + end + + # localhost... + + Xiki.def(/\Alocalhost(:\d+)?(\/|\z)/) do |path| + Xiki.expand "xiki://#{path}" + end + + # foo@bar.com remote path... + + Xiki.def(/\A\/\w+@.+\z/) do |path, options| + Remote.expand path, options + end + + + Xiki.def(/\A\+\w[\w ]+(\z|\/)/) do |path, options| + Command.expand_define path, options + end + + + # :search... + + Xiki.def(/\A\:[a-z][\w -]*(\/|\z)/i) do |path, options| + path = path.sub(/\A:/, '') + + # Add args to the path + + path = Path.split(path) + path += options[:items] if options[:items] + + Xiki["search", path, options] + end + + + + # .xiki... + + # Xiki.def(/\A\:([\w -]+)/) do |path, options| + Xiki.def(/\A_([\w -]+)/) do |path, options| + + # New > call .drill directly > works + items = Path.split path + + # name = items.slice!(0).sub(/^:/, '') + name = items.slice!(0).sub(/^_/, '') + name.gsub! " ", "_" + + path = File.expand_path("~/xiki/#{name}.xiki") + + # Use .link if not found and it exists + if ! File.exists?(path) && File.exists?(found = path.sub(/\.xiki$/, ".link")) + path = found + end + + txt = Notes.drill path, *items, options + + end + + + # @ (by itself)... + + Xiki.def(/\A@\z/) do |path, options| + " + | Use only first names? + | - how to integrate with =contacts? + | - Make it look in it optionally? + | - use it only? + | - and have full names be in parens? + | =contacts/ + | > Steve Jones (steve) + | > Ed Smith + " + + # /name/, so make parent be @... + + Xiki["mail"].gsub(/^- /, "<< @").gsub(/^=.+\n/, "") + + end + + + # NNN-NNN-NNNN, so treat as phone number... + + Xiki.def(/\A\d{3}-\d{3}-\d{4}/) do |path, options| + + Launcher.append_log path + + Xiki.expand "twilio/#{path}", options.select{|k, v| [:task].include?(k) } + end + + + # :@foo, so remove colon... + + Xiki.def(/\A:@\w.*/) do |path, options| + Line.sub! /^:/, '' + Launcher.launch + end + + + # @foo, so treat xikihub user... + + Xiki.def(/\A@\w.*/) do |path, options| + + options[:halt] = 1 + + XikihubClient.user path, options + + end + + + # email@address.com Message... + + Xiki.def(%r"^[a-z][a-z.]+@[a-z][a-z.]+[a-z] ") do |path, options| + + email, subject = path.split(" ", 2) + section = Notes.current_section_object.text + + # New session, so move to top of 'misc' topic first + + XikihubClient.message email, subject, section, options + + options[:halt] = 1 + "<* - Message sent" + end + + + # > foo $bar... + + Xiki.def(/^> .+ \$\w+:?$/) do |path| + path = path[/\$\w+/] + View.open path + end + + # # alert(... > run in the browser... + + # 7af8ec8 > git commit hashes... + + Xiki.def(/\A[0-9a-f]{7}(\/|\z)/) do |path| + Xiki.expand "%xiki/=git/log/#{path}" + end + Xiki.def(/\A[0-9a-f]{40}(\/|\z)/) do |path| + Xiki.expand "%xiki/=git/log/#{path}" + end + + # ... : ..., so maybe memorize table... + + Xiki.def(/\A[^\n; ]+ : [^\n; ]+\z/) do |path, options| + + # Do nothing if already handled by a different pattern + next if options[:expanders].length > 1 + + # right-clicked root, so show "memorize" option... + + next "* memorize\n* save to Memorize.com" if options[:task] == [] + Kernel.require "#{Xiki.dir}roots/memorize" + next Memorize.tasks_memorize if options[:task] == ["memorize"] + next Memorize.tasks_save_to_memorize if options[:task] == ["save to Memorize.com"] + + nil + end + + # :-... or :+..., so save as before and after... + + Xiki.def(%r"^:[?+-]") do |path, options| + + txt = Tree.siblings :quotes=>":", :string=>1 + before = txt.scan(/^[?-].+/).join("\n").gsub(/^./, '') + after = txt.scan(/^\+.+/).join("\n").gsub(/^./, '') + + task = options[:task] + + # ~, so return options... + + next "* file search\n* broad search\n* filename search" if task == [] + if task && task[0] =~ /search/ + + # Pull search string string from current line + + txt = Line.value.sub(/^:[+-?]/, '') + + # Use red as search, or green if no red + + file = Keys.bookmark_as_path :prompt=>"Enter a bookmark to search in: " + file = Files.tilde_for_home file + View.to_after_bar + + # ~ flexible search, so turn before into a regex + filter = task == ["broad search"] || task == ["filename search"] ? + TextUtil.regex_case(txt) : + Regexp.quote(txt) + + if task == ["filename search"] + FileTree.grep_with_hashes file, filter, '**' # Open buffer and search + next "" + end + + FileTree.grep_with_hashes file, filter + + next "" + end + + + # :+foo, so just save... + + Clipboard.register("1", before) + Clipboard.register("2", after) + + "<* Saved these as 'before' and 'after'" + end + + # ^foo, so must not have been handled as a file, so say bookmark doesn't exist... + + Xiki.def(%r"^%[a-z]+(/|$)") do |path, options| + # Note that this only runs if FileHandler doesn't handle it already + "<* Bookmark doesn't exist: #{path}" + end + + # .. or ..., etc, so replace with absolute dir, n-1 higher... + + Xiki.def(/^\.\.+$/) do |path, options| + dir = View.dir + + # Chop off one dir for each dot (minus 1) + (path.length-1).times do + dir.sub! /\/[^\/]+$/, '' + end + + "<< #{dir}" + end + + + # Todo > Probably bring this back + # + # # %foo/, so filter output for pattern... + # + # Xiki.def(/^%[^ \n]/) do |path, options| + # path = path[/\A%(.+)\/?/, 1] + # txt = Xiki.expand "filter/#{path}", options.select{|k, v| [:task, :ancestors].include?(k) } + # txt + # end + + + # ##foo/, so prompt for bookmark and do search inline... + + Xiki.def(/\A(##|\*\*).+\z/) do |path, options| + + # Idea > if ends in "/", look in filenames instead of contents + + # ~, so show task to do search in inline... + + next "* inline" if options[:task] == [] + + symbols = path[/##|\*\*/] + + # path.sub! /^##/, "" + path.sub! symbols, "" + path.sub! /\/$/, "" + + # Prompt for bookmark... + + file = Keys.bookmark_as_path :prompt=>"Enter a bookmark to search in: ", :include_file=>1 + file = Files.tilde_for_home file + + # "* inline" task, so open in inline... + + if options[:task] == ["inline"] + Line.sub! /.+/, "#{file}\n + #{symbols}#{path}" + Move.next + Launcher.launch + next + end + + # No task, so just do in new view... + + View.to_after_bar + FileTree.grep_with_hashes(file, path, symbols) + + "" + end + + # > Heading, so render dropdown for moving and deleting section... + + Xiki.def(/\A>( |\z)/i) do |path, options| + + args, task = options[:args], options[:task] + + # No items /*, so show root tasks... + + if ! args && task # || task == [] + + task[0] = "* #{task[0]}" if task[0] + + result = Xik.new(" + * delete + ! Notes.cut_block :no_clipboard + * cut + ! Notes.cut_block + * copy + ! Notes.copy_block + + * to top + ! Notes.move_block_to_top + * previous + ! Notes.move_block :up=>1 + * next + ! # failed > # View.remove_last_undo_boundary + ! Notes.move_block + ").expand(task, :eval=>1) + + end + + end + + # "topic >> Heading", so navigate to it... + + Xiki.def(/\A[a-z][a-z0-9 ]+ ?>>/i) do |path, options| + path = Path.split path + + topic = path[0][/(.+?) ?>> ?/, 1] + + path[0].sub!(/^.+? ?>> ?/, "\\1> ") + + options[:task] = ["navigate"] + + options.delete :expanders + options.delete :path + + Xiki["#{topic}/", path, options] + + options[:halt] = 1 # Don't let "foo > bar" pattern go next + + "" + end + + + # ":search > Heading", so delegate to notes/ command... + + Xiki.def(/\A:[a-z][a-z0-9 ]+ ?>/i) do |path, options| + + path = Path.split path + + # Don't do > because it won't escape crazy chars in heading + + topic = path[0][/:(.+?) ?> ?/, 1] + path[0].sub!(/^.+? ?> ?/, "\\1> ") + + # Delegate to topic + # Delegate to path + result = Xiki[":#{topic}/", path, options] + + result || "<* - not found, ^O to create" + end + + # "topic > Heading", so delegate to notes/ command... + + Xiki.def(/\A[a-z][a-z0-9 ]+ ?>/i) do |path, options| + path = Path.split path + + topic = path[0][/(.+?) ?> ?/, 1] + path[0].sub!(/^.+? ?> ?/, "\\1> ") + + # Delegate to topic + result = Xiki["#{topic}/", path, options] + result || "<* - not found, ^O to create" + end + + + Xiki.def(/\A[.~]\z/i) do |path, options| + Line.add_slash + Launcher.launch + end + + + + # "1. Foo", treat as "-> Foo:", plus "* update numbers"... + Xiki.def(/\A\d+\.(\z| .*\z)/) do |path, options| + + task = options[:task] + + words = path[/. (.+)/, 1] #> !!! + # " ->" on end is optional, so ignore it + words.sub!(/^> /, '') + + # Ctrl+T, so get tasks from "->" and add on + if task + # Use tasks from "-> foo", adding our own + if task == [] + next Xiki["-> #{words}", options] + "\n* update numbers" + next "* update numbers" + end + # Handle if > * update numbers + if task == ["update numbers"] + Notes.fix_numbers + next "" + end + end + + # Delegate to > "-> Foo:" + Xiki["-> #{words}:", options] + + end + + # "#+", show all #+... comments in > xiki... + Xiki.def(/\A#\+\z/i) do |path, options| + Launcher.open("#{Bookmarks["%s"]}\n - ##^ *#\\+/") + end + + # "#++", show all #+... comments in > xiki hub... + Xiki.def(/\A#\+\+\z/i) do |path, options| + Launcher.open("#{Bookmarks["%h"]}\n - ##^ *#\\+/") + end + + + + # "->", so list headings in same view... + + Xiki.def(/\A->(\/|$)/i) do |path, options| + + # -> only, so list the headings + + if path == "->" + + file = View.file + return if ! file + + txt = File.read file + txt.encode!('UTF-8', 'binary', :invalid=>:replace, :undef=>:replace, :replace=>'') + headings = txt.scan(/^> [^:\n].*/) + next headings.join("\n") + end + + Tree.to_parent + Tree.collapse + + path = Path.split path + Line.sub! /.*/, "-#{path[1]}" + + "" + + end + + # "-> Heading", so jump to heading in same view... + + Xiki.def(/\A-> /i) do |path, options| + path.sub! /^-> /, '' + + # Show and handle tasks + task = options[:task] + next "* create" if task == [] + if task == ["create"] + + View.to_top + # Notes.to_block :prefix=>:u + + View << "> #{path}\n" + View >> "\n\n\n\n" + + next "" + end + + + # Search below for this heading + + orig = View.cursor #> nil + result = Search.forward "^> \\(@[a-z]* \\)?#{Search.quote_elisp_regex path}$", :from_top=>1, :beginning=>1 + + if ! result + View.cursor = orig + View.flash "- Heading not found in this view > Ctrl+T to create!" + next + end + + View.recenter_top + + Move.down + + nil + end + + + # "<- topic" or "<- topic > Heading", so jump to heading in same view... + + Xiki.def(/\A<- /i) do |path, options| + + # path = "<- protonight > Foo > Bar" + # path = "<- protonight > Just the emails" + # path = "<- protonight" + path.sub! /^<- /, '' + topic, heading = path.split(" > ", 2) + + # Jump to topic > if it exists + Xiki[path, :task=>["view source"]] + + end + + # ".foo", so call the heading in this file... + + Xiki.def(/\A\.[a-z]/i) do |path, options| + + path = path.sub /^\./, '' # Remove dot at beginning + + pathified = View.file.sub(/\.xiki$/, '//') #> "/Users/craig/xiki/notes.xiki" + + options.keys.each do |key| + options.delete key if ! [:items, :task, :path, :ancestors].member? key + end + + # May cause problems > passing items on! + Xiki["#{pathified}#{path}", options[:items], options] #> ||||||||| + end + + # "foo.../", so treat as autocomplete + + Xiki.def(/\A[a-z][a-z ]*\.\.\.\//i) do |path, options| + + # Only replace subsequent ' foo' siblings, in case autocompleting under something else!!" #> # Only replace subsequent ' foo' siblings, in case autocompleting under something else + + bounds = Tree.sibling_bounds(:must_match=>"[a-z]") + View.delete bounds[0], bounds[-1] + path = Path.split path + + Move.up + Line.gsub! /.+/, path[-1] + + Launcher.launch + + nil + end + + # Just "foo...", so do same as Tab on "foo"... + + Xiki.def(/\A[a-z][a-z ]*\.\.\.\z/i) do |path, options| + # "todo > treat like tab" + Topic.tab_key :flashing_ok=>1 + nil + end + + + # "#: foo", so search for "foo" below... + + Xiki.def(/\A#:(.+)/) do |path, options| + match = options[:expanders][0][:match][1] + + Line.to_right + Search.forward match + # "todo > treat like tab" + nil + end + +end diff --git a/misc/default_home_xiki/bar.xiki b/misc/default_home_xiki/bar.xiki new file mode 100644 index 00000000..8bba7f64 --- /dev/null +++ b/misc/default_home_xiki/bar.xiki @@ -0,0 +1,84 @@ +> Summary +These notes describe what the key shortcuts in the bottom bar of xsh +do. The first five shortcuts work in bash and xsh. The last three work +only in xsh. + + +> Open +Ctrl+O in xsh: +- On a "$ foo" line: Runs the shell command. +- On a "foo" line: Opens the topic "foo" (lists the notes in it). +- Various other lines: Opens them (does something appropriate with them). + - such as directory and file paths, url's, notes, snippets +- Double-clicking does the same as Ctrl+O (if mouse support is enabled) + +Ctrl+O in bash: +- On an empty prompt: Just opens xsh. +- After typing a shell command: Opens xsh and runs the shell command. + + +> Go +Ctrl+G in xsh: +- On a "$ foo" line: Goes to bash, and runs the shell command. +- On a "foo" line: Goes to the topic file ("~/xiki/foo.xiki"). +- On a file: Goes to the file in your default editor. +- On a directory: Goes to bash, and cds to it. + +Ctrl+G in bash: +- On an empty prompt: Goes to the last place you were in xsh. +- After typing a topic: Goes to the topic file. + + +> Tasks +Ctrl+T in xsh: +- On a "$ foo" line: Shows the tasks for the shell command. +- On a "foo" line: Shows the tasks for the topic. +- Right-clicking does the same as Ctrl+T (if mouse support is enabled) + +Ctrl+T in bash: +- On an empty prompt: Shows the tasks for the current directory. +- After typing a shell command: Shows the tasks for it. + + +> Xiki +Ctrl+X in xsh: +- On an empty line: Lists your topics (ie. your "xiki topics"). +- On a "$ foo" line: Lists your notes in topic "foo". +- On a "foo" line: Lists your notes in topic "foo". +- On a "foo bar" line: Lists your notes in topic "foo" with "bar" in their heading. + +Ctrl+X in bash: +- On an empty prompt: Lists your topics. +- After typing "foo": Lists your notes in topic "foo". +- After typing "foo bar": Lists your notes in topic "foo" with "bar" in their heading. + + +> Search +Ctrl+S in xsh: +- On a "$ foo" line: Searches shared notes for the shell command. +- On a "foo" line: Searches shared notes for topic "foo". +- On a "foo bar" line: Searches shared notes in topic "foo" with "bar" in their heading. + +Ctrl+S in bash: +- After typing "foo": Searches shared notes (from all users). +- After typing "foo bar": Searches shared notes in topic "foo" with "bar" in their heading. + + +> Content +Ctrl+C in xsh: +Shows the "content" keyboard shortcuts. They include saving, closing +the view, making a new view, and navigating to various places, etc. + + +> Keys +Ctrl+K in xsh: +Shows the rest of the keyboard shortcuts. They include splitting and +navigating views, and listing all kinds of things, etc. + + +> Quit +Ctrl+Q in xsh: +Quits xsh and goes back to bash. Then from bash, you can type Ctrl+G +on an empty prompt to go back to where you were when you quit. + + diff --git a/misc/default_home_xiki/disk.xiki b/misc/default_home_xiki/disk.xiki new file mode 100644 index 00000000..59a0f869 --- /dev/null +++ b/misc/default_home_xiki/disk.xiki @@ -0,0 +1,49 @@ +> Space available +$ df -H + +Shows available disk space on all disks and partitions. + + +- sample output: + | Filesystem Size Used Avail Capacity iused ifree %iused Mounted on + | /dev/disk1 250G 178G 72G 72% 43435838 17520832 71% / + | devfs 192k 192k 0B 100% 653 0 100% /dev + | map -hosts 0B 0B 0B 100% 0 0 100% /net + | map auto_home 0B 0B 0B 100% 0 0 100% /home + + +> Find large files on the disk +& find / -type f -size +1000M + +Finds all files larger than a gigabyte. Takes a while to complete. + + +> Mount remote disk +$ sshfs user@hostname:/path/to/folder /local/folder + +Only works on Linux. + + +source: +https://ubuntu-tutorials.com/2007/01/02/mount-remote-directories-securely-with-ssh-ubuntu-6061-610/ + + +> .space +!.# +! # Shows percent full, and available space out of total. +! # +! # sample output: +! # | 71% full +! # | 74G of 250G available +! # +! df_raw = `df -H` +! # Extract from 1st line like this: +! # /dev/disk0s2 250G 109G 141G 44% 26641071 34428369 44% / +! df = df_raw[/\d+G +.+%/, 0].split(/ +/) +! +! df = " +! | #{df[3]} full +! | #{df[2]} of #{df[0]} available +! ".unindent.strip + + diff --git a/misc/default_home_xiki/examples.xiki b/misc/default_home_xiki/examples.xiki new file mode 100644 index 00000000..6fbda725 --- /dev/null +++ b/misc/default_home_xiki/examples.xiki @@ -0,0 +1,114 @@ +> Overview +This topic has examples of notes that can go into Xiki files. See the +tutorial if you're looking for more of a guided tour. + +<- tutorial + +You can put any kind of note in xiki files. A quick random thought, +your shopping list, commands, url's, lists of things you want to do, +documentation, actions that perform simple tasks, actions that +delegate to other actions, actions that are lightweight or even +sophisticated interfaces to other systems, etc. + +Xiki files are just simple text files that go into the "xiki" +directory in your home directory. + +~/xiki/ + +You can edit them with any text editor, or directly with xsh. + +Notes with headings that start with "> ." are called actions. Actions +are notes that do something when you expand them. They appear like "+ +foo" when expanded to indicate that they are runnable. + +Many of these examples are arguably obvious and redundant, but I'm +leaving them in since they may spark ideas about different uses for +sticky notes. + + +> Simple note +Here is a sentence. + + +> Shell command +$ echo hi + + +> Shell command and description. +$ echo hello + +This paragraph can describe what the shell command does. You can run +the shell command, of course, by typing Ctrl+O when your cursor is on +the command. Or type Ctrl+G instead, to go to bash and run the command +there. + + +> Multiple shell commands +$ echo one + +$ echo two + + +> Multiple shell commands with steps +1. First run this +$ echo one + +2. Then run this +$ echo two + + +> Bullet points +- Bullet point +- They just start with a dash + - Put 2 spaces at the beginning to indent + - Or 4 spaces, etc. + + +> File paths +/etc/ + + hosts + +- Ctrl+O to navigate to the file. +- Ctrl+G to open it using your default editor. +- Ctrl+T to delete, rename, filter the contents, etc. + + +> File Snippets +1. Navigate +/etc/ + - hosts + : # localhost is used to configure the loopback interface + : # when the system is booting. Do not change this entry. + + +> Urls +http://xiki.com/examples +http://xiki.com/tutorial + + +> .Action that opens a url +http://xiki.com + + +> .Action that runs code +!.1 + 2 + + +> .Action that runs shell command +$ echo hi + + +> .Action with shell command that runs in bash +& echo hi + + +> .Action that calls other action +examples/action with code + + +> .Action that sends stuff to other action +html/ + |

Rendered in the browser

+ |

From the action

+ + diff --git a/misc/default_home_xiki/git.xiki b/misc/default_home_xiki/git.xiki new file mode 100644 index 00000000..6b8151e4 --- /dev/null +++ b/misc/default_home_xiki/git.xiki @@ -0,0 +1,116 @@ +> .Create example repository +& git clone https://github.com/trogdoro/git_example.git; cd git_example; git branch reasonable origin/reasonable; git branch crazy origin/crazy + +Check out a simple example git project, which has a few commits and a +few branches. Then check out all the branches. + + +> .Branch shell action +$ git branch + |:anything + +$ git + |: branch List, create, or delete branches + +!.# +! # Lets you expand "$ git branch", to list branches, switch to, and delete them. +! # +! # $ git branch +! # : * crazy +! # : master +! # : reasonable +! # +! +! if shell_command == "git" +! args.shift +! end +! +! # No args, so make branches start with colons... +! if args == [] +! shell_output ||= Shell.command("git branch", :dir=>dir).snippet +! return shell_output.gsub(/^\| /, ": ") +! end +! +! # Branch passed, so show options... +! if args.length == 1 +! return " +! + switch to +! + delete +! " +! end +! +! branch = args[0][/\w+/] +! +! if args[1] == "switch to" +! output = Shell.command("git checkout #{branch}", :dir=>dir) +! View.message output +! shell_output = Shell.command("git branch", :dir=>dir).snippet +! shell_output.gsub!(/^\| /, " : ") +! return "=replace/siblings/2/\n#{shell_output}" +! elsif args[1] == "delete" +! return ": " + Shell.command("git branch -d #{branch}", :dir=>dir) +! end +! +! branch.inspect +! dir.inspect + + +> .Log shell action +$ git log + |:anything + +$ git + |: log Show commit logs + +!.# +! # Lets you expand "log ..." lines. +! # +! # $ git +! # : log Show commit logs +! # : 90ade26 Some commit +! # : 0c7a353 Some other commit +! # +! +! if ! shell_command +! return "| This action makes the '$ git log' shell command interactive." +! end +! +! if shell_command == "git" +! args.shift +! end +! +! #return args +! +! # Just ": log...", so list commits +! +! if args.length == 0 +! # They didn't pass any args, so default to one commit per line +! shell_command = "git log --oneline" if ["git", "git log"].member?(shell_command) +! return Shell.command(shell_command, :dir=>dir).quoted.no_trailing +! end +! +! # Commit, so show files in it +! +! hash = args[-1][/\w+/] +! txt = Shell.command("git show #{hash}", :dir=>dir).snippet +! txt.gsub(/^\| ([+-])/, "|\\1").gsub(/^\| diff /, "| > diff ").gsub(/^\| /, "|") + + +> .Diff shell action +$ git diff + |:anything + +!. +! # Colorizes diff lines as red and green. +! +! shell_output.gsub(/^\| ([+-])/, "|\\1").gsub(/^\| /, "|") +! + + +> .Status shell action +$ git + |: status Show the working tree status + +!.Shell.command('git status').snippet + + diff --git a/misc/default_home_xiki/links.xiki b/misc/default_home_xiki/links.xiki new file mode 100644 index 00000000..84e43d0a --- /dev/null +++ b/misc/default_home_xiki/links.xiki @@ -0,0 +1,118 @@ +> Overview +This file, links.xiki, is a place to put file paths. The purpose is to +make it easy to navigate between many files, and even specific lines +within files. + +It's just a simple plaintext file, so you can put anything in it you +want. But there are key shortcuts that make it easy to add file paths +to it. And more key shortcuts for navigating to files based on content +in it. + +Here are some examples (remember key shortcuts exist to add links to +files like this, so you don't have to type them out yourself)... + + +> Example +~/xiki/ + - notes.xiki + + +> Example going to a specific line +~/xiki/ + + notes.xiki + | > Formatting + + +> Key Shortcuts +You can always come back to it by typing ^C ^n (for +"content nav"). Or, typing ^W ^I (for "window IDE") +will show it at the bottom-left. + +- improve from here down) +__also mention +You can type window+link__ to +list+links +list+files +list+1, list+2 + + +> Sample with a quoted line +/etc/bar/ + - hey.txt + : Hi + +> Sample with a note +/etc/bar/ + - hey.txt + - fix this! + : Hi + + + +- adapt from notes.xiki from here) +> Formatting +The file is plaintext, but notice certain strings will get colorized, like bullets, url's, file paths, and shell prompts. + +- Dashes are bullets +http://xiki.org +/tmp/foo.txt +$ echo hello + +Also the headings, like "> Overview" above, are are just lines that start with "> ". + + +> Try it for yourself +Try making a new note at the top of this file. + +- 1) Use the arrow keys to move to the beginning of the file +- 2) Type something like "> My Heading" +- 3) Type some stuff underneath it + +To save this file, type Ctrl+W Ctrl+S (for "Window Save"). + + +> Coming back to links.xiki +Since this links.xiki file is so commonly used in xsh, there are several ways to navigate to it. To open links.xiki from within xsh, type... + +- Ctrl+C Ctrl+T (for "Content Nav"). + +In bash, you can type this to jump right to links.xiki... + +$ xsh ^n + +Or, this is is an equivalent command that doesn't make use of the "t" shortcut... + +$ xsh ~/xiki/links.xiki + +This command will present an outline of all the headings in links.xiki. You can type to filter down to a heading and type Ctrl+X to view its contents. Press Ctrl+X a second time to navigate to it. + +$ xsh -t nav + +More advanced xsh users may want to use a more IDE-like view, with links.xiki at the top-left, and links.xiki at the bottom-left... + +- Ctrl+W Ctrl+I (for "Window IDE"). + + +> Running commands +To run a shell commands in xiki, just start any line with "$ "... + +$ pwd + +Then move your cursor onto the line and type Ctrl+X for "Execute". + +Try running the above command if you want. + +To run the command again, first collapse it by moving your cursor back onto the $... line and typing Ctrl+X. Then you can run again by typing Ctrl+X on it. + +If a command is going to take a long time to run, or needs to take over the whole terminal, type Ctrl+G (for "Grab") instead of Ctrl+X to run it. This will quit xsh and run the command in bash. + + +> What to do with links.xiki from here +It's common (and encouraged) to add a lot of content to links.xiki. So, feel free to leave all of these notes here, and add your own content to the top of the file. That way, they'll be here in case you want to come back and look at them later. + +After you build up a lot of content separated by "> Heading lines", there's a convenient way to jump back and forth between headings... + +Ctrl+L Ctrl+O (for "List Outline") + +After typing the above, you can type to filter down the list, and type Ctrl+X to jump to a heading. Try it now, if you want. + diff --git a/misc/default_home_xiki/memorize.xiki b/misc/default_home_xiki/memorize.xiki new file mode 100644 index 00000000..727e9155 --- /dev/null +++ b/misc/default_home_xiki/memorize.xiki @@ -0,0 +1,3 @@ +This file, memorize.xiki, is a place to put facts +you want to memorize. + diff --git a/misc/default_home_xiki/notes.xiki b/misc/default_home_xiki/notes.xiki new file mode 100644 index 00000000..2ff02375 --- /dev/null +++ b/misc/default_home_xiki/notes.xiki @@ -0,0 +1,84 @@ +> Overview +This file, notes.xiki, is a place to put miscellaneous stuff. + +You can put pretty much anything here, for example miscellaneous +notes, todo items, and commands. You can edit anything. Try adding a +couple lines after this line (or where ever else you want). + + +> Formatting +It's just a simple plaintext file, but notice certain strings will get colorized, like bullets, url's, file paths, and shell prompts... + +- Dashes are bullets + - Sub-bullets are just indented 2 spaces +http://xiki.org +/tmp/foo.txt +$ echo hello + +Also headings, like "> Formatting" above, are are just lines that start with "> ". + + +> Tutorial +You can run commands in __ + +<- tutorial + +- make this! +<- tutorial/editing notes + + +> Try it for yourself +Try making a new note at the top of this file. + +- 1) Use the arrow keys to move to the beginning of the file +- 2) Type something like "> My Heading" +- 3) Type some stuff underneath it + +To save this file, type Ctrl+W Ctrl+S (for "Window Save"). + + +> Coming back to notes.xiki +Since this notes.xiki file is so commonly used in xsh, there are several ways to navigate to it. To open notes.xiki from within xsh, type... + +- Ctrl+C Ctrl+T (for "Content Tasks"). + +In bash, you can type this to jump right to notes.xiki... + +$ xsh ^n + +Or, this is is an equivalent command that doesn't make use of the "t" shortcut... + +$ xsh ~/xiki/notes.xiki + +This command will present an outline of all the headings in notes.xiki. You can type to filter down to a heading and type Ctrl+X to view its contents. Press Ctrl+X a second time to navigate to it. + +$ xsh -t tasks + +More advanced xsh users may want to use a more IDE-like view, with notes.xiki at the top-left, and links.xiki at the bottom-left... + +- Ctrl+W Ctrl+I (for "Window IDE"). + + +> Running commands +To run a shell commands in xiki, just start any line with "$ "... + +$ pwd + +Then move your cursor onto the line and type Ctrl+X for "Execute". + +Try running the above command if you want. + +To run the command again, first collapse it by moving your cursor back onto the $... line and typing Ctrl+X. Then you can run again by typing Ctrl+X on it. + +If a command is going to take a long time to run, or needs to take over the whole terminal, type Ctrl+G (for "Grab") instead of Ctrl+X to run it. This will quit xsh and run the command in bash. + + +> What to do with notes.xiki from here +It's common (and encouraged) to add a lot of content to notes.xiki. So, feel free to leave all of these notes here, and add your own content to the top of the file. That way, they'll be here in case you want to come back and look at them later. + +After you build up a lot of content separated by "> Heading lines", there's a convenient way to jump back and forth between headings... + +Ctrl+L Ctrl+O (for "List Outline") + +After typing the above, you can type to filter down the list, and type Ctrl+X to jump to a heading. Try it now, if you want. + diff --git a/misc/default_home_xiki/search_for_notes.xiki b/misc/default_home_xiki/search_for_notes.xiki new file mode 100644 index 00000000..46bb7b36 --- /dev/null +++ b/misc/default_home_xiki/search_for_notes.xiki @@ -0,0 +1,43 @@ +> Todo +- Todo > Add link to appropriate section in tutorial! + + +> Old + | + | > Search from your shell + | Xiki turns your shell into a search engine for shell command examples, + | as well as for other Xiki notes and actions. + | + | 1. Type Ctrl+S while in bash (or zsh) + | + | If you didn't enable key shortcuts when setting up xsh, you can type + | the following instead of ^S. + | + | $ xsh : + | + | + | > Search from within Xsh + | 1. Type a search term on a blank line, and then type Ctrl+S. + | + | + | > Search in your web browser + | You can use the web interface to search for shell examples, as well as + | for other Xiki notes and actions. + | + | 1. Go to xiki.com and do a search + | + | http://xiki.com + | + | + | > Specific user + | If you know someone's xikihub username, you can open it in xsh (Just + | type it on any blank line within an @ sign) or in your web browser to + | see what that user has created. The most recent items will be at the + | top. + | + | Examples: + | + | @trogdoro + | + | http://xiki.com/@trogdoro + | diff --git a/misc/default_home_xiki/tutorial.xiki b/misc/default_home_xiki/tutorial.xiki new file mode 100644 index 00000000..72c2e02b --- /dev/null +++ b/misc/default_home_xiki/tutorial.xiki @@ -0,0 +1,421 @@ +> Preparation +1. Install xsh (Xiki Shell) if you haven't yet. + +http://xiki.com/.install + +2. Have a shell terminal window open, to type into while doing the tutorial steps. +3. Follow the numbered steps below. + +Only the numbered steps say things you need to do. Everything else is +just explanatory. + +- The web version of this tutorial is available here: +http://xiki.com/@xiki/tutorial + +- Chat with us as you're doing the tutorial here: +https://gitter.im/trogdoro/xiki + +- What if I don't use bash?: +If you use another shell like zsh, just assume "zsh" where the +tutorial says "bash". + + +> Getting in and out of xsh +Xsh (short for Xiki Shell) has many ground-breaking features. It turns +your terminal into a search engine. It makes shell commands +interactive. It creates UIs for shell commands and other tools and +databases. But let's begin with how xsh improves the basic shell +terminal experience! + +1. Make sure you're in your shell terminal at an empty bash prompt. +2. Type "xsh" then type return. + +$ xsh + +You are now in xsh. + +3. Press Ctrl+Q to quit. + +You're now back in bash. Xsh is meant to complement bash rather than +replace it. So, you'll often quickly jump into xsh and then out of it. + + +> Keyboard shortcuts on the screen +Xsh shows the key shortcuts right on the screen! So you don't have to +memorize them like in traditional shells. + +1. Type "xsh" in bash again (and return), to go back into xsh. +2. Observe the keyboard shortcuts at the bottom of the screen. + +The underlined letters indicate Ctrl shortcuts. Ctrl+O for Open. +Ctrl+G for Go. Ctrl+Q for Quit, etc. Clicking on them shows +descriptions (if mouse support is enabled). You can type these key +shortcuts even when they don't appear on the screen. + +All of the keys except for the last 3 work in bash as well as in xsh. +We'll start with Ctrl+O, the most used key in Xiki (it's kind of like +double-clicking). Then we'll cover Ctrl+G and a couple others. + +3. Press Ctrl+Q to quit and go back to bash. +4. Now press Ctrl+O. It's a shortcut for opening xsh, when on an empty bash prompt. + + +- Troubleshooting: +If you didn't select "default setup" while setting up xsh, bash key +shortcuts might not be enabled. You can run "xsh --help" in bash to +see the command line equivalents of each key shortcut. + + +> Just type letters to filter command output +In xsh, it's easy to filter the output of shell commands. Just type a +couple letters after the command runs! In traditional shells you have +to pipe to grep, which is slow and tedious. + +1. Press Ctrl+Q to go back to bash. +2. Type "cd /" and return, to change to your root directory. +3. Type "ls" but don't type return (the "l" is a lowercase L). +4. Now, press Ctrl+O. It opens xsh and runs the command. + +When you see "A-Z Filter" in the bottom bar, it means you can type +letters to filter. + +5. Type "bi" to incrementally filter down to only lines that contain "bi". + +When "A-Z Filter" isn't showing, the letter keys are for typing stuff, +since everything is editable in xsh. + +- Tip: +You can press esc while filtering to start over. Then you can type +some letters to filter again. + + +> Shell commands are interactive +Xsh makes command output into an interactive interface! For the "ls" +command, this means you can navigate directories and files visibly. +Instead of having to type a bunch of "ls" and "cd" commands. + +1. Use the arrow keys to move the cursor back to the "$ ls" line. +2. Press Ctrl+O to collapse it. +3. Press Ctrl+O again to open it (run it). + +The bottom bar shows "A-Z Filter" again, so we can type letters to +filter. + +4. Type "etc" to filter down to "etc". +5. Press return to open the "etc" dir. +6. Type "ho" to filter again. +7. Move the cursor to "hosts". + +Later we'll see how xsh makes +all kinds of shell commands interactive, not just "ls". + +- Tip, Ctrl+O vs return: +Ctrl+O runs commands and opens things. Whereas return usually inserts +linebreaks. But when "A-Z Filter" is visible, return is similar to +Ctrl+O. Double-clicking also acts like Ctrl+O (if mouse support is +enabled). + + +> Use your favorite text editor +Xsh integrates with your favorite text editor. + +1. With the cursor on the "hosts" line, press Ctrl+O to open it. + +You're now viewing the /etc/hosts file with xsh. Now we'll use your +editor. + +2. Press esc to return to the last view. +3. Press Ctrl+G ("Go" in the bottom bar). This will go to "hosts" in your default editor. + +Xiki uses the editor specified in the "EDITOR" bash environment +variable. You can set it to your favorite editor if you haven't yet +(be sure to reload via "xsh --reload" in bash afterwards). If it's not +set, xsh defaults to "nano" on linux or "TextEdit" on mac. + +Now let's quit our text editor and go back to bash. + +4. If xsh is still open, press Ctrl+Q (xsh stays open in the terminal for graphical editors). +5. Type whatever is necessary to quit your text editor (Ctrl+X quits nano). + +Later we'll show how to use your favorite text editor to create and +edit interactive notes with shell commands, and run them in xsh. + +- Tip, if you get into trouble: +You can try esc to go to the previous view, or Ctrl+Z to undo. + + +> Persistent scratchpad for commands and notes +In bash, you can always go back to the last place you were in xsh. Just press +Ctrl+G at an empty bash prompt. In this way, xsh acts like a persistent +scratchpad, that you can jump in and out of. + +1. In bash, press Ctrl+G to go back to the last place you were. +2. Move the cursor to the top left corner. +3. Type "The hosts file", then press return, just as a little note for yourself. +4. Move the cursor to the bottom and type some other words. + +In xsh, everything is editable! You can type anywhere. In traditional +shells, you can only type at the single lowest prompt. When you leave +xsh, it automatically saves the current view (and remembers the +current directory). + +Ctrl+G works in both directions. When on a shell command in xsh, it +goes to bash and runs the command. + +5. Move the cursor to the "$ ls" line, and press Ctrl+G. +6. Press Ctrl+G to go back to xsh, to the last place you were. +7. Move to the "etc/" line, and press Ctrl+G to go to bash and cd to this dir. + +Later we'll explore going back to any previous interaction, via "xsh -i" +from bash. And turning interactions into more permanent notes. + + +> Optional mouse support +There is optional mouse support. Don't be overly concerned if it's not +enabled for your terminal or xsh configuration. It's completely +optional. Advanced xsh users rarely use it. + +1. Press Ctrl+G to go back to where we were in xsh. +2. Double-click on the "$ ls" line to collapse it. +3. Double-click on it again to rerun it. (Double-clicking is just like Ctrl+O.) +4. Try clicking around to position the cursor. + +Clicking on the words in the bottom bar shows descriptions of what each +key does. + +5. Click on "Open" in the bottom bar. +6. Click on "Go" in the bottom bar. +7. Click on the "x" at the right of the bottom bar, to close the view. + +- Tip: +If mouse support isn't enabled, you can press Ctrl+\ to see the +descriptions. Or see the following link. + +<- bar + + +> Interactive notes by topic +Xsh lets you create interactive notes! Notes can be anything you +want. They can be as simple as a shell command or two and maybe a +description. They can even be powerful, lightweight user interfaces +for shell commands and other API's (more on this later). Xiki +encourages you to organize your notes into topics. + +1. Press Ctrl+Q to go back to bash. +2. Press Ctrl+X to list your Xiki topics. + +Each topic is just a text file in the "~/xiki/" directory. They +contain your notes. The topics you see were created when you installed +xsh, to get you started. Topics you create will get added to the top +of this list. Topics use a simple wiki-ish syntax, and can be created +and edited with any text editor. + +3. Type "di" to filter down to the "disk" topic. +4. Press Ctrl+G to go to the topic file (this time in xsh). + +Notice the bottom bar shows you're in the "~/xiki/disk.xiki" +file. The lines with gray backgrounds are note headings. They separate +each note. Headings are just lines that start with "> ". The "> "might +be hard to see in some terminals. + + +> Quickly accessing your notes +It's a bit overwhelming seeing all the notes in a topic at +once. Seeing just the note names would be easier. Especially for +topics with a lot of notes. Say, 100 or more. + +1. Press Ctrl+Q to go back to bash. +2. Press Ctrl+X list your Xiki topics. +3. Type "di" to filter to "disk". +4. Press return, to lists the notes in the topic. +5. Press return, to open the "Space available" note. +6. Press return, to run the command. + +Those three returns could have been Ctrl+O's instead. The only +difference is that return hides the other items before it opens. + +You could've also typed "disk space" then Ctrl+X at an empty bash +prompt, then pressed return twice. That would show the "disk" topic +and filter down to notes containing the word "space". + +- Just text files: +Creating notes is as simple as creating a text file (or adding stuff +to an existing text file). But before we do that, let's check out some +notes created by other xsh users! + + +> Search engine in the terminal +Xsh turns your terminal into a search engine! Just type a search term +at the bash prompt and press Ctrl+S. It's quicker and less distracting +than copying and pasting commands from web searches. + +1. Press Ctrl+Q to go back to bash. +2. Type "processes", but don't type return. +3. Press Ctrl+S to search. + +The search results are notes that have been shared by other xsh users. + +4. Type "cp" to filter to the "Processes taking up cpu" search result. +5. Press return open it up. +6. Press Ctrl+O to run the shell command in it. + +You could have also typed "processes cpu" and then Ctrl+S at the bash +prompt. + +You can search via the web interface as well. +http://xiki.com + +This url shows the same results as your search for "processes". +http://xiki.com/processes + +All your notes are private by default. Sharing is still in private +beta, but will be available to all xsh users soon. + + +> Creating notes +Let's create a very simple note. + +1. Press Ctrl+Q to go back to bash. +2. Type "echo" on the bash prompt, but don't press return. +3. Press Ctrl+G to go to the topic file. + +It's blank since it doesn't exist yet. Notice the file path in the +bottom bar. + +4. Type the following text. + | > Username + | $ echo $USER + +5. Press Ctrl+Q to return to bash (xsh will autosave). +6. Press Ctrl+X to list your topics. + +You can see your new topic is there, with the note inside. It's at the +top, since it's the most recently updated file. You could have +accomplished the same thing by creating "~/xiki/foo.xiki" with any +text editor, and typing that stuff. + + +> Git log +Let's use xsh to easily navigate our git log! + +First let's create an example repository. If we look in our "git" +topic, we'll find a note that came with xsh that creates example +repositories. It's helpful when doing little experiments with git. + +1. In bash, type "cd" and return, to switch to your home dir. +2. Type "git" in bash, but don't press return. +3. Press Ctrl+X to list your "git" Xiki notes. + +Notice the "+ create example repository" note. It has "+ " at the +beginning because it's an action (a special note that runs something +when you open it). To create an action, just put a dot at the +beginning of its name (eg. "> .Foo"). + +4. Press return, and the action will clone a repo and its branches and cd into it. +5. Type "git log" in bash, then Ctrl+O to open it in xsh. Xsh makes it interactive. +6. Use Ctrl+O to open one or more of the commits to see the diffs. + + +> Git branches +Now let's try switching between branches. This time we'll type "git" +and filter down to "branch" (though we could type "git branch" +directly if we wanted to). + +1. Press Ctrl+Q, then "git" and Ctrl+O to open it in xsh. +2. Type "br" and then press return to expand the "branch..." line. +3. Open the "reasonable" branch (Ctrl+O) and open "* switch to". + +You can see that we've switched branches. + +4. Move your cursor down to an empty line, and press Ctrl+R to show recent commands. +5. Type "lo" to narrow to "git log", then press return. + +You'll see there are "reasonable" commits in this branch. You can open +them to see the diffs if you want. + + +> Web browser +Let's use xsh to run html in the web browser. Skip this section if +you're using xsh via ssh or docker (if you can't start up a web +browser from the shell). + +1. In bash, type "html/" (including the slash) and Ctrl+O. + +The slash on the end tells xsh to run a special action that shows +snippets of html in the browser. + +2. Press Ctrl+O to run the HTML in the browser. +3. In xsh, type "Updated" after "Sample", and press Ctrl+O again. + +You can see your change reflected in the browser. By default, a new +tab is opened each time. This can be avoided by installing a dependency +that lets xsh have full control of the browser. See the following link +for how to do this. + +<- xsh/browser control + + +> Notes with web examples +Now let's say we forgot (or just don't know) how to make a div with +rounded corners in css. + +1. Press Ctrl+Q to return to bash. +2. Type "css corners" then Ctrl+S, to search everyone's shared notes. +3. Press Ctrl+O to open "Rounded corners (border-radius)". +4. Press Ctrl+X to expose the note, so we can run it in place. + +(Alternately we could have pressed Ctrl+O and navigated to the topic +file.) + +5. Press Ctrl+O to open it in the browser. + +You can change some of the values and press Ctrl+O again. You could +also try searching for "bootstrap", "d3", "threejs", "fontawesome" or +"react" to run some examples. + + +> SQLite +Let's interact with some database tables. First let's create a couple +sample tables, using a note that was installed by default to help you +do the tutorial. (In the future you can use Ctrl+S to search for other +notes for your own use, and install them so they'll be convenient to +access.) + +1. In bash, type "sqlite3", then Ctrl+X to show your Xiki notes for sqlite3. +2. Press Ctrl+O to run "create sample tables and records". +3. Move down to "tables" and press Ctrl+O to see the tables. + +You could type "sqlite3/tables" and Ctrl+X in bash to go directly to +the "tables" action. But there's a "tables" topic to make it even +quicker. + +4. In bash, type "tables/" (including the slash) and Ctrl+X. + +The slash at the end means to run the "slash action" for the +topic. Slash actions are special notes that let topics optionally +behave like actions. + +5. Press Ctrl+O to expand one of the tables. +6. Type some letters to filter down to a record. +7. Edit the person's name, and press Ctrl+O to save back to the db. + +You can also type "sqlite3/" in bash and then Ctrl+X. This lets you +run sql statements. + + +> Congratulations +Congrats, you've finished the tutorial! + +Chat with us about how you liked the tutorial: +https://gitter.im/trogdoro/xiki + +Follow @xiki on twitter: +http://twitter.com/xiki + +Join the Xiki mailing list: +http://groups.google.com/group/xiki + +Web interface for xiki notes: +http://xiki.com + + diff --git a/misc/default_home_xiki/xsh.xiki b/misc/default_home_xiki/xsh.xiki new file mode 100644 index 00000000..dcaaf785 --- /dev/null +++ b/misc/default_home_xiki/xsh.xiki @@ -0,0 +1,53 @@ +> Install +Here are the steps for installing xsh (Xiki Shell). + +1. Copy and paste this one-line installer into your terminal. (Don't include the "$ ".) + +$ curl -L https://raw.githubusercontent.com/trogdoro/xiki/actions/install_xsh -o ~/install_xsh; bash ~/install_xsh + +2. Follow the setup steps that appear. + +After installing, consider going through the tutorial to learn how to +get around... + +<- tutorial + + +> .Setup +!.# +! # Once you've installed Xiki, this makes the "xsh" command available. +! # And it optionally sets up some keyboard shortcuts. +! # +! +! SetupXsh.action args, options + + +> .Reload +!.Xiki.reload + + +> Uninstall +1. Remove references +~/ + - .bashrc + : source ~/.xsh + - .bash_profile + : source ~/.xsh + +2. Delete files +~/ + + .xsh + + +> Browser Control +- Todo > explain browser control here! + + +> Tips already shown +This is where xsh remembers which tips it has already shown you. If +you want them to be shown again, just delete them from here (and +reload xsh). + +- No tips shown yet + + diff --git a/misc/emacs/el4r/.el4rrc.rb b/misc/emacs/el4r/.el4rrc.rb new file mode 100644 index 00000000..1783e417 --- /dev/null +++ b/misc/emacs/el4r/.el4rrc.rb @@ -0,0 +1,35 @@ +@lisp_object_gc_trigger_count = 100 +@lisp_object_gc_trigger_increment = 100 +@ruby_gc_trigger_count = 100 +@ruby_gc_trigger_increment = 100 +@log_buffer = "*el4r:log*" +@output_buffer = "*el4r:output*" +@unittest_lisp_object_gc_trigger_count = 5000 +@unittest_lisp_object_gc_trigger_increment = 5000 +@unittest_ruby_gc_trigger_count = 5000 +@unittest_ruby_gc_trigger_increment = 5000 +@temp_file = "/var/folders/66/3h81t2y913vf344z6fb2cks00000gn/T/el4r-tmp.tmp" + +# This file is loaded once from lisp and once from ruby, so this var sometimes isn't set +XIKI_DIR = ENV['XIKI_DIR'] if ! defined?(XIKI_DIR) + +@instance_program = "#{XIKI_DIR}/misc/emacs/el4r/el4r-instance" + +### El4r bootstrap code +def __conf__ + if ENV['EL4R_ROOT'] + $: << File.join(ENV['EL4R_ROOT'], "lib") + end + require "#{XIKI_DIR}/misc/emacs/el4r/el4r-sub.rb" + ConfigScript.new(__FILE__) +end + +def __elisp_init__ + $> << "(setq \n" + instance_variables.map{|iv| [iv[1..-1], instance_variable_get(iv)]}.each {|k,v| $> << "el4r-#{k.gsub(/_/,'-')} #{v.inspect}\n" if Numeric === v or String === v} + $> << ')' << " +" +end + +at_exit { __elisp_init__ if __FILE__==$0 } + diff --git a/misc/emacs/el4r/el4r-instance b/misc/emacs/el4r/el4r-instance new file mode 100755 index 00000000..6d45f133 --- /dev/null +++ b/misc/emacs/el4r/el4r-instance @@ -0,0 +1,1231 @@ +# A slimmed-down and network-ified version of the el4r project. +# El4r was created by rubikitch, and was incorporated into Xiki +# with his permission. + +xiki_dir = File.expand_path $0 +4.times { xiki_dir = File.dirname xiki_dir } +XIKI_DIR = xiki_dir + +require "#{XIKI_DIR}/lib/xiki/core/ol.rb" + +ruby_too_old = RUBY_VERSION =~ /^1\.[78]/ +if ruby_too_old + STDOUT.print('(el4r-ruby18-error)') + STDOUT.print("\0") + STDOUT.flush +end + +require 'etc' + +require 'forwardable' + +load "#{XIKI_DIR}/misc/emacs/el4r/.el4rrc.rb"; + +module El4r + ErrorUtils = Module.new + class << ErrorUtils + def error_description(exception = nil) + exception ||= $! + sprintf("%s (%s)", exception.to_s, exception.class).gsub("\n", " ") + end + + def backtrace_message(backtrace = nil) + backtrace ||= caller(1) + lines = [] + backtrace.each do |line| + lines << sprintf(" from %s", line) + end + lines.join("\n") + end + + def stacktrace_message(exception = nil) + exception ||= $! + lines = [] + + lines << error_description(exception) + lines << backtrace_message(exception.backtrace) + lines.join("\n") + end + end + + El4rError = Class.new(StandardError) + ELError = Class.new(El4rError) + + ELISP_INTEGER_RANGE = (-134217727..134217727) + + class << self + def lisp_dump_string(string) + dumped = string.dup + # \ -> \\ + dumped.gsub! %r"\\" do '\\\\' end + # " -> \" + dumped.gsub! %r'"' do '\\"' end + # (zero byte) -> \0 + dumped.gsub! %r'\0' do "\\\0" end + %Q'"#{dumped}"' + end + + def name_rb2el(rbname) + rbname.gsub(/_/, "-") + end + end + + ELExpression = Struct.new(:expression) + + # Reference of an EmacsLisp object. + class ELObject < Struct.new(:instance, :object_id) + def initialize(*args) + ObjectSpace.define_finalizer(self, ELObject.finalizer_proc(*args)) + super(*args) + end + + def to_lisp + "(el4r-lisp-object-from-id #{object_id})" + end + + def inspect + "#<#{self.class} id=#{object_id}>" + end + + def to_s + instance.prin1_to_string(self) + end + alias :to_str :to_s + + def ==(x) + ELObject === x or return false + object_id == x.object_id or instance.equal(self,x) + end + + def self.finalizer_proc(instance, object_id) + proc { + instance.el4r_add_garbage_lispobj(object_id) + } + end + end + + class ELSequence < ELObject + include Enumerable + + def inspect + "#{self.class.to_s.split(/::/)[-1]}#{to_a.inspect}" + end + + def check(x) + @length ||= to_a.length + Fixnum === x or raise TypeError, "must be Fixnum" + @length <= x and raise ArgumentError, "Args out of range #{inspect}[#{x}]" + end + + def [](*args) + check args[0] + to_a[*args] + end + + def each(&block) + to_a.each(&block) + end + + def to_a + # subclass must define `to_a_func' method + @ary ||= instance.__send__(to_a_func, self) + end + alias :to_ary :to_a + + end + + # An Array like object converted from a list object of EmacsLisp. + class ELListCell < ELSequence + def to_a_func () :el4r_list_to_rubyary end + + # Convert alist -> Hash + def to_hash + hash = {} + each { |cell| + cell.kind_of?(ELListCell) or raise(TypeError, "Malformed assoc list.") + hash[instance.car(cell)] = instance.cdr(cell) + } + hash + end + end + + # An Array like object converted from a cons cell of EmacsLisp. + class ELConsCell < ELListCell + def to_a_func () :el4r_cons_to_rubyary end + end + + # An Array like object converted from a vector object of EmacsLisp. + class ELVector < ELSequence + def []=(x,y) + check x + x < 0 and x = @length+x + + @ary[x]=y + instance.aset(self, x, y) + end + + def to_a_func () :el4r_vector_to_rubyary end + end + + + # EmacsLisp objects stock. + class ELRubyObjectStock + attr_accessor :gc_trigger_count, :gc_trigger_increment + def initialize(instance) + @instance = instance + conf = instance.conf + @oid_to_obj_hash = {} + @gc_trigger_count = conf.ruby_gc_trigger_count + @gc_trigger_increment = conf.ruby_gc_trigger_increment + end + + def garbage_collect_if_required + if count_of_stocked_objects >= @gc_trigger_count + garbage_collect + @gc_trigger_count = + count_of_stocked_objects + @gc_trigger_increment + end + end + + def pre_gc_hook + end + + def post_gc_hook + end + + def garbage_collect + pre_gc_hook + stock_ids = @oid_to_obj_hash.keys + stock_ids.sort! + @instance.el4r_debug { "(GC) 1" } + alive_ids = @instance.el4r_rubyobj_get_alive_ids.to_a # funcall + @instance.el4r_debug { "(GC) 2" } + alive_ids.collect! { |id| id.to_i; } + alive_ids.sort! + + @instance.el4r_debug { "(GC) Stocked IDs: #{stock_ids.inspect}"; } + @instance.el4r_debug { "(GC) Alive IDs: #{alive_ids.inspect}"; } + + freed_ids = [] + while aid = alive_ids.pop + while true + sid = stock_ids.pop or raise(El4rError, "Can't happen!") + + break if sid == aid + freed_ids << sid + end + end + freed_ids.concat(stock_ids) + + @instance.el4r_debug { "(GC) IDs to free: #{freed_ids.inspect}"; } + freed_ids.each { |id| + @oid_to_obj_hash.delete(id) + } + + @instance.el4r_debug { "(GC) Count of stocked object is reduced to #{count_of_stocked_objects}"; } + post_gc_hook + end + + def obj2lisp(obj) + # NOTE: Ruby's object ID exceeds elisp's 28-bit integer limitation. + "(el4r-rubyobj-create \"#{obj2id(obj)}\")" + end + + def obj2id(obj) + garbage_collect_if_required + oid = obj.__id__ + @oid_to_obj_hash[oid] ||= obj + oid + end + + def id2obj(oid) + @oid_to_obj_hash[oid] or + raise(El4rError, "No such object for ID: #{oid}") + end + + def count_of_stocked_objects + @oid_to_obj_hash.size + end + end + + # A Struct like object to handle EmacsLisp variables. + class ELVariables < Struct.new(:instance) + def [](name) + instance.getq(name) + end + + def []=(name, value) + instance.setq(name, value) + end + + def method_missing(name, value = nil) + name = name.to_s + if name[-1] == ?= + instance.setq(name[0..-2], value) + else + instance.getq(name) + end + end + end + + # EmacsLisp wrapper methods + module ELMethodsMixin + # Call an EmacsLisp function + def funcall(name_or_lambda, *args, &block) + func = case name_or_lambda + when Symbol, String + El4r.name_rb2el(name_or_lambda.to_s) + when ELObject, ELExpression + "funcall #{el4r_ruby2lisp(name_or_lambda)}" + else + raise(TypeError, + "Invalid 1st argument for funcall: #{name_or_lambda.inspect}") + end + funcall_internal(func, *args, &block) + end + alias method_missing funcall + + def funcall_internal(funcexpr, *args, &block) + el4r_lisp_eval("(#{funcexpr} #{el4r_args_to_lispseq(*args, &block)})") + end + + # Call (func FORMS...) type function + def with(name, *args, &block) + args << el("(funcall #{el4r_rubyproc_to_lambda(&block)})") + funcall(name, *args) + end + + # EmacsLisp's defun. + # + # +attrs+ is a Hash. + # attrs[:interactive]:: If the function is interactive, set +true+ or String. + # attrs[:docstring]:: The docstring. + # + # The function arguments are block arguments. + def defun(name, attrs = nil, &block) + String === name and name = el("'#{name}") + funcall_internal("el4r-define-function", name, el_lambda(attrs, &block)) + end + + def defvar(name, value=nil, docstring="") + funcall_internal :defvar, el(name), value, docstring + end + + # EmacsLisp's define-key. + # + # +map+ is a +Symbol+ or +ELObject+ refering to a keymap object. + # This method can be called with block. + def define_key(map, key, command = nil, &block) + map = el(map) unless map.kind_of?(ELObject) + key = el(%Q'"#{key}"') if key.kind_of?(String) + command = el_lambda(:interactive => true, &block) if block_given? + funcall_internal("define-key", map, key, command) + end + + def symbol_value(name) + el4r_lisp_eval(El4r.name_rb2el(name.to_s)) + end + alias getq symbol_value + + def setq(name, value) + el4r_lisp_eval("(setq #{El4r.name_rb2el(name.to_s)} #{el4r_ruby2lisp(value)})") + end + alias set setq + + def _init_eval_after_load + @eval_after_load_func = "el4r-eval-after-load-function-1" + end + + # EmacsLisp's eval-after-load + def eval_after_load(lib, &block) + raise ArgumentError, "must have block" unless block + defun(@eval_after_load_func, &block) + el4r_lisp_eval "(eval-after-load #{el4r_ruby2lisp(lib)} '(#{@eval_after_load_func}))" # ' + @eval_after_load_func.succ! + nil + end + + # EmacsLisp's let. + # +name_and_value_list+ is [variable_name, value, variable_name, value...]. + # +name_and_value_list.length+ must be even. + # +variable_name+ is a Symbol. + def let(*name_and_value_list, &block) + (name_and_value_list.size % 2) == 0 or + raise(ArgumentError, "Invalid count of arguments.") + + letexpr = "(let (" + until name_and_value_list.empty? + name = El4r.name_rb2el(name_and_value_list.shift.to_s) + value = el4r_ruby2lisp(name_and_value_list.shift) + letexpr << "(#{name} #{value}) " + end + letexpr << ") (funcall #{el4r_rubyproc_to_lambda(&block)}))" + el4r_lisp_eval(letexpr) + end + + # Create a new buffer with some initialization. + # With block, newbuf execute it by the context of the generated buffer. + # + # A parameter is a Hash. + # :name :: buffer-name + # :file :: find-file-noselect / insert-file-contents [with :name] + # :contents :: buffer-string + # :line :: goto-line + # :point :: goto-char [default is (point-max)] + # :display :: :pop / :only / true + # :current :: set-buffer + # :read_only :: buffer-read-only + # :bury :: bury-buffer + def newbuf(x) + Hash === x or raise ArgumentError, "argument must be a hash!" + x[:name] || x[:file] or raise ArgumentError, "`:name' or `:file' key is mandatory!" + x[:name] and b = get_buffer_create(x[:name]) + x[:file] && !x[:name] and b = find_file_noselect(x[:file]) + + check = lambda{|key, type| x[key] && (type===x[key] or raise ArgumentError) } + with(:with_current_buffer, b) { + elvar.buffer_read_only = nil + # TODO: coding-system + x[:name] and erase_buffer + x[:name] && x[:file] and insert_file_contents(x[:file]) + x[:contents] and insert x[:contents].to_s + check[:line,Integer] and goto_line x[:line] + check[:point,Integer] and goto_char x[:point] + block_given? and yield + x[:read_only] and elvar.buffer_read_only = true + } + + case x[:display] + when :pop; pop_to_buffer b + when :only; delete_other_windows; switch_to_buffer b + when true; display_buffer b + else + end + + x[:bury] and bury_buffer b + x[:current] and set_buffer b + + b + end + + # Extended buffer-string. + # +buf+ is a buffer object. + def bufstr(buf=current_buffer) + with(:with_current_buffer, buf) { buffer_string } + end + + def ad_do_it + el4r_lisp_eval("(funcall --defadvice-ad-do-it--)") + end + + # EmacsLisp's defadvice. + # +func+ is Symbol or String refering to the function. + # +args+ is parameters to defadvice such as :before, :after, :around, :activate. + # In the block, you can call +ad_do_it+. [around advice] + + def defadvice(func, *args, &block) + Hash === args[-1] and attrs = args.pop + + param = args.map{|a| El4r.name_rb2el(a.to_s)}.join(' ') + forms = "#{El4r.name_rb2el(func.to_s)} (#{param})\n" + + if attrs + _handle_attrs attrs, forms, false + end + + forms << "(setq --defadvice-ad-do-it-- (lambda () ad-do-it))\n" + with(:defadvice, el(forms), &block) + end + + def _handle_attrs(attrs, forms, quote) + docstring = attrs[:docstring] + forms << el4r_ruby2lisp(docstring) << "\n" if docstring + interactive = attrs[:interactive] + if interactive + forms << "'" if quote + case interactive + when Proc; + lmd = el4r_ruby2lisp(interactive) + el4r_lisp_eval %Q((el4r-register-lambda #{lmd})) + forms << "(interactive (eval (list #{lmd})))" + when true; forms << "(interactive)\n" + else; forms << "(interactive #{el4r_ruby2lisp(interactive)})\n" + end + end + end + + # Call defun-type macro. `mode' is an EmacsLisp function to define. + # Most case the first argument is the function name. + # + # `define_derived_mode' and `define_minor_mode' are examples of this method's usage. + def with_preserved_block(funcexpr, mode, *args, &block) + mode = el(mode) + subfuncexpr = "#{mode.expression}--el4r-function" + block ||= lambda{} + defun(subfuncexpr, &block) + args << el("(#{subfuncexpr})") + + funcall_internal(funcexpr, mode, *args) + end + + # EmacsLisp's define-derived-mode. + def define_derived_mode(child, parent, name, docstring=nil, &block) + with_preserved_block "define-derived-mode", child, el(parent), name, docstring, &block + end + + # EmacsLisp's define-minor-mode + def define_minor_mode(mode, doc, init_value=nil, lighter=nil, keymap=nil, &block) + with_preserved_block "define-minor-mode", mode, doc, init_value, lighter, keymap, &block + end + + # Ruby's require. + def require(*args) + Kernel.require(*args) + end + + # EmacsLisp's require. + def el_require(*args) + funcall_internal("require", *args) + end + + # EmacsLisp's load. + def el_load(*args) + funcall_internal("load", *args) + end + + # EmacsLisp's lambda. + def el_lambda(attr = nil, &block) + el(el4r_rubyproc_to_lambda(attr, &block)) + end + + # Bare EmacsLisp expression. + def el(expression) + case expression + when Symbol; ELExpression.new(El4r.name_rb2el(expression.to_s)) + when String; ELExpression.new(expression) + when ELExpression; expression + else + raise(TypeError, + "Cannot treat as lisp expression: <#{expression.inspect}>") + end + end + + # If no input stream was passed, we're just generating and caching elisp to run later + def caching + ! @emacs_in + end + + $el4r_cached_lisp ||= {} + + # Cache elisp as block is run. If we already cached it, just run the cached version. + def cache key, &block + + # Normal running, with full $el, so use cache or run normally... + if ! caching + cached = $el4r_cached_lisp[key] + + # Cache exists, so eval... + if cached + el4r_lisp_eval cached + return + end + + # No cache, so just load as normal... + + block.call + + else + + # Pre-loading with partial $el, so just cache it... + + block.call + + $el4r_cached_lisp[key] = "(progn #{@emacs_out.string})".freeze + + @emacs_out.reopen + end + + nil + + end + end + + # Pseudo $stdout object for el4r. + # This object appends to *el4r output* buffer + class El4rOutput + def initialize(instance) + @instance = instance + end + + def write(s) + @instance.instance_eval do + princ(s.to_s, get_buffer_create(conf.output_buffer)) + end + nil + end + + def flush + self + end + end + + def self.create_instance options={} + input = options[:input] || STDIN + output = options[:output] || STDOUT + + el4r = El4r::ELInstance.new __conf__, input, output + STDERR.reopen(el4r.el4r_log_io) if ! options[:boot_standalone] + el4r + end + + # class ChildKickedException < RuntimeError; end + + + def self.main options={} + + # Trap and handle signal when user types C-g... + + Signal.trap( "SIGUSR1" ){ + throw :child_kicked_exception, :child_kicked_exception + } + + $el ||= self.create_instance(options) + + + # Assign intput and output streams + + $el.emacs_in = options[:input] if options[:input] + $el.emacs_out = options[:output] if options[:output] + + $el.el4r_log "Booted." + $el.el4r_wait_expr_loop + + end + + class ELInstance + include ELMethodsMixin + extend Forwardable + + attr_accessor :outer # maybe thread-unsafe(no problem) + attr_reader :el4r_rubyobj_stock + + # An Struct like object to handle EmacsLisp's variable. + attr_reader :elvar + + # If +true+, verbose log output. + attr_accessor :el4r_is_debug + + # For setting streams after instanciating + attr_accessor :emacs_in, :emacs_out + + # settings by ~/.el4rrc.rb + attr_reader :conf + + def_delegators :@conf, :root_dir, :site_dir + def log_path + "/tmp/el4r-#{Etc.getlogin}.#{Process.pid}.log" + end + + def initialize_log + @log = File.open(log_path, "w") + end + + def initialize(conf, input=STDIN, output=STDOUT) + @conf = conf + @emacs_in = input + @emacs_out = output + @call_level = 0 + @last_error = nil + @el4r_is_debug = ENV["EL4R_DEBUG"] + + @el_backtrace_reset_threshold = 1 # very nasty hack! + @el_backtrace = [] + + return if ! conf # If caching + + @elvar = ELVariables.new(self) + @el4r_rubyobj_stock = ELRubyObjectStock.new(self) + @el4r_garbage_elobj_ids = [] + + @el4r_output = El4rOutput.new self + + initialize_log + + _init_eval_after_load + + end + + # -------------------------------- + # Methods for user + + def instance_eval_invoker path + source = File.read(path) + instance_eval source, path + end + + # Eval an EmacsLisp expression. + def el4r_lisp_eval(lispexpr) + el4r_interrupt if el4r_callback? + el4r_with_call { + el4r_send(lispexpr) + + if ! caching + result = el4r_get + el4r_debug{"}"} + result + end + + } + end + + # Convert a Ruby Regexp into EmacsLisp. + def el4r_conv_regexp(re) + s = re.source.dup + s.gsub!(/[\|\(\)]/){'\\'+$&} + s.sub!(/\\A/){'\`'} + s.sub!(/\\Z/){'\\\''} + s.sub!(/\\w/, '[0-9A-Za-z_]') + s.sub!(/\\W/, '[^0-9A-Za-z_]') + + s + end + + # Convert a Ruby object into EmacsLisp. + def el4r_ruby2lisp(obj) + case obj + when nil, false; "nil" + when true; "t" + when String; El4r.lisp_dump_string(obj) + when Regexp; El4r.lisp_dump_string(el4r_conv_regexp(obj)) + when Symbol; "'#{El4r.name_rb2el(obj.to_s)}" + when Proc; el4r_rubyproc_to_lambda(&obj) + when Integer + (ELISP_INTEGER_RANGE === obj) or + raise(RangeError, + "Integer #{obj} exceed elisp limitation (#{ELISP_INTEGER_RANGE})") + obj.to_s + when Numeric; obj.to_s + when Array; "(list #{el4r_args_to_lispseq(*obj)})" + when ELObject; obj.to_lisp + when ELExpression; obj.expression + else; el4r_rubyobj_stock.obj2lisp(obj) + end + end + + # Convert a Ruby Proc into EmacsLisp + # +attrs+ is the same as that of +defun+. + def el4r_rubyproc_to_lambda(attrs = nil, &block) + forms = + ["el4r-lambda-for-rubyproc \"#{el4r_rubyobj_stock.obj2id(block)}\""] + if attrs + _handle_attrs attrs, forms, true + end + "(#{forms.join(' ')})" + end + + def el4r_args_to_lispseq(*args, &block) + elargs = args + elargs << block if block_given? + elargs.collect! { |form| + el4r_ruby2lisp(form) + } + elargs.join(' ') + end + + # Write a log message. + def el4r_log(msg) + @log.print(Time.now.strftime("%H:%M:%S:%L ")) + # @log.print(Time.now, ":") + @log.puts(msg); @log.flush + end + + # String representation. obj.inspect and (prin1-to-string obj). + def el4r_prin1_to_string(obj) + "[ruby] #{obj.inspect} / [lisp] #{prin1_to_string(obj)}" + end + + # Write string representation(both in Ruby and in EmacsLisp) of all the argument to the log. + def el4r_prin1(*objs) + objs.each { |obj| + el4r_log("el4r_prin1: #{el4r_prin1_to_string(obj)}") + } + end + + # Write string representation(only in Ruby) of all the argument to the log. + def el4r_p(*objs) + el4r_log("el4r_p: #{objs.inspect}") + end + + # Write a backtrace message to the log. + def el4r_backtrace(msg = nil) + msg ||= "*backtrace*" + # el4r_log "#{msg}\n#{ErrorUtils.backtrace_message(caller(2))}" + el4r_log "- #{msg}\n#{ErrorUtils.backtrace_message(caller(2))}" + end + + # -------------------------------- + # Methods for internal use + + # Startup el4r without loading init.rb. + def el4r_boot__noinit + logbuf = get_buffer_create conf.log_buffer + elvar.el4r_log_path = el4r_log_io.path + + el4r_lisp_eval('(defun el4r-show-log () (interactive) + (with-current-buffer (get-buffer-create el4r-log-buffer) + (setq buffer-read-only nil) + (erase-buffer) + (insert-file-contents el4r-log-path) + (setq buffer-read-only t) + (goto-char (point-max)) + (pop-to-buffer (current-buffer))))') + el4r_install_builtin_functions + $> = @el4r_output + end + + # def el4r_process_autoloads(dir=conf.autoload_dir) + # Dir["#{dir}/[0-9][0-9]*.rb"].sort.each do |rb| + # el4r_load rb + # end + # end + + # Startup el4r. + def el4r_boot + el4r_boot__noinit + instance_eval File.read "#{XIKI_DIR}/misc/emacs/el4r/init.rb" + end + + # Obsolete. + def el4r_shutdown + end + + def el4r_add_garbage_lispobj(id) + @el4r_garbage_elobj_ids << id + end + + def el4r_get_garbage_lispobj_ids + GC.start + ids = @el4r_garbage_elobj_ids + @el4r_garbage_elobj_ids = [] + ids + end + + def el4r_readfile(file) + File.open(file) { |io| io.read || ""; } + end + + def el4r_wait_expr_loop + result = catch(:child_kicked_exception) do + el4r_log "Starting, waiting for expression." + el4r_wait_expr until @emacs_in.eof? + end + + # Start loop again if there was an error... + if result == :child_kicked_exception + + # Kill all child threads... + + # current = Thread.current + current = Thread.main + + Thread.list.each do |thread| + # thread.raise("Gotcha") + + # Temporarily removed! + thread.kill if thread != current + end + + # Kill all grandchildren of "bash" children (they should die when command stops)... + + mypid = Process.pid + + children = `ps -eo ppid,pid,args`.split("\n").grep(/^ *#{mypid} /) + # Only kill children of bash processes + children = children.grep(/ bash$| \(bash\)$/) + children.map!{|o| o[/ (\d+) /, 1].to_i} + children.each do |child| + # Get grandchildren + grands = `ps -eo ppid,pid,args`.split("\n").grep(/^ *#{child} /) + grands.map!{|o| o[/ (\d+) /, 1].to_i} + + # Temporarily removed! + grands.each{|grand| Process.kill(9, grand)} + end + + # Go back into el4r run loop... + + el4r_wait_expr_loop + end + + rescue Exception => e + el4r_log El4r::ErrorUtils.stacktrace_message(e) + ensure + el4r_log "Exiting." + end + + def el4r_wait_expr + @last_error = nil + el4r_with_call { + lispexpr = nil + begin + result = el4r_get + el4r_debug { "Result: <#{result.inspect}>"; } + lispexpr = el4r_ruby2lisp(result) + rescue ELError + el4r_debug { "Passing lisp error: #{ErrorUtils.stacktrace_message($!)}"; } + lispexpr = "(el4r-signal-last-error)" + rescue StandardError, ScriptError + @last_error or + el4r_log("Error: #{ErrorUtils.stacktrace_message($!)}") + @last_error = $! + lispexpr = "(signal 'el4r-ruby-error nil)" + end + el4r_send(lispexpr) + } + end + + def el4r_get + expr = el4r_recv + expr.force_encoding("binary") if expr.respond_to? :force_encoding # For ruby 1.9 + while expr.empty? + el4r_debug { "Received callback interrupt."; } + el4r_wait_expr + expr = el4r_recv + end + el4r_ruby_eval(expr) + end + + def el4r_recv + el4r_debug { "Waiting for Ruby expression"; } + expr = @emacs_in.readline("\0\n") + expr.slice!(-2, 2) + el4r_debug { "Received Ruby expression[ #{expr}"; } + expr + end + + def el4r_send(lispexpr) + el4r_debug { "Sending Lisp expression{ #{lispexpr}"; } + + @emacs_out.print(lispexpr) + + # Delimit with ascii 0 normally, or "\n" when caching + caching ? + @emacs_out.print("\n") : + @emacs_out.print("\0") + + @emacs_out.flush + end + + def el4r_interrupt + el4r_debug { "Sending callback interrupt."; } + @emacs_out.print("\0") + end + + # Create an ELObject. + def el4r_elobject_new(id, klass = nil) + (klass || ELObject).new(self, id) + end + + # Write a log message if el4r is debug. + def el4r_debug(msg = nil, &block) + if @el4r_is_debug + msg ||= yield + el4r_log("[DEBUG] (#{@call_level}) #{msg}") + end + end + + # Log IO object. + def el4r_log_io + @log + end + + def el4r_with_call(&block) + @call_level += 1 + begin + yield + ensure + @call_level -= 1 + @call_level <= @el_backtrace_reset_threshold and @el_backtrace = [] + end + end + + # Eval +source. + # When an Exception is raised, write a stacktrace message to the log. + def el4r_ruby_eval(source) + begin + # instance_eval(source) + result = instance_eval(source, "/tmp/el4r") # Passing fake path makes it show all the way up the stack for some reason + el4r_debug{"]"} + result + rescue Exception + el4r_debug { "Error in evaluating '#{source}': #{ErrorUtils.stacktrace_message($!)}"; } + raise + end + end + + def el4r_reraise_last_error + raise @last_error + end + + def el4r_raise_lisp_error + msg = el4r_lisp_eval("(prin1-to-string el4r-last-error-desc)") + @el_backtrace << el4r_lisp_eval("(prin1-to-string el4r-error-lisp-expression)") + raise(ELError, "Error in lisp code.:#{msg}\n#{@el_backtrace.join("\n")}") + end + + def el4r_callback? + @call_level != 0 + end + + # Treat EmacsLisp strings containing C-c, C-d, C-q, C-s, C-v, C-w, C-z + def el4r_treat_ctrl_codes(&block) + let(:el4r_treat_ctrl_codes, true, &block) + end + + private + # Install the built in functions. + def el4r_install_builtin_functions + path = "#{XIKI_DIR}/misc/emacs/el4r/stdlib.rb" + instance_eval File.read(path), path + + el4r_install_xemacs_workaround + end + + # Install an xemacs workaround. + def el4r_install_xemacs_workaround + # delete-other-windows at xemacs workaround! very nasty hack! + if elvar.noninteractive and featurep(:xemacs) + defun(:delete_other_windows) do + windows = [] + walk_windows{|w| windows << w} + + curwin = selected_window + windows.each do |w| + delete_window w unless eq(w,curwin) + end + select_window curwin + nil + end + end + + end + + end +end + +module El4rAccessor + # The el4r object. + def el4r + $el + end + +end + +# A mix-in to add EmacsRuby features. +module ElMixin + include El4rAccessor + # Eval the block in the el4r context. + # +outer+ is the caller of this method. + def elisp(&block) + el4r.outer = self + el4r.instance_eval(&block) + end + + def method_missing(func, *args, &block) +#Ol() + el4r.__send__(func, *args, &block) + end + +end + +# A class with EmacsRuby features. +class ElApp + include ElMixin + extend El4rAccessor + @@instances = {} + + # Run the application. + def self.run(params={}) + obj = new(params) + (@@instances[self] ||= []) << obj # preserve from GC + process_defun(obj) + obj + end + + def self._change_receiver_of(orgblock, obj, name) + ## HACK Yuck! + defunmeth = "#{name}__defun__" + define_method(defunmeth, orgblock) + private defunmeth + + obj.instance_eval{ lambda{|*args| __send__(defunmeth, *args)}} + end + private_class_method :_change_receiver_of + + def self.process_defun(obj) + (@defuns || []).each do |name, attrs, block| + block = _change_receiver_of(block, obj, name) + obj.defun(name, attrs, &block) + end + end + private_class_method :process_defun + + def initialize(params) + end + + # EmacsLisp's defun. See El4r::ELMethodsMixin#defun. + # + # It is a convenient defun. Note that +block+ is evaluated within a + # context of INSTANCE. + def self.defun(name, attrs=nil, &block) + (@defuns ||= []) << [name, attrs, block] + end + + extend SingleForwardable + + # Import EmacsLisp (and El4r::ELMethodsMixin) functions within class definition. + + def self.import_function(*funcs) + def_delegators :el4r, *funcs + end + import_function :defvar, :el4r_lisp_eval + +end + +if __FILE__ == $0 + if ARGV[0] == 'forker' + + # Scenario 1: Started by emacs, to create parent that forks... + + STDIN.close + grandparent_pid = Process.pid + Signal.trap("TERM") { puts "the xiki process is listening on ~/.xikisock and ready to fork!"; exit } + + parent_pid = Process.fork do + + # Parent (server process) + + $0 = "xsh forker" + Process.setsid # Makes sure this process is in its own process group and session + + # Slow boot stuff... + + el4r = El4r.create_instance + el4r.instance_eval_invoker "#{XIKI_DIR}/misc/emacs/el4r/init.rb" + + $el = el4r + + require 'socket' + + socket_file = File.expand_path "~/.xikisock" + + File.delete socket_file if File.exists? socket_file + + orig = File.umask 077 # Create xikisock with tighter security + $server = UNIXServer.new socket_file + File.umask orig + + Process.kill("TERM", grandparent_pid) # The initial process just waits around until the server started. Tell it to die so el4r will continue on. + + GC.start # Force garbage collection, so it won't potentially happen after each fork + + while true + + $socket = $server.accept + + # Comment this to do single process + child_pid = Process.fork do + + $0 = "xsh fork" + + $server.close + # ...notifying parent via socket when done + + # Enable verbose el4r logging during startup (slows down startup time a lot) + if false # Todo > Make setting for whether to log during startup (default to off) + el4r.initialize_log + el4r.el4r_is_debug = 1 + end + + # Fast boot stuff (emacs will call el4r_boot again after we return)... + + El4r.main :input=>$socket, :output=>$socket # , :boot_first=>1 + end + Process.detach child_pid # Release pid into pool, so we don't hog them + $socket.close + end + + end + + Process.detach parent_pid # Release pid into pool, so we don't potentially hog them + sleep # Just wait until your child kills you because it's ready to handle a connection. Control will return to elisp... + + elsif ARGV[0] == 'single_process' + + # Scenario 2: Started manually in shell (a single emacs will connect to us)... + + puts "starting el4r (no pre-load)..." + require 'socket' + server = TCPServer.new "127.0.0.1", 8161 + while true + socket = server.accept + El4r.main :input=>socket, :output=>socket + end + + elsif ARGV[0] == 'single_process_with_boot' + + # Scenario 3: Started manually in shell (and boot is run)... + + puts "starting el4r bridge..." + + el4r = El4r.create_instance + + el4r.instance_eval_invoker "#{XIKI_DIR}/misc/emacs/el4r/init.rb" + + $el = el4r + + require 'socket' + server = TCPServer.new "127.0.0.1", 8161 + while true + Ol() + socket = server.accept + El4r.main :input=>socket, :output=>socket + end + + + elsif ARGV[0] == 'single_process_unixdomain' + + puts "starting el4r bridge..." + el4r = El4r.create_instance + el4r.instance_eval_invoker "#{XIKI_DIR}/misc/emacs/el4r/init.rb" + + $el = el4r + + require 'socket' + server = UNIXServer.new "/tmp/xikisock" + while true + socket = server.accept + El4r.main :input=>socket, :output=>socket + end + + elsif ARGV[0] == 'test_boot' + + el4r = El4r.create_instance :boot_standalone=>1 + + el4r.instance_eval_invoker "#{XIKI_DIR}/misc/emacs/el4r/init.rb" + + puts "Booted." + $el = el4r + El4r.main + + elsif ARGV[0] == nil + # Scenario 4: Emacs started us as slave process (controlled via pipe)... + + El4r.main + end + +end + +# Local Variables: +# mode: ruby +# End: diff --git a/misc/emacs/el4r/el4r-sub.rb b/misc/emacs/el4r/el4r-sub.rb new file mode 100644 index 00000000..522aaea0 --- /dev/null +++ b/misc/emacs/el4r/el4r-sub.rb @@ -0,0 +1,1023 @@ +# This script is auto-generated. DON'T EDIT!!! +#!/usr/bin/env ruby + +#### print +class Object + # puts MSG = self.inspect + # for debug. + def pr(msg=nil) + if msg then + print "#{msg} = " + end + + display + print "\n" + self + end + + # pr when $VERBOSE + def vpr(msg=nil) + if $VERBOSE then + self.pr msg + else + self + end + end + + # eval and print + def ev(str) + puts "#{str} = #{eval str}" + end +end + +#### array +class Array + # Make random value array. length==n + def Array.rand(n, r=0) + (1 .. n).collect{super(r)} + end + + # join ' ' + def js + join ' ' + end +end + +#### vrequire +# verbose require +def vrequire(f) + puts "loading " + f + "..." + require f + puts "loading " + f + "...done" +end + +#### vsystem +# verbose system +def vsystem(cmd) + puts cmd + system cmd +end + +#### time +class Time + # Simple benchmark function. + # Time.time(start_msg) { ... } + def Time.time(smsg="", emsg="%s seconds") + s=Time.now + print smsg + puts if smsg != "" + yield + sec=Time.now - s + printf emsg, sec + puts "" + end +end + +#### nonzero +class Numeric + # 1 : self >=0 / -1 : self < 0 + def sign + if self >= 0 then 1 else -1 end + end + + # If self is negative, returns 0 + def nonzero + [self, 0].max + end +end + + +class Vector + # If self is negative, returns 0 + def nonzero + map{|x| x.nonzero} + end +end + +class Matrix + # If self is negative, returns 0 + def nonzero + map{|x| x.nonzero} + end +end + +#### sigma +module Math + # Math.sigma(b, e) { expr } + def sigma(b,e) + if b > e then + 0 + else + s=0 + b.upto(e) do |i| + s += yield(i) || 0 + end + s + end + end + alias sum sigma + module_function :sigma +end + +#### nonempty +class Object + # Is self non-nil and not-empty. + def nonempty? + if self and !self.empty? + self + end + end +end + + +#### tempfile +require 'pathname' +require 'tempfile' +require 'tmpdir' +class << Tempfile + # Create a temporary file whose contents is CONTENT. + # Returns the file's path. + def path(content, dir=Dir.tmpdir) + x = Tempfile.open("content", dir) + x.write content + x.close + x.open + x.path + end + + # Similar to Tempfile.path. But returns Pathname object. + def pathname(content, dir=Dir.tmpdir) + Pathname.new(path(content, dir=Dir.tmpdir)) + end +end + +#### untar +module Untar + # Returns a command to uncompress an archive FILE. + def untar_command(file) + f = file + case f + when /\.tar\.gz$/, /\.tgz$/ + "(tar xzvf #{f} || tar xvf #{f})" + when /\.tar\.bz2$/ + "(tar xjvf #{f} || tar xvf #{f})" + when /\.tar$/, /\.gz$/ + "tar xf #{f}" + when /\.zip$/ + "unzip #{f}" + when /\.lzh$/ + "lha x #{f}" + when /\.afz$/ + "afio -ivZ #{f}" + when /\.rar$/i + "unrar %s" + when /\.sit$/i + "ln -s %s tmp.sit; unstuff tmp.sit" + else + nil + end + end +end + +#### getopts +def _getopts_sub(argv, single_opts, *long_opts) + require 'optparse' + opt = OptionParser.new + + (single_opts || "").split(//).each do |single_opt| + opt.on("-#{single_opt}"){ eval "$OPT_#{single_opt}=true" } + end + + long_opts.each do |long_opt| + have_arg_p = (long_opt[-1,1] == ':') + long_opt.chomp!(':') + block = lambda{|x| eval "$OPT_#{long_opt}=x"} + if have_arg_p + if long_opt.length == 1 # -x arg + opt.on("-#{long_opt} [ARG]",&block) + else # --long arg + opt.on("--#{long_opt}=[ARG]",&block) + end + else # --long + opt.on("--#{long_opt}"){ eval "$OPT_#{long_opt}=true"} + end + end + + opt.parse! argv +end + +# getopts compatibility layer using optparse.rb +def getopts(single_opts, *long_opts) + _getopts_sub ARGV, single_opts, *long_opts +end + +#### run config +# run config +# c = RunConfig.new("test") +# c.run_user_config +# c.run_local_config +# +class RunConfig + + def initialize(name) + @name = name + end + + def run_user_config + run_config [File.expand_path("~/.#{@name}rc")] if ENV.key?("HOME") + end + + def run_local_config + rcs = [] + rcs.push ".#{@name}rc" + rcs.push "#{@name}.rc" + rcs.push "_#{@name}rc" + rcs.push "$#{@name}rc" + rcs.push "#{@name}rc" + run_config rcs + end + + private + def run_config(rcs) + catch(:EXIT) do + for rc in rcs + begin + load rc + throw :EXIT + rescue LoadError, Errno::ENOENT + rescue + print "load error: #{rc}\n" + print $!.class, ": ", $!, "\n" + for err in $@[0, $@.size - 2] + print "\t", err, "\n" + end + throw :EXIT + end + end + end + end +end + +#### set_attr +class Object + # Defines a singleton attribute. for testing purpose. + def set_attr(ivar_name, init_value) + eval("class << self; attr_accessor :#{ivar_name} end") + self.instance_variable_set("@#{ivar_name}", init_value) + end +end + +#### quote +class String + def quote(q="'") + %Q[#{q}#{self}#{q}] + end + + # This function is different from dump. + def dquote + quote('"') + end +end + + +#### assert-file +# Example1: +# es = AssertFile.new("testdata/shorten-test.e") +# el = AssertFile.new("testdata/shorten-test.el") +# system "shorten.rb -l #{el} -e #{es} testdata/shorten-test-input.e" +# assert_file(es) +# assert_file(el) +# +# Example2: +# assert_file(:expected=>expected, :actual=>actual, :no_remove=>true) +# +# Example3: +# AssertFile.transaction(expected) {|asf| +# system "foo input > #{asf}" +# } +class AssertFile + require 'fileutils' + @@basedir = nil + def self.basedir=(basedir) + @@basedir = basedir + end + + def self.transaction(*args, &block) + if block_given? + testcase = eval("self", block) + assert_files = args.map{|x| new(x) } + + if @@basedir + Dir.chdir(@@basedir) { yield assert_files } + else + yield(assert_files) + end + assert_files.each{|asf| testcase.assert_file(asf)} + else + raise ArgumentError, "must have block" + end + end + + + + + # new("expected_filename") + # new(:expeced=>"expected_filename", :actual=>"actual_filename") + # new(:expeced=>"expected_filename", :actual=>"actual_filename", :diff=>"diff") + + def initialize(arg) + require 'test/unit' + + case arg + when String # expected + @expected = arg + @actual = arg+".actual" + @diff = arg+".diff" + when Hash + @basedir = arg[:basedir] + @expected = arg[:expected] + @no_remove = arg[:no_remove] + + @actual = arg[:actual] || (@expected+".actual") + @diff = arg[:diff] || (@expected+".diff") + else + raise TypeError, "AssertFile.new: must be String or Hash." + end + @basedir ||= @@basedir + FileUtils.mkdir_p @basedir if @basedir + @expected = pathconv(@expected) + @actual = pathconv(@actual) + @diff = pathconv(@diff) + end + attr_accessor :expected, :actual, :diff, :no_remove + + def pathconv(path) + if @basedir + File.expand_path(path, @basedir) + else + path + end + end + + + def unlink_diff + File.unlink(diff) if File.exist?(diff) + end + + def make_diff + system "diff -u #{expected} #{actual} | tee #{diff}" + end + + def to_s + actual + end + alias :to_str :to_s +end + +module Test + module Unit + + # Use at Test::Unit::Assertions::AssertionMessage#convert + class System + def initialize(cmd) + @cmd = cmd + end + + def inspect + `#{@cmd}`.to_s + end + end + + module Assertions + def assert_file(assert_file, message=nil) + AssertFile === assert_file or assert_file = AssertFile.new(assert_file) + $>.sync = true + assert_file.unlink_diff + diff = System.new("diff -u #{assert_file.expected} #{assert_file.actual} | tee #{assert_file.diff}") + full_message = build_message(message, <#{content}" + end + + # Same as textarea_ize. But the string is not escaped. + # It is expected that the string is HTML. + def textarea_ize_noconv(cols=nil, rows=nil) + textarea_ize(cols, rows, false) + end + +end + +#### redirect +class << IO + # Redirect stdout to STDOUT and executes the block. + def redirect(stdout) + begin + stdout_sv = STDOUT.dup + STDOUT.reopen(stdout) + yield + ensure + STDOUT.flush + STDOUT.reopen(stdout_sv) + end + end +end + +#### system_to_string depend:redirect +# Similar to `` [backquotes]. If multiple arguments are given, the +# second and subsequent arguments are passed as parameters to command +# with no shell expansion. +require 'tmpdir' +require 'fileutils' + + + +# Commenting might cause problems! +# @@__system_to_string_count__ = 0 + + + +def system_to_string(*args) + begin + tmpf = File.join(Dir.tmpdir, "#{$$}-#{@@__system_to_string_count__}") + @@__system_to_string_count__ += 1 + ret = nil + open(tmpf,"w") do |f| + IO.redirect(f) { + system *args + } + end + File.read(tmpf) + ensure + FileUtils.rm_f tmpf + end +end + +#### EmacsLisp depend: system_to_string +module EmacsLisp + # Executes an EmacsLisp string by gnudoit. + def elisp(lisp) + system_to_string("gnudoit", lisp).chomp + end + + # Converts a Ruby string to EmacsLisp string. + # [imported from el4r] + def dump_string(string) + dumped = string.dup + # \ -> \\ + dumped.gsub! %r"\\" do '\\\\' end + # " -> \" + dumped.gsub! %r'"' do '\\"' end + # (zero byte) -> \0 + dumped.gsub! %r'\0' do "\\\0" end + %Q'"#{dumped}"' + end + +end + +#### flib +class Object + # Same as File.read. But FILENAME is expanded. + def readf(filename) + File.read( File.expand_path(filename.to_s) ) + end + + # Write an object's string form. FILENAME is expanded. + def writef(filename) + open(File.expand_path(filename.to_s), "w"){|f| f.write(self.to_s)} + end +end + +#### notify_exit +def notify_exit + # Notify when the program is exited. + at_exit do + bell_message "#$0 exited." + end +end + +#### region +class String + # Scans a regexp once. Then cut matched part from string. Returns the matched part. + def kill_region!(regexp) + ret = "" + sub!(regexp) { + ret = $& + "" + } + ret + end +end + +#### ext +class String + # Returns a string which is replaced the filename's extension with NEWEXT. + def ext(newext=nil) + if newext + newext[0,1] != '.' and newext="."+newext + sub(/\.[^\.]+?$/, newext) + else + File.extname(self) + end + end + + # Returns a string which is stripped the filename's extension. + def noext + sub(/\.[^\.]+$/,'') + end +end + +#### StructWithType +class StructWithType < Struct + + # TODO: document + def self.new(*args) + + keys = [] + types = [] + args.each_with_index do |x,i| + if i%2 == 0 + keys << x + else + types << x + end + end + + unless keys.length > 0 && + types.length > 0 && + keys.length == types.length + raise ArgumentError, "#{self}: args.length must be even" + end + + + klass = super(*keys) + + klass.instance_eval do + @@__type_dic__ = {} + @@__keys__ = keys + keys.each_with_index{|k,i| @@__type_dic__[k] = types[i]} + end + + klass + end + + def initialize(*args) + args.each_with_index do |x, i| + args[i] = __convert__(@@__keys__[i], x) + end + + class << self + @@__keys__.each do |k| + define_method("#{k}="){|v| self[k]=v} + end + end + + super *args + end + + def __convert__(k,v) + __send__(@@__type_dic__[k.to_sym],v) + end + private :__convert__ + + def []=(k,v) + v = __convert__(k,v) + super(k,v) + end + + +end + +#### ep +class String + # Expand tilde + def ep + case self + when /^~/ + File.expand_path(self) + else + self + end + end +end + + +#### change_home +class File + def self.change_home(dir) + oldhome = ENV['HOME'] + begin + ENV['HOME'] = dir + yield(dir) + ensure + ENV['HOME'] = oldhome + end + end +end + +#### mapf +module Enumerable + # + # "map function" + # enum.mapf(:x) + # is short for + # enum.map { |elt| elt.x } + # + def mapf(message) + self.map { |elt| elt.send(message) } + end +end + +#### build_hash +module Enumerable + # + # Like #map/#collect, but it generates a Hash. The block + # is expected to return two values: the key and the value for the new hash. + # numbers = (1..3) + # squares = numbers.build_hash { |n| [n, n*n] } # 1=>1, 2=>4, 3=>9 + # sq_roots = numbers.build_hash { |n| [n*n, n] } # 1=>1, 4=>2, 9=>3 + # + def build_hash + result = {} + self.each do |elt| + key, value = yield elt + result[key] = value + end + result + end + +end + +#### map_with_index +module Enumerable + # + # Same as Enumerable#map, but the index is yielded as well. See + # Enumerable#each_with_index. + # puts files.map_with_index { |fn, idx| "#{idx}. #{fn}" } + # print "Please select a file (0-#{files.size}): " + # + def map_with_index + result = [] + self.each_with_index do |elt, idx| + result << yield(elt, idx) + end + result + end +end + + + +#### bug! +class ScriptBug < Exception; end + +# Raises ScriptBug exception. +def bug!( message = 'must not happen' ) + raise ScriptBug, "\n[SCRIPT BUG] " + message +end + +#### must +class Object + + # Assert: type === obj + # ex. obj.must Fixnum, Float + def must( *args ) + args.each {|c| return self if c === self } + raise TypeError, "wrong arg type '#{self.class}' for required #{args.join('/')}" + end + + # Assert: obj.respond_to? meth + # ex. obj.must_have :read, :readlines + # ex. obj.needed :read, :readlines + def must_have( *args ) + args.each do |m| + self.respond_to? m or + raise ArgumentError, "receiver #{inspect} does not have '#{m}'" + end + self + end + + alias needed must_have + + # Assert: self == obj + def must_be( obj ) + self == obj or + raise ArgumentError, "expected #{obj.inspect} but is #{inspect}" + self + end + + # Assert: self != nil + def must_exist + nil? and raise ArgumentError, 'receiver is wrongly nil' + end + +end + + + +#### Contents +class GenericContents + require 'forwardable' + extend Forwardable + methods = String.instance_methods(false) - %w[to_s to_str] + def_delegators(:@to_s, *methods) + attr :to_s + alias :to_str :to_s +end + +# str = FileContents.new(filename) +class FileContents < GenericContents + def initialize(filename) + @to_s = File.read(filename) + end +end + +# str = URIContents.new(uri) +class URIContents < GenericContents + def initialize(uri) + require 'open-uri' + @to_s = URI(uri).read + end +end + + +#### show_usage +# Prints the script's first comment block. +def show_usage(msg=nil) + name = caller[-1].sub(/:\d+$/, '') + $stderr.puts "\nError: #{msg}" if msg + $stderr.puts + File.open(name) do |f| + while line = f.readline and line.sub!(/^# ?/, '') + $stderr.puts line + end + end + exit 1 +end + +#### UnTable +# Strip table-related tags in HTML +class UnTable + # Strip table-related tags in SRC + def untable!(src) + src.gsub!(%r!]*>!i,'') +# src.gsub!(%r!]*>!i,' ') + src.gsub!(%r!]*>!i,'') + src.gsub!(%r!]*>!i,'
') + src.gsub!(%r!!i, '') + src + end + + def untable(src) + untable!(src.dup) + end +end + + +#### system_safe +# mswin32 ruby's `system' workaround. +def system_safe(*x) + begin + system *x + rescue + end +end + + +#### unproc +class Object + def unproc(*x) + self + end +end + +class Proc + def unproc(*x) + call *x + end +end + +#### ConfigScript depend: set_attr, build_hash, unproc +require 'forwardable' +class ConfigScript + extend Forwardable + include Enumerable + def initialize(arg) + unless Hash === arg + eval(readf(arg.to_s)) + arg = instance_variables.map{|iv| + [iv[1..-1], instance_variable_get(iv)] + }.build_hash{|kv| kv} + end + + s_class = class << self; self end + arg.each do |k,v| + if Symbol === k + arg.delete k + k = k.to_s + arg[k]=v + end + + s_class.class_eval do + define_method(k) {arg[k].unproc} + define_method("#{k}=") {|v| arg[k]=v} + end + end + @hash = arg + end + + alias :[] :__send__ + def_delegators :@hash, :keys, :each + + def []=(k,v) + @hash[k.to_s]=v + end + + def method_missing(name, *args, &block) + nil + end +end + + +#### dump +class Numeric + def dump() self end +end + +#### __funcall +class Object + # Call a method even if it is private. + def __funcall(meth, *args, &block) + m = method(meth) + instance_eval { m.call(*args, &block) } + end +end + + +#### scriptdir +class Dir + def Dir.scriptdir + File.dirname(File.expand_path(caller(1)[0].scan(/^(.+?):\d+:/).to_s)) + end +end + +#### URLConv +module URLConv + def relative2absolute(html, baseurl) + # relativelink to absolute link + html.gsub!(/(href|src)=['"]?\s*(.*?)\s*?['"]?(>|\s)/mi) do |x| + begin + uri = URI.parse($2) + + absolute_url = if uri.scheme.nil? + URI.join(baseurl, $2) + else + $2 + end + + "#{$1}=\"#{absolute_url}\"#{$3}" + rescue URI::InvalidURIError + next + end + end + end +end + +#### END OF LIBRARY +# To add a new code (mylib-rb-add) + +#### test +if __FILE__==$0 + + class MylibCommand + def initialize + @lines = File.readlines($0) + end + + def run + meth = "do_#{ARGV[0]}" + if respond_to?(meth) + __send__ meth + else + do_list + end + end + + def do_list + @lines.select{|line| + line =~ /^ *(class|module|def|attr|attr_reader|attr_writer|attr_accessor) |^#### / + }.display + end + + def do_pieces + @lines.inject([]){|result, line| + if line =~ /^#### (.+?)/ + result + ["#{$1}\n"] + else + result + end + }.display + end + alias :do_piece :do_pieces + end + + MylibCommand.new.run +end + diff --git a/misc/emacs/el4r/el4r.el b/misc/emacs/el4r/el4r.el new file mode 100644 index 00000000..b50c3433 --- /dev/null +++ b/misc/emacs/el4r/el4r.el @@ -0,0 +1,814 @@ +;; A slimmed-down and network-ified version of the el4r project. +;; El4r was created by rubikitch, and was incorporated into Xiki +;; with his permission. + +; Define keys to reload and jump... +; This function is called from the Xiki config +(defun el4r-recommended-keys () + + ; Reload + (global-set-key (kbd "M-o") 'el4r-kill-and-restart) ; Alt+O, Meta+O, Option+O + (global-set-key [174] 'el4r-kill-and-restart) ; For Terminal.app, in emacs24 + (global-set-key [2222] 'el4r-kill-and-restart) ; For Terminal.app, in emacs22 + + ; Jump to log and show error + (global-set-key (kbd "M-m") 'el4r-jump-to-error) ; Show el4r error message > Option+M > Alt+M + (global-set-key [172] 'el4r-jump-to-error) ; For Terminal.app, in emacs24 + (global-set-key [2220] 'el4r-jump-to-error) ; For Terminal.app, in emacs22 + + + ; Set some keys for doing what lisp normally does - defined here because they need to exist even if the el4r load fails after this poirt + + (global-set-key (kbd "M-s") 'eval-last-sexp) ; Option+s > elisp script > Like emacs C-x C-e + (global-set-key (kbd "M-q") 'save-buffers-kill-emacs) ; quit, when Ctrl+Q doesn't work > Like emacs C-x C-q + (global-set-key (kbd "M-g") 'keyboard-quit) ; Escape out of emacs trouble > like Ctrl+G + ; Emacs help + (global-set-key (kbd "M-/") 'help) + + ; Enable and disable control lock (move these definitions to control-lock.el__?) + (global-set-key (kbd "M-C-L") 'control-lock-enable) + (global-set-key (kbd "M-L") 'control-lock-enable) + +) + +(defun ol (txt) + (el4r-ruby-eval + (if (stringp txt) + (concat "Ol \"" (replace-regexp-in-string "\n" "\\\\n" txt) "\"") ; String, so no need to inspect + ;; (concat "Ol \"" txt "\"") ; String, so no need to inspect + (concat "Ol \"" (replace-regexp-in-string "\"" "\\\\\"" (pp-to-string txt)) "\"") + ) + ) +) + +(or (>= emacs-major-version 21) + (error "Sorry, el4r requires (X)Emacs21 or later, because it uses weak hashes.")) + +(put 'el4r-ruby-error + 'error-conditions + '(error el4r-ruby-error)) + +(setq el4r-error-text "Type Alt+O (or Option+O) to reload xsh. Or quickly type esc then o, if that didn't work. Alt+M to show error message.") +;; (setq el4r-error-text "Type Alt+R (or Option+R) to reload xsh. Or quickly type esc then r, if that didn't work. Alt+M to show error message.") + +;; (setq el4r-error-text "Error! Type Alt+R (or Option+R) to reload or Alt+L (or Option+L) to show the log with the error message.") +(put 'el4r-ruby-error 'error-message el4r-error-text) + +(defvar el4r-ruby-program + "ruby" + "The name of Ruby binary.") + +(defvar el4r-instance-program (expand-file-name "~/src/el4r/bin/el4r-instance") + "Full path of el4r-instance.") + +(defvar el4r-instance-args nil) +(defvar el4r-debug-on-error nil) +(defvar el4r-coding-system nil) + +(defvar el4r-process nil) +(defvar el4r-process-name "el4r") +(defvar el4r-process-bufname "*el4r:process*") +(defvar el4r-call-level 0) +(defvar el4r-last-error-desc nil) + +(defvar el4r-ruby-object-ids nil) +(defvar el4r-ruby-object-weakhash nil) +(defvar el4r-defun-lambdas nil) +(defvar el4r-lisp-object-hash nil) +(defvar el4r-lisp-object-lastid 0) +(defvar el4r-lisp-object-gc-trigger-count 100) +(defvar el4r-lisp-object-gc-trigger-increment 100) + +(defun call-process-to-string (program &rest args) + (with-temp-buffer + (apply 'call-process program nil t nil args) + (buffer-string))) + +(defun call-process-and-eval (program &rest args) + (eval (read (apply 'call-process-to-string program args)))) + +(defun el4r-running-p () + (not (null el4r-process))) + +(unless (fboundp 'process-send-signal) + (defun process-send-signal (signal process-or-name) + (signal-process (process-id (get-process process-or-name)) signal)) + ) + +(defun el4r-boot (&optional noinit) + "Start el4r process, load ~/.el4r/init.rb, and prepare log buffer." + (interactive "P") + + ;(if (not (get-buffer "*el4r:log*")) + + (with-current-buffer (get-buffer-create el4r-process-bufname) (erase-buffer)) + (with-current-buffer (get-buffer-create "*el4r:log*") + (let ((buffer-read-only)) + (buffer-disable-undo) + (erase-buffer))) + + ; Start ruby and make it listen... + + (el4r-init) + + ; Load init.el... + + (el4r-ruby-eval + (if noinit "el4r_boot__noinit" "el4r_boot")) +) + +(defun el4r-shutdown () + "Shutdown el4r." + (interactive) + (when (el4r-running-p) + (el4r-ruby-eval "el4r_shutdown") + (process-send-signal 'SIGTERM (process-name el4r-process)) + (setq el4r-process nil) + )) + +(defun el4r-restart () + "Shutdown then start el4r." + (interactive) + (el4r-shutdown) + (el4r-boot)) + +(defun el4r-recover () + (interactive) + (with-current-buffer el4r-process-bufname + (erase-buffer))) + +(defun el4r-override-variables ()) + +(defun el4r-ruby18-error () + (global-set-key "\C-q" 'kill-emacs) + + (run-with-idle-timer 0.2 nil (lambda () + (switch-to-buffer "ruby 1.8 error") + (xterm-mouse-mode -1) + (delete-region (point-min) (point-max)) + (insert "Your ruby version is 1.8, which is a really old version. + +Ruby 1.9 or newer is required. Please install it, so that +when you run this, it shows ruby 1.9 or higher: + + $ ruby --version + +1. Type Ctrl+Q to exit xsh. +2. Then upgrade ruby. +3. Then try running xsh again. + +You may need to do something like: + + $ sudo apt-get install ruby1.9.3 + $ sudo ln -sf /usr/bin/ruby1.9.3 /usr/bin/ruby + +") + )) + nil +) + +(defun el4r-init () + ;; In many cases (eq el4r-process (get-buffer-process el4r-process-bufname)) + ;; But this sexp is nil when el4r-instance is accidentally dead. + (and (get-buffer-process el4r-process-bufname) (el4r-shutdown)) + + (setq + el4r-lisp-object-gc-trigger-count 100 + el4r-lisp-object-gc-trigger-increment 100 + el4r-ruby-gc-trigger-count 100 + el4r-ruby-gc-trigger-increment 100 + el4r-log-buffer "*el4r:log*" + el4r-output-buffer "*el4r:output*" + el4r-unittest-lisp-object-gc-trigger-count 5000 + el4r-unittest-lisp-object-gc-trigger-increment 5000 + el4r-unittest-ruby-gc-trigger-count 5000 + el4r-unittest-ruby-gc-trigger-increment 5000 + el4r-temp-file "/var/folders/66/3h81t2y913vf344z6fb2cks00000gn/T/el4r-tmp.tmp" + + el4r-instance-program (concat (getenv "XIKI_DIR") "/misc/emacs/el4r/el4r-instance") + + ) + + (el4r-override-variables) + (setq el4r-lisp-object-hash (make-hash-table :test 'eq)) + (setq el4r-ruby-object-weakhash (make-hash-table :test 'eq :weakness 'value)) + (setq el4r-defun-lambdas nil) + + (let ((buffer el4r-process-bufname) + (process-connection-type nil)) ; Use a pipe. + (and (get-buffer buffer) (kill-buffer buffer)) + (get-buffer-create buffer) + (with-current-buffer buffer + (buffer-disable-undo) + + (setq el4r-process + (if (and (boundp 'xiki-no-socket) xiki-no-socket) + + ; We're told not to use a socket, so start our own ruby process... + + (apply 'start-process el4r-process-name buffer + el4r-ruby-program el4r-instance-program el4r-instance-args) + + + ; Else, connect to separate ruby process, or start it... + + (condition-case err + + + + + ;; (make-network-process :remote (expand-file-name "~/.xikisock") :name el4r-process-name :buffer buffer) + (make-network-process :remote (expand-file-name "~/.xikisock") :name el4r-process-name :buffer buffer :service t) + + + + + + (file-error + (let ((process-name (start-process "xiki-forker" (get-buffer-create "*xiki-forker*") el4r-ruby-program el4r-instance-program "forker"))) + (accept-process-output process-name) + ) + + ; If the script returned (lisp code) while forking, it's probably an error about ruby 1.8, so eval it + (with-current-buffer "*xiki-forker*" + (when (and (> (point-max) 1) (string= (buffer-substring 1 2) "(")) + ;; (let ((txt (el4r-scan-expr-from-ruby-inner))) (eval (read txt))) + (eval (read (el4r-scan-expr-from-ruby-inner))) + ) + ) + + (message "el4r > tried 2nd time") + ;; (message "before") + ;; (message buffer) + (make-network-process :remote (expand-file-name "~/.xikisock") :name el4r-process-name :buffer buffer :service t) + ;; (message "after") + ) + (error + (message "el4r > unspecified error when connecting") + (signal (car err) (cdr err)) + ) + ) + ) + + ) + + (and el4r-coding-system + (set-process-coding-system el4r-process + el4r-coding-system el4r-coding-system))) + (setq xiki-child-pid (el4r-ruby-eval "Process.pid")) + + (message "") + )) + + +(defun el4r-check-alive () + (or (eq (process-status el4r-process) 'run) (eq (process-status el4r-process) 'open) + (error el4r-error-text))) + ;; (error "el4r-instance is dead."))) + +; Grabs elisp code from the *el4r:process* buffer +(defun el4r-scan-expr-from-ruby () + (with-current-buffer el4r-process-bufname + (el4r-scan-expr-from-ruby-inner) + ) +) + +; Pulls (ruby code) out of the current buffer, up until ^@. +(defun el4r-scan-expr-from-ruby-inner () + (goto-char (point-min)) + (save-match-data + (let ((point-after-zero (search-forward "\0" nil t)) + expr) + (if point-after-zero + (progn + (setq expr + (buffer-substring (point-min) (- point-after-zero 1))) + (delete-region (point-min) point-after-zero) + expr + ) + ) + ) + ) +) + + +; Alternate method that loops and continually checks to see if el4r is +; still running. If we hit escape, it cancels out of it. +; We're using this all the time now, because it allows user to interrupt +; long-running operations by typing esc. + +(setq el4r-taking-a-while-message nil) + +(if t + + ; Work-around + (defun el4r-recv () + (let ((lock-time 20000) (expr) (deactivate-mark-orig deactivate-mark) (limit 0) (succeeded t)) + + ; 1. Check frequently for a couple seconds, with no chance of interruption... + + (while (and (< limit lock-time) (eq nil (progn (setq expr (el4r-scan-expr-from-ruby)) expr))) + (el4r-check-alive) + (sleep-for 0.00001) + (setq limit (1+ limit)) + ) + + ; 2. Exceeded that, so check somewhat frequently, watching for an interruption... + + (when (>= limit lock-time) + + (message (or el4r-taking-a-while-message "Command taking a while. You can press esc to cancel.")) + + (setq succeeded (sit-for 0.1)) + + (while (eq nil (progn (setq expr (el4r-scan-expr-from-ruby)) expr)) + ; Wait a bit + (setq succeeded (sit-for 0.1)) + + ; Original > check for escape and following events + (if (and (not succeeded) (next-event-is-escape)) + (progn + + ; Move to the beginning of the command, to indicate it's cancelled + (beginning-of-line) + (skip-chars-forward " -$%") + (setq quit-flag t) + + ) + ; Re-display message > it's going away for some reason + (message (or el4r-taking-a-while-message "Command taking a while. You can press esc to cancel.")) + ) + + ; Else, keep on going + + ) + (message "") ; To clear "Command taking a while..." message + ) + + (if (not deactivate-mark-orig) ; If mark wasn't set to deactivate before, don't let the above elisp deactivate it + (setq deactivate-mark nil) + ) + expr + )) + + ; Original method > disabled for now + (defun el4r-recv () + (let ((expr) (deactivate-mark-orig deactivate-mark)) + (while (eq nil (progn (setq expr (el4r-scan-expr-from-ruby)) expr)) + (el4r-check-alive) + (accept-process-output el4r-process)) + (if (not deactivate-mark-orig) ; If mark wasn't set to deactivate before, don't let the above elisp deactivate it + (setq deactivate-mark nil) + ) + expr)) + +) + +(defun el4r-send (rubyexpr) + (el4r-check-alive) + (process-send-string el4r-process rubyexpr) + (process-send-string el4r-process "\0\n")) + +(defvar el4r-error-lisp-expression nil) +(defun el4r-get () + (let ((result (el4r-recv)) expr) + (while (eq (length result) 0) + (el4r-wait-expr) + (setq result (el4r-recv))) + (condition-case err + (eval (setq expr (read result))) + (el4r-ruby-error (signal 'el4r-ruby-error nil)) + (error (setq el4r-error-lisp-expression expr) + (signal (car err) (cdr err)))) + )) + +(defun el4r-signal-last-error () + (signal (car el4r-last-error-desc) (cdr el4r-last-error-desc))) + +(defun el4r-enter-call () (setq el4r-call-level (+ el4r-call-level 1))) +(defun el4r-leave-call () (setq el4r-call-level (- el4r-call-level 1))) +(defun el4r-callback-p () (not (eq el4r-call-level 0))) +(defun el4r-send-interrupt () (el4r-send "")) + +(defun el4r-no-properties (str) + (setq str (copy-sequence str)) + (set-text-properties 0 (length str) nil str) + str) + +(defvar el4r-treat-ctrl-codes nil) + +(defvar el4r-temp-file nil) +(defvar obj nil) +(defsubst el4r-string-to-rubystr (str) + (let ((file-read "File.read(conf.temp_file)")) + (if (or (not el4r-treat-ctrl-codes) + (string= str file-read)) + (concat "%q" (prin1-to-string (el4r-no-properties obj))) + (cond ((eq el4r-treat-ctrl-codes 'use-file) ;experimental + ;; !FIXME! coding-system @ XEmacs + (with-temp-buffer + (insert str) + ;; suppress "wrote file-name" message + ;; (find-efunctiondescr 'write-region "VISIT is neither") + (write-region 1 (point-max) el4r-temp-file nil 0)) + file-read) + (t + (concat "%Q" + (with-temp-buffer + (insert (prin1-to-string (el4r-no-properties str))) + (mapcar (lambda (x) + (goto-char 1) + (while (search-forward (car x) nil t) + (replace-match (cdr x)))) + '(("#" . "\\\\#") + ("\003" . "\\\\cc") + ("\004" . "\\\\cd") + ("\021" . "\\\\cq") + ("\023" . "\\\\cs") + ("\026" . "\\\\cv") + ("\027" . "\\\\cw") + ("\031" . "\\\\cy") + ("\032" . "\\\\cz") + )) + (buffer-string))) + + ))))) + + +(defun el4r-proper-list-p (expression) + ;; Tell if a list is proper, id est, that it is `nil' or ends with `nil'. + (cond ((not expression)) + ((consp expression) (not (cdr (last expression)))))) + +(defun el4r-lisp2ruby (obj) + (cond ((eq obj nil) "nil") + ((eq obj t) "true") + ((numberp obj) (number-to-string obj)) + ((stringp obj) (el4r-string-to-rubystr obj)) + ((el4r-rubyexpr-p obj) (el4r-rubyexpr-string obj)) + ((el4r-rubyobj-p obj) + (format "el4r_rubyobj_stock.id2obj(%s)" + (el4r-rubyobj-id obj))) + ((el4r-proper-list-p obj) + (format "el4r_elobject_new(%d, ELListCell)" + (el4r-lisp-object-to-id obj))) + ((and (consp obj) (atom (cdr obj))) + (format "el4r_elobject_new(%d, ELConsCell)" + (el4r-lisp-object-to-id obj))) + ((vectorp obj) + (format "el4r_elobject_new(%d, ELVector)" + (el4r-lisp-object-to-id obj))) + (t + (format "el4r_elobject_new(%d)" + (el4r-lisp-object-to-id obj))) + )) + +(defun el4r-wait-expr () + (el4r-enter-call) + (let (evaled rubyexpr) + (condition-case err + (progn (setq evaled (el4r-get)) + (setq rubyexpr (el4r-lisp2ruby evaled))) + (el4r-ruby-error (setq rubyexpr "el4r_reraise_last_error")) + (error (setq el4r-last-error-desc err) + (setq rubyexpr "el4r_raise_lisp_error"))) + (el4r-send rubyexpr)) + (el4r-leave-call)) + +(defun el4r-ruby-eval (rubyexpr) + (and (eq 0 (length rubyexpr)) (error "Empty expression is not evaluatable.")) + (and (el4r-callback-p) (el4r-send-interrupt)) + (el4r-enter-call) + (el4r-send rubyexpr) + (let ((result + (let ((inhibit-quit t) (result2 nil) (succeeded nil)) + (with-local-quit + (setq result2 (el4r-get)) + (setq succeeded t) ; If it gets here, it succeeded + ) + (if succeeded + result2 + (progn + ; Do stuff when C-g aborted half way through... + + (setq quit-flag nil) + + ; Send signal to raise exception Kill process and start new? + (signal-process xiki-child-pid 'SIGUSR1) + + ) + ) + ) + + )) + (el4r-leave-call) + result + ) +) + +(defun el4r-lisp-object-from-id (id) + (or (gethash id el4r-lisp-object-hash) + (error "No such object for given ID: %d" id))) + +(defun el4r-lisp-object-to-id (obj) + (el4r-gc-lisp-objects-if-required) + (let ((id el4r-lisp-object-lastid)) + (setq el4r-lisp-object-lastid (+ id 1)) + (puthash id obj el4r-lisp-object-hash) + id + )) + +(defun el4r-garbage-collect () + "Force garbage collection for el4r." + (interactive) + (el4r-gc-lisp-objects) + (el4r-ruby-eval "el4r_rubyobj_stock.garbage_collect") + (message "el4r garbage collected")) + + +(defun el4r-gc-lisp-objects () + (let ((ids (el4r-ruby-call nil 'el4r_get_garbage_lispobj_ids))) + (while ids + (remhash (car ids) el4r-lisp-object-hash) + (setq ids (cdr ids)) + ))) + +(defun el4r-gc-lisp-objects-if-required () + (if (>= (hash-table-count el4r-lisp-object-hash) + el4r-lisp-object-gc-trigger-count) + (progn (el4r-gc-lisp-objects) + (setq el4r-lisp-object-gc-trigger-count + (+ (hash-table-count el4r-lisp-object-hash) + el4r-lisp-object-gc-trigger-increment))) + )) + +(defun el4r-rubyobj-p (rubyobj) + (and (listp rubyobj) (eq (car rubyobj) 'el4r-rubyobj))) +(defun el4r-rubyobj-id (rubyobj) + (cdr rubyobj)) +(defun el4r-rubyobj-create (id) + (let ((rubyobj (cons 'el4r-rubyobj id))) + (setq el4r-ruby-object-ids (cons id el4r-ruby-object-ids)) + (puthash id rubyobj el4r-ruby-object-weakhash) + rubyobj)) +(defun el4r-rubyobj-get-alive-ids () + (garbage-collect) + + ;; Introduce new variable `el4r-ruby-object-ids' and stop using + ;; maphash to avoid fatal exception. I do not know why maphash + ;; causes fatal. This idea is borrowed from pymacs. + (let ((ids el4r-ruby-object-ids) + used-ids) + (while ids + (let ((id (car ids))) + (setq ids (cdr ids)) + (if (gethash id el4r-ruby-object-weakhash) + (setq used-ids (cons id used-ids)) + ))) + (setq el4r-ruby-object-ids used-ids) + used-ids)) + +(defun el4r-rubyexpr-p (rubyexpr) + (and (listp rubyexpr) (eq (car rubyexpr) 'el4r-rubyexpr))) +(defun el4r-rubyexpr-string (rubyexpr) + (cdr rubyexpr)) +(defun el4r-rubyexpr-quote (string) + (cons 'el4r-rubyexpr string)) + +;; disabled +' (defun el4r-list-to-rubyseq (list) + (let (tokens) + (while list + (setq tokens + (cons ", " (cons (el4r-lisp2ruby (car list)) tokens))) + (setq list (cdr list))) + (setq tokens (nreverse (cdr tokens))) + (apply 'concat tokens))) +(defun el4r-list-to-rubyseq (list) + (mapconcat (lambda (x) + (el4r-lisp2ruby x)) + list ", ")) + +(defun el4r-list-to-rubyary (list) + (el4r-rubyexpr-quote (format "[%s]" (el4r-list-to-rubyseq list)))) +(defun el4r-cons-to-rubyary (cons) + (el4r-list-to-rubyary (list (car cons) (cdr cons)))) + +(defalias 'el4r-vector-to-rubyseq 'el4r-list-to-rubyseq) +(defalias 'el4r-vector-to-rubyary 'el4r-list-to-rubyary) + +(defun el4r-ruby-call (receiver name &rest args) + "Invoke ruby's method. (RECEIVER can be nil.)" + (setq name (cond ((symbolp name) (symbol-name name)) + ((stringp name) name) + (t (error "Invalid value for method name: %s" name)))) + (setq receiver (cond ((eq receiver nil) "self") + (t (format "el4r_rubyobj_stock.id2obj(%s)" + (el4r-rubyobj-id receiver))))) + (el4r-ruby-eval (format "%s.%s(%s)" + receiver + name + (el4r-list-to-rubyseq args)))) + +(defun el4r-lambda-for-rubyproc (rubyproc-id &rest preform) + (let ((lmd (eval (append '(lambda (&rest args)) ;; Lazy list play! + preform + (list (list 'el4r-ruby-call-proc-by-id + rubyproc-id + 'args))) + ))) + (setq el4r-ruby-object-ids (cons rubyproc-id el4r-ruby-object-ids)) + (puthash rubyproc-id lmd el4r-ruby-object-weakhash) + lmd)) + + +(unless (fboundp 'region-active-p) + (defun region-active-p () + (and transient-mark-mode mark-active))) + +(defun el4r-ruby-call-proc-by-id (rubyproc-id args) + (el4r-ruby-eval (format "el4r_rubyobj_stock.id2obj(%s).call(%s)" + rubyproc-id (el4r-list-to-rubyseq args))) +) + + +(defun el4r-ruby-call-proc (rubyproc &rest args) + (el4r-ruby-call-proc-by-id (el4r-rubyobj-id rubyproc) args)) + +(defun el4r-ruby-eval-prompt (expr) + "Read and execute Ruby code." + (interactive "Eval Ruby: ") + (message (prin1-to-string (el4r-ruby-eval expr))) + ) + +(defun el4r-ruby-eval-region (point mark) + "Execute the region as Ruby code." + (interactive "r") + (el4r-ruby-eval-prompt (buffer-substring point mark))) + +(defun el4r-ruby-eval-buffer () + "Execute the buffer as Ruby code." + (interactive) + (el4r-ruby-eval-prompt (buffer-string))) + +(defun el4r-debug-ruby-eval-report (expr) + (interactive "Eval Ruby: ") + (insert (format "%s\n => %s\n" + expr + (prin1-to-string (el4r-ruby-eval expr))))) + +(defun el4r-register-lambda (func) + (setq el4r-defun-lambdas (cons func el4r-defun-lambdas))) + +(defun el4r-define-function (name func) + (fset name func) + (el4r-register-lambda func) + nil) + + +(defun el4r-kill-and-restart () (interactive) + "Load .emacs (reloading EmacsRuby)" + + ;(message "--1") + + (prin1 default-directory) + + (if (not (file-exists-p default-directory)) + (error (concat "This directory no longer exists!: " default-directory)) + ) + + + (let ((lock (control-lock-enabled))) + + ; Only kill if xiki running + (when (or (eq (process-status el4r-process) 'run) (eq (process-status el4r-process) 'open)) + (el4r-ruby-eval "Xiki.kill") + ; Give it time to die, so we don't error when trying to use ~/.xikisock again + (sleep-for 0.1) + ) + + ;(message "--6") + ; Kill el4r process buffer + (when (get-buffer "*el4r:process*") + (let (kill-buffer-query-functions) ; So it doesn't prompt us to delete the buffer + (kill-buffer "*el4r:process*") + ) + ) + ;(message "--8") + + ; Kill el4r buffer for async saving (so it'll get recreated upon next save) + (when (get-buffer "*fork-and-eval*") + (let (kill-buffer-query-functions) ; So it doesn't prompt us to delete the buffer + (kill-buffer "*fork-and-eval*") + ) + ) + + ;(message "--9") + (load-file (concat (getenv "XIKI_DIR") "/misc/emacs/xsh.emacs")) + ;(message "--10") + + ; Re-do client-specific init stuff with this new process clone + (el4r-ruby-eval "Xiki.init_in_client") + ;(message "--11") + + + (message "reloaded") + ) +) + +(defun control-lock-enabled () + (and (boundp control-lock-mode-p) control-lock-mode-p) +) + +(defun el4r-jump-to-error () + (interactive) + "Go to EmacsRuby error" + + + ; Switch to the last view (just so it's not in the top-left) + (select-window (frame-first-window)) + (other-window -1) + + ; Display error log + (find-file el4r-log-path) + (revert-buffer t t t) + (setq truncate-lines t) + (end-of-buffer) + + (re-search-backward "^ from ") + (re-search-backward "^\\S-") + (recenter 0) + +) + +(defun el4r-fork-and-eval (ruby callback) + (setq el4r-fork-and-eval-callback callback) + (make-network-process :remote (expand-file-name "~/.xikisock") :name "fork-and-eval" :buffer "*fork-and-eval*" :service t) + + (defun el4r-fork-and-eval-filter (process output) + (setq el4r-fork-and-eval-output output) + + ;; (message "-----------output received") + ;; (prin1 output) + ; Remove trailing ^@ and surrounding quotes, and unescape + (setq output (replace-regexp-in-string "\0$" "" output)) + (setq output (replace-regexp-in-string "^\"\\(.*\\)\"$" "\\1" output)) + (setq output (url-unhex-string output t)) + ;; (message "") + ;; (prin1 output) + + (cond + ; Just single ^@, so do nothing... + ;; ((string= output "\0") + ((string= output "") + ;; (message "-----------1") + nil + ) + ; Multiple lines, so show them in a view + ((string-match "\n" output) + ;; (message "-----------2") + ;--- + (setq output (url-unhex-string output t)) + + (prin1 output) + (message "") + + + + (split-window-vertically) + (other-window 1) + (switch-to-buffer "My Message") + (insert output) + (beginning-of-buffer) + + ;; (el4r-ruby-eval el4r-fork-and-eval-callback) + ;--- + ) + ; Single line, so just echo it + (t + ;; (message "-----------3") + (message output) + ) + ) + + + + + + + + t ; In case return value matters + ;(el4r-ruby-eval callback) + ) + + (set-process-filter (get-process "fork-and-eval") 'el4r-fork-and-eval-filter) + (process-send-string (get-process "fork-and-eval") + ; Send code to ruby process to eval + ; (concat ruby "\0\n") + ; (concat "Xiki::Code.fork_and_eval_wrapper #{ruby.inspect}" "\0\n") + (concat "Xiki::Code.fork_and_eval_wrapper " ruby "\0\n") + ) +) + + +(provide 'el4r) diff --git a/misc/emacs/el4r/init.rb b/misc/emacs/el4r/init.rb new file mode 100644 index 00000000..c9d951d8 --- /dev/null +++ b/misc/emacs/el4r/init.rb @@ -0,0 +1,92 @@ +$LOAD_PATH << "#{XIKI_DIR}/lib" + +require 'xiki' +Xiki.init + +$el.message "" if $el # Keep it from showing junk at bottom + +module ::Xiki + + # init.rb gets called twice (before and after the fork): + # 1. Once to pre-load stuff to improve performance. + # 2. Again after preloaded process is forked (each time xsh is run). + + class << self + attr_accessor :before_fork + end + self.before_fork = ! $el + + if self.before_fork + + # We're in the initial run, so pre-load and cache some stuff and exit... + # Ruby classes are loading without an editor. + # $el won't exist the first time through, so exit + # (we just wanted to load the classes and then fork). + + # Make caching version of $el, and generate some lisp for improved subsequent startup time + $el = El4r::ELInstance.new nil, nil, StringIO.new + KeyShortcuts.jump_keys + Color.define_styles + Effects.define_styles + FileTree.define_styles + Notes.define_styles + Notes.apply_styles + + end +end + +if ! ::Xiki.before_fork + module ::Xiki + + KeyShortcuts.keys + Keys.el4r_init + KeyShortcuts.right_click # Use default key shortcuts + + # Move this into a theme? + $el.tool_bar_mode(-1) if Environment.gui_emacs + + if Styles.dark_bg? + + $el.el4r_lisp_eval %<(progn + (set-face-attribute (make-face 'ediff-fine-diff-A) nil :background "#990000" :foreground "#ffffff") + (set-face-attribute (make-face 'ediff-current-diff-A) nil :background "#440000" :foreground "#ff6666") + + (set-face-attribute (make-face 'ediff-even-diff-A) nil :background "#220000" :foreground "#dd4444") + (set-face-attribute (make-face 'ediff-odd-diff-A) nil :background "#221111" :foreground "#dd4444") + + (set-face-attribute (make-face 'ediff-fine-diff-B) nil :background "#228800" :foreground "#ffffff") + (set-face-attribute (make-face 'ediff-current-diff-B) nil :background "#2c4400" :foreground "#44dd33") + + (set-face-attribute (make-face 'ediff-even-diff-B) nil :background "#0c2200" :foreground "#33cc22") + (set-face-attribute (make-face 'ediff-odd-diff-B) nil :background "#1a2211" :foreground "#33cc22") + )> + end + + end + + elvar.javascript_mode_map = make_sparse_keymap unless $el.boundp :javascript_mode_map + + module ::Xiki + + $el.el4r_lisp_eval "(set-frame-parameter nil 'alpha '(100 100))" + + if true + Styles.define :isearch, :bg=>"0c0", :fg=>"bfb", :bold=>1 # Light Green + + Styles.define :lazy_highlight, :bg=>"006", :fg=>"33f", :bold=>1 # Blue + else + Styles.define :isearch, :bg=>"3b0", :fg=>"fff", :bold=>1 + Styles.define :lazy_highlight, :bg=>"8d7", :fg=>"fff", :bold=>1 + end + + end + + elvar.sort_fold_case = true # To avoid stupid bug + + module ::Xiki + # Try loading user's yours.rb if it exists + yours_rb = File.expand_path "~/.xiki/misc/yours.rb" + Code.load yours_rb if File.exists? yours_rb + end + +end diff --git a/misc/emacs/el4r/stdlib.rb b/misc/emacs/el4r/stdlib.rb new file mode 100644 index 00000000..608ad52f --- /dev/null +++ b/misc/emacs/el4r/stdlib.rb @@ -0,0 +1,37 @@ + +# +## [2005/06/27] winconf-push +@winconf_stack = [] +defun(:winconf_push, :interactive=>true) do + funcall(:message, "pushd winconf") + @winconf_stack.push([current_window_configuration, point]) +end + +# +## [2005/06/27] winconf-pop +defun(:winconf_pop, :interactive=>true) do + winconf, pt = @winconf_stack.pop + if winconf + set_window_configuration(winconf) + goto_char pt + funcall(:message, "popped winconf") + else + funcall(:message, "winconf_pop: winconf stack is empty") + end +end + +# +## [2005/06/27] winconf-command +defun(:winconf_command, :interactive=>"P") do |arg| + if arg + winconf_push + else + winconf_pop + end +end + +# + +## / Local Variables +## / eeb-defaults: (eeel4r ee-delimiter-hash nil t t) +## / End: diff --git a/misc/emacs/libs/control-lock.el b/misc/emacs/libs/control-lock.el new file mode 100644 index 00000000..6f80fa52 --- /dev/null +++ b/misc/emacs/libs/control-lock.el @@ -0,0 +1,160 @@ +;;; control-lock.el --- Like caps-lock, but for your control key. Give your pinky a rest! + +(defun control-lock-letter (l ch) + "Called when keys are pressed. If we deem control-lock to be enabled, it returns the control-version of the key. Otherwise it just returns the key." + + ; If it's not as escape sequence + + (let* ((this-keys (this-command-keys)) (this-keys (if (stringp this-keys) (string-to-char this-keys) ""))) + (if (and (not (eq this-keys 27)) (control-lock-enabled-p)) + ch l + ) + ) +) + +(defun control-lock-enabled-p () + "Returns whether control lock should be enabled at a given point" + (and control-lock-mode-p + ; If not disable once (turning off if set) + (if control-lock-disable-once + (progn + (setq control-lock-disable-once nil) + nil ; Not enabled this time + ) + t ; It's enabled as far as we know here + ) + + ; not defined and true + (not (and (boundp 'xiki-filter-options) xiki-filter-options)) + + (not cursor-in-echo-area) + (not isearch-mode) + (not (string-match "\\*Minibuf" (buffer-name))))) + +; Make ctrl-lock be off by default +(setq control-lock-mode-p nil) + +(setq control-lock-disable-once nil) + +(defun control-lock-map-key (l ch fun &optional shift disable) + "Makes function to handle one key, and maps it to that key" + + (eval (read + (concat + "(progn " + "(defun control-lock-" fun " (p) " + "(setq control-lock-shift " (if shift "t" "nil") ")" + "(control-lock-letter (kbd \"" l "\") (kbd \"" ch "\"))" + ") " + "(define-key key-translation-map (kbd \"" l "\") 'control-lock-" fun ")" + ")" + )))) + +; Map lowercase keys +(let ((c ?a) s) + (while (<= c ?z) + (setq s (char-to-string c)) + (control-lock-map-key s (concat "C-" s) s) + (setq c (+ c 1)))) + + + +; Temp > Map uppercase keys to control also +(let ((c ?A) s) + (while (<= c ?Z) + (setq s (char-to-string c)) + (control-lock-map-key s (concat "C-" (downcase s)) s t) + (setq c (+ c 1)))) + + +; ; Map uppercase keys to lowercase +; (let ((c ?A) s) +; (while (<= c ?Z) +; (setq s (char-to-string c)) +; (control-lock-map-key s (downcase s) s t) +; (setq c (+ c 1)))) + + +; Map numbers +(let ((c ?0) s) + (while (<= c ?9) + (setq s (char-to-string c)) + (control-lock-map-key s (concat "C-" s) s) + (setq c (+ c 1)))) + + +; Map misc keys +(control-lock-map-key "'" "C-'" "apostrophe") +(control-lock-map-key "," "C-," "comma") +(control-lock-map-key "`" "C-`" "backtick") +(control-lock-map-key "\\\\" "C-\\\\" "backslash") + + +(control-lock-map-key "/" "C-/" "slash") +(control-lock-map-key "SPC" "C-@" "space") +(control-lock-map-key "[" "C-[" "lsqrbracket") +(control-lock-map-key "]" "C-]" "rsqrbracket") +(control-lock-map-key ";" "C-;" "semicolon") +(control-lock-map-key "." "C-." "period") +(control-lock-map-key "=" "C-=" "equals") +(control-lock-map-key "-" "C--" "dash") + + +(defun control-lock-keys () + "Sets default keys - C-z enables control lock." + (global-set-key (kbd "C-,") 'control-lock-enable)) + +(defun control-lock-apply-bar-color () + (if (boundp 'control-lock-color) (progn + + ; Remember old color unless it's already orange (sometimes we're called after a theme that didn't change the bg, and control lock might have been on) + (let ((color (face-attribute 'mode-line :background))) + (if (not (string= control-lock-color color)) + (set 'control-lock-color-old color) + ) + ) + (let ((color (face-attribute 'mode-line-transparent-activatable :background))) + (if (not (string= control-lock-color-transparent color)) + (set 'control-lock-color-transparent-old color) + ) + ) + + (set-face-attribute 'mode-line nil :background control-lock-color) + (set-face-attribute 'mode-line-transparent-activatable nil :background control-lock-color-transparent) + (set-face-attribute 'mode-line-transparent-underline-activatable nil :background control-lock-color-transparent) + + (set-face-attribute 'mode-line-transparent-activatable nil :foreground "#ffc") + (set-face-attribute 'mode-line-transparent-underline-activatable nil :foreground "#ffc") + + (set-face-attribute 'mode-line-transparent-underline-activatable nil :underline nil) + + )) +) + +(defun control-lock-enable () (interactive) + "Enable control lock. This function should be mapped to the key the user uses to enable control-lock." + (if control-lock-mode-p ; If control lock was enabled, disable it + (progn + (setq control-lock-mode-p nil) + (save-window-excursion ; Unnecessary, but forces cursor to refresh immediately + (customize-set-variable 'cursor-type '(bar . 3))) + + (when (and (boundp 'control-lock-color-old) control-lock-color-old) + (set-face-attribute 'mode-line nil :background control-lock-color-old) + (set-face-attribute 'mode-line-transparent-activatable nil :background control-lock-color-transparent-old) + (set-face-attribute 'mode-line-transparent-underline-activatable nil :background control-lock-color-transparent-old) + + (set-face-attribute 'mode-line-transparent-activatable nil :foreground "#999") + (set-face-attribute 'mode-line-transparent-underline-activatable nil :foreground "#999") + (set-face-attribute 'mode-line-transparent-underline-activatable nil :underline t) + ) + ) + (progn ; Else, enable it + (setq control-lock-mode-p t) + (save-window-excursion ; Unnecessary, but forces cursor to refresh immediately + (customize-set-variable 'cursor-type '(hbar . 3))) + + (control-lock-apply-bar-color) + ))) + +(provide 'control-lock) diff --git a/misc/emacs/libs/normal-escape.el b/misc/emacs/libs/normal-escape.el new file mode 100644 index 00000000..5f101c3c --- /dev/null +++ b/misc/emacs/libs/normal-escape.el @@ -0,0 +1,144 @@ +(setq normal-escape-enabled t) ; Makes sure we enable escape checking at the very beginning +(setq xiki-escape-override nil) ; Gives other code a chance to control what's run when esc + +; 1. define function for when esc is pressed: +(defun normal-escape () (interactive) + + ; Pull in all events that come in quickly + (let ((events '()) next (re-add-escape t) (keep-going t)) + + ; Always get first char after escape... + + (when (setq next (read-event nil nil 0.15)) (push next events)) + + ; Keep reading subsequent chars until > nil or esc... + + ;; (let ((keep-going t)) + (while keep-going + (setq next (read-event nil nil 0.05)) + (when next + (push next events) + ) + ; esc or nil, so quit + (when (or (equal next nil) (equal next 27)) + (setq keep-going nil) + ) + ) + + (if (= (length events) 0) + (progn + (setq normal-escape-enabled t) ; Makes sure we re-enable escape checking + + (cond + + ; Over-ride set, so just call it + ; Todo > call callback if there (and wasn't esc) + (xiki-escape-override + (apply xiki-escape-override '()) + (setq xiki-escape-override nil) + ) + + ; In the minibuffer, so just quit + ; Not sure if we get called when in the minibuffer? + ((window-minibuffer-p) + (keyboard-escape-quit) + ) + (mark-active + (goto-char (mark)) + (deactivate-mark) + (message "") ; So it doesn't show "Cleared Mark" + ) + (t + (el4r-ruby-eval "Xiki::ControlTab.go :from_escape=>1") + ) + ) + + ) + + ; Recent event happened right after, so just put it back in effect... + + ; It wasn't esc, so delete the over-ride if there was one, because it was meant for only esc + (setq xiki-escape-override nil) + + ; Todo > 1. > remove (99 59 53 57 59 48 62 91) from end + + ; Remove weird event from queue + + ; Xterm sends this > when you type too quickly + (when (equal (last events 11) '(99 48 59 55 57 50 59 49 52 62 91)) + (setq events (butlast events 11)) + (setq re-add-escape nil) + ) + + ; Terminal.app sends this > when you type too quickly + (when (equal (last events 6) '(99 50 59 49 63 91)) + (setq events (butlast events 6)) + (setq re-add-escape nil) + ) + + ; Iterm sends this > when you type too quickly + (when (equal (last events 8) '(99 59 53 57 59 48 62 91)) + (setq events (butlast events 8)) + (setq re-add-escape nil) + ) + + ; Todo > 2. > only do this code if > any left + + (when (> (length events) 0) ; Check again, since we removed a potential weird event + + (dolist (event events) + (setq unread-command-events (cons event unread-command-events)) + ) + + (when re-add-escape + (setq unread-command-events (cons 27 unread-command-events)) + ) + + (setq normal-escape-enabled nil) ; Disable intercepting esc for a moment, so it'll go through as normal + + ) + + + ) + ) +) + +; 2. turn on before each command: +(defun normal-escape-pre-command-handler () (interactive) + (setq normal-escape-enabled t) +) +(add-hook 'pre-command-hook 'normal-escape-pre-command-handler) + +; 3. Define map: +; This is needed because > without a map, there wouldn't be a separate +; command for escape. The command would be meta-something. +(setq normal-escape-map + '((normal-escape-enabled keymap + (27 . normal-escape)) + ) +) + +; 4. Add to end: +(add-to-ordered-list 'emulation-mode-map-alists 'normal-escape-map 300) + + +; Function to read one event and know whether it's escape +; It throws the event away if it's not escope. +(defun next-event-is-escape () + + (let ((escape nil) (first-char (read-event nil nil 0.05))) ; Read any others that follow imediately + + ; If 1st char in sequence is esc or Ctrl+Q + (when (or (eq first-char 27) (eq first-char 17)) + (setq escape t) + ) ; Read 1st event + + ; Ignore any characters typed right after + (while (read-event nil nil 0.05) + (setq escape nil) + ) + escape + ) +) + +(provide 'normal-escape) diff --git a/misc/emacs/start_xiki.el b/misc/emacs/start_xiki.el new file mode 100644 index 00000000..2b0ccb04 --- /dev/null +++ b/misc/emacs/start_xiki.el @@ -0,0 +1,103 @@ +(message "") + +; This line makes > the top bar not flash white bg +; must be before > the sit-for line +(menu-bar-mode -1) + +; This line makes > the bottom bar not flash gray bg +; must be before > the sit-for line +(set-face-attribute 'mode-line nil :background "unspecified") + +; These 2 lines being in this order > is critical to help the flashing +; This line makes > the bottom bar not flash black text +; must be before > the sit-for line +(setq-default mode-line-format "") ; Clear out modeline so it doesn't flash during startup + +(sit-for 0) + +; Load some libraries separately, because if not it loads during find-file and shows annoying message +(load "image") +(load "disp-table") +(load "cua-base") +(load "edmacro") +(load "xt-mouse") +(message "") + + +(setenv "XIKI_DIR" + (replace-regexp-in-string "/misc/emacs/start_xiki.\\w+$" "" load-file-name) +) + +; Remove annoying messages during startup > suppress... + +(setq message--old (symbol-function 'message)) +(defun message (fmt &rest args) + "Ignore useless messages." + (cond + ((string-equal "Mark saved where search started" fmt)) + ((string-equal "Mark set" fmt)) + ((string-equal "Mark activated" fmt)) + ((string-equal "Mark cleared" fmt)) + ((string-match "^Loading places from " fmt)) + + ((string-match "^Beginning of buffer" fmt)) + + ((and + args + (or + (string-match "^When done with this frame, type " (car args)) + (string-match "^Beginning of buffer" (car args)) + ) + )) + + ((apply message--old fmt args)) + ) +) + +; Suppress "Beginning of Buffer" message. + +(defadvice previous-line (around silencer activate) + (condition-case nil + ad-do-it + ((beginning-of-buffer)))) +(defadvice next-line (around silencer activate) + (condition-case nil + ad-do-it + ((end-of-buffer)))) + +(defadvice scroll-down (around silencer activate) + (condition-case nil + ad-do-it + ((beginning-of-buffer)))) +(defadvice scroll-up (around silencer activate) + (condition-case nil + ad-do-it + ((end-of-buffer)))) + +(setq-default mode-line-format "") ; Clear out modeline so it doesn't flash during startup +(set-face-attribute 'mode-line nil :background 'unspecified) + +; Starts up el4r... + +(load (concat (getenv "XIKI_DIR") "/misc/emacs/xsh.emacs")) +(message "") + +; Call ruby method that processes command line args... + +(defun populate-xsh-command-line-args () + (setq xsh-command-line-args '()) + (let ((i 1) val) + (while + (setq val (getenv (concat "XSH_COMMAND_LINE_ARG_" (int-to-string i)))) + (setq xsh-command-line-args (append xsh-command-line-args (list val))) + (setq i (+ 1 i)) + ) + ) +) + +(populate-xsh-command-line-args) + +(if (not (and (boundp 'xiki-emacs-daemon) xiki-emacs-daemon)) + (el4r-ruby-eval "Xiki::Xsh.run :args_via_env=>1") +) + diff --git a/misc/emacs/start_xiki_daemon.el b/misc/emacs/start_xiki_daemon.el new file mode 100644 index 00000000..0bed5daa --- /dev/null +++ b/misc/emacs/start_xiki_daemon.el @@ -0,0 +1,27 @@ +; Clear out scratch, otherwise it would flash first... + +(with-current-buffer "*scratch*" + (erase-buffer)) + +; It's our job to make this emacs ready for emacsclient sessions to join, so set up the hook, define variable, and load start_xiki.el in this dir... + +(add-hook 'after-make-frame-functions (lambda (frame) (interactive) + + (set-buffer "*scratch*") + (run-with-idle-timer 0 nil (lambda () + (el4r-ruby-eval "::Xiki::Xsh.run :args_via_daemon=>1") + + ;; Hack to simulate key press, since it seems to wait for one + (setq unread-command-events (cons 2 unread-command-events)) + + )) + + nil + +)) + +(setq xiki-emacs-daemon t) + +; Load start_xiki.el in this dir, which will start xiki... + +(load (replace-regexp-in-string "_daemon.el$" ".el" load-file-name)) diff --git a/misc/emacs/start_xiki_no_socket.el b/misc/emacs/start_xiki_no_socket.el new file mode 100644 index 00000000..2efdfc61 --- /dev/null +++ b/misc/emacs/start_xiki_no_socket.el @@ -0,0 +1,5 @@ +; Just set a variable and load start_xiki.el in this dir... + +(setq xiki-no-socket t) +(load (replace-regexp-in-string "_no_socket.el$" ".el" load-file-name)) +(message "") ; Suppress message showing it loaded the above file diff --git a/misc/emacs/xsh.emacs b/misc/emacs/xsh.emacs new file mode 100644 index 00000000..545ccfee --- /dev/null +++ b/misc/emacs/xsh.emacs @@ -0,0 +1,269 @@ +(message "") ; This stops some flickering + +; Where is this being over-written?? +(set-frame-parameter nil 'background-mode 'dark) ; Temp....................... + +; C-k deletes remaining linebreak +(setq kill-whole-line t) + +; Change this back to emacs 23 behavior, if emacs 24 +(define-key isearch-mode-map (kbd "C-y") 'isearch-yank-line) ; jump+yet + +(setq-default indent-tabs-mode nil) ; Otherwise, it'll insert tabs? +(setq-default tab-width 2) +(setq ring-bell-function 'ignore) ; Don't make noise when escape + +(setq js-indent-level 2) ; 2-space indent in javascript + +; Add to end of path, so libs (like ruby-mode) won't super-cede ones already installed +(add-to-list 'load-path (concat (getenv "XIKI_DIR") "/misc/emacs/libs/") t) + +; Control Lock +(require 'control-lock) +; Allow treating escape like cancel +(require 'normal-escape) + +; Isearch > Move cursor to beginning of match after search +(add-hook 'isearch-mode-end-hook 'isearch-goto-beginning-after-finished) +(defun isearch-goto-beginning-after-finished () + (when (and isearch-forward isearch-success) + (goto-char isearch-other-end))) + +; Backup files > with tilda and hash in names + +(setq backup-inhibited t) ; Disable backup +(setq auto-save-default nil) ; Disable auto save + + +; Try to enable mouse support, but doesn't matter if it fails +(condition-case nil + (progn + (require 'mouse) + (xterm-mouse-mode t) + (defun track-mouse (e)) + ) + (error nil) ; Rescue, and do nothing when error happens +) + +; Make clicking on a vertical bar hide other windows... + +(global-set-key [vertical-line mouse-1] 'delete-other-windows) + +; Right-clicking in mode-line do "expose links" + +(global-set-key [mode-line mouse-3] (lambda nil (interactive) (el4r-ruby-eval "Xiki::View.link_views"))) + +; Make mouse scroll wheel work... + +(global-set-key (kbd "") (lambda () (interactive) + ; Wake it scroll 1 line at a time + ; - Store each time we scroll + ; - Only scroll if more than N miliseconds has passed since last + + (let ((elapsed (if (boundp 'xiki-scroll-last-time) (- (float-time) xiki-scroll-last-time) 0))) + ; Scroll if it's been more than .01s + (when (> elapsed 0.005) + (mouse-select-window last-command-event) + (scroll-down 1) + ) + ) + + (setq xiki-scroll-last-time (float-time)) + + ; Ignore any characters typed right after > they cause weirdness in Emacs 22 + (while (read-event nil nil 0.005) + (setq escape nil) + ) +)) + +(global-set-key (kbd "") (lambda () (interactive) + + (let ((elapsed (if (boundp 'xiki-scroll-last-time) (- (float-time) xiki-scroll-last-time) 0))) + ; Scroll if it's been more than .01s + (when (> elapsed 0.005) + (mouse-select-window last-command-event) + (scroll-up 1) + ) + + ) + + (setq xiki-scroll-last-time (float-time)) + + ; Ignore any characters typed right after > they cause weirdness in Emacs 22 + (while (read-event nil nil 0.005) + (setq escape nil) + ) +)) + +(menu-bar-mode -1) + +; Disable chars at end when wrapping or truncating +(set-display-table-slot standard-display-table 0 ?\ ) +(set-display-table-slot standard-display-table 'wrap ?\ ) +(set-display-table-slot standard-display-table 'vertical-border ?\ ) + +(setq inhibit-startup-message t) +(fset 'yes-or-no-p 'y-or-n-p) + +(message "") +(cua-mode 1) +(message "") +(setq cua-auto-tabify-rectangles nil) ; Seems to be working + +; Make Shift+Rightarrow enable selection and move +(global-set-key (kbd "") (lambda () (interactive) + (if (not (region-active-p)) (cua-set-mark)) + (forward-char) +)) +; Make Shift+Leftarrow enable selection and move +(global-set-key (kbd "") (lambda () (interactive) + (if (not (region-active-p)) (cua-set-mark)) + (backward-char) +)) +; Make Shift+Downarrow enable selection and move +(global-set-key (kbd "") (lambda () (interactive) + (if (not (region-active-p)) (cua-set-mark)) + (next-line) +)) +; Make Shift+Uparrow enable selection and move +(global-set-key (kbd "") (lambda () (interactive) + (if (not (region-active-p)) (cua-set-mark)) + (previous-line) +)) + +; Make M-h etc simulate vim key movement by default - HJKL +(global-set-key (kbd "M-h") 'backward-char) +(global-set-key (kbd "M-j") 'next-line) +(global-set-key (kbd "M-k") 'previous-line) +(global-set-key (kbd "M-l") 'forward-char) + +(global-set-key (kbd "M-a") 'move-beginning-of-line) +(global-set-key (kbd "M-e") 'move-end-of-line) + +; "Clear" > kill-line alternative > since C-k and M-k don't do it +(global-set-key (kbd "M-c") 'kill-line) + +(add-to-list 'load-path (concat (getenv "XIKI_DIR") "/misc/emacs/el4r/")) +(require 'el4r) +(el4r-recommended-keys) +(el4r-boot) + +; Save cursor location when closing, and restore next time the file is visited +(require 'saveplace) +(setq-default save-place t) +(setq-default save-place-file "~/.xiki/misc/saved_places") + +(custom-set-variables + '(auto-revert-interval 1) + '(echo-keystrokes 0.5) ; Delay until showing the prefix keys at the bottom (when user types C-x or C-c) + '(search-ring-max 30) ; Remember a few more searches + '(show-trailing-whitespace t) ; Expose trailing whitespace + + ; Does this keep backups from being made? + '(auto-save-interval 999999) ; Don't do auto-saving + '(auto-save-timeout 999999) ; Don't do auto-saving + + '(cua-prefix-override-inhibit-delay 0.1) ; Only small delay after Ctrl+C, for cua-mode + + '(search-whitespace-regexp nil) ; Otherwise it includes linebreaks + '(electric-indent-mode nil) ; Stop auto-indenting in ruby (etc) modes + '(backup-inhibited t) + '(auto-save-default nil) + '(require-final-newline t) +) + +; Lines from .emacs that should be uncommented?... + +(setq ediff-split-window-function 'split-window-horizontally) ; Make ediff horizontal + +(setq ediff-diff-options "-w") + +; This makes the file be colorzed as elisp +; Local Variables: +; mode: lisp +; End: + +; Selection is active, so show "^C Copy" etc... + +(setq xiki-selection-text-in-bar-text "Arrows keys to select ^C Copy ^X Cut ^V Paste (or esc)") + +; Todo > Find better place to put this? +(defun xiki-selection-text-in-bar () (interactive) + (if (and (region-active-p) (or (not (boundp 'xiki-bar-special-text)) (not xiki-bar-special-text))) + (setq xiki-bar-special-text + xiki-selection-text-in-bar-text + ) + (if (and + (or deactivate-mark (not mark-active)) + (boundp 'xiki-bar-special-text) + + + (eq (type-of xiki-bar-special-text) 'string) + + + (string= xiki-bar-special-text xiki-selection-text-in-bar-text) + ) + + (setq xiki-bar-special-text + nil + ) + ) + ) +) +(add-hook 'post-command-hook 'xiki-selection-text-in-bar) + +; Make C-d not interfere when in shell-mode. This delays running it until shell-mode runs (to save some startup time) +(add-hook 'shell-mode-hook + (lambda () + (define-key shell-mode-map (kbd "C-d") nil) + ) +) + +; Indent when pasting, if pasted on and indented line +(defadvice cua-paste (after around-fun activate) + (el4r-ruby-eval "Xiki::Clipboard.cua_paste_advice rescue nil") +) + +; Remove trailing spaces after enter+group +(defadvice cua--deactivate-rectangle (after around-fun activate) + (el4r-ruby-eval "Xiki::Clipboard.cua_rectangle_advice rescue nil") +) + +; Show "Jump to:" prompt instead of "I-Search:" when ^J +; Also handles variants + +(defadvice isearch-message-prefix (after around-fun activate) + (setq ad-return-value + (replace-regexp-in-string "^I-Search: " "jump: " + (replace-regexp-in-string "^I-Search backward: " "jump (reverse): " + (replace-regexp-in-string "^Failing \\(.+\\): " "\\1: [not found] " + (replace-regexp-in-string "^[Ww]rapped \\(.+\\): " "\\1: [wrapped] " + (replace-regexp-in-string "^[Oo]verwrapped \\(.+\\): " "\\1: [overwrapped] " + ad-return-value + ) ) ) ) ) ) +) + + +; Make ^Z deselect when there's a selection +(defun xiki-deselect () (interactive) (deactivate-mark)) +(define-key cua--prefix-override-keymap (kbd "C-z") 'xiki-deselect) + +; Only scroll down one line when the cursor is moved off-screen +(setq scroll-step 1) + +; Now, trying to wrap by default +(setq-default truncate-lines 1) + +; Suppress annoying "file locked by...(s, q, p, ?)" message (annoying because you have to type a weird key to make it go away) +(defun ask-user-about-lock (file opponent) + "always grab lock" + t) + +; Disable sh mode > until we figure out "file mode specification error wrong-type-argument" error +(dotimes (i 6) + (setq auto-mode-alist (remove (rassoc 'sh-mode auto-mode-alist) auto-mode-alist)) +) +; Removes shebang mappings +(dotimes (i 10) + (setq interpreter-mode-alist (remove (rassoc 'sh-mode interpreter-mode-alist) interpreter-mode-alist)) +) diff --git a/misc/install/.xsh.default b/misc/install/.xsh.default new file mode 100644 index 00000000..ee4fc29c --- /dev/null +++ b/misc/install/.xsh.default @@ -0,0 +1,8 @@ +# Define some key shortcuts for quickly switching from your shell to xsh +xiki_open_key="\C-o" # Ctrl+O to open in xsh +xiki_search_key="\C-s" # Ctrl+S to search shared commands on XikiHub +xiki_key="\C-x" # Ctrl+X to show a xiki files +xiki_tasks_key="\C-t" # Ctrl+T to show the tasks dropdown +xiki_go_key="\C-g" # Ctrl+G to grab commands between xsh and your shell +xiki_reverse_key="\er" # Alt+R to search shell history + diff --git a/etc/install/el4r_setup.sh b/misc/install/el4r_setup.sh similarity index 100% rename from etc/install/el4r_setup.sh rename to misc/install/el4r_setup.sh diff --git a/misc/install/install_source_dependency.sh b/misc/install/install_source_dependency.sh new file mode 100644 index 00000000..eafd70ff --- /dev/null +++ b/misc/install/install_source_dependency.sh @@ -0,0 +1,54 @@ +# Called by xsh when starting up and you don't have dependencies installed (and if source code install is the only viable option for you) + +set -e + +function panic(){ + proj="`shift`" + echo "`shift`" + if [ -n "$proj" ]; then + echo please look in ~/.xiki_dependencies/"$proj"_build.log for more details + fi + exit 1 +} + +if [ "$1" = 'ruby' ]; then + + url="http://cache.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p598.tar.gz" + + tarball="`basename $url`" + extracted_dir="`basename -s '.tar.gz' $url`" + mkdir -p ~/.xiki_dependencies + cd ~/.xiki_dependencies + wget --no-clobber $url || panic "" "wget failed" + + echo "4136bf7d764cbcc1c7da2824ed2826c3550f2b62af673c79ddbf9049b12095fd ruby-2.0.0-p598.tar.gz" \ + | sha256sum -c || panic "" "sha256sum failed" + + gunzip -c $tarball | tar xf - || panic "ruby" "untar failed" + cd $extracted_dir + echo putting ruby build logs in `pwd`/ruby_build.log + touch ruby_build.log + chmod 644 ruby_build.log + ./configure --prefix=~/.xiki_dependencies >/tmp/ruby_build.log || panic "ruby" "configure failed" + make >>/tmp/ruby_build.log || panic "ruby" "make failed" + make test >>/tmp/ruby_build.log || panic "ruby" "make test failed" + make install >>/tmp/ruby_build.log || panic "ruby" "make install failed" + +else + + url="https://ftp.gnu.org/gnu/emacs/emacs-24.4.tar.gz" + tarball="`basename $url`" + extracted_dir="`basename -s '.tar.gz' $url`" + mkdir -p ~/.xiki_dependencies + cd ~/.xiki_dependencies + wget --no-clobber $url || panic "" "wget failed" + gunzip -c $tarball | tar xf - || panic "" "untar failed" + cd $extracted_dir + echo putting emacs build logs in `pwd`/emacs_build.log + touch emacs_build.log + chmod 644 emacs_build.log + ./configure --prefix=~/.xiki_dependencies >/tmp/emacs_build.log || panic "emacs" "configure failed" + make >>/tmp/emacs_build.log || panic "emacs" "make failed" + make install >>/tmp/emacs_build.log || panic "emacs" "make install failed" + +fi diff --git a/misc/install/install_when_flag.rb b/misc/install/install_when_flag.rb new file mode 100644 index 00000000..44bc1093 --- /dev/null +++ b/misc/install/install_when_flag.rb @@ -0,0 +1,8 @@ +path = File.expand_path __FILE__ +path = File.dirname path + +require "#{path}/save_config.rb" + +Xiki::SaveConfig.save_config "/xiki", "some shell key shortcut remappings" + + diff --git a/misc/install/save_config.rb b/misc/install/save_config.rb new file mode 100644 index 00000000..30b8a168 --- /dev/null +++ b/misc/install/save_config.rb @@ -0,0 +1,106 @@ +module Xiki + class SaveConfig + def self.save_config xiki_dir, choice + + # Ol "tmp!!!" + # return "- tmp!!!" + + xiki_dir.sub!(/\/$/, '') # Remove dir at end + + # puts "SaveConfig.save_config!!!" + + txt = %` + #!/bin/sh + + `.strip.gsub(/^ */, '')+"\n\n" + txt << File.read("#{xiki_dir}/misc/install/.xsh.default") + + # commit/Moved lines in .xsh with project path close together. + + txt << %` + # Make the 'xsh' command available in the shell + export PATH=#{xiki_dir}/bin:$PATH + + # Enable the key shortcuts and the xsh wrapper function + source #{xiki_dir}/bin/.xsh + `.strip.gsub(/^ */, '')+"\n\n" + + case choice + when "default" + # Do nothing + when "safer key shortcuts" + # Change \C to \e\C + + txt.sub! '\C-x', '\e\C-x' + txt.sub! '\C-s', '\e\C-s' + txt.sub! '\C-w', '\e\C-w' + txt.sub! '\C-o', '\e\C-o' + txt.sub! '\C-g', '\e\C-g' + txt.sub! '\C-r', '\e\C-r' + + else "just the xsh command" + # Comment everything out + + txt.gsub!(/^[xs]/, "# \\0") + + txt.gsub!("# Define", "\n# Commented out, since user chose no key shortcuts:\n\n\\0") + end + + dot_xsh_path = File.expand_path("~/.xsh") + + # Make backup if it exists + if File.exists? dot_xsh_path + FileUtils.cp dot_xsh_path, "#{dot_xsh_path}.backup.#{rand 9999}" + end + + # Write the new file... + + File.open(dot_xsh_path, "w") { |f| f << txt } + + # Include it from the other files... + + + files = self.files_to_update + + files.each do |file| + file = File.expand_path file + txt = File.read(file) rescue "" + + File.open(file, "w") { |f| f << "#{txt.sub(/\n+\z/, '')}\n\n\nsource ~/.xsh\n\n" } + end + end + + + def self.files_to_update + + # Start with files that exist (plus always .bashrc)... + + files = [ + "~/.bashrc", + self.bash_conf_file, + self.zsh_conf_file, + ].compact + + # Remove ones that exist and contain "source ~/.xsh" already + + files = files.select do |file| + txt = File.read(File.expand_path file) rescue nil + next true if ! txt # Only .bashrc might not exist, and we still want to create it, so leave it in the list + next false if txt =~ /^source ~\/\.xsh\b/ # It's already there, so do nothing + true + end + + files + end + + def self.bash_conf_file + file = ["~/.bash_profile", "~/.bash_login", "~/.profile"].find{|o| File.exists? File.expand_path(o)} + file || "~/.bash_profile" # In case none exists, create this one + end + + def self.zsh_conf_file + ["~/.zshrc"].find{|o| File.exists? File.expand_path(o)} + end + + end +end diff --git a/etc/js/menu1.js b/misc/js/menu1.js similarity index 100% rename from etc/js/menu1.js rename to misc/js/menu1.js diff --git a/etc/js/xiki.js b/misc/js/xiki.js similarity index 100% rename from etc/js/xiki.js rename to misc/js/xiki.js diff --git a/misc/libs/session.rb b/misc/libs/session.rb new file mode 100755 index 00000000..a2a76510 --- /dev/null +++ b/misc/libs/session.rb @@ -0,0 +1,665 @@ +# From the "session" gem. +# Copyright (c) 2014 Ara T. Howard +# +# License: +# same as Ruby's +# http://www.ruby-lang.org/en/LICENSE.txt + +require 'open3' +require 'tmpdir' +require 'thread' +require 'yaml' +require 'tempfile' + +module Session + VERSION = '3.2.0' + def self.version() VERSION end + + def Session.description + 'persistent connections with external programs like bash' + end + + @track_history = ENV['SESSION_HISTORY'] || ENV['SESSION_TRACK_HISTORY'] + @use_spawn = ENV['SESSION_USE_SPAWN'] + @use_open3 = ENV['SESSION_USE_OPEN3'] + @debug = ENV['SESSION_DEBUG'] + + class << self + attr :track_history, true + attr :use_spawn, true + attr :use_open3, true + attr :debug, true + def new(*a, &b) + Sh::new(*a, &b) + end + alias [] new + end + + class PipeError < StandardError; end + class ExecutionError < StandardError; end + + class History + def initialize; @a = []; end + def method_missing(m,*a,&b); @a.send(m,*a,&b); end + def to_yaml(*a,&b); @a.to_yaml(*a,&b); end + alias to_s to_yaml + alias to_str to_yaml + end # class History + class Command + class << self + def cmdno; @cmdno ||= 0; end + def cmdno= n; @cmdno = n; end + end + + # attributes + attr :cmd + attr :cmdno + attr :out,true + attr :err,true + attr :cid + attr :begin_out + attr :end_out + attr :begin_out_pat + attr :end_out_pat + attr :begin_err + attr :end_err + attr :begin_err_pat + attr :end_err_pat + + def initialize(command) + @cmd = command.to_s + @cmdno = self.class.cmdno + self.class.cmdno += 1 + @err = '' + @out = '' + @cid = "%d_%d_%d" % [$$, cmdno, rand(Time.now.usec)] + @begin_out = "__CMD_OUT_%s_BEGIN__" % cid + @end_out = "__CMD_OUT_%s_END__" % cid + @begin_out_pat = %r/#{ Regexp.escape(@begin_out) }/ + @end_out_pat = %r/#{ Regexp.escape(@end_out) }/ + @begin_err = "__CMD_ERR_%s_BEGIN__" % cid + @end_err = "__CMD_ERR_%s_END__" % cid + @begin_err_pat = %r/#{ Regexp.escape(@begin_err) }/ + @end_err_pat = %r/#{ Regexp.escape(@end_err) }/ + end + def to_hash + %w(cmdno cmd out err cid).inject({}){|h,k| h.update k => send(k) } + end + def to_yaml(*a,&b) + to_hash.to_yaml(*a,&b) + end + alias to_s to_yaml + alias to_str to_yaml + end # class Command + class AbstractSession + + # class methods + class << self + def default_prog + return @default_prog if defined? @default_prog and @default_prog + if defined? self::DEFAULT_PROG + return @default_prog = self::DEFAULT_PROG + else + @default_prog = ENV["SESSION_#{ self }_PROG"] + end + nil + end + def default_prog= prog + @default_prog = prog + end + attr :track_history, true + attr :use_spawn, true + attr :use_open3, true + attr :debug, true + def init + @track_history = nil + @use_spawn = nil + @use_open3 = nil + @debug = nil + end + alias [] new + end + + # class init + init + + # attributes + attr :opts + attr :prog + attr :stdin + alias i stdin + attr :stdout + alias o stdout + attr :stderr + alias e stderr + attr :history + attr :track_history + attr :outproc, true + attr :errproc, true + attr :use_spawn + attr :use_open3 + attr :debug, true + alias debug? debug + attr :threads + + # instance methods + def initialize(*args) + @opts = hashify(*args) + + @prog = getopt('prog', opts, getopt('program', opts, self.class::default_prog)) + + raise(ArgumentError, "no program specified") unless @prog + + @track_history = nil + @track_history = Session::track_history unless Session::track_history.nil? + @track_history = self.class::track_history unless self.class::track_history.nil? + @track_history = getopt('history', opts) if hasopt('history', opts) + @track_history = getopt('track_history', opts) if hasopt('track_history', opts) + + @use_spawn = nil + @use_spawn = Session::use_spawn unless Session::use_spawn.nil? + @use_spawn = self.class::use_spawn unless self.class::use_spawn.nil? + @use_spawn = getopt('use_spawn', opts) if hasopt('use_spawn', opts) + + if defined? JRUBY_VERSION + @use_open3 = true + else + @use_open3 = nil + @use_open3 = Session::use_open3 unless Session::use_open3.nil? + @use_open3 = self.class::use_open3 unless self.class::use_open3.nil? + @use_open3 = getopt('use_open3', opts) if hasopt('use_open3', opts) + end + + @debug = nil + @debug = Session::debug unless Session::debug.nil? + @debug = self.class::debug unless self.class::debug.nil? + @debug = getopt('debug', opts) if hasopt('debug', opts) + + @history = nil + @history = History::new if @track_history + + @outproc = nil + @errproc = nil + + @stdin, @stdout, @stderr = + if @use_spawn + Spawn::spawn @prog + elsif @use_open3 + Open3::popen3 @prog + else + __popen3 @prog + end + + @threads = [] + + clear + + if block_given? + ret = nil + begin + ret = yield self + ensure + self.close! + end + return ret + end + + return self + end + def getopt opt, hash, default = nil + key = opt + return hash[key] if hash.has_key? key + key = "#{ key }" + return hash[key] if hash.has_key? key + key = key.intern + return hash[key] if hash.has_key? key + return default + end + def hasopt opt, hash + key = opt + return key if hash.has_key? key + key = "#{ key }" + return key if hash.has_key? key + key = key.intern + return key if hash.has_key? key + return false + end + def __popen3(*cmd) + pw = IO::pipe # pipe[0] for read, pipe[1] for write + pr = IO::pipe + pe = IO::pipe + + pid = + __fork{ + # child + pw[1].close + STDIN.reopen(pw[0]) + pw[0].close + + pr[0].close + STDOUT.reopen(pr[1]) + pr[1].close + + pe[0].close + STDERR.reopen(pe[1]) + pe[1].close + + exec(*cmd) + } + + Process::detach pid # avoid zombies + + pw[0].close + pr[1].close + pe[1].close + pi = [pw[1], pr[0], pe[0]] + pw[1].sync = true + if defined? yield + begin + return yield(*pi) + ensure + pi.each{|p| p.close unless p.closed?} + end + end + pi + end + def __fork(*a, &b) + verbose = $VERBOSE + begin + $VERBOSE = nil + Kernel::fork(*a, &b) + ensure + $VERBOSE = verbose + end + end + + # abstract methods + def clear + raise NotImplementedError + end + alias flush clear + def path + raise NotImplementedError + end + def path= + raise NotImplementedError + end + def send_command cmd + raise NotImplementedError + end + + # concrete methods + def track_history= bool + @history ||= History::new + @track_history = bool + end + def ready? + (stdin and stdout and stderr) and + (IO === stdin and IO === stdout and IO === stderr) and + (not (stdin.closed? or stdout.closed? or stderr.closed?)) + end + def close! + [stdin, stdout, stderr].each{|pipe| pipe.close} + stdin, stdout, stderr = nil, nil, nil + true + end + alias close close! + def hashify(*a) + a.inject({}){|o,h| o.update(h)} + end + private :hashify + def execute(command, redirects = {}) + $session_command = command if @debug + + raise(PipeError, command) unless ready? + + # clear buffers + clear + + # setup redirects + rerr = redirects[:e] || redirects[:err] || redirects[:stderr] || + redirects['stderr'] || redirects['e'] || redirects['err'] || + redirects[2] || redirects['2'] + + rout = redirects[:o] || redirects[:out] || redirects[:stdout] || + redirects['stdout'] || redirects['o'] || redirects['out'] || + redirects[1] || redirects['1'] + + # create cmd object and add to history + cmd = Command::new command.to_s + + # store cmd if tracking history + history << cmd if track_history + + # mutex for accessing shared data + mutex = Mutex::new + + # io data for stderr and stdout + err = { + :io => stderr, + :cmd => cmd.err, + :name => 'stderr', + :begin => false, + :end => false, + :begin_pat => cmd.begin_err_pat, + :end_pat => cmd.end_err_pat, + :redirect => rerr, + :proc => errproc, + :yield => lambda{|buf| yield(nil, buf)}, + :mutex => mutex, + } + out = { + :io => stdout, + :cmd => cmd.out, + :name => 'stdout', + :begin => false, + :end => false, + :begin_pat => cmd.begin_out_pat, + :end_pat => cmd.end_out_pat, + :redirect => rout, + :proc => outproc, + :yield => lambda{|buf| yield(buf, nil)}, + :mutex => mutex, + } + + begin + # send command in the background so we can begin processing output + # immediately - thanks to tanaka akira for this suggestion + threads << Thread::new { send_command cmd } + + # init + main = Thread::current + exceptions = [] + + # fire off reader threads + [err, out].each do |iodat| + threads << + Thread::new(iodat, main) do |iodat, main| + + loop do + main.raise(PipeError, command) unless ready? + main.raise ExecutionError, iodat[:name] if iodat[:end] and not iodat[:begin] + + break if iodat[:end] or iodat[:io].eof? + + line = iodat[:io].gets + + # In case their are weird chars, this will avoid a "invalid byte sequence in US-ASCII" error + line.force_encoding("binary") if line.respond_to? :force_encoding + + buf = nil + + case line + when iodat[:end_pat] + iodat[:end] = true + # handle the special case of non-newline terminated output + if((m = %r/(.+)__CMD/o.match(line)) and (pre = m[1])) + buf = pre + end + when iodat[:begin_pat] + iodat[:begin] = true + else + next unless iodat[:begin] and not iodat[:end] # ignore chaff + buf = line + end + + if buf + iodat[:mutex].synchronize do + iodat[:cmd] << buf + iodat[:redirect] << buf if iodat[:redirect] + iodat[:proc].call buf if iodat[:proc] + iodat[:yield].call buf if block_given? + end + end + end + + true + end + end + ensure + # reap all threads - accumulating and rethrowing any exceptions + begin + while((t = threads.shift)) + t.join + raise ExecutionError, 'iodat thread failure' unless t.value + end + rescue => e + exceptions << e + retry unless threads.empty? + ensure + unless exceptions.empty? + meta_message = '<' << exceptions.map{|e| "#{ e.message } - (#{ e.class })"}.join('|') << '>' + meta_backtrace = exceptions.map{|e| e.backtrace}.flatten + raise ExecutionError, meta_message, meta_backtrace + end + end + end + + # this should only happen if eof was reached before end pat + [err, out].each do |iodat| + raise ExecutionError, iodat[:name] unless iodat[:begin] and iodat[:end] + end + + + # get the exit status + get_status if respond_to? :get_status + + out = err = iodat = nil + + return [cmd.out, cmd.err] + end + end # class AbstractSession + class Sh < AbstractSession + DEFAULT_PROG = 'sh' + ECHO = 'echo' + + attr :status + alias exit_status status + alias exitstatus status + + def clear + stdin.puts "#{ ECHO } __clear__ 1>&2" + stdin.puts "#{ ECHO } __clear__" + stdin.flush + while((line = stderr.gets) and line !~ %r/__clear__/o); end + while((line = stdout.gets) and line !~ %r/__clear__/o); end + self + end + def send_command cmd + stdin.printf "%s '%s' 1>&2\n", ECHO, cmd.begin_err + stdin.printf "%s '%s' \n", ECHO, cmd.begin_out + + stdin.printf "%s\n", cmd.cmd + stdin.printf "export __exit_status__=$?\n" + + stdin.printf "%s '%s' 1>&2\n", ECHO, cmd.end_err + stdin.printf "%s '%s' \n", ECHO, cmd.end_out + + stdin.flush + end + def get_status + @status = get_var '__exit_status__' + unless @status =~ /^\s*\d+\s*$/o + raise ExecutionError, "could not determine exit status from <#{ @status.inspect }>" + end + + @status = Integer @status + end + def set_var name, value + stdin.puts "export #{ name }=#{ value }" + stdin.flush + end + def get_var name + stdin.puts "#{ ECHO } \"#{ name }=${#{ name }}\"" + stdin.flush + + var = nil + while((line = stdout.gets)) + m = %r/#{ name }\s*=\s*(.*)/.match line + if m + var = m[1] + raise ExecutionError, "could not determine <#{ name }> from <#{ line.inspect }>" unless var + break + end + end + + var + end + def path + var = get_var 'PATH' + var.strip.split %r/:/o + end + def path= arg + case arg + when Array + arg = arg.join ':' + else + arg = arg.to_s.strip + end + + set_var 'PATH', "'#{ arg }'" + self.path + end + def execute(command, redirects = {}, &block) + # setup redirect on stdin + rin = redirects[:i] || redirects[:in] || redirects[:stdin] || + redirects['stdin'] || redirects['i'] || redirects['in'] || + redirects[0] || redirects['0'] + + if rin + tmp = + begin + Tempfile::new rand.to_s + rescue + Tempfile::new rand.to_s + end + + begin + tmp.write( + if rin.respond_to? 'read' + rin.read + elsif rin.respond_to? 'to_s' + rin.to_s + else + rin + end + ) + tmp.flush + command = "{ #{ command } ;} < #{ tmp.path }" + #puts command + super(command, redirects, &block) + ensure + tmp.close! if tmp + end + + else + super + end + end + end # class Sh + class Bash < Sh + DEFAULT_PROG = 'bash' + class Login < Bash + DEFAULT_PROG = 'bash --login' + end + end # class Bash + class Shell < Bash; end + # IDL => interactive data language - see http://www.rsinc.com/ + class IDL < AbstractSession + class LicenseManagerError < StandardError; end + DEFAULT_PROG = 'idl' + MAX_TRIES = 32 + def initialize(*args) + tries = 0 + ret = nil + begin + ret = super + rescue LicenseManagerError => e + tries += 1 + if tries < MAX_TRIES + sleep 1 + retry + else + raise LicenseManagerError, "<#{ MAX_TRIES }> attempts <#{ e.message }>" + end + end + ret + end + def clear + stdin.puts "retall" + stdin.puts "printf, -2, '__clear__'" + stdin.puts "printf, -1, '__clear__'" + stdin.flush + while((line = stderr.gets) and line !~ %r/__clear__/o) + raise LicenseManagerError, line if line =~ %r/license\s*manager/io + end + while((line = stdout.gets) and line !~ %r/__clear__/o) + raise LicenseManagerError, line if line =~ %r/license\s*manager/io + end + self + end + def send_command cmd + stdin.printf "printf, -2, '%s'\n", cmd.begin_err + stdin.printf "printf, -1, '%s'\n", cmd.begin_out + + stdin.printf "%s\n", cmd.cmd + stdin.printf "retall\n" + + stdin.printf "printf, -2, '%s'\n", cmd.end_err + stdin.printf "printf, -1, '%s'\n", cmd.end_out + stdin.flush + end + def path + stdout, stderr = execute "print, !path" + stdout.strip.split %r/:/o + end + def path= arg + case arg + when Array + arg = arg.join ':' + else + arg = arg.to_s.strip + end + stdout, stderr = execute "!path='#{ arg }'" + + self.path + end + end # class IDL + module Spawn + class << self + def spawn command + ipath = tmpfifo + opath = tmpfifo + epath = tmpfifo + + cmd = "#{ command } < #{ ipath } 1> #{ opath } 2> #{ epath } &" + system cmd + + i = open ipath, 'w' + o = open opath, 'r' + e = open epath, 'r' + + [i,o,e] + end + def tmpfifo + path = nil + 42.times do |i| + tpath = File::join(Dir::tmpdir, "#{ $$ }.#{ rand }.#{ i }") + v = $VERBOSE + begin + $VERBOSE = nil + system "mkfifo #{ tpath }" + ensure + $VERBOSE = v + end + next unless $? == 0 + path = tpath + at_exit{ File::unlink(path) rescue STDERR.puts("rm <#{ path }> failed") } + break + end + raise "could not generate tmpfifo" unless path + path + end + end + end # module Spawn +end # module Session diff --git a/misc/logo.png b/misc/logo.png new file mode 100644 index 00000000..b2788624 Binary files /dev/null and b/misc/logo.png differ diff --git a/etc/presentations/bootstrap.deck b/misc/old/presentations/bootstrap.deck similarity index 100% rename from etc/presentations/bootstrap.deck rename to misc/old/presentations/bootstrap.deck diff --git a/etc/presentations/databases.deck b/misc/old/presentations/databases.deck similarity index 100% rename from etc/presentations/databases.deck rename to misc/old/presentations/databases.deck diff --git a/etc/presentations/diffs.deck b/misc/old/presentations/diffs.deck similarity index 100% rename from etc/presentations/diffs.deck rename to misc/old/presentations/diffs.deck diff --git a/etc/presentations/documentation.deck b/misc/old/presentations/documentation.deck similarity index 100% rename from etc/presentations/documentation.deck rename to misc/old/presentations/documentation.deck diff --git a/etc/presentations/effects.deck b/misc/old/presentations/effects.deck similarity index 100% rename from etc/presentations/effects.deck rename to misc/old/presentations/effects.deck diff --git a/etc/presentations/face.deck b/misc/old/presentations/face.deck similarity index 100% rename from etc/presentations/face.deck rename to misc/old/presentations/face.deck diff --git a/etc/presentations/files.deck b/misc/old/presentations/files.deck similarity index 100% rename from etc/presentations/files.deck rename to misc/old/presentations/files.deck diff --git a/etc/presentations/icons.deck b/misc/old/presentations/icons.deck similarity index 100% rename from etc/presentations/icons.deck rename to misc/old/presentations/icons.deck diff --git a/etc/presentations/images.deck b/misc/old/presentations/images.deck similarity index 100% rename from etc/presentations/images.deck rename to misc/old/presentations/images.deck diff --git a/etc/presentations/key_shortcuts.deck b/misc/old/presentations/key_shortcuts.deck similarity index 81% rename from etc/presentations/key_shortcuts.deck rename to misc/old/presentations/key_shortcuts.deck index 926b9369..ca92fe46 100644 --- a/etc/presentations/key_shortcuts.deck +++ b/misc/old/presentations/key_shortcuts.deck @@ -11,6 +11,6 @@ Keys.Z { Line.gsub!(/ +/, " ") } > Define shortcuts -Keys.do_hi { Line << "hey there" } +Keys.just_hi { Line << "hey there" } diff --git a/etc/presentations/macros.deck b/misc/old/presentations/macros.deck similarity index 100% rename from etc/presentations/macros.deck rename to misc/old/presentations/macros.deck diff --git a/etc/presentations/menu_classes.deck b/misc/old/presentations/menu_classes.deck similarity index 100% rename from etc/presentations/menu_classes.deck rename to misc/old/presentations/menu_classes.deck diff --git a/etc/presentations/menu_directories.deck b/misc/old/presentations/menu_directories.deck similarity index 100% rename from etc/presentations/menu_directories.deck rename to misc/old/presentations/menu_directories.deck diff --git a/etc/presentations/notes.deck b/misc/old/presentations/notes.deck similarity index 100% rename from etc/presentations/notes.deck rename to misc/old/presentations/notes.deck diff --git a/etc/presentations/other_languages.deck b/misc/old/presentations/other_languages.deck similarity index 100% rename from etc/presentations/other_languages.deck rename to misc/old/presentations/other_languages.deck diff --git a/etc/presentations/other_wiki_syntaxes.deck b/misc/old/presentations/other_wiki_syntaxes.deck similarity index 100% rename from etc/presentations/other_wiki_syntaxes.deck rename to misc/old/presentations/other_wiki_syntaxes.deck diff --git a/etc/presentations/piano.deck b/misc/old/presentations/piano.deck similarity index 100% rename from etc/presentations/piano.deck rename to misc/old/presentations/piano.deck diff --git a/etc/presentations/potential/diffs.deck b/misc/old/presentations/potential/diffs.deck similarity index 100% rename from etc/presentations/potential/diffs.deck rename to misc/old/presentations/potential/diffs.deck diff --git a/etc/presentations/potential/evolution.deck b/misc/old/presentations/potential/evolution.deck similarity index 100% rename from etc/presentations/potential/evolution.deck rename to misc/old/presentations/potential/evolution.deck diff --git a/etc/presentations/potential/intro.deck b/misc/old/presentations/potential/intro.deck similarity index 100% rename from etc/presentations/potential/intro.deck rename to misc/old/presentations/potential/intro.deck diff --git a/etc/presentations/potential/intro1.deck b/misc/old/presentations/potential/intro1.deck similarity index 100% rename from etc/presentations/potential/intro1.deck rename to misc/old/presentations/potential/intro1.deck diff --git a/etc/presentations/potential/intro2.deck b/misc/old/presentations/potential/intro2.deck similarity index 100% rename from etc/presentations/potential/intro2.deck rename to misc/old/presentations/potential/intro2.deck diff --git a/etc/presentations/potential/make_mysql_menu.deck b/misc/old/presentations/potential/make_mysql_menu.deck similarity index 100% rename from etc/presentations/potential/make_mysql_menu.deck rename to misc/old/presentations/potential/make_mysql_menu.deck diff --git a/etc/presentations/potential/ruby_development.deck b/misc/old/presentations/potential/ruby_development.deck similarity index 100% rename from etc/presentations/potential/ruby_development.deck rename to misc/old/presentations/potential/ruby_development.deck diff --git a/etc/presentations/potential/ui_prototyping.deck b/misc/old/presentations/potential/ui_prototyping.deck similarity index 100% rename from etc/presentations/potential/ui_prototyping.deck rename to misc/old/presentations/potential/ui_prototyping.deck diff --git a/etc/presentations/potential/web_dev.deck b/misc/old/presentations/potential/web_dev.deck similarity index 100% rename from etc/presentations/potential/web_dev.deck rename to misc/old/presentations/potential/web_dev.deck diff --git a/etc/presentations/potential/web_development.deck b/misc/old/presentations/potential/web_development.deck similarity index 100% rename from etc/presentations/potential/web_development.deck rename to misc/old/presentations/potential/web_development.deck diff --git a/etc/presentations/potential/wiki.deck b/misc/old/presentations/potential/wiki.deck similarity index 100% rename from etc/presentations/potential/wiki.deck rename to misc/old/presentations/potential/wiki.deck diff --git a/etc/presentations/presentations.deck b/misc/old/presentations/presentations.deck similarity index 100% rename from etc/presentations/presentations.deck rename to misc/old/presentations/presentations.deck diff --git a/etc/presentations/rails_development.deck b/misc/old/presentations/rails_development.deck similarity index 100% rename from etc/presentations/rails_development.deck rename to misc/old/presentations/rails_development.deck diff --git a/etc/presentations/search_key_shortcuts.deck b/misc/old/presentations/search_key_shortcuts.deck similarity index 100% rename from etc/presentations/search_key_shortcuts.deck rename to misc/old/presentations/search_key_shortcuts.deck diff --git a/etc/presentations/simplest_possible_ui.deck b/misc/old/presentations/simplest_possible_ui.deck similarity index 100% rename from etc/presentations/simplest_possible_ui.deck rename to misc/old/presentations/simplest_possible_ui.deck diff --git a/etc/presentations/svg.deck b/misc/old/presentations/svg.deck similarity index 100% rename from etc/presentations/svg.deck rename to misc/old/presentations/svg.deck diff --git a/etc/presentations/testing.deck b/misc/old/presentations/testing.deck similarity index 100% rename from etc/presentations/testing.deck rename to misc/old/presentations/testing.deck diff --git a/etc/presentations/the_end.deck b/misc/old/presentations/the_end.deck similarity index 100% rename from etc/presentations/the_end.deck rename to misc/old/presentations/the_end.deck diff --git a/etc/presentations/type_something_and_double_click.deck b/misc/old/presentations/type_something_and_double_click.deck similarity index 100% rename from etc/presentations/type_something_and_double_click.deck rename to misc/old/presentations/type_something_and_double_click.deck diff --git a/etc/presentations/type_the_acronym.deck b/misc/old/presentations/type_the_acronym.deck similarity index 98% rename from etc/presentations/type_the_acronym.deck rename to misc/old/presentations/type_the_acronym.deck index 19e8f516..4b777808 100644 --- a/etc/presentations/type_the_acronym.deck +++ b/misc/old/presentations/type_the_acronym.deck @@ -128,7 +128,7 @@ files/docs/keys/ - definitions: - /projects/xiki/ - + key_bindings.rb + + key_shortcuts.rb > Existing emacs shortcuts diff --git a/etc/presentations/web_browser.deck b/misc/old/presentations/web_browser.deck similarity index 100% rename from etc/presentations/web_browser.deck rename to misc/old/presentations/web_browser.deck diff --git a/etc/presentations/xiki_command.deck b/misc/old/presentations/xiki_command.deck similarity index 100% rename from etc/presentations/xiki_command.deck rename to misc/old/presentations/xiki_command.deck diff --git a/etc/presentations/xiki_url.deck b/misc/old/presentations/xiki_url.deck similarity index 100% rename from etc/presentations/xiki_url.deck rename to misc/old/presentations/xiki_url.deck diff --git a/misc/shell_wrappers/docker.rb b/misc/shell_wrappers/docker.rb new file mode 100644 index 00000000..65f5e7f0 --- /dev/null +++ b/misc/shell_wrappers/docker.rb @@ -0,0 +1,28 @@ +shell_command = options[:shell_command] + +# $ docker, so just decorate the output... + +if shell_output = options[:shell_output] + + if shell_command == "docker help" + shell_output.gsub! /^\| (\w)/, ": \\1" + return + end + + return shell_output.gsub! /^\|( +(images|ps) +)/, ":\\1" +end + +item = args[0][/^\| (\w+)/, 1] + +# $ docker help, so invoke help... + +if shell_command == "docker help" + return Shell.command("docker help #{item}").gsub(/^/, "| ") +end + +# $ docker/: foo, so just run it + +if shell_command == "docker" && args.length == 1 + return Shell.command("docker #{item}").gsub(/^/, ": ") +end + diff --git a/misc/shell_wrappers/foo.rb b/misc/shell_wrappers/foo.rb new file mode 100644 index 00000000..858d0be2 --- /dev/null +++ b/misc/shell_wrappers/foo.rb @@ -0,0 +1,29 @@ +# Let's say a command looks like this: +# $ foo +# : bar +# : bah +# +# If a user expands bah, then the args variable will +# contain [": bah"], so this block will be run: + +if args == [": bah"] + # Just return a string, to insert it underneath. + return ": ram\n: ewe" +end + +# Then the command looks like this: +# $ foo +# : bar +# : bah +# : ram +# : ewe +# +# If a user later expands ewe, args will contain +# [": bah", ": ewe"]. + +if args == [": bah", ": ewe"] + return ": what did you say?" +end + +# Return something if they expanded any other item +": You expanded #{args[-1]}" diff --git a/misc/shell_wrappers/git.rb b/misc/shell_wrappers/git.rb new file mode 100644 index 00000000..cd3ca548 --- /dev/null +++ b/misc/shell_wrappers/git.rb @@ -0,0 +1,82 @@ + +# /, so require "$ git" to be there (must be the raw "shell git" menu)... + +command = options[:shell_command] +dir = options[:dir] # || Shell.dir + +# /, so put colons before which lines are expandable? + +if shell_output = options[:shell_output] + # $ docker help, so colonize all commands... + return shell_output.gsub! /^\|( +(log|branch|diff|status) +)/, ":\\1" if command == "git" + return shell_output.gsub! /^\|( +([a-z]+) +)/, ":\\1" if command == "git help" +end + + +if command == "git help" + command = args[0][/[a-z]+/] + return Tree.pipe Shell.command("git help #{command}") +end + +# /$ git/: log (1st arg only), so run the command... + +if args.length == 1 && args[0] =~ /^: (\w+)/ + command = $1 + command << " --oneline" if command == "log" + txt = Shell.sync "git #{command}", :dir=>dir + + if command == "diff" + return Git.format_as_quote txt + end + + return Tree.quote txt +end + +# 75f8540..., so show files in commit... + +if args[-1] =~ /^: ([a-f0-9]{7})/ + hash = $1 + return Tree.quote Shell.sync "git show --name-status #{hash}", :dir=>dir +end + +# M foo.txt, so show diff for one file... + +if args[-1] =~ /^: [MAD]\t(.+)/ + file = $1 + # Pull out hash from higher up + hash = args.join('/')[/: ([a-f0-9]{7})/, 1] + if hash + txt = Shell.sync "git show --oneline -U1 #{hash} -- '#{file}'", :dir=>dir + + Git.format_as_quote txt, :one_file=>1 + + return txt#.sub(/.+?^@/m, '@') + end +end + +# /, so return main items... + +menu = " + * hi/ + * init/ + * status/ + * diff/ + * log/ + - programatically add these! + * more/ + - checkout/ + - bisect/ + " + +if args == [] && options[:task].blank? + return menu if options[:mouse] # Mouse, so return them all... + return Tree.children menu, options[:task] # Keyboard, so just root items... +end + +# $ git/something, so if /^ foo/ run it as command... + +return "- was normal output, so run it" if options[:path] =~ /^ \w/ + +# /item/lastitem, so git > $ git lastitem... + +"- Unhandled!" diff --git a/misc/shell_wrappers/rails.rb b/misc/shell_wrappers/rails.rb new file mode 100644 index 00000000..35ab1a6b --- /dev/null +++ b/misc/shell_wrappers/rails.rb @@ -0,0 +1,23 @@ +command = options[:shell_command] + +return "<$ #{args.last}" if args.last =~ /^\$ / + +dir = options[:dir] + +if args[0] =~ /^: generate / + return Shell.sync("rails generate", :dir=>dir).gsub(/^/, ': ') if ! args[1] + if args[1] =~ /^: model/ + return "+ examples/\n"+Shell.sync("rails generate model", :dir=>dir).gsub(/^/, ': ') if ! args[2] + return "$ rails generate model foo name:string details:text quantity:integer price:decimal delivery:boolean purchased_at:datetime" if args[2] == "examples" + end + +end + +["console", "server", "dbconsole"].each do |arg| + if args[1] =~ /^: #{arg} / + Shell.async("rails #{arg}", :dir=>dir) + return "" + end +end + +"- This line isn't recognized" diff --git a/etc/snippets/html.notes b/misc/snippets/html.xiki similarity index 100% rename from etc/snippets/html.notes rename to misc/snippets/html.xiki diff --git a/etc/snippets/notes.notes b/misc/snippets/notes.xiki similarity index 100% rename from etc/snippets/notes.notes rename to misc/snippets/notes.xiki diff --git a/etc/snippets/rb.notes b/misc/snippets/rb.xiki similarity index 100% rename from etc/snippets/rb.notes rename to misc/snippets/rb.xiki diff --git a/misc/themes/Bottom_Bar_Items.xiki b/misc/themes/Bottom_Bar_Items.xiki new file mode 100644 index 00000000..86b26f2b --- /dev/null +++ b/misc/themes/Bottom_Bar_Items.xiki @@ -0,0 +1,313 @@ +# The main bottom bar theme in xsh + +$el.el4r_lisp_eval %` + (progn + + (setq xiki-bar-keymap-expand '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::Keys.docs :key=>'open'"))))) + (setq xiki-bar-keymap-go '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::Keys.docs :key=>'go'"))))) + (setq xiki-bar-keymap-options '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::Keys.docs :key=>'tasks'"))))) + (setq xiki-bar-keymap-xiki '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::Keys.docs :key=>'xiki'"))))) + (setq xiki-bar-keymap-search '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::Keys.docs :key=>'search'"))))) + + + (setq xiki-bar-keymap-content '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::Keys.docs :key=>'content'"))))) + (setq xiki-bar-keymap-keys '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::Keys.docs :key=>'keys'"))))) + (setq xiki-bar-keymap-quit '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::Keys.docs :key=>'quit'"))))) + + (setq xiki-bar-keymap-expose '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (el4r-ruby-eval "Xiki::View.expose"))))) + + (setq xiki-bar-keymap-close '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (mouse-set-point last-command-event) (el4r-ruby-eval "Xiki::View.close"))))) + + (setq xiki-bar-keymap-toggle '(keymap (mode-line keymap (mouse-1 lambda nil (interactive) (mouse-set-point last-command-event) (el4r-ruby-eval "Xiki::View.toggle_bar_mode"))))) + + ; Make these be defined + (setq xiki-bar-special-text nil) + (setq xiki-bar-hidden nil) + + ; Bottom bar > when key shortcuts + + (setq xiki-paint-bar-shortcuts + (lambda (&optional normal underline) + (let* ( + (underline (or underline 'mode-line-transparent-underline-activatable)) + (normal (or normal 'mode-line-transparent-activatable)) + + (line-next (buffer-substring (point-at-bol 2) (point-at-eol 2))) + (match1 (string-match "^ *" line-next)) + (line-next-indent (match-end 0)) + (has-child (> line-next-indent line-indent)) + + ; (prompt (string-match "^[ =]*[$%&] ." line)) + (x-word (if has-child + (concat (propertize "Cl" 'face normal 'keymap xiki-bar-keymap-expand) (propertize "o" 'face underline 'keymap xiki-bar-keymap-expand) (propertize "se" 'face normal 'keymap xiki-bar-keymap-expand) (propertize " " 'face normal)) + (concat (propertize " " 'face normal) (propertize "O" 'face underline 'keymap xiki-bar-keymap-expand) (propertize "pen" 'face normal 'keymap xiki-bar-keymap-expand) (propertize " " 'face normal)) + )) + ) + (concat + + ; Angle bracket to toggle bar mode > between key shortcuts and path + (propertize " > " 'face (list 'fade6 normal) 'keymap xiki-bar-keymap-toggle) + + x-word + + (propertize "G" 'face underline 'keymap xiki-bar-keymap-go) ; Go + (propertize "o" 'face normal 'keymap xiki-bar-keymap-go) + (propertize " " 'face normal) + + (propertize "T" 'face underline 'keymap xiki-bar-keymap-options) ; Options + (propertize "asks" 'face normal 'keymap xiki-bar-keymap-options) + (propertize " " 'face normal) + + (propertize "X" 'face underline 'keymap xiki-bar-keymap-xiki) ; Tasks + (propertize "iki" 'face normal 'keymap xiki-bar-keymap-xiki) + + (propertize " " 'face normal) + (propertize "S" 'face underline 'keymap xiki-bar-keymap-search) ; Shared + (propertize "earch" 'face normal 'keymap xiki-bar-keymap-search) + + (propertize " " 'face normal) + + (propertize "C" 'face underline 'keymap xiki-bar-keymap-content) ; Content + (propertize "ontent" 'face normal 'keymap xiki-bar-keymap-content) + (propertize " " 'face normal) + + (propertize "K" 'face underline 'keymap xiki-bar-keymap-keys) ; Keys + (propertize "eys" 'face normal 'keymap xiki-bar-keymap-keys) + (propertize " " 'face normal) + + (propertize "Q" 'face underline 'keymap xiki-bar-keymap-quit) ; Quit + (propertize "uit" 'face normal 'keymap xiki-bar-keymap-quit) + + (propertize " x " 'face (list 'fade6 normal) 'keymap xiki-bar-keymap-close) + ) + ) + ) + ) + + + ; Bottom bar > Filtering > A-Z Filter, Expand, Go, etc + + (setq xiki-paint-bar-shortcuts-filtering + (lambda (&optional normal underline) + (let* ( + (underline (or underline 'mode-line-transparent-underline-activatable)) + (normal (or normal 'mode-line-transparent-activatable)) + (line (buffer-substring (point-at-bol) (point-at-eol))) + + ) + (concat + (propertize " " 'face normal) + + ;(if xiki-noob-mode + (concat + (propertize "A-Z" 'face (list 'filter-highlight 'underline normal)) + (propertize " filter " 'face (list 'filter-highlight normal)) + + (propertize " " 'face normal) + ) + + (propertize "O" 'face underline) + (propertize "pen or return " 'face normal) + + (propertize "G" 'face underline) + (propertize "o " 'face normal) + + (propertize "Q" 'face underline) + (propertize "uit " 'face normal) + + (propertize " (or arrow keys)" 'face normal) + + (propertize " x " 'face (list 'fade6 normal) 'keymap xiki-bar-keymap-close) + ) + ) + ) + ) + + + ; Add hook to always update modeline when editing a line > since it could make "^X Expand" appear + (defun xiki-before-change (begin end) + (force-mode-line-update) + ) + (add-hook 'before-change-functions 'xiki-before-change) + + + ; Update variable that identifies the actual window that's selected () + (defun actual-selected-window-handler () + (when (not (minibuffer-selected-window)) + (setq actual-selected-window (selected-window)) + ) + ) + + ; Hook to store actual selected window + (add-hook 'post-command-hook 'actual-selected-window-handler) + + (setq-default mode-line-format + (quote ( + (:eval + + (let ( + (multiple-p (> (length (window-list)) 1)) + (active-p (or (not (boundp 'actual-selected-window)) (eq (selected-window) actual-selected-window))) + (buffer-name (buffer-name)) + ) + + ; Doing this above + (make-local-variable 'xiki-bar-default-kind) + (setq xiki-bar-default-kind "keys") ; Assume default is keys unless a file + + (concat + + ; Not in "xsh" buffer and no special text etc., so display the file path... + + ; (if (and (or (not active-p) (not xiki-bar-special-text)) (not (and (string= "xsh" buffer-name) (not (buffer-file-name)))) (not (string-match "/$" buffer-name))) + + (cond + + ( + (or + (and (boundp 'xiki-bar-mode) (string= xiki-bar-mode "path")) + (and + (not xiki-bar-hidden) + (or (not active-p) (not xiki-bar-special-text)) + (not (and (string= "xsh" buffer-name) (not (buffer-file-name)))) + (not (string-match "/$" buffer-name)) + (not (string= (concat (getenv "HOME") "/.xiki/interactions/") default-directory)) + (not (and (boundp 'xiki-bar-mode) (string= xiki-bar-mode "keys"))) + ) + ) + + ; Display the file path + + (let + ( + (dir (replace-regexp-in-string (getenv "HOME") "~" (replace-regexp-in-string "^//$" "/" default-directory)) ) + (star (if (and (or (buffer-file-name) (string-match "\\\\." buffer-name)) (buffer-modified-p)) (propertize "*" 'face 'mode-line-file) " ")) + ) + + ; If not in ~/xiki/, don't show the path + (if (not (string-match "^~/xiki/" dir)) + (setq dir "") + ) + + (setq xiki-bar-default-kind "path") + + ; "xiki.com/@x/x" view, so show it in bar + (when (string-match "^\\\\(xiki\\.[a-z]+\/\\\\)\\\\(@.+\\\\)" buffer-name) + (setq dir (match-string 1 buffer-name)) + (setq buffer-name (match-string 2 buffer-name)) + (setq star "") + ) + + (xiki-bar-add-padding (concat + + (propertize " < " 'face (list 'fade6) 'keymap xiki-bar-keymap-toggle) + star + + dir + + (propertize buffer-name 'face 'mode-line-file 'local-map xiki-bar-keymap-expose) + + ":" + (number-to-string (line-number-at-pos)) + + (if (and xiki-noob-mode (not multiple-p)) + (concat + " " + (propertize "C" 'face 'mode-line-file-underline 'keymap xiki-bar-keymap-content) + (propertize "ontent" 'face 'mode-line-file 'keymap xiki-bar-keymap-content) + " " + (propertize "K" 'face 'mode-line-file-underline 'keymap xiki-bar-keymap-keys) + (propertize "eys" 'face 'mode-line-file 'keymap xiki-bar-keymap-keys) + "" + ) + ) + + (propertize " x " 'face (list 'fade6) 'keymap xiki-bar-keymap-close) + ) + 'mode-line-sides-underline) + ) + ) + + ; Not the file path, so try other options... + + (xiki-bar-hidden (progn + + ; Hidden menu bar, so show nothing... + (let* ( + (face + (if multiple-p + (if active-p + 'mode-line-transparent-underline-activatable + 'mode-line-transparent-underline + ) + (if active-p + 'mode-line-transparent-activatable + 'mode-line-transparent + ) + ) + ) + ) + (propertize (xiki-bar-add-padding "") 'face face) + ) + )) + + (xiki-bar-special-text + + ; Special text, just show it... + + ; Face should be underline only if split + (let* ( + (face (if multiple-p 'mode-line-transparent-underline-activatable 'mode-line-transparent-activatable)) + ) + + ; It's a function, so call it + (if (eq (type-of xiki-bar-special-text) 'cons) + (xiki-bar-add-padding (funcall xiki-paint-bar-shortcuts-filtering 'mode-line-transparent 'mode-line-transparent-underline) 'mode-line-transparent) + ; It's a string, so just use it + (propertize (xiki-bar-add-padding xiki-bar-special-text) 'face face) + ) + + ) + ) + + (t (progn + + ; Else, show key shortcuts... + + (force-mode-line-update) + (let* ( + (line (buffer-substring (point-at-bol) (point-at-eol))) + (match1 (string-match "^ *" line)) + (line-indent (match-end 0)) + ) + + + ; Face should be underline only if split + (if multiple-p + + (xiki-bar-add-padding + (funcall xiki-paint-bar-shortcuts 'mode-line-file 'mode-line-file-underline) + + 'mode-line-transparent-underline + ) + + (xiki-bar-add-padding + (if active-p + (funcall xiki-paint-bar-shortcuts 'mode-line-transparent-activatable) + (funcall xiki-paint-bar-shortcuts 'mode-line-transparent) + ) + 'mode-line-transparent) + + ) + ) + + )) + ) + ) + ) + + ) + ))) + ) +` + diff --git a/misc/themes/Bottom_Bar_Styles.xiki b/misc/themes/Bottom_Bar_Styles.xiki new file mode 100644 index 00000000..200c5c9b --- /dev/null +++ b/misc/themes/Bottom_Bar_Styles.xiki @@ -0,0 +1,28 @@ +module Xiki + + Styles.mode_line :bg=>'#999', :fg=>"#222", :bold=>true + Styles.mode_line_inactive :bg=>'#999', :fg=>"#222", :bold=>true + Styles.mode_line_underline :fg=>"#222", :bold=>true, :underline=>true + Styles.mode_line_inactive_underline :fg=>"#222", :bold=>true, :underline=>true + + Styles.mode_line_transparent :bg=>"unspecified", :fg=>"#aaa", :underline=>nil + Styles.mode_line_transparent_activatable :bg=>"unspecified", :fg=>"#aaa", :underline=>nil + Styles.mode_line_transparent_underline :bg=>"unspecified", :fg=>"#aaa", :underline=>1, :bold=>true + Styles.mode_line_transparent_underline_activatable :bg=>"unspecified", :fg=>"#aaa", :underline=>1, :bold=>true + + Styles.mode_line_sides :bg=>"unspecified", :fg=>"#aaa", :underline=>nil + Styles.mode_line_sides_underline :bg=>"unspecified", :fg=>"#aaa", :underline=>1, :bold=>true + + Styles.mode_line_file :fg=>"#fff" + Styles.mode_line_file_underline :fg=>"#fff", :underline=>true, :bold=>true + + Styles.vertical_border :fg=>"#999", :bg=>'#999', :underline=>nil + + Styles.region :bg=>"#333" + + # Message area at bottom + Styles.minibuffer_prompt :size=>110, :face=>"Monaco", :fg=>"#999" + Styles.minibuffer :size=>110, :face=>"Monaco", :fg=>"#999" + Styles.echo_area :size=>110, :face=>"Monaco", :fg=>"#999" + +end diff --git a/misc/themes/Default.xiki b/misc/themes/Default.xiki new file mode 100644 index 00000000..8155385a --- /dev/null +++ b/misc/themes/Default.xiki @@ -0,0 +1,24 @@ +module Xiki + Themes.use 'Rainbow Fonts' + Themes.use "Bottom Bar Items" # Long complicated elisp that draws text + + # Put this last, since it changes the bar, and will thus fix the control lock color + Themes.use "Bottom Bar Styles" # Styles + +end + +$el.tool_bar_mode -1 if Xiki::Environment.gui_emacs + +$el.el4r_lisp_eval %` + (progn + (custom-set-variables + '(tabbar-mode nil) ; No tabs + ) + (set-face-attribute 'region nil :background "#333333") ;; More mac-like selection color + + (setq-default + frame-title-format + '("%b")) + (setq frame-title-format '("%b" )) + ) +` diff --git a/etc/themes/Rainbow_Fonts.notes b/misc/themes/Rainbow_Fonts.xiki similarity index 69% rename from etc/themes/Rainbow_Fonts.notes rename to misc/themes/Rainbow_Fonts.xiki index 49985b7c..e8303fbe 100644 --- a/etc/themes/Rainbow_Fonts.notes +++ b/misc/themes/Rainbow_Fonts.xiki @@ -1,11 +1,15 @@ module Xiki if Styles.dark_bg? - Styles.define :font_lock_comment_face, :fg=>'555' # gray + Styles.define :link, :fg=>"#09c" + Styles.define :font_lock_builtin_face, :fg=>'#77b' # purple + Styles.define :font_lock_comment_face, :fg=>'#777' # gray Styles.define :font_lock_function_name_face, :fg=>'f50' # orange Styles.define :font_lock_type_face, :fg=>'0a1' # green - Styles.define :font_lock_variable_name_face, :fg=>'fd0' # yellow - Styles.define :font_lock_string_face, :fg=>'e10' # red + Styles.define :font_lock_variable_name_face, :fg=>'cc0' # yellow + + Styles.define :font_lock_string_face, :fg=>'97f' # blueer purple Styles.define :font_lock_keyword_face, :fg=>'999' + else Styles.define :font_lock_comment_face, :fg=>'aaa' # gray Styles.define :font_lock_function_name_face, :fg=>'f50' # orange diff --git a/etc/vim/vim_status.notes b/misc/vim/vim_status.notes similarity index 100% rename from etc/vim/vim_status.notes rename to misc/vim/vim_status.notes diff --git a/etc/vim/xiki.vim b/misc/vim/xiki.vim similarity index 73% rename from etc/vim/xiki.vim rename to misc/vim/xiki.vim index afc14918..8a646495 100644 --- a/etc/vim/xiki.vim +++ b/misc/vim/xiki.vim @@ -1,10 +1,15 @@ function! XikiLaunch() ruby << EOF + xiki_dir = ENV['XIKI_DIR'] - ['ol', 'vim/line', 'vim/tree'].each {|o| require "#{xiki_dir}/lib/xiki/#{o}"} + ['core/ol', 'vim/line', 'vim/tree'].each {|o| load "#{xiki_dir}/lib/xiki/#{o}.rb"} + include Xiki + line = Line.value + next_line = Line.value 2 + indent = line[/^ +/] - command = "xiki #{line}" + command = "xiki '#{line}'" result = `#{command}` Tree << result EOF diff --git a/etc/www/index.rb b/misc/www/index.rb similarity index 100% rename from etc/www/index.rb rename to misc/www/index.rb diff --git a/etc/www/public/error.html b/misc/www/public/error.html similarity index 100% rename from etc/www/public/error.html rename to misc/www/public/error.html diff --git a/etc/www/sinatra_control.rb b/misc/www/sinatra_control.rb similarity index 70% rename from etc/www/sinatra_control.rb rename to misc/www/sinatra_control.rb index 343b654f..a38a0292 100644 --- a/etc/www/sinatra_control.rb +++ b/misc/www/sinatra_control.rb @@ -1,7 +1,14 @@ +require "/projects/xiki/lib/xiki/core/ol.rb" +Ol() + require 'rubygems' require 'daemons' +# Temp + pwd = Dir.pwd +Ol "pwd", pwd +Ol RUBY_VERSION result = Daemons.run_proc('xiki_web', {:dir_mode=>:normal, :dir=>"/tmp/", :log_output=>true}) do Dir.chdir(pwd) diff --git a/misc/www/xiki_web_server.rb b/misc/www/xiki_web_server.rb new file mode 100644 index 00000000..a133cf14 --- /dev/null +++ b/misc/www/xiki_web_server.rb @@ -0,0 +1,186 @@ +# Tmp +require "/projects/xiki/lib/xiki/core/ol.rb" +Ol RUBY_VERSION + + +require "sinatra/base" +require "cgi" +require "json" +require 'open3' + +xiki_directory = `xiki dir`.strip +Ol "xiki_directory", xiki_directory + +$:.unshift "#{xiki_directory}lib" +require 'xiki/core/ol.rb' +require 'xiki/core/files.rb' +require 'xiki/core/core_ext.rb' +require 'xiki/core/html.rb' + +Ol["!"] + +# TODO: Enable basic auth for security + # Add menu to let users create a password +# use Rack::Auth::Basic, "Nope" do |u, p| +# u == 'foo' && p == 'bar' +# end + +class XikiWebServer < Sinatra::Base + + set :port, 8163 + set :bind, '127.0.0.1' + + get %r{^/_reload$} do + xiki_directory = File.expand_path "#{File.dirname(__FILE__)}/../.." + load "#{xiki_directory}/etc/www/xiki_web_server.rb" + "<* done" + end + + get '/*' do + index env['REQUEST_PATH'].sub(/^\//, '') + end + + post '/*' do + index env['REQUEST_PATH'].sub(/^\//, '') + end + + # Handles / + def default + txt = call_command "web/docs", env + end + + # Handles most requests + def index menu + + # Probably only do this if it's requesting an image! + # For now, if ends in .jpg, just assume they're requesting an image? + # What if it's an actual menu item? How to distinguish? + menu.gsub! /\.jpg$/, '' + + no_keys = false + + return default if menu == "" + + # Run command... + + # menu.sub! /^@/, '' + menu.sub! /^=/, '' + menu.gsub! /-/, ' ' + + txt = call_command menu, env + + # TODO: return different string when the service is down + + if txt.empty? + menu = menu.sub /\/$/, '' + + no_keys = true + + if menu =~ /\/./ # If slash that's not at end + puts "
Nothing returned.  Maybe service is down, or maybe menu\njust returned nothing, run xiki command\n\n  $ xiki\n"
+      else
+        puts %`
+          

Menu '#{menu}' doesn't exist yet. Create it?

+ +
+ + +
+ +
+ + + + `.gsub(/^ /, '') + end + end + + # if a file, read it manually... + if txt =~ /^@file\/(.+)/ + file = $1 + content_type = {".jpg"=>"image/jpeg"}[File.extname file] + content_type content_type + return File.read(file, *Xiki::Files.encoding_binary) + end + + txt + + rescue Exception=>e + "
#{e.message}\n#{e.backtrace}
" + end + + + def call_command path, env + + # Only process as html if browser and not ajax... + + client = "" + + is_browser = env['HTTP_USER_AGENT'] =~ /\AMozilla/ || + env['HTTP_ACCEPT'] =~ /\Atext\/html/ + + client = " -web" if is_browser && env["HTTP_X_REQUESTED_WITH"] != "XMLHttpRequest" + + # If POST, pipe params and path to command... + + if env['REQUEST_METHOD'] == "POST" + options = "@options/#{JSON[params]}" + return self.class.shell_command "xiki#{client} -", :stdin=>"#{options}\n#{path}" + end + + # Otherwise, run command normally... + + command = "xiki#{client} '#{path}'" + `#{command}` + + end + + + def self.shell_command command, options={} + + # stdin, stdout, stderr = Open3.popen3("#{profile}cd \"#{dir}\";#{command}") + stdin, stdout, stderr = Open3.popen3(command) + + if txt = options[:stdin] + stdin.puts txt + stdin.close + end + + result = "" + result << stdout.readlines.join('') + result << stderr.readlines.join('') + + result.force_encoding("binary") if result.respond_to? :force_encoding + result.gsub!("\c@", '.') # Replace out characters that el4r can't handle + result + end + + + def camel_case txt + return txt if txt !~ /_/ && txt =~ /[A-Z]+.*/ + txt.split('_').map{|e| e.capitalize}.join + end +end + +XikiWebServer.run! diff --git a/etc/xiki.org/demo.notes b/misc/xiki.org/demo.notes similarity index 100% rename from etc/xiki.org/demo.notes rename to misc/xiki.org/demo.notes diff --git a/etc/xiki.org/demo2.notes b/misc/xiki.org/demo2.notes similarity index 100% rename from etc/xiki.org/demo2.notes rename to misc/xiki.org/demo2.notes diff --git a/etc/xiki.org/demo3.notes b/misc/xiki.org/demo3.notes similarity index 100% rename from etc/xiki.org/demo3.notes rename to misc/xiki.org/demo3.notes diff --git a/etc/xiki.org/demoN.notes b/misc/xiki.org/demoN.notes similarity index 100% rename from etc/xiki.org/demoN.notes rename to misc/xiki.org/demoN.notes diff --git a/etc/xiki.org/xiki_bootstrap_source.notes b/misc/xiki.org/xiki.org_bootstrap_source.notes similarity index 71% rename from etc/xiki.org/xiki_bootstrap_source.notes rename to misc/xiki.org/xiki.org_bootstrap_source.notes index e8e5fc42..5a9926c8 100644 --- a/etc/xiki.org/xiki_bootstrap_source.notes +++ b/misc/xiki.org/xiki.org_bootstrap_source.notes @@ -1,77 +1,109 @@ -> Deploy -- /tmp/ - $ scp tmp.html xiki@xiki.org:/var/www/xiki.org/index.html - $ scp tmp.html xiki@xiki.org:/var/www/xiki.org/screencasts/index.shtml +> New directions > update site +~/projects/ + - xiki_org/ + 1. Download home page + $ scp xiki@xiki.org:/var/www/xiki.org/index.html index.html + - xiki_org/ + 2. Edit + + index.html + + - xiki_org/ + 3. Upload home page + $ scp index.html xiki@xiki.org:/var/www/xiki.org/index.html http://xiki.org -http://xiki.org/screencasts -> Home page -- it's currently hard-coded to .org - - how to fix?! - -bootstrap/ +> Old directions > 1. Edit home page +xiki loc/ + | - project name/Xiki - navbar/ - | + | - container/ - hero/ - -