-
-
Notifications
You must be signed in to change notification settings - Fork 651
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
Dump the terminal screen into a file #1375
Conversation
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.
Hey @cosminadrianpopescu ! As I mentioned in the original issue, I'm extremely happy to see this being worked on.
I'd also like to start by saying that as a newcomer to rust, this is very impressive! I can imagine this required wading through quite some deep and gnarly code in a non-trivial code-base.
I left some comments in the code, but otherwise there are two main things I'd like to discuss:
- Tests. As I briefly mentioned in the original issue, I think the best place to test this would be in the tab_integration_tests. We fake the os_input_output interface there (as you've seen), and I think the best approach would be to do something like (I didn't check the exact naming or code here, just a general outline):
struct FakeInputOutput {
file_dumps: HashMap<String, String> // <file_name, dumped_contents>
}
impl FakeInputOutput {
...
fn dump_to_file(&self, file_name: String, contents: String) {
self.file_dumps.insert(file_name, file_contents);
}
}
Then write a test that would call the relevant method on Tab
and assert that the file contents were written to the HashMap properly. Makes sense?
- UX - the way things are implemented here, I think it would be best to leave this as an
Action
and not yet add it to the default or other configs. The reason for this is that we don't yet have an easy way to send messages to the user. So this would be an action that the user has no way of knowing whether it succeeded or not. It makes a lot of sense as part of your custom solution, but I feel not so much as part of the default setup.
I think a next step here would be to continue the work you've done and work towards the solution we talked about - where we write it to a temporary file and then temporarily switch out the current pane with your default editor open to that file and that line. If you're interested, I'd be happy to guide you through that implementation (after merging this) - I have a feeling it won't be too difficult for you. What say you?
About the test cases, I will have a look and try to implement what you suggested. I'll come back to you if I have things that I don't understand. I will add to this PR.
Sure. What would be the starting point? An idea would be to just create a new command ( Please also see this discussion. Implementing what I suggest there would also achieve the same purpose. |
Yes, with a few additions:
Let's get this merged (as I think it's a good thing to have anyway) and then discuss the details of how to implement the rest. Wdyt?
Already answered. Personally I really like the temporarily-replace-pane-with-current-editor solution best, but I think all of these should be implemented. |
Yes, sure. I will have a quick commit very soon with the small fixes that we discussed. This will be this evening or tomorrow. Then I will try to do the test cases with another commit. How do you want to proceed? Do you want to merge this without test cases and I open another PR for the test cases? Or you want to wait that I also implement the test cases? For the test cases it might be after this week-end, since tomorrow I'm leaving town and returning on Monday evening. So, the commit with the test cases will be probably after Monday. |
Personally I prefer to wait for the test cases. I'm a little wary of having something like this untested inside main. I don't anticipate many changes in main that would cause merge conflicts here, but if there are i'll help merging if you like. |
I had a commit implementing the changes discussed. I will have something for th etest cases as soon as possible, but probably not before Monday. |
No rush - safe travels. |
I'm back. I made progress on the test cases. I hope to have a commit soon. Today or tomorrow. |
I have a question. After I added the Something like |
Hmm - the easy way would be to clone os_api and have it returned by the struct FakeInputOutput {
fake_file_dump: Rc<RefCell<HashMap<String, String>>>
}
let fake_file_dump = Rc::new(RefCell::new(HashMap::new()));
let os_api = FakeInputOutput { fake_file_dump: fake_file_dump.clone() };
// ... create the tab, return fake_file_dump from create_new_tab to the test
assert_eq(fake_file_dump.get(my_file_name).unwrap(), expected_file_output) |
More info on reference counters in rust: https://doc.rust-lang.org/std/rc/struct.Rc.html |
Just a quick status. I'm fightig with the language. As I mentioned in the beginning, I did a lot of laguages in my career (starting with VBA and C++, PHP, .NET, Java and Javascript with any framework that existed there, like Jquery UI, Kendo, Dojo, AngularJS and Angular) and I have never had such issues (with any language) to just declare a constant or share a variable or return a result. :) So, at the moment I'm struggling a little with returning those tuples. If I'm trying to return a tuple, like this: fn create_new_tab(size: Size) -> (Tab, Box<FakeInputOutput>) {
...
let os_api = Box::new(FakeInputOutput {
...
(tab, os_api)
} I'm getting this error:
So meanwhile, if anybody has some idea... I will also look at the link you recommended about reference couters. |
We've all been there friend, don't sweat it! Rust has a very steep learning curve and you've plunged very deeply into it on your first try. You are essentially hitting the Rust ownership semantics, described in detail here: https://doc.rust-lang.org/stable/book/ch04-00-understanding-ownership.html They are unique to any other language (as far as I'm aware) and so it makes total sense that they don't make sense to you. In brief (and in a simplified way), what's happening is that you're trying to have two entities own the same piece of memory. Rust's memory safety guarantees cannot allow that. Usually you'd be able to get out of this by cloning That's why I recommended using a reference counter. A reference counter, combined with a refcell, offers internal mutability. This means (again, briefly and in a simplified way) that you'd be able to clone it as much as you like and still refer to the same part of memory, using the Here, we need a further level of indirection though. We can't just wrap Makes sense? I'm happy to answer more questions or try to explain this differently if this is unclear. Please don't hesitate to reach out. |
Well, I read the documentation. Thank you for it. I will have a look also at But I can say, as a developer with more than 20 years of experience with several languages, that I have never heard of such a stupid idea like this ownership in Rust. And (I'm asking out of pure curiosity), do they say what are the advantages of their system over Garbage Collecting? Because the disadvantages are so painfully obvious. :) But now at least I understand the problem. |
Well, you're comparing apples and oranges here. Ownership didn't come to replace garbage collection, but rather to replace unsafe memory freeing in lower level languages such as C or C++. If your question is "why manage your own memory rather than have a garbage collector manage it for you" then the answer often has to do with performance and/or predictability. For this project it was a combination of the first for our lower-level performance critical parts (eg. the terminal state parsing and rendering) and predictability for the rest of the app. That being said, the more I work on this project the more I find that it might make sense in the future to move the less-performance-critical parts to a higher level language. But we're very far from that still and have lots of other things to deal with :) |
Btw @cosminadrianpopescu - IMO unless you want to go deeper into these constructs, you don't need to look too deeply into Again, happy to go into further details if you like. |
Then this makes it even worse, in my oppinion. :) I would rather manage my own memory (btw, I love pointers in C / C++, so for me it's not an issue) rather than having the variable only valid for the visible scope. I assumed it's as a replacement of the garbage collector specifically because they (rust developers and architects) don't want to let the developers handling the memory allocations. I suppose is one of those things that it has to make a click when you see it and to let you have that "uau" moment. I am a
Actually my question is why choose this path? Managing memory by yourself makes much more sense for me than having the variable available only to the visible scope and having to go to a lot of workarounds to just extend the scope of a variabile. On a serious note, I've tried also the |
I'll look into this also. Thank you. |
Even so (and I'm not in agreement here, just for the record) - when working with other people, you can't 100% trust that they'll do the right thing when freeing memory. Especially when editing your code with the hidden assumptions you put in there. Also, when editing your code years and years later, you'll have the same issue. We're Humans, we make mistakes, and sometimes we use a certain variable inside a very much nested block after freeing it in another very much nested block. Multiple research papers have been written about this, both in academy and in very large enterprise companies. With this method, the problem simply goes away without costing you performance. I respect that you don't like it - it's certainly not for everyone - but as a project maintainer boy do I love not having to audit other people's code for this particular wholly avoidable mistake.
Huh - where is it being moved between threads? Would you like to share the error? Maybe I missed something. There's also a multi-threaded solution in the form of: |
Yes, this is a very valid point. Still, I would prefer then a garbage collector (with the added cost of performance) :)
Yes, as I was saying, this is probably not my cup of tea.
|
Could you share the entire error? I think sometimes the compiler points out why it needs to be sent between threads. Will help me out since I can't see the code |
@cosminadrianpopescu - just looked at the trait and saw that it itself is So instead of So my above example will become: struct FakeInputOutput {
fake_file_dump: Arc<Mutex<HashMap<String, String>>>
}
let fake_file_dump = Arc::new(Mutex::new(HashMap::new()));
let os_api = FakeInputOutput { fake_file_dump: fake_file_dump.clone() };
// ... create the tab, return fake_file_dump from create_new_tab to the test
let fake_file_dump = fake_file_dump.lock().unwrap(); // here we lock().unwrap() the mutex so that we can get access to it and read its internal data to assert against
assert_eq(fake_file_dump.get(my_file_name).unwrap(), expected_file_output) Makes sense? You can read up on Arc and Mutex if you like, but for this relatively small case it doesn't matter a whole lot. |
Finally. Test case implemented. Please let me know if there is something else needed for merging the PR. I will have a look to see what we discussed that I can work on next. |
I've had another small commit. I replaced the regexp, doing it with the |
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.
Hey @cosminadrianpopescu - this looks great. Good job on the test!
I left some minor nitpicks, after which I'll be happy to merge this.
All modifications requested have been implemented with the last commit. |
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.
Looks great! I removed the dump_screen
implementation from PluginPane
now that we don't need it and ran rustfmt
. Will merge once the CI passes.
Are you starting to work on #1403 ? Would you like a walkthrough or some tips?
The fork I will keep it for the other things that I should do. So, what is the next thing? However, before this, I have another very small PR which puts some logic into the tab names. Right now, when I open 3 tabs, I have The issue is if I close What the PR will do, is that first, after closing After this another tab will be added at position 4 and it will be accessible via shortcut 4. What do you think? |
Sounds great - just bare i mind that the tab names can be changed ( |
Yes, I know. But that's exactly what this PR will do. So, let's say that the tabs are renamed For me, in my day to day workflow, the names don't matter, but the order in which they are created. I know that the first tab is whatever server I'm running, second one is the file explorer, 3rd one is my vim editor and so on. So, even if I close the file explorer (by mistake or on purpose), I still expect to access the vim tab with the shortcut 3. Are you telling me that this should only be the case if the tabs are not renamed? Or you think that the logic I've just described is ok? I can have my PR doing this change only if the tabs are not renamed. What do you think it's best? How do you use these tabs in your day to day work? And most important, how are the majority of people using it? For me it's confusing for the shortcut of a given tab to change, no matter what the name is saying. |
Hum. I think I understand what you're saying now. You'd want the same shortcut to lead to the same tab. In my mind this might be a little confusing. I mostly work in the same tab (got strider as the file explorer, several vim windows which I switch around, floating pane as a scratch terminal). I open new tabs arbitrarily when I want to delve into another project for a second or whatnot. I close and open them as needed, and don't often really remember which one is which. I'd fine it confusing if 1 led to the first, 2 and 3 did nothing and 4 led to the second. Especially for a session that could have been running for days. |
I think I could go for something like this if we add a UI for it. I guess at least some people will see it my way and some your way. So maybe we should eg. add a colored index number to every tab that gets highlighted (or changes color) when we go to Tab mode. This might be a little bit of work though before we overhaul the plugin system. |
Yes, I see your point and you are right. How about having an option like When this option is set to What do you think about this? Also, maybe we should move this discussion in a new issue or a new discussion and then when we decide, create a new PR out of it? |
Sure, @cosminadrianpopescu - if you want to implement this, go ahead and whip up a PR. This sounds good to me. Personally, I'd prefer to see the "replace scrollbuffer with default editor" feature first because it would be cool to release this all together in the next release. But as you prefer, of course. |
Then I would start with the #1403. How would I go about it? What is the idea? How do I replace the content of the pane with a command without putting that command in the history of the current pane's shell? Also, when running the command, is there something to know about it regarding different operating systems? My idea would be to create a new temporary pane which would replace the current one. Is there a better approach? If this is the right approach, how do I know when this temporary panel is closed, to react and put back the old pane? Any hints? |
I'm moving this also to #1403. |
No description provided.