Modo daemon: adiar solicitações interativas na inicialização?

16

(Observe que, título pelo contrário, esta pergunta não é a mesma que Como iniciar no modo daemon e suprimir diálogos interativos?, Pois essa pergunta foi "respondida" pelo remetente, eliminando o que estava causando o aparecimento de um prompt específico.)

Gostaria de saber se existe uma maneira geral de impedir que você fique emacs --daemonsempre esperando uma resposta para um prompt exibido em um minibuffer que ainda não existe.

É impossível conectar-se a um emacsclient para responder a essas solicitações, porque o servidor não inicia até o Emacs concluir a sequência de inicialização. (Isso significa que, se você tiver ALTERNATE_EDITOR definido como uma string vazia, o que faz com emacsclientque um servidor que não consegue encontrar um servidor inicie um novo daemon, você pode acabar com vários daemons do Emacs todos presos e aguardando.) Tenho que killall emacsresolver o problema antes de continuar.

Eu posso jogar whack-a-mole com cada coisa causando um prompt na inicialização quando eu o identifico (iniciando o Emacs no modo não daemon e vendo o que ele está pedindo), mas não é uma solução porque não pode parar o próximo daemon pendurado na inicialização por um novo motivo.

Para dar um exemplo: um motivo comum para travar foi após uma reinicialização do sistema ou uma falha no Emacs, quando o primeiro Emacs pós-reinicialização quis saber se era bom roubar arquivos de bloqueio do Emacs desativado. Eu poderia consertar isso criando conselhos para que esse prompt sempre respondesse "sim" sem interação. Porém, um dos arquivos que foram abertos na sessão anterior foi salvo, um arquivo TRAMP que requer uma senha sudo ou SSH; portanto, o daemon fica parado aguardando um prompt de senha. Portanto, corrijo isso editando manualmente o arquivo da sessão (com viou emacs -q!) Para remover os arquivos incorretos - mas isso não impede que isso aconteça na próxima vez.

Portanto, posso parar de carregar minha sessão automaticamente na inicialização e alterá-la para um comando que devo executar manualmente no meu primeiro emacsclient. Mas se ela não estiver carregando minha sessão em segundo plano, estará pronta quando eu estiver pronta para usá-la, todo o objetivo do daemon será perdido!

Então, o que eu gostaria é:

  • (Melhor) Alguma maneira de adiar os prompts do minibuffer até eu abrir um emacsclient, enquanto ainda concluo o restante da inicialização.
  • (OK) Alguma maneira de fazer todos os prompts do minibuffer que eu ainda não noavisei, como descrito acima, retornará, a menos que um emacsclient esteja em execução. Eu posso viver com meus buffers TRAMP em erro desde que funcione principalmente.

Existe alguma maneira de alcançar um desses objetivos?

Trey
fonte
Existe uma maneira de reproduzir esses tipos de problemas programaticamente para que a comunidade possa solucionar problemas?
Melioratus 02/04/19
1
Bem, como escrevi na primeira linha, é bastante fácil de corrigir um dado exemplo ... "Doutor, dói quando eu faço isso ..." "Então não faça isso." A questão é o caso geral. Mas uma maneira simples de criar o problema é fazer com que a inicialização restaure a área de trabalho (read-desktop), antes de executar emacs --daemon, crie um arquivo de bloqueio falso colocando um número inteiro em .emacs.desktop.lock (onde colocar esse arquivo, infelizmente, depende da sua configuração , mas provavelmente seja seu diretório de usuário ou ~ / .emacs.d / .
Trey
1
Este é um caso mencionado com frequência aqui, por exemplo: emacs.stackexchange.com/questions/8147/… ou emacs.stackexchange.com/questions/31621/… pode fornecer contexto.
Trey
Este bug parece relacionado: Bug # 13697 - Uma maneira de saber se o Emacs pode interagir com o usuário , mas ninguém trabalhou nele, até onde eu sei.
npostavs
@npostavs Obrigado pelo link - eu anotei o bug, apesar de ter sido um começo falso que comentei aqui (desde que excluído) antes de descobrir!
Trey

Respostas:

2

Nossa discussão esclareceu que você não possui um servidor X executando isso, tornando minha primeira solução inútil para você.

A seguir, apresento uma segunda solução que funciona com quadros de terminais de texto.

Quando sua inicialização requer a entrada do usuário por meio de uma, as funções recomendadas pelo avoid-initial-terminalEmacs aguardam até você abrir um quadro de terminal de texto. O prompt aparece no minibuffer desse quadro e você pode dar sua resposta interativa.

As informações relacionadas ao código são fornecidas como comentários no código. Existem TODOmarcadores com descrições que mostram onde inserir sua própria configuração. Atualmente, existem formulários de teste que validam o código.

;; TODO: Do here configure the server if needed.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Startup the server:
;; Analysis of read_from_minibuffer in src/minibuf.c and daemon_type in src/emacs.c
;; shows that daemon-initialized must have run before read-passwd / read-string
;; works on frames. Before it only works on stdin & stdout.
(server-start) ;;< early start
(let ((after-init-time before-init-time))
  (daemon-initialized)) ;; Finalize the daemon, 

(advice-add 'daemon-initialized :override #'ignore)
;;< Ignore `daemon-initialized' after initialization. It may only run once!
;; Now the background emacs is no longer marked as daemon. It just runs the server.

(defun prevent-server-start (&rest _ignore)
  "Prevent starting a server one time after `server-start' has been advised with this."
  (advice-remove 'server-start #'prevent-server-start))

(advice-add 'server-start :override #'prevent-server-start)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Prepare waiting for a real terminal frame when user input is required:

(defun avoid-initial-terminal (fun &rest args)
  "Wait until we are no longer on \"intial-terminal\".
Afterwards run `fun' with frame on the other terminal selected."
  (message "Avoiding initial terminal. Terminal: %S" (get-device-terminal nil))
  (while (string-equal
      (terminal-name (get-device-terminal nil))
      "initial_terminal")
    (sleep-for 1))
  ;; (message "Selected frame: %S; Running %S with %S." (selected-frame) fun args)
  (apply fun args))

(advice-add 'read-string :around #'avoid-initial-terminal)

(advice-add 'read-passwd :around #'avoid-initial-terminal)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO: Your initialization that is not daemon related
;; and may require user input:

;; Currently this is just a test.
(read-passwd "Passwd: ")

(read-string "String: ")

(y-or-n-p "y-or-n query")

Teste: versão Emacs: 26.1

1º) Executar emacs --daemonem um console.

2) Execute emacsclient --ttyem outro console. Você é solicitado a fornecer uma senha e uma string. Posteriormente, você também precisará responder a uma consulta y-ou-np.

Tobias
fonte
3

Não é exatamente o que você está pedindo, mas talvez uma solução para o seu problema original:

Gostaria de saber se existe uma maneira geral de impedir o emacs --daemon de ficar pendurado para sempre, aguardando uma resposta para um prompt exibido em um minibuffer que ainda não existe.

Se o daemon fornecer um quadro gráfico para responder a perguntas que surjam na fase de inicialização, você não ficará mais preso.

O código abaixo define um conselho geral my-with-initial-frameque abre um quadro no primeiro visor disponível (por exemplo, :0.0).

Esse conselho pode ser facilmente adicionado à consulta de comandos como y-or-n-pou read-passwd, conforme demonstrado abaixo.

Apenas a abertura de um quadro oferece uma possibilidade bastante grossa de responder às consultas na interface do usuário. Pode-se também usar uma caixa de diálogo para, y-or-n-pmas isso exigiria soluções especiais para comandos de consulta específicos. Eu queria evitar isso.

Se você tentar esse código no seu arquivo init, verifique se é a primeira coisa que existe.

(when (daemonp)

  (defun my-with-initial-frame (&rest _)
    "Ensure a frame on display :0.0 and ignore args."
    (let* ((display-list (x-display-list))
           (display-re (and display-list (regexp-opt display-list)))
           (term (and display-re (cl-some (lambda (term) (and (string-match display-re (terminal-name term)) term)) (terminal-list))))
           (frame (and term (cl-some (lambda (frame) (and (frame-live-p frame) frame)) (frames-on-display-list term)))))
      (select-frame (or frame (make-frame-on-display (getenv "DISPLAY"))))))

  (message "Advising querying functions with `my-with-initial-frame'.")
  (advice-add 'y-or-n-p :before #'my-with-initial-frame)
  (advice-add 'read-passwd :before #'my-with-initial-frame))

Teste:

Premissas:

Tenha um xserver em execução ao qual os programas possam se conectar por DISPLAYmeio da variável de ambiente.

Entrada no xterm:

emacs --daemon

emacsclient --eval '(y-or-n-p "A")'

Abre um quadro com o y-or-n-pprompt de consulta A (y or n). Responda a essa consulta e tente novamente:

emacsclient --eval '(y-or-n-p "B")'

Nova consulta com prompt B (y or n)no mesmo quadro. Feche esse quadro, por exemplo, com C-x 5 0e tente novamente:

emacsclient --eval '(y-or-n-p "C")'

Um novo quadro é aberto com o prompt de consulta C (y or n).

O mesmo funciona para a entrada de senha.

Tobias
fonte
@Rey Eu tive algum problema com o meu código (na verdade, com o teste). A inicialização do servidor x não funcionou na primeira vez. Não notei inicialmente, pois não reiniciei o daemon. Eu corrigi isso agora. Por favor teste novamente. Obrigado.
21319 Tobias
Meu virtual Linux não tem um terminal gráfico conectado, então não posso executar xterm. Além disso, não acho que essa solução funcione para aqueles que o fazem - se você tiver o daemon configurado para ser executado na inicialização, ele tentará abrir um quadro na parte superior da tela de login, o que não é permitido; trava.
Trey
Tobias, desculpe-me se o som acima soou brusco - eu respondi no meu telefone e pode ter me interrompido, então deixe-me tentar elaborar: a única vantagem que eu posso ver do que você descreve ao não usar o daemon e executar server-startno final da inicialização em vez disso, se você tiver uma inicialização limpa, não precisará esperar. Mas ... você terá que esperar, porque, a menos que eu entenda errado, você não pode colocar a tarefa para iniciar o daemon Emacs no script de login do sistema, pois uma GUI não estará disponível. (E em um caso como o meu, nunca será mais tarde, também.)
Trey
@Trey Você pode participar de um bate - papo ?
Tobias
1

Acho que adiar as solicitações será difícil em geral, mas deve ser bastante fácil alterar o Emacs para que essas solicitações sinalizem imediatamente um erro.

Não apenas isso, mas se você não pode responder a essas solicitações sem muita ginástica, acho que se qualifica como um bug, por isso recomendo que você envie um relatório de bug para isso.

Stefan
fonte
Eu acho que preciso de um pouco mais de detalhes. Considere o bloqueio de arquivo salvo na área de trabalho que mencionei no comentário da recompensa acima. Como alguém poderia mudar o Warning: desktop file appears to be in use by PID xxx. Using it may cause conflicts. Use it anyway? (y or n)prompt para um erro, sem se referir especificamente a "área de trabalho" de alguma forma (porque dessa maneira, por não ser geral, fica uma porcaria)?
Trey
Stefan, também há um problema que não me incomoda, porque eu não executo o daemon Emacs dessa maneira, mas para fazer uma resposta geralmente útil pode precisar ser abordada: errar fatalmente fará com que o Emacs seja iniciado via systemd ou outros cães de guarda para reiniciar em um loop. Mas ignorar erros e apenas fazer logon *Messages*é provavelmente um alerta insuficiente na conexão do primeiro cliente, para que algo possa estar seriamente errado e precise de atenção imediata antes que o usuário tente executar operações com estado.
Trey
(Para esclarecer aqueles que não usam o daemon - se você o iniciar manualmente, via emacs --daemonou iniciando emacsclientcom a ALTERNATE_EDITORvariável de ambiente definida como uma sequência vazia, você verá a saída que normalmente vai *Messages*ecoar no terminal até o daemon completar a inicialização e Emacs está pronto Mas muitos têm Emacs iniciar o daemon na inicialização do sistema ou tempo de login, e a saída seja registrado ou jogado fora..
Trey
1
@Rey: a sinalização de erro não deve estar, desktopmas na y-or-n-pfunção (ou ainda mais baixa). Temos algum mecanismo para atrasar a exibição dos erros que ocorreram durante a inicialização, portanto, podemos usá-lo para exibi-los quando o primeiro emacsclient se conectar ao daemon.
Stefan
Em ambos os casos, no entanto, a maioria dos usuários não lê atentamente *Messages*- e enquanto o *Warnings*sistema pouco usado abre uma janela para o buffer se houver um quadro ativo quando o aviso for gerado, nesse caso, não existe nenhum quadro e não parece fácil adiar o pop-up até o primeiro emacsclient após o problema do aviso. Se isso pudesse ser feito, sua sugestão de fazer um yes-or-no-paviso antes do cliente seria bastante ideal. (Duvido usuários pente *Messages*no arranque!)
Trey