editorconfig-textmate
editorconfig-textmate copied to clipboard
Syntax highlighting breaks after save
Since using this plugin with TextMate version 2.0-rc.4 the syntax highlighting frequently breaks after saving a file. By breaking I mean the whole file or a part of the file is only displayed in black/white text. An example is the screenshot of a ruby file below after pressing CMD+S. Syntax highlighting is missing in lines 31-49. Before saving it (and therefore before applying the editorconfig rules), the syntax highlighting was just fine. When I insert some key and save again, it is displayed correctly again.
This is my .editorconfig file:
[*]
charset = utf-8
end_of_line = lf
[*.{rb,yml,html,js,css,scss,erb}]
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
Is there any information I could provide to help finding the cause of this?
Ugh, that looks terrible. I’ll take a look at this as soon as I have time and am able, but I’m not sure if that will be any sooner than later this week.
It’s a good bet that this is due to updates to the document just prior to saving (https://github.com/Mr0grog/editorconfig-textmate/blob/master/source/additions/NSView%2BEditorConfig.m#L145-L147). That’s caused by the insert_final_newline and trim_trailing_whitespace settings. If it’s the problem, removing those or setting them to false should resolve the issue in the short term. I know that’s not a great solution, though :\
Need to see if I can create my own std::multimap<std::pair<size_t, size_t>, std::string> here and pass it to OakDocument -performReplacements:replacements checksum:crc32 (where the checksum isn’t checked for a loaded doc) or maybe directly to OakDocumentEditor -performReplacements:replacements.
Oh, actually, I might be able to just leverage OakDocument -matchesForString:options:, since its find results include a CRC32. (See values of find::options_t at https://github.com/textmate/textmate/blob/fb929aee10c271c1a5b87ddf1ff5e172bc7a8396/Frameworks/regexp/src/find.h#L8-L22)
See how TM does this internally: https://github.com/textmate/textmate/blob/52fda481dc9ee52fcf71a1134b900b61bc8f839d/Frameworks/Find/src/Find.mm#L242-L277
Existing code that needs to utilize the above stuff is at https://github.com/Mr0grog/editorconfig-textmate/blob/b305927b538b29027cfcc8fc1a7e3471eab1f9d7/source/additions/NSView%2BEditorConfig.m#L76-L143
@Mr0grog did you manage to progress on this? Anything I can do to help?
Hey @KevinSjoberg, I’ve been super busy lately and have not had time to get to it :(
If you are interested in trying out the approach outlined in the comments above, that would be wonderful! I’d certainly be happy to take a PR.
From an old e-mail list exchange with Alan Odgaard:
The most abstract way to change the document content (from the outside) is via search and replace, although this is easier done from a macro than a plug-in, so you might have to basically execute a macro to get this done.
So possibly the ideal approach is to see if there's a way to register and trigger a macro from ObjC code.
Tripped over this issue again today after a long while. I still need to get around to #45 first, and I don’t think I’ll be able to address this in the next couple weeks, but I did go down a small rabbit-hole looking into possible fixes here.
I think there are 4 main options:
-
OakTextView(where we are catching thedocumentWillSavenotification and running the code noted above) has a(void) insertText:replacementRange:method that is ObjC-friendly and looks like it should let us replace text ranges pretty easily. Using that instead of straight-up replacing the document content should hopefully mean we don’t need to manage selections and should also keep the syntax highlighting intact. This is probably the most straightforward fix, assuming it works. -
OakTextViewalso has a(void) performFindOperation:method that can do a “replace all” operation, which would also do the trick. It takes an object implementingOakFindServerProtocol, which unfortunately involves two C++ types (find_operation_tandfind::options_t), but they are both simple enums, so that might not be too big a deal. It’s messier, though. -
I couldn’t find an obvious way to call an arbitrary macro, BUT
OakTextViewhas an(IBAction) playScratchMacro:method that we could abuse. It’s designed to replay a macro you just recorded (either because you just did something you want to repeat a bunch, or you want to test it before saving). We could save a copy of the current scratch macro, replace it, call the method, then put the old scratch macro back:NSArray* scratchMacro = [NSUserDefaults.standardUserDefaults arrayForKey:@"OakMacroManagerScratchMacro"]; // Replace the scratch macro and run it. NSArray* macroToRun = (Make an NSArray with the macro contents); [NSUserDefaults.standardUserDefaults setObject:[macroToRun copy] forKey:@"OakMacroManagerScratchMacro”]; macroToRun = nil; [textView playScratchMacro: self]; // Reset everything. [NSUserDefaults.standardUserDefaults setObject:[scratchMacro copy] forKey:@"OakMacroManagerScratchMacro”]; scratchMacro = nil; -
Put a bundle with the relevant macros inside the plugin, then install it on startup if it isn’t already installed using the
[BundlesManager sharedInstance]. Then you can run theOakTextView(void) performBundleItem:method. This is pretty complicated! It’s also not entirely clear how to get a pointer to the actual bundle item after installing (maybe by digging around in the bundles menu? Ugh. This also raises weird race conditions, like what happens if someone uninstalls the bundle before saving their document? Not great.
I think (1) is the most straightforward if it works, otherwise (3) is probably the next best. ((2) is pretty slick and feels better, perf-wise, than running all the macro machinery, but relies on more complicated internal enums that are probably also more prone to change, and therefore hard make compatible across TM versions.)
As far as the various macro solutions go, here are the two macros we’d want in text plist format (macros — at this level — are just plists in NSArray/NSDictionary form):
Insert final newline:
{
commands = (
{
argument = {
action = replaceAll;
findString = "([^\\r\\n\\s])\\z";
regularExpression = YES;
replaceString = "$1\\n";
};
command = "findWithOptions:";
},
);
name = "Insert Final Newline";
uuid = "<UUID GOES HERE>";
}
Trim trailing spaces:
{
commands = (
{
argument = {
action = replaceAll;
findString = "[\\t ]+$";
regularExpression = YES;
replaceString = "";
wrapAround = YES;
};
command = "findWithOptions:";
},
);
name = "Trim Trailing Spaces";
uuid = "<UUID GOES HERE>";
}