How to launch a shell script with an emacs keybinding, passing the word under the cursor as a variable - linux

Executing the script below on osx via emacs, didn't work, I got a permission denied message, the answer to this question solved that problem: https://stackoverflow.com/a/12276562/912475
Tldr: How can I set up a system to automatically pass the word under the cursor in emacs directly to my shell script as a variable and then run that script?
I've created a somewhat rudimentary system for "linking" to folders from plain text files in a robust way. It uses a timestamp that's generated by a script and then set as the "value" of the clipboard. From the clipboard, the timestamp is then pasted into a txt with related notes and into the name field of a folder.
To find the folders when reading the txt I use an emacs function with a keybinding that makes it possible to copy the timestamp (as a word, it's all numbers) to the clipboard and search for it in spotlight (on osx). What I'd like to do instead is to automatically launch a shell script that searches for a directory whose name ends with that string and then opens it. I already have a script that does something like that, but I don't really know how to tie the elisp function and shell script together. I'd greatly appreciate a solution that works on either osx and linux (I use both). It will probably be easy to "port" a solution that works on either one for use with the other.
This is the emacs function for copying a word under the cursor:
;;; function for copying a word under the cursor http://www.emacswiki.org/emacs/CopyWithoutSelection
(global-set-key (kbd "C-c o") (quote copy-word))
(defun copy-word (&optional arg)
"Copy words at point into kill-ring"
(interactive "P")
(copy-thing 'backward-word 'forward-word arg)
;;(paste-to-mark arg)
)
This is the script to find and open the directory whose name ends with the timestamp:
#!/bin/bash
PATH=/opt/local/bin:/opt/local/sbin:$PATH #need this to make the gnu coreutils work on osx
file_number="20130812193913"
path_to_open=$(gfind ~/x/ | grep -e $file_number$) # $ means the end of the line, makes it possible to search for directories without finding their content
open "${path_to_open}"
An edited version of the script that accepts arguments from the commandline like this:
me$ sh script_path.sh 20130812193913
The script:
#!/bin/bash
PATH=/opt/local/bin:/opt/local/sbin:$PATH #need this to make the gnu coreutils work on osx
file_number=$1
echo $file_number
path_to_open=$(gfind ~/x/ | grep -e $file_number$) # $ means the end of the line, makes it possible to search for directories without finding their content
open "${path_to_open}"
See: http://www.bashguru.com/2009/11/how-to-pass-arguments-to-shell-script.html

You could try something like this:
(defvar script-name "/foo/bar/my-script")
(defun call-my-script-with-word ()
(interactive)
(shell-command
(concat script-name
" "
(thing-at-point 'word))))
(global-set-key (kbd "C-c o") 'call-my-script-with-word)

Related

How to fzf recent files of vim/nvim, not inside vim but from terminal

I know how to fzf.vim, but I'd like to open from terminal.
Grepping history or viminfo may be achieve thst, but I wonder if there is any smart way.
This is how you can save the list of recent files from vim to a file:
vim -c "call append(0, v:oldfiles)" -c "write vim-oldfiles.tmp" -c exit
Put v:oldfiles (the list of recent files saved in ~/.viminfo) into the first (new and empty at the start) buffer, write the buffer to a file, exit.
Now you can pass the content of file to fzf.
Not exact solution. But you could open a terminal buffer on the lower part of your vim edit like an IDE and use your terminal fzf
However, not sure if this will let you open a file in a new vim tab
I have an zsh autoloaded function called old:
function old(){
vim -c 'redir >> /tmp/oldfiles.txt | silent oldfiles | redir end | q'
sed -i '/NvimTree$/d' /tmp/oldfiles.txt
local fname
fname=$(awk '/home/ && !/man:/ {print $2}' /tmp/oldfiles.txt | fzf) || return
vim "$fname"
\rm /tmp/oldfiles.txt
}
If you're having trouble executing vim on files that have ~ in their path (vim open a new blank file instead of the desired file) because fzf and vim don't expand tilde (~), here's how I do it:
export FZF_DEFAULT_OPTS=$FZF_DEFAULT_OPTS"
--bind 'ctrl-e:execute(vim -c \"execute \\\"edit\\\" expand({})\" >/dev/tty)'
"
It's trial and error, based on this.
Combining some of the other answers, here's a version that does not need a temporary file and writes to stdout (so you can pipe this into another command, or capture the output using $(...)).
vim -e -c "redir >> /dev/fd/100 | for f in v:oldfiles | silent echo substitute(f, \"^\\\\~\", \$HOME, \"g\") | endfor | redir end | q" 100>&1 &>/dev/null
This solution combines elements from other solutions, but with some improvements:
It uses some shell redirection to duplicate stdout to some free fd (100>&1) and then uses /dev/fd/100 to force writing output there. This ensures that vim actually writes to stdout rather than the terminal. Note that this can also be made to work using /dev/fd/1 (but only when omitting redir end for some reason), but then we cannot apply the next point.
It redirects stdout (and for good measure) also stderr to /dev/null, to prevent vim writing some terminal escape codes to stdout on startup, so using a different fd ensures clean output.
It uses vim in "ex" mode (vim -e) to suppress the "Vim: Warning: Output is not to a terminal" output and accompanying delay. [source]
It uses a for-loop to iterate over v:oldfiles to output just the filenames (the oldfiles command used by https://stackoverflow.com/a/70749181/740048 adds line numbers).
It uses a substitute to expand ~ in the filenames returned by vim (making the returned filenames easier to proces. Normally, shells like bash expand ~ in arguments passed to commands, but this happens only for tildes in the command typed, not tildes that result from variables or command substitution. To prevent having to rely on unsafe eval'ing later, better to expand (just) the tildes beforehand.
I also tried using the append / write combo from https://stackoverflow.com/a/60018642/740048, which worked with the /dev/fd/100 trick, but then ended up putting /dev/fd/100 in the list of oldfiles, so I did not use that approach.

How to write a string in a file using vim editor from command line

I want to create a new file using vi editor from command line and add a string to it multiple times say 100. Using vi -S command.script file.txt is supposed to do the trick where a new file file.txt will be created and the commands given in command.script file can write to this file. My command.script contains
:%100a hello world
:wq
But its's not working, what I am doing wrong?
If you interactively execute :%100a hello world in a Vim session, you'll get E488: Trailing characters. Looking up :help :a:
:{range}a[ppend][!] Insert several lines of text below the specified
line. If the {range} is missing, the text will be
inserted after the current line. [...]
These two commands will keep on asking for lines, until you type a line
containing only a ".".
tells you that the text has to be put in following lines (and concluded by a line with only a . character).
Or did you mean to use the normal mode a command? (That one takes a [count] to multiply; your %100 range is wrong, too!)
You can also use the low-level function append(), repeating the string with repeat().
summary
$append
hello world
[...]
hello world
.
execute "$normal! 100ahello world\<CR>"
" Easier with o instead of a:
$normal! 100ohello world
call append('$', repeat(['hello world'], 100))
non-Vim alternatives
But honestly, if that is your real use case (and not just a simplified toy example), you don't need Vim at all for this. Here's one example for the Bash shell:
$ for i in $(seq 100); do echo "hello world" >> file.txt; done

How to prevent execution of command in ZSH?

I wrote hook for command line:
# Transforms command 'ls?' to 'man ls'
function question_to_man() {
if [[ $2 =~ '^\w+\?$' ]]; then
man ${2[0,-2]}
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook preexec question_to_man
But when I do:
> ls?
After exiting from man I get:
> zsh: no matches found: ls?
How can I get rid of from message about wrong command?
? is special to zsh and is the wildcard for a single character. That means that if you type ls? zsh tries find matching file names in the current directory (any three letter name starting with "ls").
There are two ways to work around that:
You can make "?" "unspecial" by quoting it: ls\?, 'ls?' or "ls?".
You make zsh handle the cases where it does not match better:
The default behaviour if no match can be found is to print an error. This can be changed by disabling the NOMATCH option (also NULL_GLOB must not be set):
setopt NO_NOMATCH
setopt NO_NULL_GLOB
This will leave the word untouched, if there is no matching file.
Caution: In the (maybe unlikely) case that there is a file with a matching name, zsh will try to execute a command with the name of the first matching file. That is if there is a file named "lsx", then ls? will be replaced by lsx and zsh will try to run it. This may or may not fail, but will most likely not be the desired effect.
Both methods have their pro and cons. 1. is probably not exactly what you are looking for and 2. does not work every time as well as changes your shells behaviour.
Also (as #chepner noted in his comment) preexec runs additionally to not instead of a command. That means you may get the help for ls but zsh will still try to run ls? or even lsx (or another matching name).
To avoid that, I would suggest defining a command_not_found_handler function instead of preexec. From the zsh manual:
If no external command is found but a function command_not_found_handler exists the shell executes this function with all command line arguments. The function should return status zero if it successfully handled the command, or non-zero status if it failed. In the latter case the standard handling is applied: ‘command not found’ is printed to standard error and the shell exits with status 127. Note that the handler is executed in a subshell forked to execute an external command, hence changes to directories, shell parameters, etc. have no effect on the main shell.
So this should do the trick:
command_not_found_handler () {
if [[ $1 =~ '\?$' ]]; then
man ${1%\?}
return 0
else
return 1
fi
}
If you have a lot of matching file names but seldomly misstype commands (the usual reason for "Command not found" errors) you might want to consider using this instead:
command_not_found_handler () {
man ${1%?}
}
This does not check for "?" at the end, but just cuts away any last character (note the missing "\" in ${1%?}) and tries to run man on the rest. So even if a file name matches, man will be run unless there is indeed a command with the same name as the matched file.
Note: This will interfere with other tools using command_not_found_handler for example the command-not-found tool from Ubuntu (if enabled for zsh).
That all being said, zsh has a widget called run-help which can be bound to a key (in Emacs mode it is by default bound to Alt+H) and than runs man for the current command.
The main advantages of using run-help over the above are:
You can call it any time while typing a longer command, as long as the command name is complete.
After you leave the manpage, the command is still there unchanged, so you can continue writing on it.
You can even bind it to Alt+? to make it more similar: bindkey '^[?' run-help

Escaped strings in elisp

I am very new to Lisp - and Elisp especially - and I have a problem with string handling:
I want to convert a Windows style path to a Unix style path - especially I need to convert
a path I get from Visual Studio to a Cygwin path, as I want to be able to open a file from
Visual Studio in Emacs (I hope to use emacsclient --eval for this):
The Visual Studio path has the following format:
C:\Users\name\documents\visual studio 2010\projects\test
I want to change it into the appropriate Cygwin path which would be:
/cygdrive/c/Users/name/documents/visual studio 2010/projects/test
However trying the following in the scratch-buffer already fails:
(replace-regexp-in-string "\\" "\/" "C:\users\someone")
(subst-char-in-string ?\ ?/ "C:\users\someone")
>> Debugger entered--Lisp error: (error "Non-hex digit used for Unicode escape")
Is there any way to make Elisp not escape the backslashes in every string?
EDIT:
Here is how I call emacs from Visual Studio via external tools:
emacsclient -d 127.0.0.1:0.0 --eval '(convert-win-to-cygwin-path $(ItemPath))'
$(ItemPath) will be replaced with C:\Users\asf which I can not influence - so it will pass a String with single backslashes to emacs that I need to modify in emacs.
Can I make emacs KNOW that it needs to make double backslashes out of the single backslashes?
EDIT 2: Solution
I changed the way I attempt to start emacs by actually calling a shell-script that will start emacs - this way I can make sure that emacs gets the right path:
#!/bin/bash
export PATH="/usr/bin/:/bin/:$PATH"
filename=$1
line=$2
column=$3
cyged_path=$(cygpath "$filename")
echo "Cyged path: $cyged_path"
emacsclient -d 127.0.0.1:0.0 -n +$line:$column "$cyged_path"
And I call it from Visual Studio with the following arguments in the external tools window:
Path: <path_to_cygwin>\bin\bash.exe
Arguments: <path_to_script> $(ItemPath) $(CurLine) $(CurCol)
You don't need to do any of that.
Cygwin provides a command specifically for converting Windows paths to Cygwin paths, so that you don't need to bother about this sort of thing.
cmd> C:\cygwin\bin\cygpath.exe "C:\Users\name\documents\visual studio 2010\projects\test"
/cygdrive/c/Users/name/documents/visual studio 2010/projects/test
Edit: I was curious about a Windows shell equivalent of backticks, and found Batch equivalent of Bash backticks, which suggests you might be able to do it all with this one-liner?
cmd> for /f "usebackq tokens=*" %a in (`C:\cygwin\bin\cygpath.exe "$(ItemPath)"`) do emacsclient -d 127.0.0.1:0.0 "%a"
Another way to look at your problem is that your emacsclient -d 127.0.0.1:0.0 --eval '(convert-win-to-cygwin-path $(ItemPath))' passes the file name inside an Elisp string, which is why the backslashes are treated as escapes. So the solution is to pass it as data, as in emacsclient -d 127.0.0.1:0.0 $(ItemPath). The next problem is to make your Emacs understand those filenames. If you use the normal Windows build of Emacs that will work without doing anything special, but if you use the Cygwin build, you'll need to do something like the equivalent (but in reverse) of cygwin-mout.el.
Here's the answer in emacs lisp, even though I like Phils answer. This function converts the region which is expected to be a full windows filepath including the drive, into a cygwin path.
For example:
C:\Users\name\documents\visual studio 2010\projects\test
becomes...
/cygdrive/C/Users/name/documents/visual studio 2010/projects/test
Here's the code:
(defun win32-to-cygwin-path()
"Converts a win32 path into a cygwin happy one"
(interactive)
(save-excursion
(save-restriction
(narrow-to-region (point) (mark))
(goto-char (point-min))
(insert "/cygdrive/")
(goto-char (point-min))
(while (search-forward ":" nil t)
(replace-match "" nil t))
(while (search-forward "\\" nil t)
(replace-match "/" nil t)))))
A backslash needs to be escaped. This would work:
ELISP> (subst-char-in-string ?\\ ?/ "C:\\users\\someone")
"C:/users/someone"
Are you sure Windows isn't sending preescaped backslashes? It would be strange if it didn't do that.
See this wiki page for how other have solved this problem.
None of these solutions worked for me (emacs 24.3.1), anyway, a simple .bat script will do:
#ECHO OFF
c:\cygwin64\bin\cygpath.exe %1 > tmp.txt
set /p FILE_TO_EDIT= < tmp.txt
del tmp.txt
C:\cygwin64\bin\cygstart.exe -- /bin/emacs-w32.exe %FILE_TO_EDIT%
Save that in a file.bat and send the file you want to edit as the argument, for WinSCP, will look like:
C:\Users\xxxxx\Desktop\open.bat !.!

Is it possible to access vim's command-line arguments in vimscript?

I found the answer to this question while writing it, so I've broadened it a little. I wanted to access the --servername argument, in order to create dynamic settings in my .vimrc file.
Through vim's help, I found the v:servername variable, and my script is working. However, now I'm curious if it's possible to access any arbitrary command-line argument. For example, if I wanted to know if vim was in Lisp mode (-l) or Debugging mode (-D), how would I do it? There seems to be no corresponding v: variable for them.
Here are the variables I found by autocompleting :help v:<Tab>
Is there a general way to access command-line arguments from vimscript?
Strangely, I think the answer may be "No, there is no direct way to access startup options specified on the command line".
The :args command and argv() can be used to access the filename(s) specified on startup, but that's not what you want.
I see on Vim's forums that someone offered this solution to get the startup command line on Linux:
:exe '!tr "\0" " " </proc/' . getpid() . '/cmdline'
I assume there's analogous command on Windows. . . .
You can look over that forum thread here:
http://groups.google.com/group/vim_use/browse_thread/thread/43773f27cdc10265/ad17ae8180c0fb6e?show_docid=ad17ae8180c0fb6e
My googling indicates that this feature has been proposed but never implemented. However I did come up with a bit of a kludge that nevertheless works:
:echo split( system( "ps -o command= -p " . getpid() ) )
# => [ 'vim', ... arguments ... ]
(Tested on OS X Lion.)
The getpid() function gets Vim's PID, then we call ps externally with options to return nothing but the "command" value for process, then use split() to split the command into a list.

Resources