-
-
Notifications
You must be signed in to change notification settings - Fork 423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Headless mode (#1354) rebase #2054
Conversation
@Ambrevar, how does one set prompt input? See tests/renderer-online/set-url.lisp. |
Indeed, you must call We must fix this bad API. I suggest documenting it (e.g. mentioning |
Thanks for this update! It's a great start, but as you can see, it's a lot of boilerplate code just to drive a simple In my opinion, we should aim for something much simpler.
So what does it mean to aim for "short code"? Let's try to reach this pseudo-code: (let ((url-channel (nyxt::make-channel 1))
(url "http://example.org/"))
(setf nyxt::*headless-p* t)
(nhooks:add-hook
nyxt:*after-SOME-hook*
(make-instance
'nhooks:handler
:fn (lambda ()
(nyxt::set-prompt-buffer-input (nyxt:current-prompt-buffer) url)
(nyxt/prompt-buffer-mode:return-selection)
(nhooks:add-hook
(buffer-loaded-hook (current-buffer))
(make-instance
'nhooks:handler
:fn (lambda (buffer)
(calispel:! url-channel (nyxt:render-url (nyxt:url buffer)))
(nhooks:remove-hook (buffer-loaded-hook buffer) 'example-org-loaded))
:name 'example-org-loaded)))
:name 'browser-started))
(nyxt:start :no-init t :no-auto-config t
:socket "/tmp/nyxt-test.socket"
:data-profile "test")
(prove:is (calispel:? url-channel 10) url)) To reach there, it would be nice if the first buffer load could be controlled somehow. |
And here is the same pseudo code, using the channel paradigm: (let ((url-channel (nyxt::make-channel 1))
(url "http://example.org/"))
(setf nyxt::*headless-p* t)
(when (calispel:? nyxt:*after-SOME-startup-channel*)
(nyxt::set-prompt-buffer-input (nyxt:current-prompt-buffer) url)
(nyxt/prompt-buffer-mode:return-selection)
(when (calispel:? nyxt:*after-buffer-loaded-channel*)
(prove:is (nyxt:url buffer) url)))
(nyxt:start :no-init t :no-auto-config t
:socket "/tmp/nyxt-test.socket"
:data-profile "test")) As you can see, it's much shorter and easier to write. |
Also now that internal buffers have URLs, we can set the URL to something internal, thus no network is needed and we can remove the timeouts :) |
Thanks for the hint!
Documenting could indeed help. I'd export both, as |
Regarding hooks vs. channels: I don't like channels in the context of event handling, as those block the thread they are on, are extremely time-sensitive, and are a concept that is hard to understand at first. Hooks are familliar, somewhat-asynchronous, and more flexible in general. The issue that you identified is merely a notation one -- hook addition is overly verbose and nested. But it's all Lisp -- we can write macros to shorten the code. I suggest at least two macros to handle hooks: (once-on (buffer-loaded-hook buffer) ; Hook.
(buffer) ; Lambda list for the callback.
(calispel:! buffer-loaded-channel t)) ; hooks:remove-hook is hidden.
(on (after-download-hook)
(download)
(uiop:launch-program (list "xdg-open" (nyxt::destination-path download)))) Obviously, JavaScript with its Alternatively, we can introduce something similar to Haskell's (hook-do
(buffer) <- (buffer-loaded-hook (current-buffer)) ; BUFFER is only set when `buffer-loaded-hook' fires
(echo "Buffer ~a loaded ~a" (id buffer) (render-url (url buffer))) ; Inside `buffer-loaded-hook'.
(calispel:! buffer-loaded-channel t) ; Inside `buffer-loaded-hook'.
(request-data) <- (request-resource-hook buffer) ; REQUEST-DATA is only set when the hook fires.
(not (search "google" (render-url (url request-data))))) ; Inside `request-resource-hook` and `buffer-loaded-hook'. |
Good points!
I'd like to clarify something about what I meant with my "broadcast"
channels: what we are really dealing with here is called "signal" in the
GTK sense (close to Unix signals, not to be confused with unrelated CL
signals).
Blocking is indeed a benefit: more often than not you don't want to
execute the following code until you got some result.
Another approach would be to use futures and promises. Are you familiar
with these? What do you think?
|
Now I understand it. But still, why introduce channels as a user-facing thing, if we have hooks?
The code I suggested is heavily inspired by JavaScript Promises handling, so yes, I am familliar with those. But still, our pseudo-promise hooks would work just fine, given enough syntactic sugar, so why introduce yet another abstraction? EDIT: ouR, pseudO |
Can you explain what you mean with |
Oh, that was confusing, I suppose. It was basically a copy-paste from the test/renderer-online/set-url.lisp test, so
No :)
Hmmmmmm. I mean, why would we need to? All the code that needs to depend on the hook activating goes inside |
Fair enough :)
Can you give these macros a try then?
|
Sure! |
Those are also used in tests/renderer-online/set-url.lisp now.
Do they maybe belong to |
And can we make |
No loss, the prompt-buffer-channel was introduce exactly for the purpose
we are discussing, if we are going for hooks then it makes sense to turn
it into a hook!
|
Do you need the `sleep 1` if you set the URL to an internal 1?
Also by "set-url thread", you mean the renderer thread?
|
Forgot to answer your other question: yes, I think we can add them to |
Haskell sucks (irrelevant to topic sorry). What about good'ol direct style (w.r.t. CPS).
With regard to this use case of hook, it does seem to fit nicely, but I have to think more, there's something that I feel off.
Maybe that's exactly what I'm worried about, but for hooks. I think the channel behavior is more understandable as it resembles the command loop paradigm in Emacs (writing channels explicitly is confusing, but if you hide them in functions, those functions just look like normal functions that take longer time to run). The simple control flow is already broken to a certain degree in Nyxt, for goodness or badness. On the other hand, hooks+CPS transform (or call it by some other name) causes following code to run in a different thread, and the order of execution can be unpredictable (from mental model aspect) if multiple handlers are registered. I'm not saying the Emacs command loop is the way to go, people are cursing it forever for its single-threadness and non-responsiveness, but there're other aspects of it that I like a lot. Just want to add this factor to the discussion. |
The |
`sleep 1` is there for a different purpose: to ensure that the thread
that invokes `set-url` has already invoked `set-url`. Otherwise we are
at risk of querying the non-existent prompt-buffer and failing the
test.
This does not feel right, we should have a hook then.
|
We can just use |
@BlueFlo0d Can you provide an example of how we could leverage continuations for headless / prompt-buffer scripting? |
I'll have a look at the
|
See my direct style example above.
You can call |
As an addition, should we have |
Yes indeed!
|
Should I address the remaining issues and merge this? |
@BlueFlo0d Sorry, I don't understand the code completely. What do |
Yes, if you don't mind :) |
@@ -23,7 +27,7 @@ It can be initialized with | |||
It's possible to run multiple interfaces of Nyxt at the same time. You can | |||
let-bind *browser* to temporarily switch interface.") | |||
|
|||
(declaim (type hooks:hook-void *after-init-hook*)) | |||
(declaim (type hooks:hook-void *after-init-hook* *after-startup-hook*)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move this to the declaration site.
ARGS can be | ||
- A symbol if there's only one argument to the callback. | ||
- A list of arguments. | ||
- An empty list, if the hook is void." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean "if the hook handlers take no argument"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly :)
@@ -34,6 +34,7 @@ It's a function of the window argument that returns the title as a string.") | |||
:export nil | |||
:type boolean | |||
:documentation "Whether the window is displayed in fullscreen.") | |||
;; TODO: each frame should have a status buffer, not each window |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this related to the pull request?
What do you mean? We don't have frames (beside panel buffers), do we?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment was there before the patch. Seems like it was deleted on master 0_o
|
||
(class-star:define-class nyxt-user::test-data-profile (nyxt:data-profile) | ||
((nyxt:name :initform "test")) | ||
(:documentation "Test profile.")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to be updated since master no longer has data-profiles.
Replacing |
Yes, could work! |
Behaviorally, There're several possibility about how to specify what value to return, maybe the most straightforward one being using the syntax The magic CPS transform brings is that,
and this will be registered as a handler of |
Understood.
If I understood you right, you are talking about a hypothetical `cps`
macro, not cl-cont.
If you implement the continuation passing by adding the continuation as a
handler to the hook, is it different from the `on` macro that Artyom
just implemented?
|
They're apart from each other by one CPS transform. Basically, using |
So it's mostly a syntactic benefit you are proposing.
Fair enough, but that would drag in an additional dependency for
something that seems (in my current opinion) relatively minor.
I suggest we merge `on-hook` for now and if you think your CPS style is
superior, please send a working patch to exhibit the benefits, it will
help with the discussion I'm sure :)
|
I don't think this is syntactic. I think the "factoring some asynchronous code into functions" section shows it pretty clearly. Blocking computation within a dynamic extent from anywhere inside is a straightforward insertion of The well-know definition of "syntactic sugar", an synonym of "macro expressibility", defines equi-expressibility as mutually translatable via local syntactic transform (See the classic paper
Sure, I'll try to find sometime to do it. |
Merged with efa3b7d. |
1 similar comment
Merged with efa3b7d. |
Note: I found this library for events / notifications: https://github.com/fukamachi/event-emitter Looks very similar to @aartaka's implementation! |
This is a fresh rebase of #1354. Not much changed -- a good sign of
prompter
having a good and approachable API!Things to DIscuss
prompt-buffer-channel
be a hook, actually?Things to do
nyxt-ready-hook
to start interactive commands from.Closes #1168.