A modern code editor for JUCE?


#21

I added a little text box with profiling info, and popup menu to switch the rending type. As you probably expected, tokenizing is negligible. Given that, “immediate mode” for the syntax highlighting is probably the way to go, as it’s far less complex than caching token locations. (However it still needs to handle multi-line tokens).

Here’s the summary (OS X, 2013 MacBook Pro, time / paint call):

  1. Uniformly formatted AttributedString: 8.0 ms
  2. Single GlyphArrangement: 11.5 ms
  3. AttributedString with multiple formats: 15.5 ms

I don’t have the “glyph-filtering” approach working, but it’s probably not worthwhile as it’s quite a bit more complex. System profiling reveals the time is spent in Core Graphics anyway.

So, the only way to further increase performance is likely to fall back on a 3rd party text rendering library. I don’t think that’s in the cards right now, but just for fun, this:


#22

Here’s an update!

Syntax highlighting is now fully functional - at least using the CppTokeniserFunctions. The other tokenizers would need either (a) an adaptor class since they currently require a CodeDocument::Iterator or (b) to be modified to take a more abstract iterator. (b) would be easy since mcl::TextDocument::Iterator and juce::CodeDocument::Iterator already have the same interface.

I learned a lot about the performance characteristics of different text rendering approaches. Here are the two that seem fastest (only tested on 2013 MacBook Pro 10.12 so far)

  1. For uniform formating (~3 ms/frame): a single AttributedString containing the contents of the on-screen text. This generates a native text layout if possible, and is very fast when the formatting is a single color and style. However, it incurs huge overhead (easily 80 ms / frame) if the attributed string has many glyph runs. I believe the overhead comes from switching at the Core Graphics level.

  2. Syntax highlighting (~8 – 15 ms/frame): raw GlyphArrangement, filtered on the token type. The rendering does multiple passes over the onscreen glyphs, rendering those of each style in turn. This eliminates the overhead of switching, but it’s not quite as fast as the native text layout. Tokens are regenerated each frame for the onscreen portion of the document, typically taking 0.5 – 2 ms / frame.

Currently the component displays some timing info and allows you to fiddle with the rendering options via the right-click menu. I would prefer to delegate the rendering to a separate class TextEditor::Renderer or something if more approaches emerge.

Still to do:

  • Word wrap
  • Fix some styling and behavior glitches
  • Public API resembling the existing components
  • Custom avigation and indent behaviors via lambda’s
  • Code folding? gutter annotations? Requests?

#23

Nice. I’ll test it this evening.

Word wrap is definitely awesome and code folding would be great, but it would need some kind of parser to do this correctly, differentiate bracket style languages vs. messy languages like Lua :slight_smile:

And everything that makes it easy for people to add functionality is great. I think two elements that might help there would be

  1. A regex selector. Give it a regex and it returns an Array<Selection> with all matches.
  2. A custom paint routine that can be applied on a Array<Selection>. Editors like Sublime have multiple different highlight styles (just the outline for search matches, underline for spelling mistakes, eg) and it would be great to write other styles for customized highlighting. Maybe something like this (Please forgive the crude mix between pseudo code and C++):
// Store lambdas that hold a Graphics reference and a Selection reference.
using SelectionPainter = std::function<void(Selection&, Graphics&)>;

// Adds a custom paint routine to the given selection
Selection::setPaintRoutine(const SelectionPainter& pr);

// Some dummy lambdas...
auto outline = [](Selection& s, Graphics& g)
{
    g.setColour(Colours::blue);
    g.drawRect(s.getBounds()); // or whatever
};

auto selectedResult= [](Selection& s, Graphics& g)
{
    g.setColour(Colours::blue);
    g.drawRect(s.getBounds()); // or whatever
    g.setColour(Colours::blue.withAlpha(0.6f);
    g.fillRect(s.getBounds());
};

// returns and Array<Selection>
auto searchResults = funkyRegexMatcher.getMatches("[A-Z]+[0-9]?");

int currentSelectionIndex = 0;

bool isSelectedSearchResult(const Selection& s) const
{
    return searchResults.indexOf(s) == currentSelectionIndex;
}

for(auto& s: searchResults)
{
    if(isSelectedSearchResult(s))
        s.setPaintRoutine(selectedResult);
    else
        s.setPaintRoutine(outline);
}

// In the paint routine later on:
for(auto& s: allSelections)
{
    if(auto pr = s.getPaintRoutine())
    {
        pr(g, s);
    }
}

This would mimic the Search/Replace behaviour of Sublime (not 100% but you get the idea). This is just the first idea that came into my mind, but you can use this to write Linters, autocomplete things (Visual Studio has this nice feature where it highlights the currently selected token, if you select it with double click).


#24

Hey all -

This project is pretty much stabilized for now. It’s still lacking a couple of key features that would make it usable as a drop-in replacement for TextEditor or CodeEditorComponent: namely word wrap, scroll bars, context menu, a general tokenizer (C++ is currently hard-coded), listener methods, and some public API functions. It also needs an option to disable the dev/profile mode. I will gradually make these improvements if there is sufficient interest out there.

With regard to performance, it’s significantly faster in most use cases (at least on OSX) than the JUCe text editor. For uniform text formatting it’s extremely fast (100-200 fps). With syntax highlighting the frame rates are typically 30-60 fps, sometimes slower than Sublime Text or the XCode editor, especially for large window sizes.

Thanks to many for helpful suggestions! -Z


#25

I am actually considering switching to this editor for my project. It won‘t be in the immediate future because it‘s a big task (I made some modifications to the original JUCE code editor that have to be ported and I heavily rely on the CodeDocument as data structure.)

I am not that concerned about performance (the majority of the time is spent in OS functions anyway so there‘s little you can do on this level) but it‘s the extendability and hackability that caught my attention.

So please keep working on the project (I can offer to do a few pull requests for easy features like adding listeners or making the tokeniser optional). The one thing that I couldn‘t figure out is how to implement word wrap, so if you tackle this next, it would be great.