- Key bindings (so you could easily create an emacs instead of a vi)
- Rendering
- Scripting language (for customized behavior)
Finally, make sure you thoroughly document these building blocks, so others can create really cool stuff with it. Also, think of possible use-cases when defining the modules. A smart architecture could allow for a collaborative editor, for example.
Hi, thanks for the suggestions!
Actually, the internal representation, is completely separate from the layout. It's not in another repository, but it's decoupled.
The key bindings are also separate. Getting emacs bindings is not much more than changing this line [0]. Only adding the bindings for the window management and emacs command line is still to be done. (I know that emacs is actually much more than only its key bindings, but you know what I mean.)
The rendering is also independent. There are two backends: vt100 terminals and the windows console. (Honesly, my main focus is vt100, but any render back-end is possible. I think even graphical)
The same for the event loops by the way, it can run on a couple of event loops. For instance asyncio.
Documentation will follow. prompt-toolkit has already quite a lot of examples, and there's a lot of documentation in the code itself. But I agree that we should keep improving.
I want to compliment you on your code. It's so rare to see clean python code. I am almost never able to be able to jump into a file in a module and understand exactly what's happening, but you fucking nailed it. Well done!
There are plenty of examples in the repo that cover most of the features in the library. The awesome thing is all of the examples fit in a single page, a testament to the power and simplicity of the library.
"Python, huh? Seems like typos in uncommon branches of the code would cause it to randomly fail at runtime, losing your work!"
"Now evmar, don't be such a internet nay-sayer, plenty of people write reliable Python code. You just need tests and... yep, there's a tests directory right there in the repository."
I am, right now, or at least two minutes ago before I started reading HN, refactoring big chunks of _my_ text editor, WordGrinder. It's a almost-pure-Lua word processor.
In the process I have been writing unit tests. Previously the program didn't have any, which was really dumb. It was dumb because, in the three days I've spent doing this, I have found (and fixed) so, _so_ many bugs.
And now I have a decent set of tests, I can change something, rerun them, and have a pretty good idea of whether it worked or not. I don't even have to run the program!
I had actually gone and looked for Lua static type checkers. Some exist, but none that'll work with Lua 5.2.
I had a really hilarious bug today where under some circumstances all the text in the display would be replaced by numbers. Small integers, each placed where the word should be.
What had happened is that I'd added a layer of indirection; where previously, after line wrapping, the data structure for a rendered paragraph was an array of pointers to the word objects, now it was an array of indices into the paragraph's word array. And I'd forgotten about one particular exotic code path. Lua was seeing the array of indices, automatically casting the ints to strings, and then using those strings instead of the word data itself...
Static types would have made this bug impossible.
...way back when, there was a really nice and thoroughly obscure language called Strongtalk; it was a Smalltalk 80 clone with optional strict types. You could annotate your classes and methods with type information. If it was there, it would be checked; if it wasn't, you got the traditional behaviour. The JIT knew about the type information and could use it to produce really fast code. It combined the ultimate dynamic language with an expressive static type system (complete with parametric polymorphism).
It was open sourced in 2006 and sank without trace. Sigh.
For your example bug I think you're wrong that static types would have made the bug impossible. What you need is strong type checking and a lack of auto-conversion. For example, in Java, this compiles:
int[] bodies = {1, 2, 3};
for (int body : bodies) {
String formatted_body = "<p>" + body + "</p>";
callMethodWithStrArg(formatted_body);
}
And if in this hypothetical case it was previously a `String[] bodies` and a `String body`, I bet a lot of programmers would use an auto-refactoring tool because "static types and auto-refactoring go together for being confident in changes like apples and pie" and I bet the error wouldn't have been noticed even at review time. God help you if you're using a static language without generics that has implicit type conversions. In Python, though, this raises an error:
bodies = [1, 2, 3]
for body in bodies:
formatted_body = '<p>' + body + '</p>'
The error is: "TypeError: cannot concatenate 'str' and 'int' objects".
That's very true (and I really wish that Lua didn't do implicit type conversion of numbers to strings --- it's a major wart on an otherwise very nice language).
I had totally forgotten that Java does it too, despite having done `""+i` lots of times as a cheap and easy and evil way to convert numbers to strings.
...I am currently rewriting a big chunk of the primary data storage to use immutable data structures, because it makes implementing Undo easier. I am having to fight the urge to redo it all in Haskell.
Ignoring trivial cases and dependent types, types tell you one of two things: "this might be correct" or "this is incorrect". Again ignoring trivial cases where exhaustive checking is possible, that's the same thing that tests tell you - and it's a hugely useful thing to be told!
It is a substitute for those unit tests that are essentially checking type invariants. It is not a substitute for all unit tests, but I don't think anyone made (much less intended to make) that claim anywhere in this thread.
"They only tell you that you are matching function signatures correctly."
All of computation can be expressed as application of functions, so for sufficiently expressive function signatures that's not much of a limitation. Of course, if you want to guarantee that your compiles terminate, you need to apply some limits to the expressiveness of your function signatures... but there are powerful guarantees you can get out of even so simplistic a type system as C's, if you work with it rather than against it.
Type signatures can completely encode specifications in systems with dependent types. Of course no mainstream programming language supports dependent types, but it's worth pointing out that in principle a type can encode any property checked by a unit test. Actually, dependent types are strictly more powerful, since you can prove undecidable properties which can't be checked by unit tests (e.g., that a function does the right thing for all possible inputs).
You know, I hear people use this excuse to justify bad behavior a lot. I once heard a bike messenger tell me that brakes gave him a false sense of security so that's why he didn't have a brake on his fixed gear bicycle.
Compiles, runs, fails if i is out of bounds. Which means that you need to test the code that you write, and that even if you have 100% line coverage (or branch coverage or MC/DC coverage or...), it doesn't mean i won't get the wrong value.
People who claim that "once my C++/Haskell/Agda/whatever program compiles, I know it probably has no bugs" thus tempt others to mention "a false sense of security." (Agda might be going further than anyone else with proving that i cannot be out of bounds, AFAIK... though a generally undecidable problem will remain generally undecidable.)
I've never seen anyone claim that types (even a very strong type system like Haskell or Rust) mean you don't have to write tests. They just mean you don't have to write the very silly tests you would otherwise have to to feel secure in a dynamically typed language.
I dunno. If you cover every source line at least once, that catches the dumb type errors as it does dumb bounds errors etc. If you don't have a test covering every source line at least once, then you will have dumb type errors as well as dumb bounds errors in those uncovered lines. So I don't think dynamic languages force you to write silly tests when you look at it that way.
If however you say, "hey, I seriously don't want to cover all the lines including say error messages", then in Python,
if error:
print obj.name
...might be a problem because obj doesn't have a name; in C++,
if(error)
cout << obj.name;
...might only blow up because obj is a null reference (which is what you get when you dereference the null pointer, in practice, even though null references aren't supposed to exist); and in a language with non-nullable pointers, the equivalent of the above can only blow up if printing the name (which is surely valid if obj is non-nullable and type-checks as having a name) somehow blows up, which for a string in a memory-safe language is very very unlikely.
So if you leave uncovered lines in your code which we all do then yes, the stricter the type system, the better your chances are, statistically, all else being equal (for instance, the number of lines being the same... which might not be the case.)
Overall the silly tests people sometimes write in dynamic languages are needless IMO and result from over-applying "TDD" or "unit testing" or some other buzzword and/or paranoia of someone coming from a statically typed language background. I personally think I have pretty much the same amount of tests regardless of the type system.
> They just mean you don't have to write the very silly tests
I have seen claims and have a hunch that more than just very silly tests can be eliminated by types, but I'm struggling to come up with or remember any examples.
I'm hoping someone who has one will reply with one of these claims or any examples.
Unfortunately, unsafeIndex is often spelled "!". There is a push (which I support!) away from partial functions, but there are still plenty of partial functions provided by standard libraries under names that sound reasonable.
I regret that this post is the top of the page. I failed to anticipate that people will take any opportunity to have yet another boring static typing debate.
But to be clear, in my day job I work on an app with 350k lines of Python in it and the reason I know it mostly works is due to our test coverage. As someone else mentioned in this thread, a lack of tests should not give you confidence regardless of the language.
"There is no roadmap. I mostly implement the stuff which I need or interests me, or which gives me the opportunity to learn."
Pretty much says it all. Seems it was primarily written to satisfy personal needs, not be the end-all, be-all of professionally written software. It is, incidentally, potentially interesting to others who might want to learn from it, or use it, knowing the background.
So, you might cut the guy a break. The requirements for personal projects aren't the same.
> "Python, huh? Seems like typos in uncommon branches of the code would cause it to randomly fail at runtime, losing your work!"
Maybe not - Python itself can provide some crash protection. Run it this way: python -i run_pyvim.py.
Then if pyvim crashes, Python will still be running with all your work still there. Maybe you can just type run()
at the Python prompt to resume the session in almost the
state you left it -- depending on how run() is coded, how much re-initialization it does.
Hi, I once tried that, but I ran into problems with lazyness. In particular my data structure was rather simple (famous gap data structure), but editing "large" files (>1000LOC) became rather unpleasant (too big input-feedback latency).
However I managed to build a _very_ basic proof-of-concept editor (no dependencies) in just a few hundreds lines of code which I could explain, but until now I was too shy to share it as it did not involve magic abstract Haskell-foo ... ;)
It's got an amazing (I'm not sure if that's in a good or bad way) system for reloading config. Config is code. Reconfiguration means recompiling. If you reload the config, you recompile yi and reexec it - file handles and other information is left laying around so you end up in exactly the same state as before. It's slightly scary from the programming side...
Yi is really nice, overall. The code is clean too.
I don't remember all of the issues, but there are a ton of small things that make the editor unusable to me. I used it for a couple of weeks, and I spent some time working on these issues, but never had PR-worthy code. Here's what I can remember off the top of my head:
- Startup time is very slow because of the way configuration works. In my local copy, I made a version without runtime configuration, and that solved this problem. This conflicts pretty badly with the whole architecture, so I didn't make a PR.
- :n :N don't work. Opening multiple files from the command line doesn't work.
- :cq doesn't work. I fixed this, but my fix was a hack, so I didn't make a PR.
- Operating on regions with '{' and '}' is off by one line in some directions.
- You can't replace regions with shell commands. For example, using '!}sort' to sort a paragraph.
The main reason is maybe because it was a small step after I created the Python prompt-toolkit library. That is a library which is actually only a simply pure Python readline replacement, but with some nice additions like syntax highlighting and multiline editing. It was never intended to be a toolkit for full-screen terminal applications, but at some point I realised that everything we need for an editor was in there and I liked to challenge its design. So, I started an editor and the first proof of concept was literally just a few hundred lines of code, but it was already a working editor.
The creation of pyvim will make sure that we have a solid architecture for prompt-toolkit, but it also aims to demonstrate the flexibility of the library. When it makes sense, features of pyvim will move back to prompt-toolkit, which in turn also results in a better Python REPL. (see ptpython, an alternative REPL.)
So is it vi clone on vim clone, exactly? I just noticed some usual things are not working (ZQ, for example), but I don't remember, maybe it's supposed to be that way in vi.
It's a blackbox re-implementation. In Python, things are done different from C and there are other libraries are available (Pygments for instance). I also don't want to claim that it is as powerful as Vim is. But it should be stable, easy to install, and especially usable for Python development.
Every clone I see try to reproduce vim usage, but none really try to run vimfiles. I think running vimfiles is a must have for a clone to get real users.
This is really nice. Does anybody know whether there's a comparable (real time, asynchronous, highlighting symbols not lines) syntax checker for neovim?
- Core editor (internal representation, etc.)
- Key bindings (so you could easily create an emacs instead of a vi)
- Rendering
- Scripting language (for customized behavior)
Finally, make sure you thoroughly document these building blocks, so others can create really cool stuff with it. Also, think of possible use-cases when defining the modules. A smart architecture could allow for a collaborative editor, for example.