Como fecho o Vim externamente?

23

Digamos que eu tenho um servidor X11 pendurado, impedindo-me de salvar o trabalho da sessão do XTerm Vim que o servidor X11 controla. (Não GVim, apenas Vim-in-XTerm regular.)

Existe uma maneira que eu poderia (de um terminal diferente) dizer ao processo Vim em execução para "salvar tudo e sair" na linha de comando? Enviando um sinal ou através de outros meios?

Eu sei sobre os arquivos de troca do Vim, e que eu poderia simplesmente matar o Vim e me recuperar da troca. Estou perguntando se existe uma maneira "mais limpa".

DevSolar
fonte
6
Se essas sessões do Vim foram iniciadas com um servidor ativado (como o gvim faz por padrão), você pode usar a funcionalidade cliente-servidor do Vim para fazer isso. Outra opção pode ser usar reptyr para forçar os programas Vim para um novo terminal, digitar os TTYs e fechá-los.
Muru

Respostas:

25

Tendo recentemente encontrado esse problema (de outra maneira: o Vim rodando em um servidor remoto e esqueci a tela), decidi procurar uma maneira.

A primeira idéia foi procurar os descritores de arquivo usados ​​pelo Vim e tentar escrever nele. Os fds de Vim apontam para o psedoterminal aberto pelo emulador de terminal, naturalmente:

$ ls -l /proc/$(pgrep -n vim)/fd/
total 0
lrwx------ 1 muru muru 64 Nov 17 01:25 0 -> /dev/pts/14
lrwx------ 1 muru muru 64 Nov 17 01:25 1 -> /dev/pts/14
lrwx------ 1 muru muru 64 Nov 17 01:25 2 -> /dev/pts/14
lrwx------ 1 muru muru 64 Nov 17 01:25 3 -> socket:[99564312]

No entanto, minhas primeiras tentativas falharam:

echo '^[:wq^M' > /proc/$(pgrep -n vim)/fd/0
echo ':wq^M' > /proc/$(pgrep -n vim)/fd/0
echo ':wq^M' > /proc/$(pgrep -n vim)/fd/0
echo '^C' > /proc/$(pgrep -n vim)/fd/0
printf "%s" '^[:wqa!^M' > /proc/$(pgrep -n vim)/fd/0

O ^[e ^Mforam obtidos por CtrlVEsce CtrlVEnter, respectivamente.

Todos eles resultaram nos caracteres aparecendo no terminal (eu estava testando isso localmente, antes de aplicá-lo à sessão remota). Pesquisando, encontrei este post do SO , usando Python para escrever no dispositivo pseudoterminal:

#!/usr/bin/python

import sys,os,fcntl,termios
if len(sys.argv) != 3:
   sys.stderr.write("usage: ttyexec.py tty command\n")
   sys.exit(1)
fd = os.open("/dev/" + sys.argv[1], os.O_RDWR)
cmd=sys.argv[2]
for i in range(len(cmd)):
   fcntl.ioctl(fd, termios.TIOCSTI, cmd[i])
fcntl.ioctl(fd, termios.TIOCSTI, '\n')
os.close(fd)

E testá-lo em um shell python interativo funcionou:

$ sudo python3
Python 3.5.0 (default, Sep 20 2015, 11:28:25) 
[GCC 5.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os, fcntl, termios
>>> fd = os.open('/dev/pts/14', os.O_RDWR)
>>> a = '\033:wqa!\n'
>>> for i in a: fcntl.ioctl(fd, termios.TIOCSTI, i);
... 
b'\x1b'
b':'
b'w'
b'q'
b'a'
b'!'
b'\n'
>>> 

Feito!

muru
fonte
1
Observe que o script python requer permissões de root para acessar o terminal.
martinkunev
6

Você pode enviar comandos para o vim externamente se estiver executando ...

Servidores Vim

Por exemplo, fazendo:

vim --servername vim

fará com que o vim inicie um servidor com o nome "vim". Chame duas vezes e o novo servidor será chamado "vim1", três vezes e "vim2" etc. Você pode querer criar um apelido para esse comando.

Você pode saber em qual servidor uma instância específica é nomeada olhando o título da janela. Quando você vê:

[Sem nome] + - VIM3

o nome do servidor não diferencia maiúsculas de minúsculas "VIM3" ("vim3" se refere à mesma instância.). Observe que se você vir:

[Sem nome] + - VIM

isso não significa necessariamente que ele tenha um servidor chamado "VIM". Você pode garantir que o servidor exista listando os nomes dos servidores com:

vim --serverlist

Ainda assim, a questão surge apenas para o "VIM", especificamente. Se você vir "GVIM" ou algum outro nome com um número anexado, significa que é um servidor.

Como usar o cliente

Agora, na sua pergunta, você pode salvar tudo e sair de uma determinada instância do vim fazendo, por exemplo:

vim --servername vim2 --remote-send $'\e:wqa\n'

Usamos o escape para retornar ao modo normal, caso você esteja no modo de inserção ou comando. Você pode fazer algo diferente :wqa, mas isso parece o mais apropriado para mim, porque deixará os arquivos de troca de buffers que não puderam ser salvos (porque são novos e não têm um nome de arquivo etc.).

Se você quiser fazer isso para todas as instâncias como no seu caso aqui, basta percorrer a lista de servidores da seguinte maneira:

for instance in $(vim --serverlist); do
  vim --servername $instance --remote-send $'\e:wqa\n'
done

Se, por algum motivo, você não gostar --remote-send, poderá usar a --remote-exprque tem a vantagem de fazer com que o cliente produza o resultado ou o erro que ele possa ter causado, da seguinte forma:

$ vim --servername vim2 --remote-expr 'execute("wqa")'

E141: No file name for buffer 1

Observe que o uso da funcionalidade do servidor do Vim requer que o Vim tenha sido construído com a +clientserveropção

JoL
fonte
5

Instale o reptyrcomando usando o gerenciador de pacotes do sistema, como:

sudo apt install reptyr
pacman -Sy reptyr

Em seguida, use o reptyrcomando para alternar o tty remoto para o local (novo) tty, da seguinte maneira:

ssh user@remote-hostname
ps auxw | grep -i vim
reptyr PID

Onde PIDestá o ID do processo na pssaída do comando.

Após o erro:

Não foi possível anexar ao pid 12345: permissão negada

Altere o "escopo ptrace" para 0:

sudo su -
echo 0 > /proc/sys/kernel/yama/ptrace_scope

Depois que a sessão do vim tiver sido trocada da sessão antiga para a nova, salve e saia como de costume. Observe que você pode ter que pressionar Enterpara atualizar o console.

Dave Jarvis
fonte
0

E se você graciosamente matar o Vim?

kill -s 15 -p [PID for Vim]

O kill -s (sinal) 15 é chamado SIGTERM, que informa esse processo para se desligar normalmente.

Para obter o PID (Process ID) do Vim, use:
ps ax | grep vim

Gustav Blomqvist
fonte
1
O Vim não grava automaticamente alterações não salvas, independentemente de quais sinais foram enviados.
muru 15/09/17