From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Arthur Miller Newsgroups: gmane.emacs.devel Subject: Re: Info-mode patch Date: Wed, 28 Jun 2023 23:52:10 +0200 Message-ID: References: <86fs6ertto.fsf@mail.linkov.net> <86wmzpqva6.fsf@mail.linkov.net> <867cropyh3.fsf@mail.linkov.net> <86mt0kulli.fsf@mail.linkov.net> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="8639"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Cc: emacs-devel@gnu.org To: Juri Linkov Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Thu Jun 29 06:52:43 2023 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1qEje6-000231-A4 for ged-emacs-devel@m.gmane-mx.org; Thu, 29 Jun 2023 06:52:43 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qEjd3-0005rE-Ie; Thu, 29 Jun 2023 00:51:37 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qEdAD-0000MV-R7 for emacs-devel@gnu.org; Wed, 28 Jun 2023 17:57:25 -0400 Original-Received: from mail-am0eur02olkn20811.outbound.protection.outlook.com ([2a01:111:f400:fe13::811] helo=EUR02-AM0-obe.outbound.protection.outlook.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qEdA7-000378-Q3 for emacs-devel@gnu.org; Wed, 28 Jun 2023 17:57:25 -0400 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=lOeFKQKVuX1gg+jdWlIbj4oTmIW56DuOHNqkBUYs2nHcF3i0VkMF6cOLpQZPoas3AqU2hhzMnQkE3qsIc6t8VxxBWyIzTPH+gACMBZCwv6cD11n+xkLq2i+olrYx3plrp5ptZcOOf4Fa5fuQf4IRjUtB1KY0aaFWaSaeyxWajPwdzb2Oor+fYQZmVRQ4Ge651u+I+2/l6Fi3VFLkyMM5wBo7HH4VupVd0Ldj9bKi9Q9Wk8iGd5NFYHnRC3woyTva8x6qlZq60GmlN1HbbGovWbkKvkG6Ne0xXceEvyrxf6a+1wtWzsng4g0P+xskzABTBMoJUvXquqJmbB+Kcpu6Rw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=WTJCZrNv+N7rlQlJRMk/3Svb8NkeUJiWQ0xMGmDEGck=; b=dRz6w9I5GVS35AsYePHq3bBSs1nIJ9bg3kqa0F5OX+lZWdpMO3/KokDkDknStsPFtHGuR092hFLimOCxYeWACMWJ+eZc9W5Sxxe6WYOS90UNbyffqjWTKD6WJ68WUgS0fLALGnflvQ53g2md/mXO03woLL+v1wCwqxsFzljITmveNJubkiHjYWYzETA8RLyhS0/11NiqOD1ZvIVAL4vjhXwQ9TVj4h/kHQaVO5ski8WYyUH5/5nHf6QZbxeNuJOqyhm5D3alNUbnug1DWQfb3jzOoMVTBM/Arg/IyQ33KylSOQcBrPMY1tbcPWjeeOckboeKIX1ZeTsAHdNElfWLxQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none; dkim=none; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=live.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=WTJCZrNv+N7rlQlJRMk/3Svb8NkeUJiWQ0xMGmDEGck=; b=JqTZyVvLkWohdqHvtZmMttM/hJphYyeJBnbl3nx+dgnMQ2Qa0W9lZVfmoCJnfCkNDkl55iXiJf12zfTt8PlouJ4g+vOI0gk8UlDH/So497T50ifj9C4Yc7hdozUal9NlnV0fmjyG05q3uW1DBqSVIIhhnIRwGbz48YhYnPoMRMFMpJol/LeB2VcQCgk4u44hVvGqUY85vxKkifmihY6ZqT75uQGAy9RknzUkTLD4t7acb2ObA4oDPiRJXrIDZ54t4CUalv5UkZYxTJzkA43mWi2/2O1a3lS86tF8wTva4RAKH56MEzsoT+LZSrITsu7fc9Ul7oHzqVUgtsMDETeFcw== Original-Received: from AM9PR09MB4977.eurprd09.prod.outlook.com (2603:10a6:20b:304::20) by DB9PR09MB6886.eurprd09.prod.outlook.com (2603:10a6:10:4cb::5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6521.24; Wed, 28 Jun 2023 21:52:14 +0000 Original-Received: from AM9PR09MB4977.eurprd09.prod.outlook.com ([fe80::4a5f:42f9:d78f:2021]) by AM9PR09MB4977.eurprd09.prod.outlook.com ([fe80::4a5f:42f9:d78f:2021%4]) with mapi id 15.20.6521.026; Wed, 28 Jun 2023 21:52:14 +0000 In-Reply-To: <86mt0kulli.fsf@mail.linkov.net> (Juri Linkov's message of "Wed, 28 Jun 2023 09:50:17 +0300") X-TMN: [yq0DpUR2Q+dfV9qc0/YhYhxE3SfPOve8] X-ClientProxiedBy: BE1P281CA0304.DEUP281.PROD.OUTLOOK.COM (2603:10a6:b10:85::11) To AM9PR09MB4977.eurprd09.prod.outlook.com (2603:10a6:20b:304::20) X-Microsoft-Original-Message-ID: <82jzvnuuet.fsf@live.com> X-MS-Exchange-MessageSentRepresentingType: 1 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: AM9PR09MB4977:EE_|DB9PR09MB6886:EE_ X-MS-Office365-Filtering-Correlation-Id: c411c2c3-b2aa-4d98-1532-08db7821ee58 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: qDdl+2vHK38M9c3zyc6RrXXazD3GTZNN9HFQFuNYsslzXBDg1VNaE3+0fUqcN1IZc8l+rLlouFCKJ6bDE/nYP1OOCRG5GKJjtCKULpZAG1KP4VxhAp988U9qClcvPbJJJTNHyJ9pMiN7avVVVvjiWtvtxgOa2pGWH2r3WDZBssHuPpIW8cfN/Uj42vw7sKl0Lo556jI+XxHnV3XlOPib7Un4FfeT5YW2RhK7K0l2NHysXP3/srP6tChhrDzVGcoHAHER2Xb2guP0FSwm88sMVDtX67TF27Z5wRVWZHwaai3B5AjeqD+g3ky4Ik0cih8CI+ylUj8zXKXga9CxhQ9wKJVYxmxJqVqqwgaMk6KzMcE7SQqMY2llkU7XN4BuYMRvxNbephD9ZmxFK6GFH54iHVuHWj6GwpAlDpKTvgsGz7NCunW0j8Y7B+Zte7lO03ck62CXEObZfDFBe8P87bt1Lo19NahSJWpMlseVFVWGCnoOGtzeAYsbl7spt+sb525Q+kkAtZFB39Kv/ajhYZLRZ6fh1tXQKsTmk0S5flanKK/i1TFyAM4EN6/Cqo0dK6fL X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?EIKM3o0LeWdEkbj0cX/AQoYp8emWXNA/p2SHxz+/fi13NXCtSCQJ8P+HsETl?= =?us-ascii?Q?IlECFtYaLO2pcSow2MnmnciZzc70p9wHnY0ukgGCs7d9UIxF0YYIRuLL4u2v?= =?us-ascii?Q?uAGmHPlbl/qiEoAm3vXU37BUNW0t1yqpqBVmCXPVSnJQ2Is4ZKpyD+1PfY7O?= =?us-ascii?Q?poRu4qiGg18vWsk+30gXbePnKoC1Tn6Bk9BOcBlfLFK723RZrPSUQyYhl0wR?= =?us-ascii?Q?Yh6vd6x4z3Ik1XCKkbt421EkWYfwF45/BZaklaxmukSRb1Nr7jeb8X5/atn5?= =?us-ascii?Q?1RBUvseTVnEi6gw3ptzeE3H7fQ6gIY7gZglpslAQIhO38yv4bq0q7QfQFShd?= =?us-ascii?Q?PFEMrY2tzmdD2PfWU0H1BuNqzL69wQu0BvXgCdCu7HueeyVcH/VlOUSw49z0?= =?us-ascii?Q?SmSdnl0o7Bm6gE/l6nR1N8EJvviu3xDb5V8Aj6uI6JxIZcGaMNbIWPWw3f9E?= =?us-ascii?Q?1Kp/LagdCvYbIqVU2vpmpDuIQYop9KQL7lN6RAf8Bl02XAKGXL3E91d3bt6u?= =?us-ascii?Q?kPGoBk9V8oSc72SFUkA6G+twYj1sVKNKdWY7z1UhisJPK4WyV7Cgp4r3pB0R?= =?us-ascii?Q?HZZlJKHMGKe/dLWJ0rHorAX+ZOk2hHGmNSHvWoHRDvcA7cGNxYJKQKZvhtgj?= =?us-ascii?Q?om X-OriginatorOrg: sct-15-20-4755-11-msonline-outlook-64da6.templateTenant X-MS-Exchange-CrossTenant-Network-Message-Id: c411c2c3-b2aa-4d98-1532-08db7821ee58 X-MS-Exchange-CrossTenant-AuthSource: AM9PR09MB4977.eurprd09.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Jun 2023 21:52:14.2712 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa X-MS-Exchange-CrossTenant-RMS-PersistedConsumerOrg: 00000000-0000-0000-0000-000000000000 X-MS-Exchange-Transport-CrossTenantHeadersStamped: DB9PR09MB6886 Received-SPF: pass client-ip=2a01:111:f400:fe13::811; envelope-from=arthur.miller@live.com; helo=EUR02-AM0-obe.outbound.protection.outlook.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Thu, 29 Jun 2023 00:51:36 -0400 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:307293 Archived-At: --=-=-= Content-Type: text/plain Juri Linkov writes: >>>>> But it seems this is not enough because with-selected-frame >>>>> still fails to switch focus to another frame. You need also >>>>> to use select-frame-set-input-focus. >>>> Where it fails? For me it prompts me on correct frame. I didn't want to switch >>>> focus on the info frame. I am aware of select-frame-set-input-focus, have used >>>> it in some test actually. >>> >>> Probably the behaviour depends on the window manager. >> >> Yes, I am quite sure it isn't "probably" but "surely" :), as I wrote earlier by >> the way. >> >>> With my window manager with-selected-frame displays >>> the prompt in another frame, but input is inserted >>> into the original buffer. Maybe we should have >>> a new option whether to use select-frame-set-input-focus? >> >> I am not sure I understand what you mean with input being inserted in the >> original buffer. > > For example, the original buffer is *scratch*. The minibuffer > pops up in another frame. I start typing. But letters get > inserted to *scratch*, not to the minibuffer. This is because > focus is not switched to another frame. Aha, ok, I understand now. I haven't checked that one, tbh. When I realized the minibufer pops on other frame, I realized I didn't liked it, so I reworked everything so focus stays on the frame where I type. As I remember now, I also have set Emacs option to auto select window based on the cursor, mouse-autoselect-window, so it might be that one that interfers too. I totally forgott that one earlier. >>>> Have you tested *everything*? Interactively and from lisp? >>> >>> I see no problems with this both interactively and from lisp: >> >> If you don't care to ask the user which window to choose when ambigous, then you >> don't have to care about this at all. If you don't want to take care of >> multiple windows with possibly ambigous names, user misstyping a name and >> possibly irregular name, then you don't need to prompt the user at all, just >> take the first info buffer you find or force user to *always* select manually a >> window and you are all good. But in my opinion it is not hard to have it slightly >> more polished and automated as I did. > > I don't know what do you mean by the words "don't care" > since this implementation still uses your function > 'info-window' that asks the user which window to choose. > Please look carefully at Info-index-other-window: > >>> #+begin_src emacs-lisp >>> (defmacro with-selected-window-frame (window &rest body) >>> `(let ((old-frame (selected-frame)) >>> (frame (window-frame ,window))) >>> (unless (eq frame old-frame) >>> (select-frame frame 'norecord) >>> (select-frame-set-input-focus frame 'norecord)) >>> (prog1 (with-selected-window ,window >>> ,@body) >>> (select-frame old-frame 'norecord) >>> (select-frame-set-input-focus old-frame 'norecord)))) >>> >>> (defun Info-index-other-window (topic &optional window) >>> (interactive >>> (with-selected-window-frame (info-window) >>> (append (eval (cadr (interactive-form 'Info-index))) >>> (list (selected-window))))) >>> (with-selected-window (or window (info-window)) >>> (Info-index topic))) >>> #+end_src I thought it was just left over while testing. Actually you want to keep the part I disslike most, and want to redo the part(s) I think are good :). When I wrote about the patch, I had three sections of text: the good, the bad and the ugly. Part about info-window and find-window-for-help was "the ugly" part. The bad one was about alterered signatures and the good one was about everythign been possible to implement user-friendly. >>> You can't avoid adding the window argument. Otherwise, you need >>> to invent such hacks as sending the window selected by the user >>> to the command body via a symbol property. >>> >>> But in the wrapper command like above there is no problem >>> of adding the window argument to the new command. >> >> I wasn't familiar with interactive form enough and didn't know how to connect >> the optional argument from within interactive form. Symbol-plist was just >> workaround for lack of a better knowledge :). I stated that explicitly in the >> mail with the patch, but you perhaps didn't have time to read the mail? > > I read all your mails carefully, and noted that there is no better alternative > than adding the window argument. Ok, I am sorry if I missunderstand you, I thought you mean I want add the window argument to *all commands*, which I don't want. Only those that are called possibly more then once, either because they are called from other command, or recursively. The argument is there just to avoid the prompting more then once. Now if you want to wrap everything, then I think you can avoid the argument completely *iff* you can switch to the info window before you call the wrapped command. >> Anyway, I understand now how is it done: >> >> (append (eval (cadr (interactive-form 'Info-index))) >> (list (selected-window))) >> >> I have to return a list of all arguments from interactive form; it was that >> simple. Thanks for the example. > > Indeed, this is only an example. More handling may be needed for > the return value of interactive-form. I never wrote complex interactive forms before, so I was a unfamiliar with the detials and a bit lazy to look around andd see how it is done, so I did a hack, but from your example I understand now how it is used. It was a couple of minutes to rework the patch without symbol-plist hack :). Thanks for showing it. >>> Maybe it's possible even to write a macro that will generate >>> such wrapper commands automatically from existing commands. >>> >>> It seems you assume that all commands should take a window. >> >> Why would I assume that? I wrote explicitly which commands were extended with >> an optional window argument and why. I don't feel for repeating entire text here, >> if you have interest, please take a look at the original email, you don't seem >> to have read it all. That mail answered your opening question, but I didn't want >> to point it out earlier to not sound impolite, instead I tried to clarify the >> problems and choices further. > > I was referring to your words from another mail that I read carefully: > >> What I am trying to say is that command has to be written with the assumptions >> that user can call it from any window and Emacs frame, and that all prompting >> should be done in the frame at which user types, so I have reworked info commands >> to work with those assumptions. > > I don't agree that all commands should be written with the assumption > that they can be called from any window. Only commands with global > keybindings usually prepare the buffer with the predefined name and mode, > then mode-local keybindings and mode-local commands assume that they are > operating in the right buffer. With "all commands" I ment all commands that wish to be callable from other buffers then those they act on. I didn't mean literally each and every in existence, since as I mentioned, command in Emacs can be anything, I have one that shutts down the computer. I am not sure if I would draw such absolut divider on what should and what would should not be written as any-window-command or how to call them. As I wrote I think it is really up to a command, but yes I do agree that not *all* commands should be callable from everywhere. I don't think I have enough understanding to yet draw some hard divider what should be and what should not be callable from any buffer. What I have got from this experiment is the understanding what is needed to write such command and what are possible problems. At least I think so, but there is probably more to it, that I haven't yet encountered. What I also think is that you could encapsulate that into a macro, similar to define-minor-mode, which encapsulate boring details and window switching, so people don't have to thinkg about it. That would eliminate need for a wrapper and perhaps made usage of prefix argument more uniform and predicatble. But that is just an idea, I haven't experimented. >t >> But there are no such assumption for most commands that work >>> only in the selected window. >> >> I am not sure what you are trying to say here. If we have several live buffers >> and wish to act on one, then the user has to choose one somehow, no? We can >> either try to automate stuff as I have tried in this patch, by prompting the >> user in ambigous case when system can't figure it on its own, or we can have a >> dumb system that forces user to *always* select a window prior to acting on a >> buffer. If you take a look at the original mail you will understand which commands >> has got the optional window argument and why, but if you don't prompt the user, >> than you don't need that argument at all. > > Only commands with global keybindings need a way to select the right buffer. > The best example of such command is the entry point to the Info browser > the command 'info' bound globally to 'C-h i'. This command allows the user > to select the right buffer "*info*" by using the prefix arg. > > What you are trying to do is to copy this logic to all Info-local commands. I am not sure how you mean to use this, and why do you think C-h i is enough? I don't know what your assumption and expectation is. From very beginning I said I wish to minimize switching between windows. So I do wish more or less all info and help mode commands to be callable from other windows. Also I definitely want to have Info-mode-map on a prefix key, so which-key can show me the popup, since I have a memory like a gold fish and forgott which key is bound to what. That was one of reasons why I went away from pre/post hook thing. To be honest I am not sure I understand how you mean to use the thing. > And here comes the disagreement about the ways of doing this. I am not sure what I can say here. I understand it is a lot, 2k sloc patch is big; and I do understand there are potentiallys (or sure) bugs, but the work is done, I am using it myself, and it seem to be fine. If more people tested it, I don't think it would be too long before bugs are fixed. In my opinion, if we do things, lets do them properly from the beginning :). Piling hack on hack will just lead to more hacks. I don't that is how I see it, but I also don't think it is very complicated patch conceptually. I have attached a patch I reworked today so I can use it with the help-mode and with removed plist hack. If you look at the help-mode functions, you will see the change to them is quite trivial. I do disslike the help-mode/info-mode though. > I know that you already said about this, so hereby I confirm that I already > have seen your opinion:t > >> Making a wrapper just to put a call to a command into with-selected-window, >> instead of wrapping the body of that command with the same macro, is not so much >> better; just different. Sure "less intrusive" on a command itself, but there is >> a new command and then we have two where one can do the jobb. > > Replying to your opinion, I expressed a preference to avoid massive changes > in the existing functions. I totally understand your sentiment, and I myself would be all for a "magic pedal" that just switches the right thing on, but there is a limit to hackiness too. >> There is also a problem of prompting and input focus. As I wrote in response to >> Eli, each command is its own little program, and can prompt user for whatever >> reason whenever. Thus each command should be written with the assumption that >> input should be presented to user on the frame where user types and with >> assumption that user is not executing it from the buffer/window on which it >> should act. You can achieve all this with tools already in Emacs, no need to >> introduce any new concepts or macros, and it will also work regardless of the >> window manager (I think). > > I have no idea how this would work for window managers that don't switch focus > to the frame with the active minibuffer. If you pre-select window (and frame) before you call an Info-mode command, than the focus will be on that frame. You don't need to do anything special, since with-selected-frame should select frame, window and the visible buffer, so original command will execute in the "right" context. Problem as stated was, that when mouse cursor is on the "old frame", the old frame gets focus, which messes things up. Try to set mouse-autoselect-window in your Emacs, and foucs follow mouse in window manager, if you use some floating wm. I guess tiling WMs don't have this problem, but I don't know really, I haven't used anything other then dwm, and it was long time ago. >> It is just that the old commands are not written with those assumptions, so I >> rewrote Info commands in this patch. I am not sure you can achive that >> with wrapping, but perhaps there is a way, you can try. > > Do you see some needs that can't be achieved with wrapping commands? Depends on your ambitions :). Focus and input are potential problems. How do you plan to use wrappers? If you create double commands, then you will have a bunch of "-other-window" commands, would you have a separate mode maps for them, or how do you plan to present them to a user? Which one do you leave out and why? I personally don't think it is pretty to switch focus to other frame, either in vertical, left/right setup, or in horizontal tob/bottom setup. I prefer minibufer popping up on the frame where I already type. >> In my opinion wrapping is OK if we for some reason can't alter the code, but in >> the case of Info and help mode, I don't see such reason, especially since it is >> possible to do everything backwards compatible. On the negative side, >> wrapping introduces double set of commands, so what are you saving? You are >> wrapping code from "outside", while I have done it from "inside", but overall, >> the principle is the same. On a plus side for wrappers is that you can actually >> write a codegen for wrappers, hopefully in form of a macro and it will work for >> other modes. So it is not only negatives, but you could also have both >> approaches at the same time too :). > > Indeed, duplication of the command set is a drawback of wrapping commands. > OTOH, wrapping commands could be created mechanically, and they are very small. I think wrapping the function body in with-selected-window is even smaller, look at help-mode in attached patch. Info commands are many, relatively big, and have relatively complex interactive form, so the patch is massive, but conceptually it is simple. On plus side is that it is backwards compatible and does not introduce any new concepts at all, changes are mostly under the hood and user can just continue to use info as before or potentially from any buffer. > So we have to decide what is worse: duplication of the command set, Eli has already decided :). I see this as a done story. It is ok for me. I build my Emacs with custom patches anywway. > or altering all existing commands by adding a new window argument to all of them Nah; you don't need to alter all existing commands with optional window argument; I haven't altered them all in this patch either. Just those that would potentially prompt user multiple times. > and making them more complicated. Nah, they are not more complicated, look at the patched help-mode in the attached patch. Alternatively, we can also say that current practice of writing commands is inadequate in some cases. Just hook pre/post hook on a key, and live with that, it will work in "most cases" :). --=-=-= Content-Type: text/x-patch; charset=gb2312 Content-Disposition: attachment; filename=0001-Use-help-and-Info-mode-commands-from-any-buffer.patch Content-Transfer-Encoding: quoted-printable >From 1650339b51ebea70a8454835145afd01c7400c80 Mon Sep 17 00:00:00 2001 From: Arthur Miller Date: Wed, 28 Jun 2023 21:43:30 +0200 Subject: [PATCH] Use help- and Info-mode commands from any buffer Allow commands that act on help-mode and Info-mode to be called from other buffers than just help and Info buffer. * lisp/help-mode.el (help-link): New symbol used to establish a connection between a buffer and a help buffer. * lisp/help-mode.el (help-window): New function used to find a help window to act on. * lisp/help-mode.el (help-view-source): * lisp/help-mode.el (help-goto-info): * lisp/help-mode.el (help-go-back): * lisp/help-mode.el (help-go-forward): * lisp/help-mode.el (help-goto-next-page): * lisp/help-mode.el (help-goto-previous-page): * lisp/help-mode.el (help-goto-lispref-info): * lisp/help-mode.el (help-customize): Help-mode commands adapted to be called from any buffer. * lisp/help-mode.el (help-quit-window): * lisp/help-mode.el (help-revert-buffer): * lisp/help-mode.el (help-describe-mode): * lisp/help-mode.el (help-beginning-of-buffer): * lisp/help-mode.el (help-end-of-buffer): * lisp/help-mode.el (help-scroll-up-command): * lisp/help-mode.el (help-scroll-down-command): * lisp/help-mode.el (help-forward-button): * lisp/help-mode.el (help-backward-button): * lisp/help-mode.el (help-button-describe): * lisp/help-mode.el (help-push-button): New commands. Do what they wrapped counterparts without 'help-' prefix do, but specifically in help buffer. * lisp/help-mode.el (help-mode-map): * lisp/help-mode.el (help-mode-menu): Update bindings to reflect the new commands for previously generic commands. * lisp/info.el (Info-virtual-file-p): * lisp/info.el (Info-menu): * lisp/info.el (Info-next): * lisp/info.el (Info-prev): * lisp/info.el (Info-up): * lisp/info.el (Info-history): * lisp/info.el (Info-history-back): * lisp/info.el (Info-history-forward): * lisp/info.el (Info-directory): * lisp/info.el (Info-toc-insert): * lisp/info.el (Info-toc): * lisp/info.el (Info-extract-menu-item): * lisp/info.el (Info-nth-menu-item): * lisp/info.el (Info-top-node): * lisp/info.el (Info-final-node): * lisp/info.el (Info-forward-node): * lisp/info.el (Info-backward-node): * lisp/info.el (Info-next-menu-item): * lisp/info.el (Info-last-menu-item): * lisp/info.el (Info-next-preorder): * lisp/info.el (Info-last-preorder): * lisp/info.el (Info-scroll-up): * lisp/info.el (Info-scroll-down): * lisp/info.el (Info-mouse-scroll-up): * lisp/info.el (Info-next-reference): * lisp/info.el (Info-prev-reference): * lisp/info.el (Info-index): * lisp/info.el (Info-index-next): * lisp/info.el (Info-find-index-name): * lisp/info.el (Info-virtual-index): * lisp/info.el (Info-apropos): * lisp/info.el (Info-finder): * lisp/info.el (Info-undefined): * lisp/info.el (Info-follow-nearest-node): * lisp/info.el (Info-summary): * lisp/info.el (Info-copy-current-node-name): Info-mode commands adapted to be called from any buffer. * lisp/info.el (Info-describe-mode): * lisp/info.el (Info-quit-window): * lisp/info.el (Info-beginning-of-buffer): * lisp/info.el (Info-end-of-buffer): New commands. Do what they wrapped counterparts without 'Info-' prefix do, but specifically in Info buffer. * lisp/info.el (Info-link): New symbol used to establish a connection between a buffer and an Info buffer. * lisp/info.el (Info-mode-map): * lisp/info.el (Info-mode-menu): Update bindings to reflect the new commands for previously generic commands. * lisp/window.el (window-list-by-mode): * lisp/window.el (get-window-from-user): * lisp/window.el (get-numbered-window): New functions. Used internally as help functions for find-window-for-help. * lisp/window.el (find-window-for-help): New function. Used to obtain a window for Info- or help-mode. --- lisp/help-mode.el | 209 +++++-- lisp/info.el | 1317 +++++++++++++++++++++++++-------------------- lisp/window.el | 67 +++ 3 files changed, 961 insertions(+), 632 deletions(-) diff --git a/lisp/help-mode.el b/lisp/help-mode.el index bf64d032b65..2a637c9805c 100644 --- a/lisp/help-mode.el +++ b/lisp/help-mode.el @@ -47,7 +47,21 @@ help-mode-map "s" #'help-view-source "I" #'help-goto-lispref-info "i" #'help-goto-info - "c" #'help-customize) + "c" #'help-customize + "g" #'help-revert-buffer + "q" #'help-quit-window + "<" #'help-beginning-of-buffer + ">" #'help-end-of-buffer + "h" #'help-describe-mode + "?" #'help-describe-mode + "DEL" #'help-scroll-down-command + "SPC" #'help-scroll-down-command + "S-SPC" #'help-scroll-up-command + "RET" #'help-push-button + "TAB" #'help-forward-button + "C-M-i" #'help-backward-button + "" #'help-backward-button + "ESC TAB" #'help-backward-button) =20 (easy-menu-define help-mode-menu help-mode-map "Menu for Help mode." @@ -60,9 +74,9 @@ help-mode-menu ["Next Topic" help-go-forward :help "Go back to next topic in this help buffer" :active help-xref-forward-stack] - ["Move to Previous Button" backward-button + ["Move to Previous Button" help-backward-button :help "Move to the Previous Button in the help buffer"] - ["Move to Next Button" forward-button + ["Move to Next Button" help-forward-button :help "Move to the Next Button in the help buffer"] ["View Source" help-view-source :help "Go to the source file for the current help item"] @@ -134,6 +148,9 @@ help-xref-stack-forward-item The format is (FUNCTION ARGS...).") (put 'help-xref-stack-forward-item 'permanent-local t) =20 +(defvar-local help-link nil + "A connection to a help-mode buffer from another buffer.") + (setq-default help-xref-stack nil help-xref-stack-item nil) (setq-default help-xref-forward-stack nil help-xref-forward-stack-item nil= ) =20 @@ -151,6 +168,48 @@ help-mode-hook :type 'hook :group 'help) =0C +;; help function to help choose a help window to act on: +(defun help-window (&optional only-selected-frame) + "Return a help window from the list of all windows. + +If the current buffer is already a help buffer return selected +window, else find a help window from the list of visible help buffers. +When ONLY-SELECTED-FRAME is true, consider window list only on +selected frame. + +When invoked with a numeric prefix N return a help window named *help*. +When called with a non-numeric prefix, C-u C-u, create a connection to a h= elp +window that will be used as the default for further choice in case of mult= iple +help windows. If the connection is set, C-u prefix can bes used to act on +another help window but the connected one." + (let* ((all-frames (not only-selected-frame)) + window) + (cond ((equal current-prefix-arg '(16)) ; always ask + (setq help-link nil + current-prefix-arg nil + window (find-window-for-help 'help-mode all-frames)) + (when window (setq help-link window))) + ((equal current-prefix-arg '(4)) ; do in other help window + (setq current-prefix-arg nil + window (find-window-for-help + 'help-mode all-frames + (unless (eq major-mode 'help-mode) help-link)))) + ((and (not current-prefix-arg) (eq major-mode 'help-mode)) + (setq window (selected-window))) + (t + (setq window (if (and help-link + (window-live-p help-link) + (not (numberp current-prefix-arg))) + help-link + (find-window-for-help 'help-mode all-frames))))) + (unless window + (user-error + (if (numberp current-prefix-arg) + (format "No help buffer named *help*<%s> found." + current-prefix-arg) + "There are no visible help buffers."))) + window)) +=0C ;; Button types used by help =20 (define-button-type 'help-xref @@ -763,7 +822,73 @@ help-xref-on-pp =20 ;;;###autoload (define-obsolete-function-alias 'help-xref-interned #'describe-symbol "25.= 1") +=0C +;; commands from special-mode wrapped to work on help-mode only +(defun help-quit-window () + "As `quit-window' but works only on *Help* buffer." + (interactive) + (with-selected-window (help-window) + (quit-window nil (selected-window)))) + +(defun help-describe-mode () + "As `describe-mode' but for *Help* buffer only." + (interactive) + (with-selected-window (help-window) + (with-current-buffer (window-buffer) + (describe-mode)))) + +(defun help-beginning-of-buffer () + "As `help-beginning-of-buffer' but for *Help* buffer only." + (interactive) + (with-selected-window (help-window) + (goto-char (point-min)))) + +(defun help-end-of-buffer () + "As `help-end-of-buffer' but for *Help* buffer only." + (interactive) + (with-selected-window (help-window) + (goto-char (point-max)))) +=0C +;; from files.el, for completeness and to eliminate potential confusion +(defun help-revert-buffer () + "As `revert-buffer', but act on help buffer specifically." + (interactive) + (with-selected-window (help-window) + (call-interactively #'revert-buffer))) +=0C +;; Commands from button.el wrapped to work on help-mode only +(defun help-forward-button () + (interactive) + (with-selected-window (help-window) + (forward-button 1))) + +(defun help-backward-button () + (interactive) + (with-selected-window (help-window) + (backward-button 1))) + +(defun help-button-describe () + (interactive) + (with-selected-window (help-window) + (button-describe))) =20 +(defun help-push-button () + (interactive) + (with-selected-window (help-window) + (push-button))) +=0C +;; Commands from window.el wrapped to work on help-mode only +(defun help-scroll-up-command (&optional arg) + "As `scroll-up-command' but works only on *Help* buffer." + (interactive "^P") + (with-selected-window (help-window) + (scroll-up-command arg))) + +(defun help-scroll-down-command (&optional arg) + "As `scroll-down-command' but works only on *Help* buffer." + (interactive "^P") + (with-selected-window (help-window) + (scroll-down-command arg))) =0C ;; Navigation/hyperlinking with xrefs =20 @@ -810,25 +935,28 @@ help-xref-go-forward (defun help-go-back () "Go back to previous topic in this help buffer." (interactive) - (if help-xref-stack - (help-xref-go-back (current-buffer)) - (user-error "No previous help buffer"))) + (with-selected-window (help-window) + (if help-xref-stack + (help-xref-go-back (current-buffer)) + (user-error "No previous help buffer")))) =20 (defun help-go-forward () "Go to the next topic in this help buffer." (interactive) - (if help-xref-forward-stack - (help-xref-go-forward (current-buffer)) - (user-error "No next help buffer"))) + (with-selected-window (help-window) + (if help-xref-forward-stack + (help-xref-go-forward (current-buffer)) + (user-error "No next help buffer")))) =20 (defun help-goto-next-page () "Go to the next page (if any) in the current buffer. The help buffers are divided into \"pages\" by the ^L character." (interactive nil help-mode) - (push-mark) - (forward-page) - (unless (eobp) - (forward-line 1))) + (with-selected-window (help-window) + (push-mark) + (forward-page) + (unless (eobp) + (forward-line 1)))) =20 (defun help-goto-previous-page () "Go to the previous page (if any) in the current buffer. @@ -836,47 +964,52 @@ help-goto-previous-page =20 The help buffers are divided into \"pages\" by the ^L character." (interactive nil help-mode) - (push-mark) - (backward-page (if (looking-back "\f\n" (- (point) 5)) 2 1)) - (unless (bobp) - (forward-line 1))) + (with-selected-window (help-window) + (push-mark) + (backward-page (if (looking-back "\f\n" (- (point) 5)) 2 1)) + (unless (bobp) + (forward-line 1)))) =20 (defun help-view-source () "View the source of the current help item." (interactive nil help-mode) - (unless (plist-get help-mode--current-data :file) - (error "Source file for the current help item is not defined")) - (help-function-def--button-function - (plist-get help-mode--current-data :symbol) - (plist-get help-mode--current-data :file) - (plist-get help-mode--current-data :type))) + (with-selected-window (help-window) + (unless (plist-get help-mode--current-data :file) + (error "Source file for the current help item is not defined")) + (help-function-def--button-function + (plist-get help-mode--current-data :symbol) + (plist-get help-mode--current-data :file) + (plist-get help-mode--current-data :type)))) =20 (defun help-goto-info () "View the *info* node of the current help item." (interactive nil help-mode) - (unless help-mode--current-data - (error "No symbol to look up in the current buffer")) - (info-lookup-symbol (plist-get help-mode--current-data :symbol) - 'emacs-lisp-mode - help-window-keep-selected)) + (with-selected-window (help-window) + (unless help-mode--current-data + (error "No symbol to look up in the current buffer")) + (info-lookup-symbol (plist-get help-mode--current-data :symbol) + 'emacs-lisp-mode + help-window-keep-selected))) =20 (defun help-goto-lispref-info () "View the Emacs Lisp manual *info* node of the current help item." (interactive nil help-mode) - (unless help-mode--current-data - (error "No symbol to look up in the current buffer")) - (info-lookup-symbol (plist-get help-mode--current-data :symbol) - 'emacs-lisp-only)) + (with-selected-window (help-window) + (unless help-mode--current-data + (error "No symbol to look up in the current buffer")) + (info-lookup-symbol (plist-get help-mode--current-data :symbol) + 'emacs-lisp-only))) =20 (defun help-customize () "Customize variable or face whose doc string is shown in the current buf= fer." (interactive nil help-mode) - (let ((sym (plist-get help-mode--current-data :symbol))) - (unless (or (boundp sym) (facep sym)) - (user-error "No variable or face to customize")) - (cond - ((boundp sym) (customize-variable sym)) - ((facep sym) (customize-face sym))))) + (with-selected-window (help-window) + (let ((sym (plist-get help-mode--current-data :symbol))) + (unless (or (boundp sym) (facep sym)) + (user-error "No variable or face to customize")) + (cond + ((boundp sym) (customize-variable sym)) + ((facep sym) (customize-face sym)))))) =20 (defun help-do-xref (_pos function args) "Call the help cross-reference function FUNCTION with args ARGS. diff --git a/lisp/info.el b/lisp/info.el index 035dff66e75..d32ffb64172 100644 --- a/lisp/info.el +++ b/lisp/info.el @@ -400,6 +400,54 @@ Info-virtual-nodes (defvar-local Info-current-node-virtual nil "Non-nil if the current Info node is virtual.") =20 +(defvar-local Info-link nil + "A connection to an Info-mode buffer.") + +(defun info-window (&optional only-selected-frame) + "Return an info window from the list of all windows. + +If the current buffer is already an info buffer return selected +window, else find an info window from the list of visible info buffers. +When ONLY-SELECTED-FRAME is true, consider window list only on +selected frame. + +When invoked with a numeric prefix N return info window named *info*. +When called with a non-numeric prefix, C-u C-u, create a connection to an = Info +window that will be used as the default for further choice in case of mult= iple +info windows. If the connection is set, C-u prefix can bes used to acto on= other +info window but the connected one. + +This function memoizes its result in symbols value slot. The last +result is not preserved between function calls." + (let* ((all-frames (not only-selected-frame)) + window) + (cond ((equal current-prefix-arg '(16)) ; always ask + (setq Info-link nil + current-prefix-arg nil + window (find-window-for-help 'Info-mode all-frames)) + (when window (setq Info-link window))) + ((equal current-prefix-arg '(4)) ; do in other info window + (setq current-prefix-arg nil + window (find-window-for-help + 'Info-mode all-frames + (unless (eq major-mode 'Info-mode) + Info-link)))) + ((and (not current-prefix-arg) (eq major-mode 'Info-mode)) + (setq window (selected-window))) + (t + (setq window (if (and Info-link + (window-live-p Info-link) + (not (numberp current-prefix-arg))) + Info-link + (find-window-for-help 'Info-mode all-frames))))) + (unless window + (user-error + (if (numberp current-prefix-arg) + (format "No info buffer named *info*<%s> found." + current-prefix-arg) + "There are no visible info buffers."))) + window)) + (defun Info-virtual-file-p (filename) "Check if Info file FILENAME is virtual." (Info-virtual-fun 'find-file filename nil)) @@ -787,7 +835,7 @@ info-setup (if (and (zerop (buffer-size)) (null Info-history)) ;; If we just created the Info buffer, go to the directory. - (Info-directory)))) + (Info-directory (selected-window))))) =20 ;;;###autoload (defun info-emacs-manual () @@ -831,12 +879,12 @@ info-standalone ;; The return value is the value of point at the beginning of matching ;; REGEXP, if the function succeeds, nil otherwise. (defun Info-node-at-bob-matching (regexp) - (and (bobp) ; are we at beginning of buffer? - (looking-at "\^_") ; does it begin with node delimiter? + (and (bobp) ; are we at beginning of buffer? + (looking-at "\^_") ; does it begin with node delimiter? (let (beg) (forward-line 1) (setq beg (point)) - (forward-line 1) ; does the line after delimiter match REGEXP? + (forward-line 1) ; does the line after delimiter match REGEXP? (re-search-backward regexp beg t)))) =20 (defun Info-find-file (filename &optional noerror no-pop-to-dir) @@ -2269,89 +2317,84 @@ Info-following-node-name (and (looking-at (Info-following-node-name-re)) (match-string-no-properties 1))) =20 -(defun Info-next () +(defun Info-next (&optional window) "Go to the \"next\" node, staying on the same hierarchical level. This command doesn't descend into sub-nodes, like \\\\[Info= -forward-node] does." (interactive nil Info-mode) - ;; In case another window is currently selected - (save-window-excursion - (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) + (with-selected-window (or window (info-window)) (Info-goto-node (Info-extract-pointer "next")))) =20 -(defun Info-prev () +(defun Info-prev (&optional window) "Go to the \"previous\" node, staying on the same hierarchical level. This command doesn't go up to the parent node, like \\\\[In= fo-backward-node] does." (interactive nil Info-mode) - ;; In case another window is currently selected - (save-window-excursion - (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) + (with-selected-window (or window (info-window)) (Info-goto-node (Info-extract-pointer "prev[ious]*" "previous")))) =20 -(defun Info-up (&optional same-file) +(defun Info-up (&optional same-file window) "Go to the superior node of this node. If SAME-FILE is non-nil, do not move to a different Info file." (interactive nil Info-mode) - ;; In case another window is currently selected - (save-window-excursion - (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) + (with-selected-window (or window (info-window)) (let ((old-node Info-current-node) - (old-file Info-current-file) - (node (Info-extract-pointer "up")) p) + (old-file Info-current-file) + (node (Info-extract-pointer "up")) p) (and same-file - (string-match "^(" node) - (error "Up node is in another Info file")) + (string-match "^(" node) + (error "Up node is in another Info file")) (Info-goto-node node) (setq p (point)) (goto-char (point-min)) (if (and (stringp old-file) - (search-forward "\n* Menu:" nil t) - (re-search-forward - (if (string-equal old-node "Top") - (concat "\n\\*[^:]+: +(" (file-name-nondirectory old-file) ")") - (concat "\n\\* +\\(" (regexp-quote old-node) - ":\\|[^:]+: +" (regexp-quote old-node) "\\)")) - nil t)) - (progn (beginning-of-line) (if (looking-at "^\\* ") (forward-char 2))) - (goto-char p) - (Info-restore-point Info-history)))) - ;; If scroll-conservatively is non-zero and less than 101, display - ;; as much of the superior node above the target line as possible. - (when (< 0 scroll-conservatively 101) - (recenter))) + (search-forward "\n* Menu:" nil t) + (re-search-forward + (if (string-equal old-node "Top") + (concat "\n\\*[^:]+: +(" (file-name-nondirectory old-f= ile) ")") + (concat "\n\\* +\\(" (regexp-quote old-node) + ":\\|[^:]+: +" (regexp-quote old-node) "\\)")) + nil t)) + (progn (beginning-of-line) (if (looking-at "^\\* ") (forward-cha= r 2))) + (goto-char p) + (Info-restore-point Info-history))) + ;; If scroll-conservatively is non-zero and less than 101, display + ;; as much of the superior node above the target line as possible. + (when (< 0 scroll-conservatively 101) (recenter)))) =20 (defun Info-history-back () "Go back in the history to the last node visited." (interactive nil Info-mode) - (or Info-history - (user-error "This is the first Info node you looked at")) - (let ((history-forward - (cons (list Info-current-file Info-current-node (point)) - Info-history-forward)) - filename nodename opoint) - (setq filename (car (car Info-history))) - (setq nodename (car (cdr (car Info-history)))) - (setq opoint (car (cdr (cdr (car Info-history))))) - (setq Info-history (cdr Info-history)) - (Info-find-node filename nodename) - (setq Info-history (cdr Info-history)) - (setq Info-history-forward history-forward) - (goto-char opoint))) + (with-selected-window (info-window) + (or Info-history + (user-error "This is the first Info node you looked at")) + (let ((history-forward + (cons (list Info-current-file Info-current-node (point)) + Info-history-forward)) + filename nodename opoint) + (setq filename (car (car Info-history))) + (setq nodename (car (cdr (car Info-history)))) + (setq opoint (car (cdr (cdr (car Info-history))))) + (setq Info-history (cdr Info-history)) + (Info-find-node filename nodename) + (setq Info-history (cdr Info-history)) + (setq Info-history-forward history-forward) + (goto-char opoint)))) =20 (defalias 'Info-last 'Info-history-back) =20 (defun Info-history-forward () "Go forward in the history of visited nodes." (interactive nil Info-mode) - (or Info-history-forward - (user-error "This is the last Info node you looked at")) - (let ((history-forward (cdr Info-history-forward)) - filename nodename opoint) - (setq filename (car (car Info-history-forward))) - (setq nodename (car (cdr (car Info-history-forward)))) - (setq opoint (car (cdr (cdr (car Info-history-forward))))) - (Info-find-node filename nodename) - (setq Info-history-forward history-forward) - (goto-char opoint))) + (with-selected-window (info-window) + (or Info-history-forward + (user-error "This is the last Info node you looked at")) + (let ((history-forward (cdr Info-history-forward)) + filename nodename opoint) + (setq filename (car (car Info-history-forward))) + (setq nodename (car (cdr (car Info-history-forward)))) + (setq opoint (car (cdr (cdr (car Info-history-forward))))) + (Info-find-node filename nodename) + (setq Info-history-forward history-forward) + (goto-char opoint)))) =0C (add-to-list 'Info-virtual-files '("\\`dir\\'" @@ -2374,10 +2417,11 @@ Info-directory-find-node (Info-insert-dir)) =20 ;;;###autoload -(defun Info-directory () +(defun Info-directory (&optional window) "Go to the Info directory node." (interactive) - (Info-find-node "dir" "top")) + (with-selected-window (or window (info-window)) + (Info-find-node "dir" "top"))) =0C (add-to-list 'Info-virtual-files '("\\`\\*History\\*\\'" @@ -2416,9 +2460,10 @@ Info-history-find-node (defun Info-history () "Go to a node with a menu of visited nodes." (interactive nil Info-mode) - (Info-find-node "*History*" "Top") - (Info-next-reference) - (Info-next-reference)) + (with-selected-window (info-window) + (Info-find-node "*History*" "Top") + (Info-next-reference) + (Info-next-reference))) =0C (add-to-list 'Info-virtual-nodes '("\\`\\*TOC\\*\\'" @@ -2453,12 +2498,13 @@ Info-toc "Go to a node with table of contents of the current Info file. Table of contents is created from the tree structure of menus." (interactive nil Info-mode) - (Info-find-node Info-current-file "*TOC*") - (let ((prev-node (nth 1 (car Info-history))) p) - (goto-char (point-min)) - (if (setq p (search-forward (concat "*Note " prev-node ":") nil t)) - (setq p (- p (length prev-node) 2))) - (goto-char (or p (point-min))))) + (with-selected-window (info-window) + (Info-find-node Info-current-file "*TOC*") + (let ((prev-node (nth 1 (car Info-history))) p) + (goto-char (point-min)) + (if (setq p (search-forward (concat "*Note " prev-node ":") nil t)) + (setq p (- p (length prev-node) 2))) + (goto-char (or p (point-min)))))) =20 (defun Info-toc-insert (nodes node-list level curr-file) "Insert table of contents with references to nodes." @@ -2568,7 +2614,7 @@ Info-toc-nodes (cdr (assoc filename Info-toc-nodes))))) =20 =0C -(defun Info-follow-reference (footnotename &optional fork) +(defun Info-follow-reference (footnotename &optional fork window) "Follow cross reference named FOOTNOTENAME to the node it refers to. FOOTNOTENAME may be an abbreviation of the reference name. If FORK is non-nil (interactively with a prefix arg), show the node in @@ -2577,91 +2623,96 @@ Info-follow-reference (interactive (let ((completion-ignore-case t) (case-fold-search t) + (window (info-window)) completions default alt-default (start-point (point)) str i bol eol) - (save-excursion - ;; Store end and beginning of line. - (setq eol (line-end-position) - bol (line-beginning-position)) - (goto-char (point-min)) - (while (re-search-forward "\\*note[ \n\t]+\\([^:]*\\):" nil t) - (setq str (match-string-no-properties 1)) - ;; See if this one should be the default. - (and (null default) - (<=3D (match-beginning 0) start-point) - (<=3D start-point (point)) - (setq default t)) - ;; See if this one should be the alternate default. - (and (null alt-default) - (and (<=3D bol (match-beginning 0)) - (<=3D (point) eol)) - (setq alt-default t)) - (setq i 0) - (while (setq i (string-match "[ \n\t]+" str i)) - (setq str (concat (substring str 0 i) " " - (substring str (match-end 0)))) - (setq i (1+ i))) - ;; Record as a completion and perhaps as default. - (if (eq default t) (setq default str)) - (if (eq alt-default t) (setq alt-default str)) - ;; Don't add this string if it's a duplicate. - (or (assoc-string str completions t) - (push str completions))) - (setq completions (nreverse completions))) - ;; If no good default was found, try an alternate. - (or default - (setq default alt-default)) - ;; If only one cross-reference found, then make it default. - (if (eq (length completions) 1) - (setq default (car completions))) - (if completions - (let ((input (completing-read (format-prompt "Follow reference na= med" - default) - completions nil t))) - (list (if (equal input "") - default input) - current-prefix-arg)) - (user-error "No cross-references in this node"))) - Info-mode) - - (unless footnotename - (error "No reference was specified")) - - (let (target i (str (concat "\\*note " (regexp-quote footnotename))) - (case-fold-search t)) - (while (setq i (string-search " " str i)) - (setq str (concat (substring str 0 i) "[ \t\n]+" (substring str (1+ = i)))) - (setq i (+ i 6))) - (save-excursion - ;; Move point to the beginning of reference if point is on reference - (or (looking-at "\\*note[ \n\t]+") - (and (looking-back "\\*note[ \n\t]+" - (save-excursion (skip-chars-backward " \n\t") - (line-beginning-position))) - (goto-char (match-beginning 0))) - (if (and (save-excursion - (goto-char (+ (point) 5)) ; skip a possible *note - (re-search-backward "\\*note[ \n\t]+" nil t) - (looking-at str)) - (<=3D (point) (match-end 0))) - (goto-char (match-beginning 0)))) - ;; Go to the reference closest to point - (let ((next-ref (save-excursion (and (re-search-forward str nil t) - (+ (match-beginning 0) 5)))) - (prev-ref (save-excursion (and (re-search-backward str nil t) - (+ (match-beginning 0) 5))))) - (goto-char (cond ((and next-ref prev-ref) - (if (< (abs (- next-ref (point))) - (abs (- prev-ref (point)))) - next-ref prev-ref)) - ((or next-ref prev-ref)) - ((user-error "No cross-reference named %s" - footnotename)))) - (setq target (Info-extract-menu-node-name t)))) - (while (setq i (string-match "[ \t\n]+" target i)) - (setq target (concat (substring target 0 i) " " - (substring target (match-end 0)))) - (setq i (+ i 1))) - (Info-goto-node target fork))) + (with-current-buffer (window-buffer window) + (save-excursion + ;; Store end and beginning of line. + (setq eol (line-end-position) + bol (line-beginning-position)) + (goto-char (point-min)) + (while (re-search-forward "\\*note[ \n\t]+\\([^:]*\\):" nil t) + (setq str (match-string-no-properties 1)) + ;; See if this one should be the default. + (and (null default) + (<=3D (match-beginning 0) start-point) + (<=3D start-point (point)) + (setq default t)) + ;; See if this one should be the alternate default. + (and (null alt-default) + (and (<=3D bol (match-beginning 0)) + (<=3D (point) eol)) + (setq alt-default t)) + (setq i 0) + (while (setq i (string-match "[ \n\t]+" str i)) + (setq str (concat (substring str 0 i) " " + (substring str (match-end 0)))) + (setq i (1+ i))) + ;; Record as a completion and perhaps as default. + (if (eq default t) (setq default str)) + (if (eq alt-default t) (setq alt-default str)) + ;; Don't add this string if it's a duplicate. + (or (assoc-string str completions t) + (push str completions))) + (setq completions (nreverse completions))) + ;; If no good default was found, try an alternate. + (or default + (setq default alt-default)) + ;; If only one cross-reference found, then make it default. + (if (eq (length completions) 1) + (setq default (car completions))) + (if completions + (let ((input (completing-read (format-prompt "Follow reference = named" + default) + completions nil t))) + (list (if (equal input "") + default input) + current-prefix-arg)) + (user-error "No cross-references in this node"))) + (list Info-mode window))) + (let ((window (if (called-interactively-p 'any) + (get 'Info-follow-reference :selected-window) + (info-window)))) + (with-selected-window window + (unless footnotename + (error "No reference was specified")) + (let (target i (str (concat "\\*note " (regexp-quote footnotename))) + (case-fold-search t)) + (while (setq i (string-search " " str i)) + (setq str (concat (substring str 0 i) + "[ \t\n]+" (substring str (1+ i)))) + (setq i (+ i 6))) + (save-excursion + ;; Move point to the beginning of reference if point is on refer= ence + (or (looking-at "\\*note[ \n\t]+") + (and (looking-back "\\*note[ \n\t]+" + (save-excursion (skip-chars-backward " \n= \t") + (line-beginning-position)= )) + (goto-char (match-beginning 0))) + (if (and (save-excursion + (goto-char (+ (point) 5)) ; skip a possible *note + (re-search-backward "\\*note[ \n\t]+" nil t) + (looking-at str)) + (<=3D (point) (match-end 0))) + (goto-char (match-beginning 0)))) + ;; Go to the reference closest to point + (let ((next-ref (save-excursion (and (re-search-forward str nil = t) + (+ (match-beginning 0) 5)))= ) + (prev-ref (save-excursion (and (re-search-backward str nil= t) + (+ (match-beginning 0) 5)))= )) + (goto-char (cond ((and next-ref prev-ref) + (if (< (abs (- next-ref (point))) + (abs (- prev-ref (point)))) + next-ref prev-ref)) + ((or next-ref prev-ref)) + ((user-error "No cross-reference named %s" + footnotename)))) + (setq target (Info-extract-menu-node-name t)))) + (while (setq i (string-match "[ \t\n]+" target i)) + (setq target (concat (substring target 0 i) " " + (substring target (match-end 0)))) + (setq i (+ i 1))) + (Info-goto-node target fork))))) =20 (defconst Info-menu-entry-name-re "\\(?:[^:]\\|:[^:,.;() \t\n]\\)*" ;; We allow newline because this is also used in Info-follow-reference, @@ -2788,8 +2839,7 @@ Info-complete-menu-item Info-complete-nodes))) (complete-with-action action completions string predicate)))))= ))) =20 - -(defun Info-menu (menu-item &optional fork) +(defun Info-menu (menu-item &optional fork window) "Go to the node pointed to by the menu item named (or abbreviated) MENU-= ITEM. The menu item should one of those listed in the current node's menu. Completion is allowed, and the default menu item is the one point is on. @@ -2798,38 +2848,39 @@ Info-menu new buffer." (interactive (let (;; If point is within a menu item, use that item as the default - (default nil) - (p (point)) - beg - (case-fold-search t)) - (save-excursion - (goto-char (point-min)) - (if (not (search-forward "\n* menu:" nil t)) - (user-error "No menu in this node")) - (setq beg (point)) - (and (< (point) p) - (save-excursion - (goto-char p) - (end-of-line) - (if (re-search-backward (concat "\n\\* +\\(" - Info-menu-entry-name-re - "\\):") - beg t) - (setq default (match-string-no-properties 1)))))) - (let ((item nil)) - (while (null item) - (setq item (let ((completion-ignore-case t) - (Info-complete-menu-buffer (current-buffer))) - (completing-read (format-prompt "Menu item" default) - #'Info-complete-menu-item nil t nil nil - default)))) - (list item current-prefix-arg))) + (default nil) + (p (point)) + beg + (case-fold-search t) + (window (info-window))) + (with-current-buffer (window-buffer window) + (save-excursion + (goto-char (point-min)) + (if (not (search-forward "\n* menu:" nil t)) + (user-error "No menu in this node")) + (setq beg (point)) + (and (< (point) p) + (save-excursion + (goto-char p) + (end-of-line) + (if (re-search-backward (concat "\n\\* +\\(" + Info-menu-entry-name-re + "\\):") + beg t) + (setq default (match-string-no-properties 1)))))) + (let ((item nil)) + (while (null item) + (setq item (let ((completion-ignore-case t) + (Info-complete-menu-buffer (current-buffer))) + (completing-read (format-prompt "Menu item" defaul= t) + #'Info-complete-menu-item nil t n= il nil + default)))) + (list item current-prefix-arg window)))) Info-mode) - ;; there is a problem here in that if several menu items have the same - ;; name you can only go to the node of the first with this command. - (Info-goto-node (Info-extract-menu-item menu-item) - (and fork - (if (stringp fork) fork menu-item)))) + (with-selected-window (or window (info-window)) + (Info-goto-node (Info-extract-menu-item menu-item) + (and fork + (if (stringp fork) fork menu-item))))) =20 (defun Info-extract-menu-item (menu-item) (setq menu-item (regexp-quote menu-item)) @@ -2869,32 +2920,35 @@ Info-nth-menu-item "Go to the node of the Nth menu item. N is the digit argument used to invoke this command." (interactive nil Info-mode) - (Info-goto-node - (Info-extract-menu-counting - (- (aref (this-command-keys) (1- (length (this-command-keys)))) ?0)))) + (with-selected-window (info-window) + (Info-goto-node + (Info-extract-menu-counting + (- (aref (this-command-keys) (1- (length (this-command-keys)))) ?0))= ))) =20 (defun Info-top-node () "Go to the Top node of this file." (interactive nil Info-mode) - (Info-goto-node "Top")) + (with-selected-window (info-window) + (Info-goto-node "Top"))) =20 (defun Info-final-node () "Go to the final node in this file." (interactive nil Info-mode) - (Info-goto-node "Top") - (let ((Info-history nil) - (case-fold-search t)) - ;; Go to the last node in the menu of Top. But don't delve into - ;; detailed node listings. - (Info-goto-node (Info-extract-menu-counting nil t)) - ;; If the last node in the menu is not last in pointer structure, - ;; move forward (but not down- or upward - see bug#1116) until we - ;; can't go any farther. - (while (Info-forward-node t t t) nil) - ;; Then keep moving down to last subnode, unless we reach an index. - (while (and (not (Info-index-node)) - (save-excursion (search-forward "\n* Menu:" nil t))) - (Info-goto-node (Info-extract-menu-counting nil))))) + (with-selected-window (info-window) + (Info-goto-node "Top") + (let ((Info-history nil) + (case-fold-search t)) + ;; Go to the last node in the menu of Top. But don't delve into + ;; detailed node listings. + (Info-goto-node (Info-extract-menu-counting nil t)) + ;; If the last node in the menu is not last in pointer structure, + ;; move forward (but not down- or upward - see bug#1116) until we + ;; can't go any farther. + (while (Info-forward-node t t t) nil) + ;; Then keep moving down to last subnode, unless we reach an index. + (while (and (not (Info-index-node)) + (save-excursion (search-forward "\n* Menu:" nil t))) + (Info-goto-node (Info-extract-menu-counting nil)))))) =20 (defun Info-forward-node (&optional not-down not-up no-error) "Go forward one node, considering all nodes as forming one sequence. @@ -2905,160 +2959,166 @@ Info-forward-node NOT-UP non-nil means don't go to parent nodes, and NO-ERROR non-nil means don't signal a user-error if there's no node to go to." (interactive nil Info-mode) - (goto-char (point-min)) - (forward-line 1) - (let ((case-fold-search t)) - ;; three possibilities, in order of priority: - ;; 1. next node is in a menu in this node (but not in an index) - ;; 2. next node is next at same level - ;; 3. next node is up and next - (cond ((and (not not-down) - (save-excursion (search-forward "\n* menu:" nil t)) - (not (Info-index-node))) - (Info-goto-node (Info-extract-menu-counting 1)) - t) - ((save-excursion (search-backward "next:" nil t)) - (Info-next) - t) - ((and (not not-up) - (save-excursion (search-backward "up:" nil t)) - ;; Use string-equal, not equal, to ignore text props. - (not (string-equal (downcase (Info-extract-pointer "up")) - "top"))) - (let ((old-node Info-current-node)) - (Info-up) - (let ((old-history Info-history) - success) - (unwind-protect - (setq success (Info-forward-node t nil no-error)) - (or success (Info-goto-node old-node))) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history))))) - (no-error nil) - (t (user-error "No pointer forward from this node"))))) + (with-selected-window (info-window) + (goto-char (point-min)) + (forward-line 1) + (let ((case-fold-search t)) + ;; three possibilities, in order of priority: + ;; 1. next node is in a menu in this node (but not in an index) + ;; 2. next node is next at same level + ;; 3. next node is up and next + (cond ((and (not not-down) + (save-excursion (search-forward "\n* menu:" nil t)) + (not (Info-index-node))) + (Info-goto-node (Info-extract-menu-counting 1)) + t) + ((save-excursion (search-backward "next:" nil t)) + (Info-next (selected-window)) + t) + ((and (not not-up) + (save-excursion (search-backward "up:" nil t)) + ;; Use string-equal, not equal, to ignore text props. + (not (string-equal (downcase (Info-extract-pointer "up")= ) + "top"))) + (let ((old-node Info-current-node)) + (Info-up nil (selected-window)) + (let ((old-history Info-history) + success) + (unwind-protect + (setq success (Info-forward-node t nil no-error)) + (or success (Info-goto-node old-node))) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history))))) + (no-error nil) + (t (user-error "No pointer forward from this node")))))) =20 (defun Info-backward-node () "Go backward one node, considering all nodes as forming one sequence. If the current node has a \"previous\" node, go to it, descending into its last sub-node, if any; otherwise go \"up\" to the parent node." (interactive nil Info-mode) - (let ((prevnode (Info-extract-pointer "prev[ious]*" t)) - (upnode (Info-extract-pointer "up" t)) - (case-fold-search t)) - (cond ((and upnode (string-search "(" upnode)) - (user-error "First node in file")) - ((and upnode (or (null prevnode) - ;; Use string-equal, not equal, - ;; to ignore text properties. - (string-equal (downcase prevnode) - (downcase upnode)))) - (Info-up)) - (prevnode - ;; If we move back at the same level, - ;; go down to find the last subnode*. - (Info-prev) - (let ((old-history Info-history)) - (while (and (not (Info-index-node)) - (save-excursion (search-forward "\n* Menu:" nil t))) - (Info-goto-node (Info-extract-menu-counting nil))) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history)))) - (t - (user-error "No pointer backward from this node"))))) + (with-selected-window (info-window) + (let ((prevnode (Info-extract-pointer "prev[ious]*" t)) + (upnode (Info-extract-pointer "up" t)) + (case-fold-search t)) + (cond ((and upnode (string-search "(" upnode)) + (user-error "First node in file")) + ((and upnode (or (null prevnode) + ;; Use string-equal, not equal, + ;; to ignore text properties. + (string-equal (downcase prevnode) + (downcase upnode)))) + (Info-up nil (selected-window))) + (prevnode + ;; If we move back at the same level, + ;; go down to find the last subnode*. + (Info-prev (selected-window)) + (let ((old-history Info-history)) + (while (and (not (Info-index-node)) + (save-excursion (search-forward "\n* Menu:" nil= t))) + (Info-goto-node (Info-extract-menu-counting nil))) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history)))) + (t + (user-error "No pointer backward from this node")))))) =20 (define-obsolete-function-alias 'Info-exit #'quit-window "27.1") =20 -(defun Info-next-menu-item () +(defun Info-next-menu-item (&optional window) "Go to the node of the next menu item." (interactive nil Info-mode) - ;; Bind this in case the user sets it to nil. - (let* ((case-fold-search t) - (node - (save-excursion - (forward-line -1) - (search-forward "\n* menu:" nil t) - (and (search-forward "\n* " nil t) - (Info-extract-menu-node-name))))) - (if node (Info-goto-node node) - (user-error "No more items in menu")))) + (with-selected-window (or window (info-window)) + ;; Bind this in case the user sets it to nil. + (let* ((case-fold-search t) + (node + (save-excursion + (forward-line -1) + (search-forward "\n* menu:" nil t) + (and (search-forward "\n* " nil t) + (Info-extract-menu-node-name))))) + (if node (Info-goto-node node) + (user-error "No more items in menu"))))) =20 (defun Info-last-menu-item () "Go to the node of the previous menu item." (interactive nil Info-mode) - (save-excursion - (forward-line 1) - ;; Bind this in case the user sets it to nil. - (let* ((case-fold-search t) - (beg (save-excursion - (and (search-backward "\n* menu:" nil t) - (point))))) - (or (and beg (search-backward "\n* " beg t)) - (user-error "No previous items in menu"))) - (Info-goto-node (save-excursion - (goto-char (match-end 0)) - (Info-extract-menu-node-name))))) + (with-selected-window (info-window) + (save-excursion + (forward-line 1) + ;; Bind this in case the user sets it to nil. + (let* ((case-fold-search t) + (beg (save-excursion + (and (search-backward "\n* menu:" nil t) + (point))))) + (or (and beg (search-backward "\n* " beg t)) + (user-error "No previous items in menu"))) + (Info-goto-node (save-excursion + (goto-char (match-end 0)) + (Info-extract-menu-node-name)))))) =20 (defmacro Info-no-error (&rest body) `(condition-case nil (progn ,@body t) (error nil))) =20 -(defun Info-next-preorder () +(defun Info-next-preorder (&optional window) "Go to the next subnode or the next node, or go up a level." (interactive nil Info-mode) - (cond ((Info-no-error (Info-next-menu-item))) - ((Info-no-error (Info-next))) - ((Info-no-error (Info-up t)) - ;; Since we have already gone thru all the items in this menu, - ;; go up to the end of this node. - (goto-char (point-max)) - ;; Since logically we are done with the node with that menu, - ;; move on from it. But don't add intermediate nodes - ;; to the history on recursive calls. - (let ((old-history Info-history)) - (Info-next-preorder) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history)))) - (t - (user-error "No more nodes")))) - -(defun Info-last-preorder () + (with-selected-window (or window (info-window)) + (cond ((Info-no-error (Info-next-menu-item (selected-window)))) + ((Info-no-error (Info-next (selected-window)))) + ((Info-no-error (Info-up t)) + ;; Since we have already gone thru all the items in this menu, + ;; go up to the end of this node. + (goto-char (point-max)) + ;; Since logically we are done with the node with that menu, + ;; move on from it. But don't add intermediate nodes + ;; to the history on recursive calls. + (let ((old-history Info-history)) + (Info-next-preorder (selected-window)) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history)))) + (t + (user-error "No more nodes"))))) + +(defun Info-last-preorder (&optional window) "Go to the last node, popping up a level if there is none." (interactive nil Info-mode) - (cond ((and Info-scroll-prefer-subnodes - (Info-no-error - (Info-last-menu-item) - ;; If we go down a menu item, go to the end of the node - ;; so we can scroll back through it. - (goto-char (point-max)))) - ;; Keep going down, as long as there are nested menu nodes. - (let ((old-history Info-history)) - (while (Info-no-error - (Info-last-menu-item) - ;; If we go down a menu item, go to the end of the node - ;; so we can scroll back through it. - (goto-char (point-max)))) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history))) - (recenter -1)) - ((and (Info-no-error (Info-extract-pointer "prev")) - (not (equal (Info-extract-pointer "up") - (Info-extract-pointer "prev")))) - (Info-no-error (Info-prev)) - (goto-char (point-max)) - (let ((old-history Info-history)) - (while (Info-no-error - (Info-last-menu-item) - ;; If we go down a menu item, go to the end of the node - ;; so we can scroll back through it. - (goto-char (point-max)))) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history))) - (recenter -1)) - ((Info-no-error (Info-up t)) - (goto-char (point-min)) - (let ((case-fold-search t)) - (or (search-forward "\n* Menu:" nil t) - (goto-char (point-max))))) - (t (user-error "No previous nodes")))) + (with-selected-window (or window (info-window)) + (cond ((and Info-scroll-prefer-subnodes + (Info-no-error + (Info-last-menu-item) + ;; If we go down a menu item, go to the end of the node + ;; so we can scroll back through it. + (goto-char (point-max)))) + ;; Keep going down, as long as there are nested menu nodes. + (let ((old-history Info-history)) + (while (Info-no-error + (Info-last-menu-item) + ;; If we go down a menu item, go to the end of the no= de + ;; so we can scroll back through it. + (goto-char (point-max)))) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history))) + (recenter -1)) + ((and (Info-no-error (Info-extract-pointer "prev")) + (not (equal (Info-extract-pointer "up") + (Info-extract-pointer "prev")))) + (Info-no-error (Info-prev (selected-window))) + (goto-char (point-max)) + (let ((old-history Info-history)) + (while (Info-no-error + (Info-last-menu-item) + ;; If we go down a menu item, go to the end of the no= de + ;; so we can scroll back through it. + (goto-char (point-max)))) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history))) + (recenter -1)) + ((Info-no-error (Info-up t)) + (goto-char (point-min)) + (let ((case-fold-search t)) + (or (search-forward "\n* Menu:" nil t) + (goto-char (point-max))))) + (t (user-error "No previous nodes"))))) =20 (defun Info-scroll-up () "Scroll one screenful forward in Info, considering all nodes as one sequ= ence. @@ -3073,25 +3133,25 @@ Info-scroll-up the menu of a node, it moves to subnode indicated by the following menu item. (That case won't normally result from this command, but can happen in other ways.)" - (interactive nil Info-mode) - (if (or (< (window-start) (point-min)) - (> (window-start) (point-max))) - (set-window-start (selected-window) (point))) - (let* ((case-fold-search t) - (virtual-end (save-excursion - (goto-char (point-min)) - (if (and Info-scroll-prefer-subnodes - (search-forward "\n* Menu:" nil t)) - (point) - (point-max))))) - (if (or (< virtual-end (window-start)) - (pos-visible-in-window-p virtual-end)) - (cond - (Info-scroll-prefer-subnodes (Info-next-preorder)) - ((Info-no-error (Info-goto-node (Info-extract-menu-counting 1)))) - (t (Info-next-preorder))) - (scroll-up)))) + (with-selected-window (info-window) + (if (or (< (window-start) (point-min)) + (> (window-start) (point-max))) + (set-window-start (selected-window) (point))) + (let* ((case-fold-search t) + (virtual-end (save-excursion + (goto-char (point-min)) + (if (and Info-scroll-prefer-subnodes + (search-forward "\n* Menu:" nil t)) + (point) + (point-max))))) + (if (or (< virtual-end (window-start)) + (pos-visible-in-window-p virtual-end)) + (cond + (Info-scroll-prefer-subnodes (Info-next-preorder (selected-wind= ow))) + ((Info-no-error (Info-goto-node (Info-extract-menu-counting 1))= )) + (t (Info-next-preorder (selected-window)))) + (scroll-up))))) =20 (defun Info-mouse-scroll-up (e) "Scroll one screenful forward in Info, using the mouse. @@ -3109,21 +3169,22 @@ Info-scroll-down beginning of a node, that goes to the previous node or back up to the parent node." (interactive nil Info-mode) - (if (or (< (window-start) (point-min)) - (> (window-start) (point-max))) - (set-window-start (selected-window) (point))) - (let* ((case-fold-search t) - (current-point (point)) - (virtual-end - (and Info-scroll-prefer-subnodes - (save-excursion - (setq current-point (line-beginning-position)) - (goto-char (point-min)) - (search-forward "\n* Menu:" current-point t))))) - (if (or virtual-end - (pos-visible-in-window-p (point-min) nil t)) - (Info-last-preorder) - (scroll-down)))) + (with-selected-window (info-window) + (if (or (< (window-start) (point-min)) + (> (window-start) (point-max))) + (set-window-start (selected-window) (point))) + (let* ((case-fold-search t) + (current-point (point)) + (virtual-end + (and Info-scroll-prefer-subnodes + (save-excursion + (setq current-point (line-beginning-position)) + (goto-char (point-min)) + (search-forward "\n* Menu:" current-point t))))) + (if (or virtual-end + (pos-visible-in-window-p (point-min) nil t)) + (Info-last-preorder (selected-window)) + (scroll-down))))) =20 (defun Info-mouse-scroll-down (e) "Scroll one screenful backward in Info, using the mouse. @@ -3170,60 +3231,62 @@ Info-prev-reference-or-link (goto-char plink)) (if pxref (goto-char (or (match-beginning 1) (match-beginning 0)))))= )) =20 -(defun Info-next-reference (&optional recur count) +(defun Info-next-reference (&optional recur count window) "Move cursor to the next cross-reference or menu item in the node. If COUNT is non-nil (interactively with a prefix arg), jump over COUNT cross-references." (interactive "i\np" Info-mode) - (unless count - (setq count 1)) - (if (< count 0) - (Info-prev-reference recur (- count)) - (while (unless (zerop count) (setq count (1- count))) - (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://") - (old-pt (point)) - (case-fold-search t)) - (or (eobp) (forward-char 1)) - (or (Info-next-reference-or-link pat 'link) - (progn - (goto-char (point-min)) - (or (Info-next-reference-or-link pat 'link) - (progn - (goto-char old-pt) - (user-error "No cross references in this node"))))) - (if (looking-at "\\* Menu:") - (if recur - (user-error "No cross references in this node") - (Info-next-reference t)) - (if (looking-at "^\\* ") - (forward-char 2))))))) - -(defun Info-prev-reference (&optional recur count) + (with-selected-window (or window (info-window)) + (unless count + (setq count 1)) + (if (< count 0) + (Info-prev-reference recur (- count) (selected-window)) + (while (unless (zerop count) (setq count (1- count))) + (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://= ") + (old-pt (point)) + (case-fold-search t)) + (or (eobp) (forward-char 1)) + (or (Info-next-reference-or-link pat 'link) + (progn + (goto-char (point-min)) + (or (Info-next-reference-or-link pat 'link) + (progn + (goto-char old-pt) + (user-error "No cross references in this node"))))) + (if (looking-at "\\* Menu:") + (if recur + (user-error "No cross references in this node") + (Info-next-reference t nil (selected-window))) + (if (looking-at "^\\* ") + (forward-char 2)))))))) + +(defun Info-prev-reference (&optional recur count window) "Move cursor to the previous cross-reference or menu item in the node. If COUNT is non-nil (interactively with a prefix arg), jump over COUNT cross-references." (interactive "i\np" Info-mode) - (unless count - (setq count 1)) - (if (< count 0) - (Info-next-reference recur (- count)) - (while (unless (zerop count) (setq count (1- count))) - (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://") - (old-pt (point)) - (case-fold-search t)) - (or (Info-prev-reference-or-link pat 'link) - (progn - (goto-char (point-max)) - (or (Info-prev-reference-or-link pat 'link) - (progn - (goto-char old-pt) - (user-error "No cross references in this node"))))) - (if (looking-at "\\* Menu:") - (if recur - (user-error "No cross references in this node") - (Info-prev-reference t)) - (if (looking-at "^\\* ") - (forward-char 2))))))) + (with-selected-window (or window (info-window)) + (unless count + (setq count 1)) + (if (< count 0) + (Info-next-reference recur (- count) (selected-window)) + (while (unless (zerop count) (setq count (1- count))) + (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://= ") + (old-pt (point)) + (case-fold-search t)) + (or (Info-prev-reference-or-link pat 'link) + (progn + (goto-char (point-max)) + (or (Info-prev-reference-or-link pat 'link) + (progn + (goto-char old-pt) + (user-error "No cross references in this node"))))) + (if (looking-at "\\* Menu:") + (if recur + (user-error "No cross references in this node") + (Info-prev-reference t nil (selected-window))) + (if (looking-at "^\\* ") + (forward-char 2)))))))) =0C (defun Info-index-nodes (&optional file) "Return a list of names of all index nodes in Info FILE. @@ -3337,72 +3400,80 @@ info--ensure-not-in-directory-node "type \\[Info-menu] to select a manual"))))) =20 ;;;###autoload -(defun Info-index (topic) +(defun Info-index (topic &optional window) "Look up a string TOPIC in the index for this manual and go to that entr= y. If there are no exact matches to the specified topic, this chooses the first match which is a case-insensitive substring of a topic. Use the \\\\[Info-index-next] command to see the other matc= hes. Give an empty topic name to go to the Index node itself." (interactive - (list - (let ((completion-ignore-case t) - (Info-complete-menu-buffer (clone-buffer)) - (Info-complete-nodes (Info-index-nodes)) - (Info-history-list nil)) - (info--ensure-not-in-directory-node) - (unwind-protect - (with-current-buffer Info-complete-menu-buffer - (Info-goto-index) - (completing-read "Index topic: " #'Info-complete-menu-item)) - (kill-buffer Info-complete-menu-buffer))))) - (info--ensure-not-in-directory-node) - ;; Strip leading colon in topic; index format does not allow them. - (if (and (stringp topic) - (> (length topic) 0) - (=3D (aref topic 0) ?:)) - (setq topic (substring topic 1))) - (let ((orignode Info-current-node) - (pattern (format "\n\\* +\\([^\n]*\\(%s\\)[^\n]*\\):[ \t]+\\([^\n]*\\)\\.= \\(?:[ \t\n]*(line +\\([0-9]+\\))\\)?" - (regexp-quote topic))) - node (nodes (Info-index-nodes)) - (ohist-list Info-history-list) - (case-fold-search t)) - (Info-goto-index) - (or (equal topic "") - (let ((matches nil) - (exact nil) - ;; We bind Info-history to nil for internal node-switches so - ;; that we don't put junk in the history. In the first - ;; Info-goto-index call, above, we do update the history - ;; because that is what the user's previous node choice into it. - (Info-history nil) - found) - (while - (progn - (goto-char (point-min)) - (while (re-search-forward pattern nil t) - (let ((entry (match-string-no-properties 1)) - (nodename (match-string-no-properties 3)) - (line (string-to-number (concat "0" (match-string 4))))) - (add-text-properties - (- (match-beginning 2) (match-beginning 1)) - (- (match-end 2) (match-beginning 1)) - '(face info-index-match) entry) - (push (list entry nodename Info-current-node line) matches))) - (setq nodes (cdr nodes) node (car nodes))) - (Info-goto-node node)) - (or matches - (progn - (Info-goto-node orignode) - (user-error "No `%s' in index" topic))) - ;; Here it is a feature that assoc is case-sensitive. - (while (setq found (assoc topic matches)) - (setq exact (cons found exact) - matches (delq found matches))) - (setq Info-history-list ohist-list) - (setq Info-index-alternatives (nconc exact (nreverse matches)) - Info--current-index-alternative 0) - (Info-index-next 0))))) + (let ((frame-to-prompt-in (selected-frame)) + (window (info-window))) + (with-selected-window window + (list + (let ((completion-ignore-case t) + (Info-complete-menu-buffer (clone-buffer)) + (Info-complete-nodes (Info-index-nodes)) + (Info-history-list nil)) + (info--ensure-not-in-directory-node) + (unwind-protect + (with-current-buffer Info-complete-menu-buffer + (Info-goto-index) + (with-selected-frame frame-to-prompt-in + (completing-read "Index topic: " + #'Info-complete-menu-item))) + (kill-buffer Info-complete-menu-buffer))) + window))) + Info-mode) + (with-selected-window (or window (info-window)) + (info--ensure-not-in-directory-node) + ;; Strip leading colon in topic; index format does not allow them. + (if (and (stringp topic) + (> (length topic) 0) + (=3D (aref topic 0) ?:)) + (setq topic (substring topic 1))) + (let ((orignode Info-current-node) + (pattern (format "\n\\* +\\([^\n]*\\(%s\\)[^\n]*\\):[ \t]+\\([^\= n]*\\)\\.\\(?:[ \t\n]*(line +\\([0-9]+\\))\\)?" + (regexp-quote topic))) + node (nodes (Info-index-nodes)) + (ohist-list Info-history-list) + (case-fold-search t)) + (Info-goto-index) + (or (equal topic "") + (let ((matches nil) + (exact nil) + ;; We bind Info-history to nil for internal node-switches = so + ;; that we don't put junk in the history. In the first + ;; Info-goto-index call, above, we do update the history + ;; because that is what the user's previous node choice in= to it. + (Info-history nil) + found) + (while + (progn + (goto-char (point-min)) + (while (re-search-forward pattern nil t) + (let ((entry (match-string-no-properties 1)) + (nodename (match-string-no-properties 3)) + (line (string-to-number (concat "0" (match-strin= g 4))))) + (add-text-properties + (- (match-beginning 2) (match-beginning 1)) + (- (match-end 2) (match-beginning 1)) + '(face info-index-match) entry) + (push (list entry nodename Info-current-node line) m= atches))) + (setq nodes (cdr nodes) node (car nodes))) + (Info-goto-node node)) + (or matches + (progn + (Info-goto-node orignode) + (user-error "No `%s' in index" topic))) + ;; Here it is a feature that assoc is case-sensitive. + (while (setq found (assoc topic matches)) + (setq exact (cons found exact) + matches (delq found matches))) + (setq Info-history-list ohist-list) + (setq Info-index-alternatives (nconc exact (nreverse matches)) + Info--current-index-alternative 0) + (Info-index-next 0)))))) =20 (defun Info-index-next (num) "Go to the next matching index item from the last \\\\[In= fo-index] command. @@ -3411,45 +3482,46 @@ Info-index-next =20 Also see the `Info-warn-on-index-alternatives-wrap' user option." (interactive "p" Info-mode) - (unless Info-index-alternatives - (user-error "No previous `i' command")) - (let ((index (+ Info--current-index-alternative num)) - (total (length Info-index-alternatives)) - (next-key (key-description (where-is-internal - 'Info-index-next overriding-local-map t)))) - (if (and Info-warn-on-index-alternatives-wrap - (> total 1) - (cond - ((< index 0) - (setq Info--current-index-alternative (- total 2)) - (message - "No previous matches, use `%s' to continue from end of lis= t" - next-key) - t) - ((>=3D index total) - (setq Info--current-index-alternative -1) - (message - "No previous matches, use `%s' to continue from start of l= ist" - next-key) - t))) - () ; Do nothing - (setq index (mod index total) - Info--current-index-alternative index) - (let ((entry (nth index Info-index-alternatives))) - (Info-goto-node (nth 1 entry)) - (if (> (nth 3 entry) 0) - ;; Forward 2 lines less because `Info-find-node-2' initially - ;; puts point to the 2nd line. - (forward-line (- (nth 3 entry) 2)) - (forward-line 3) ; don't search in headers - (Info-find-index-name (car entry))) - (message "Found `%s' in %s. %s" - (car entry) - (nth 2 entry) - (if (> total 1) - (format-message - "(%s total; use `%s' for next)" total next-key) - "(Only match)")))))) + (with-selected-window (info-window) + (unless Info-index-alternatives + (user-error "No previous `i' command")) + (let ((index (+ Info--current-index-alternative num)) + (total (length Info-index-alternatives)) + (next-key (key-description (where-is-internal + 'Info-index-next overriding-local-ma= p t)))) + (if (and Info-warn-on-index-alternatives-wrap + (> total 1) + (cond + ((< index 0) + (setq Info--current-index-alternative (- total 2)) + (message + "No previous matches, use `%s' to continue from end of l= ist" + next-key) + t) + ((>=3D index total) + (setq Info--current-index-alternative -1) + (message + "No previous matches, use `%s' to continue from start of= list" + next-key) + t))) + () ; Do nothing + (setq index (mod index total) + Info--current-index-alternative index) + (let ((entry (nth index Info-index-alternatives))) + (Info-goto-node (nth 1 entry)) + (if (> (nth 3 entry) 0) + ;; Forward 2 lines less because `Info-find-node-2' initially + ;; puts point to the 2nd line. + (forward-line (- (nth 3 entry) 2)) + (forward-line 3) ; don't search in headers + (Info-find-index-name (car entry))) + (message "Found `%s' in %s. %s" + (car entry) + (nth 2 entry) + (if (> total 1) + (format-message + "(%s total; use `%s' for next)" total next-key) + "(Only match)"))))))) =20 (defun Info-find-index-name (name) "Move point to the place within the current node where NAME is defined." @@ -3526,40 +3598,47 @@ Info-virtual-index-find-node (format "*Index for =A1=AE%s=A1=AF*::" (cdr (nth 0 nodeinfo))) (cdr (nth 0 nodeinfo))))))))) =20 -(defun Info-virtual-index (topic) +(defun Info-virtual-index (topic &optional window) "Show a node with all lines in the index containing a string TOPIC. Like `Info-index' but displays a node with index search results. Give an empty topic name to go to the node with links to previous search results." ;; `interactive' is a copy from `Info-index' (interactive - (list - (let ((completion-ignore-case t) - (Info-complete-menu-buffer (clone-buffer)) - (Info-complete-nodes (Info-index-nodes)) - (Info-history-list nil)) - (info--ensure-not-in-directory-node) - (unwind-protect - (with-current-buffer Info-complete-menu-buffer - (Info-goto-index) - (completing-read "Index topic: " #'Info-complete-menu-item)) - (kill-buffer Info-complete-menu-buffer)))) + (let ((frame-to-prompt-in (selected-frame)) + (window (info-window))) + (with-selected-window window + (list + (let ((completion-ignore-case t) + (Info-complete-menu-buffer (clone-buffer)) + (Info-complete-nodes (Info-index-nodes)) + (Info-history-list nil)) + (info--ensure-not-in-directory-node) + (unwind-protect + (with-current-buffer Info-complete-menu-buffer + (Info-goto-index) + (with-selected-frame frame-to-prompt-in + (completing-read "Index topic: " + #'Info-complete-menu-item))) + (kill-buffer Info-complete-menu-buffer))) + window))) Info-mode) (if (equal topic "") (Info-find-node Info-current-file "*Index*") - (unless (assoc (cons Info-current-file topic) Info-virtual-index-nodes= ) - (let ((orignode Info-current-node) - (ohist-list Info-history-list)) - ;; Reuse `Info-index' to set `Info-index-alternatives'. - (Info-index topic) - (push (cons (cons Info-current-file topic) Info-index-alternatives) - Info-virtual-index-nodes) - ;; Clean up unnecessary side-effects of `Info-index'. - (setq Info-history-list ohist-list) - (Info-goto-node orignode) - (message ""))) - (Info-find-node Info-current-file - (format "*Index for =A1=AE%s=A1=AF*" topic)))) + (with-selected-window (or window (info-window)) + (unless (assoc (cons Info-current-file topic) Info-virtual-index-nod= es) + (let ((orignode Info-current-node) + (ohist-list Info-history-list)) + ;; Reuse `Info-index' to set `Info-index-alternatives'. + (Info-index topic) + (push (cons (cons Info-current-file topic) Info-index-alternativ= es) + Info-virtual-index-nodes) + ;; Clean up unnecessary side-effects of `Info-index'. + (setq Info-history-list ohist-list) + (Info-goto-node orignode) + (message ""))) + (Info-find-node Info-current-file + (format "*Index for =A1=AE%s=A1=AF*" topic))))) =0C (add-to-list 'Info-virtual-files '("\\`\\*Apropos\\*\\'" @@ -3694,20 +3773,25 @@ info-apropos "Search indices of all known Info files on your system for STRING. If REGEXP (interactively, the prefix), use a regexp match. =20 -Display a menu of the possible matches." +Display a menu of the possible matches in selected window. If given +numeric prefix argument display results in window *info*. With +non-numeric argument prompt user for info buffer to display results in." (interactive "sIndex apropos: \nP") - (if (equal string "") - (Info-find-node Info-apropos-file "Top") - (let ((nodes Info-apropos-nodes) - nodename) - (while (and nodes (not (equal string (nth 1 (car nodes))))) - (setq nodes (cdr nodes))) - (if nodes - (Info-find-node Info-apropos-file (car (car nodes)) nil nil t) - (setq nodename (format "Index for =A1=AE%s=A1=AF" string)) - (push (list nodename string (Info-apropos-matches string regexp)) - Info-apropos-nodes) - (Info-find-node Info-apropos-file nodename))))) + (with-selected-window (if current-prefix-arg + (info-window) + (selected-window)) + (if (equal string "") + (Info-find-node Info-apropos-file "Top") + (let ((nodes Info-apropos-nodes) + nodename) + (while (and nodes (not (equal string (nth 1 (car nodes))))) + (setq nodes (cdr nodes))) + (if nodes + (Info-find-node Info-apropos-file (car (car nodes)) nil nil t) + (setq nodename (format "Index for =A1=AE%s=A1=AF" string)) + (push (list nodename string (Info-apropos-matches string regexp)= ) + Info-apropos-nodes) + (Info-find-node Info-apropos-file nodename)))))) =0C (add-to-list 'Info-virtual-files '("\\`\\*Finder.*\\*\\'" @@ -3834,25 +3918,33 @@ Info-finder-find-node (insert "Can=A1=AFt find package description.\n\n")))))) =20 ;;;###autoload -(defun info-finder (&optional keywords) +(defun info-finder (&optional keywords window) "Display descriptions of the keywords in the Finder virtual manual. -In interactive use, a prefix argument directs this command to read +In interactive use, a non-numeric prefix argument directs this command to = read a list of keywords separated by comma. After that, it displays a node -with a list of packages that contain all specified keywords." +with a list of packages that contain all specified keywords. Numeric +prefix argument will choose window with name *info* if such window +exists otherwise it will prompt the user to choose a window." (interactive - (when current-prefix-arg - (require 'finder) - (list - (completing-read-multiple - "Keywords (separated by comma): " - (mapcar #'symbol-name (mapcar #'car (append finder-known-keywords - (finder-unknown-keyword= s)))) - nil t)))) + (let ((window (info-window))) + (when (and current-prefix-arg + (not (numberp current-prefix-arg))) + (require 'finder) + (let ((current-prefix-arg nil)) + (with-current-buffer (window-buffer window) + (list + (completing-read-multiple + "Keywords (separated by comma): " + (mapcar #'symbol-name (mapcar #'car (append finder-known-keyw= ords + (finder-unknown-k= eywords)))) + nil t) + window)))))) + (setq current-prefix-arg nil) (require 'finder) - (if keywords - (Info-find-node Info-finder-file (mapconcat 'identity keywords ", ")= ) - (Info-find-node Info-finder-file "Top"))) - + (with-selected-window (or window (info-window)) + (if keywords + (Info-find-node Info-finder-file (mapconcat 'identity keywords ", = ")) + (Info-find-node Info-finder-file "Top")))) =0C (defun Info-undefined () "Make command be undefined in Info." @@ -3937,7 +4029,7 @@ Info-mouse-follow-nearest-node (mouse-set-point click) (and (not (Info-follow-nearest-node)) (save-excursion (forward-line 1) (eobp)) - (Info-next-preorder))) + (Info-next-preorder (selected-window)))) =20 (defun Info-follow-nearest-node (&optional fork) "Follow a node reference near point. @@ -4030,15 +4122,15 @@ Info-mouse-follow-link (Info-goto-node link-args)) ;; These special values of the `link-args' property are used ;; for navigation; see `Info-fontify-node'. - ((eq link-args 'prev) (Info-prev)) - ((eq link-args 'next) (Info-next)) - ((eq link-args 'up) (Info-up))))) + ((eq link-args 'prev) (Info-prev (selected-window))) + ((eq link-args 'next) (Info-next (selected-window))) + ((eq link-args 'up) (Info-up (selected-window)))))) =20 =0C (defvar Info-mode-map (let ((map (make-keymap))) (suppress-keymap map) - (define-key map "." 'beginning-of-buffer) + (define-key map "." 'Info-beginning-of-buffer) (define-key map " " 'Info-scroll-up) (define-key map [?\S-\ ] 'Info-scroll-down) (define-key map "\C-m" 'Info-follow-nearest-node) @@ -4060,10 +4152,10 @@ Info-mode-map (define-key map "[" 'Info-backward-node) (define-key map "<" 'Info-top-node) (define-key map ">" 'Info-final-node) - (define-key map "b" 'beginning-of-buffer) + (define-key map "b" 'Info-beginning-of-buffer) (put 'beginning-of-buffer :advertised-binding "b") (define-key map "d" 'Info-directory) - (define-key map "e" 'end-of-buffer) + (define-key map "e" 'Info-end-of-buffer) (define-key map "f" 'Info-follow-reference) (define-key map "g" 'Info-goto-node) (define-key map "G" 'Info-goto-node-web) @@ -4071,7 +4163,7 @@ Info-mode-map ;; This is for compatibility with standalone info (>~ version 5.2). ;; Though for some time, standalone info had H and h reversed. ;; See . - (define-key map "H" 'describe-mode) + (define-key map "H" 'Info-describe-mode) (define-key map "i" 'Info-index) (define-key map "I" 'Info-virtual-index) (define-key map "l" 'Info-history-back) @@ -4079,7 +4171,7 @@ Info-mode-map (define-key map "m" 'Info-menu) (define-key map "n" 'Info-next) (define-key map "p" 'Info-prev) - (define-key map "q" 'quit-window) + (define-key map "q" 'Info-quit-window) (define-key map "r" 'Info-history-forward) (define-key map "s" 'Info-search) (define-key map "S" 'Info-search-case-sensitively) @@ -4104,7 +4196,6 @@ Info-mode-map map) "Keymap containing Info commands.") =20 - (defun Info-check-pointer (item) "Non-nil if ITEM is present in this node." (condition-case nil @@ -4125,7 +4216,7 @@ Info-check-pointer :help "Go backward one node, considering all as a sequence"] ["Forward" Info-forward-node :help "Go forward one node, considering all as a sequence"] - ["Beginning" beginning-of-buffer + ["Beginning" Info-beginning-of-buffer :help "Go to beginning of this node"] ["Top" Info-top-node :help "Go to top node of file"] @@ -4323,20 +4414,21 @@ Info-copy-current-node-name The name of the Info file is prepended to the node name in parentheses. With a zero prefix arg, put the name inside a function call to `info'." (interactive "P" Info-mode) - (unless Info-current-node - (user-error "No current Info node")) - (let ((node (if (stringp Info-current-file) - (concat "(" (file-name-sans-extension - (file-name-nondirectory Info-current-file)) - ") " - Info-current-node)))) - (if (zerop (prefix-numeric-value arg)) - (setq node (concat "(info \"" node "\")"))) - (unless (stringp Info-current-file) - (setq node (format "(Info-find-node '%S '%S)" - Info-current-file Info-current-node))) - (kill-new node) - (message "%s" node))) + (with-selected-window (info-window) + (unless Info-current-node + (user-error "No current Info node")) + (let ((node (if (stringp Info-current-file) + (concat "(" (file-name-sans-extension + (file-name-nondirectory Info-current-file)) + ") " + Info-current-node)))) + (if (zerop (prefix-numeric-value arg)) + (setq node (concat "(info \"" node "\")"))) + (unless (stringp Info-current-file) + (setq node (format "(Info-find-node '%S '%S)" + Info-current-file Info-current-node))) + (kill-new node) + (message "%s" node)))) =20 =0C ;; Info mode is suitable only for specially formatted data. @@ -5230,8 +5322,7 @@ Info-speedbar-browser ;; Make sure that speedbar is active (speedbar-frame-mode 1) ;; Now, throw us into Info mode on speedbar. - (speedbar-change-initial-expansion-list "Info") - ) + (speedbar-change-initial-expansion-list "Info")) =20 ;; speedbar loads dframe at runtime. (declare-function dframe-select-attached-frame "dframe" (&optional frame)) @@ -5509,6 +5600,44 @@ info--manual-names (apply-partially #'Info-read-node-name-2 Info-directory-list (mapcar #'car Info-suffix-list)))))))) +=0C +;;; General buffer manipulation support +;; commands from special-mode wrapped to work on Info-mode only + +(defun Info-beginning-of-buffer () + "Move point to the beginning of *info* buffer." + (interactive) + (with-selected-window (info-window) + (goto-char (point-min)))) + +(defun Info-end-of-buffer () + "Move point to the beginning of *info* buffer." + (interactive) + (with-selected-window (info-window) + (goto-char (point-max)))) + +(defun Info-describe-mode () + "As `describe-mode' but for Info-mode only." + (interactive) + ;; I guess we are good if there is any buffer in Info-mode so + ;; we do a special here to prevent prompting + ;; If there are no info buffer, use (info-window) to signal + ;; the error; this to not duplicate the error message in two places + (catch 'found + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (eq major-mode 'Info-mode) + (describe-mode) + (throw 'found buffer)))) + (throw 'found + (with-temp-buffer + (Info-mode) + (describe-mode))))) + +(defun Info-quit-window () + (interactive) + (with-selected-window (info-window) + (quit-window nil (selected-window)))) =20 (provide 'info) =20 diff --git a/lisp/window.el b/lisp/window.el index 5964fe37ee6..2cc5cc6a274 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -10741,6 +10741,73 @@ window-prefix-map "0" #'delete-windows-on) (define-key ctl-x-map "w" window-prefix-map) =20 +=0C +;; help function to find an Info- or help-mode window +(defun window-list-by-mode (mode &optional exclude all-frames) + "Get list of windows to act on, displaying live buffer with major mode M= ODE. + +If EXCLUDE is not nil it should be a window to exclude from the list. +All-FRAMES has same meaning as in `walk-windows' function." + (let (windows) + (walk-windows + (lambda (w) + (with-current-buffer (window-buffer w) + (and (eq major-mode mode) + (not (eq exclude w)) + (not (eq (selected-window) w)) + (push (cons (prin1-to-string w) w) windows)))) + nil all-frames) + windows)) + +(defun get-window-from-user (window-list) + "Ask user to choose a window to act on via completing read. + +This is internal helper function used by `find-window-for-help'" + (cdr + (assoc + (completing-read "Act on window: " window-list) window-list))) + +(defun get-numbered-window (window-list N) + "Get a window from a WINDOW-LIST matching string \"<%N>\" in its name." + (let ((numbered-window-list + (let ((new-list nil)) + (dolist (elt window-list) + (when (string-match-p (format "<%s>" N) (car elt)) + (push elt new-list))) + new-list))) + (cond + ((=3D (length numbered-window-list) 0) nil) + ((=3D (length numbered-window-list) 1) + (cdar numbered-window-list)) + ((> (length numbered-window-list) 1) + (get-window-from-user numbered-window-list))))) + +(defun find-window-for-help (mode &optional all-frames exclude) + "Find window displaing buffer with major-mode MODE. + +With numeric argument N, return window containing in the name otherwis= e nil. +With non-numeric prefix, prompt user to select one of the buffers matching +MODE. + +Meaning of ALL-FRAMES is same as in `window-list-1'. +EXCLIDE is an Info window not to be considered as a candidate." + (let ((window-list (remq (selected-window) + (window-list-by-mode mode exclude all-frames)))= ) + (when (> (length window-list) 0) + (let ((window + (cond + ((numberp current-prefix-arg) + (let ((window + (get-numbered-window window-list current-prefix-arg)= )) + (if window + window + (when (> (length window-list) 0) ;; misstyped number ar= g? + (get-window-from-user window-list))))) + ((> (length window-list) 1) + (get-window-from-user window-list)) + (t (cdar window-list))))) + window)))) + (provide 'window) =20 ;;; window.el ends here --=20 2.40.0 --=-=-=--