-
Notifications
You must be signed in to change notification settings - Fork 9.2k
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
Syntax highlighted diffs #3101
Syntax highlighted diffs #3101
Conversation
Also, move the disable comment in lib/globals.d.ts to the top of the file �
Degeneralize the ESLint-disabling comment in highlighter/globals.d.ts
Is there anything I can do to help out here @shiftkey? |
app/src/lib/highlighter/types.ts
Outdated
* information contained within the ILineTokens interface. | ||
*/ | ||
export interface IToken { | ||
length: number |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
app/src/lib/git/show.ts
Outdated
@@ -39,3 +40,24 @@ export async function getBlobContents( | |||
|
|||
return Buffer.from(blobContents.stdout, 'binary') | |||
} | |||
|
|||
export async function getPartialBlobContents( |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
app/src/lib/highlighter/worker.ts
Outdated
// exists. | ||
const worker = | ||
highlightWorkers.shift() || | ||
new Worker(`file:///${__dirname}/highlighter.js`) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
📼 |
|
||
### I want to add my favorite language | ||
|
||
Cool! As long as it's a language that [CodeMirror supports out of the box](https://codemirror.net/mode/index.html) we should be able to make it work. Open an issue and we'll take it from there. It would be swell if you could also submit a PR with a sample file for the language to [niik/highlighter-tests](https://github.com/niik/highlighter-tests) (we'll find a better spot for this in the future). |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
||
When we are about to perform highlighting on a diff we start out by scanning through the diff to figure out which lines we need from which file. Context lines can be pulled from either version while added/removed lines obviously need to come from a particular version. If we find that a file consists entirely of additions or entirely of deletions we can optimize further by adding a preference for one of the versions and thus getting away with loading just one file. | ||
|
||
Once we've got that settled we load the first 256kb from both versions (256kb picked arbitrarily because I figured it should cover the majority of source files while adding a very manageable memory overhead for the feature). We then pass this content, along with which lines we want to get tokens for to one or two web workers which then run the modes. CodeMirror modes are synchronous but running them in a web worker means we can get on with other things while we're tokenizing up to half a megabyte of content in up to two different threads (threads in javascript, what have we come to). It also means that we have a real nice containment of the highlighting process and that we can terminate it should it for some reason end up taking a very long time to complete. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
app/src/lib/highlighter/worker.ts
Outdated
timeout = window.setTimeout(() => { | ||
worker.terminate() | ||
log.error('Highlighting worker timed out') | ||
reject(resolve({})) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
app/src/lib/highlighter/types.ts
Outdated
* stream to count columns. See CodeMirror's StringStream | ||
* class for more details. | ||
*/ | ||
tabSize: number |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
} | ||
}) | ||
}) | ||
if (exitCodes.has(code) || signal) { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
0, | ||
MaxHighlightContentLength - 1 | ||
) | ||
} else if (file instanceof CommittedFileChange) { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
🍸 |
@xt0rted yeah, this was generated by |
Need a preference to open/close the syntax, it's in a daze. |
@nypisces Can you open a new issue and give us a few more details about your feature request there? |
Wouldn't it be nice with some syntax highlighting in your diffs?
The Problem
Syntax highlighting is a well-understood problem with tons of options. Atom uses TextMate grammars to do theirs but since we're already using CodeMirror I took a stab at implementing ours using that.
Syntax highlighted diffs have been a much appreciated feature of GitHub.com for a long time now and one that I have missed in GitHub Desktop for a long time. Highlighting in diffs presents some added complexity over that of highlighting in a normal source file though. Pretty much all languages are contextual, in that what happened on some line "higher up" affects what's going on further down. As such you can't just pull out a line from a diff and expect it to be highlighted properly. Here's a good example
Had we just tried to highlight individual lines here we wouldn't have been able to infer that the first line was part of a multi-line comment.
Instead, we have to take the contents of the file before the change, and the contents of it after and run highlighting on both versions. Once that's done we can stitch these together to form one syntax highlighted diff.
The Approach
When we are about to perform highlighting on a diff we start out by scanning through the diff to figure out which lines we need from which file. Context lines can be pulled from either version while added/removed lines obviously need to come from a particular version. If we find that a file consists entirely of additions or entirely of deletions we can optimize further by adding a preference for one of the versions and thus getting away with loading just one file.
Once we've got that settled we load the first 256kb from both versions (256kb picked arbitrarily because I figured it should cover the majority of source files while adding a very manageable memory overhead for the feature). We then pass this content, along with which lines we want to get tokens for to one or two web workers which then run the modes. CodeMirror modes are synchronous but running them in a web worker means we can get on with other things while we're tokenizing up to half a megabyte of content in up to two different threads (threads in javascript, what have we come to). It also means that we have a real nice containment of the highlighting process and that we can terminate it should it for some reason end up taking a very long time to complete.
When we get the results from the workers we apply our own custom CodeMirror mode which takes the tokens from the language modes and applies them inside of our diff. That means that there's a small window in between when users see the diff and when highlighting gets applied. In my testing it's barely noticeable and it means we can deliver what really matters (the diff) as quickly as we've done before.
The Performance.
So far I've been blown away by how well this performs, even on my 2015 12" macbook it's real snappy. We do a couple of optimizations behind the scenes and we keep web workers alive once we've fired them up to avoid the penalty of parsing the javascript of all the modes.
Languages currently supported
We can obviously support every mode that CodeMirror supports but for now I've opted to include these until we get a sense of how well this is working.
JavaScript, JSON, TypeScript, HTML, Markdown, Yaml, XML, Objective-C, Scala, Java, C, C++, sh/bash, Go, Perl, PHP, Python, Ruby.
Feel free to clone https://github.com/niik/highlighter-tests to test some of the modes
Future improvements
Caveats
Fixes #1312