Language Server: Markdown not rendering properly

Ah, that does clarify things massively @apexskier - thanks :slightly_smiling_face:

If I’m honest, firstly I’m no TypeScript expert and secondly I haven’t dived into your code too much. But what you’ve discovered during your time working with the API it seems maybe there maybe needs to be firstly, better documentation on how Nova handles LSP responses and secondly, perhaps granular control on what formatters are applied to the returned results.

Thoughts? :thinking:

1 Like

Firstly, @apexskier, thanks for the clarification! You’re right — after all, your extensions are consistent with what I’ve seen on my own regarding the lack of Markdown (or improper Markdown) support. There must be a bug in Nova’s logic, for one simple reason: allegedly, the client ought to negotiate with the Language Server what kind of textual representation it accepts — namely, simple text or Markdown. We can override this — that’s what I do to avoid getting the non-parsed Markdown — but it should not be necessary: if Nova supports Markdown (which we know it does), it ought to render things with Markdown as per the LSP specs.

If it doesn’t — because, say, the Markdown parser has some issues (which doesn’t seem to be the case, but we could speculate), or because of a design decision (Panic might not be comfortable, for some unfathomable reason, to apply Markdown formatting to text coming from an ‘unknown, external source’) — then Nova should tell that to the Language Server: ‘sorry, we only accept unformatted text for now’. In other words, it shouldn’t be up to the extension developers to figure out what capabilities Nova exposes to the Language Server, and change their code accordingly. Instead, as Panic adds more and more features to the Language Client, the new capabilities that might get added ought to be automatically communicated to the Language Server (and, well, extension developers might love to hear about those changes, too, but I’d say that this is secondary… so long as Nova ‘talks’ LSP correctly, we’re good).

There is obviously the alternative suggested by @simondeele … allow extensions to intercept whatever messages come from the LSP (even the ‘standard’ ones that are already built-in into Nova). Or add a hook, a filter, something that allows extension developers to capture what comes from the Language Server, do some dirty work on that, and send it back for Nova to process. In some cases, this might be quite useful. In most of those cases, I’m afraid it would just unnecessarily complicate the extension code (again, I’d like to point out @apexskier’s attempt to write a ‘universal LSP extension toolkit’ that is almost language-agnostic and provides a lot of ‘missing functionality’ not exposed by the Nova API — this should not be necessary at all).

I have lost count on the many times that I look at the logs generated by ‘my’ Language Server, happily seeing flawless JSON being sent back to Nova, and then grumbling because Nova simply didn’t ‘get it right’ — if I could only change that before it gets internally processed by Nova’s code, I’d be soooo happy. Note that the same applies to having the ability to override, or capture, the standard LSP functionality already implemented; it’s a pity that Nova only allows doing that for LSP commands/notifications that haven’t been implemented yet; which means that, as time goes by, we’ll get less and less LSP functionality to ‘play’ with, and more and more is provided by Nova itself, rendering our code obsolete. But for that strategy to be successful in the long term, it means that everything that Nova doesn’t allow the extension developers to override has to be perfectly implemented according to specs and work across all Language Servers (because, as mentioned elsewhere, Microsoft’s documentation on LSP is unfortunately ambiguous, and some finer points require interpreting what Microsoft means and stating such assumptions…).

I totally agree with more documentation: if there were five times as more documentation, with usage examples, it would still not be enough. There are simply too many things that have to be attempted by trial-and-error to see what they do. Also, it’s unfortunate that not every API call is documented, even if only written in Panic’s telegraphic style typical of most of the development documentation (knowing that something exists , even if it isn’t well documented, is better than not even knowing that such functionality is available — because on the worst-case scenario we can always try out what we know that exists and see what it does…). Sometimes, when viewing someone else’s code, I stumble upon a new call that I had not seen yet, try to look it up on the developer library, and find nothing — even though the functionality is easily implied by the call, and is consistent with the API, so it’s easy to use elsewhere even without documentation. But if I hadn’t stumbled across it on someone else’s code, I had no idea it existed in the first place!

Anyway, to the original point… because some things already place hovered text decorated with Markdown, and others don’t (such as what comes out of the Language Server), I’d guess that, internally, Nova is using different functions to generate the hovered text — in one case filtering it through the Markdown parser, in the other not. Maybe it’s a simple case of having ‘forgotten’ to call the Markdown parser, and this would be a very simple fix indeed.

I’m crossing my fingers :slight_smile:

I generally agree with what you’ve both said. The Nova team did add more details at LanguageClient - Nova over what originally was specified, but the problem is the “subset” part of:

As of Nova 2 , the IDE implements a subset of version 3.15 of the LSP specification.

I don’t really want Nova to add some sort of “hook” mechanism between the language server and extensions, mostly because I think it disincentivizes conformance to the LSP (both from nova and extension developers) and generally complicates things (as @GwynethLlewelyn said).

My general approach at this point is to conform as much as possible, but avoid hacks that might need to be removed later or might cause problems later if Nova’s LSP support improves.

1 Like

You are correct. Nova currently does not support rendering Markdown in hover tooltips.

This is due in small part to the Markdown engine we currently use, which was written with the intent to be rendered in a web context (so it currently only outputs HTML fragments). Nova’s UI is not a web view, and is rendered using native macOS controls that expect Cocoa’s attributed string formatting API. I experimented a long while back with embedding a small web view into the hovers, but its performance for appearing and sizing was… really not that good.

I sadly haven’t had enough time to devote to work on implementing a Markdown parser that could output into something the native controls can handle. But it’s definitely a thing we need to do.

As for documentation, if you notice any missing documentation, please, please do let us know. We try to keep our documentation up to date and complete for the entire extension API, so if something is absent (or incorrect), we’d love to know.

2 Likes

Hi @logan — thanks so much for looking into this!

If there is something I’m totally clueless about is developing code for the Apple ecosystem. As such, maybe my humble suggestion is completely stupid, so please forgive me in advance.

When you described that the controls you use expect attributed strings, I immediately thought… well, in that case, and given my limited knowledge of the subject, it seems to me that all you need is to parse Markdown directly into attributed strings, not really creating mini-webpages hovering around the editor…

So, my first idea was just to do some clever regular expressions to capture basic Markdown (which is what the Language Servers emit, anyway), and, instead of spewing out HTML, it would construct an attributed string.

@jbroadway implemented the Smallest Markdown Parser Ever (Slimdown) with exactly that approach in mind. Sure, the code is in PHP, but as you can see from the comments there, the concept is what matters (as well as the regex!), so people ported it into all sorts of programming languages (a curiosity: it seems to be a popular college assignment for computer science students — port Slimdown to your favourite programming language!)

Slimdown naturally uses HTML as the output format, but it lays it out nicely on a table that is very easy to understand, so, in theory, you could get it to render basically anything you wish — including (why not?) Cocoa’s attributed string formatting.

Ok. But I understand that you guys have a zillion issues to work on, and no time for implementing very complex ‘goodies’ that are used by just a fraction of your clients. Fine! What about generating HTML from Markdown and then converting it directly to attributed strings, since Apple so generously provided you with an initializer method to do just that? Basically, get the Slimdown parser to generate it into Data, and then pass it along to the special HTML-based initializer, and that’s all.

O-kay. Perhaps even that is too difficult, I don’t know. Fortunately for all of us, someone already kindly created a full implementation of the paragraph above to do exactly that, parse Markdown into HTML and then let Apple’s methods do the rest of the boring job (released under the Apache License 2.0).

Granted, this Swift-MarkDownKit is a heavyweight, full-blown Markdown-to-HTML-or-Attributed-String parser, including a lot of extra tools that you might not need at all, so you might just use the juicy bits of the kit and let your own quick & dirty implementation of Slimdown drive the actual parsing.

I hope that the suggestions above make any sense to you and are useful in any way!

Hi Gwyneth,

Thanks for the links and the ideas. You are right—what I plan to do is get a parser that can output into this format properly. Apple’s initializer for HTML, while with the right intention, has its own faults and issues that we have dealt with in the past, which sort of wrote it off as an option right away.

Generally, though, this comes down to: at the moment, Nova’s development team is about 80% just me :sweat_smile: as our engineering teams have been strained with other projects, so I just haven’t had the time to fully move through all of the work that our developers have been interested in on top of the bug-fix workload that we’ve been coordinating with our support and QA teams. Note: I definitely don’t want that to appear as an excuse, though. I hope to be able to swing back around to more work on the LSP side soon, as I definitely understand how important it is to a lot of our extension developers.

As always, thank you for your feedback and I’m happy you all are so invested and willing to work with us to make Nova better.

3 Likes

I had a quick search on the interweb and one result that stood out to me was this repo CocoaMarkdown - it’s not seen an update in a couple of years but also may form the basis of an idea to get Markdown into a native-compliant format.

1 Like

That makes sense. I dealt with a similar markdown issue at work but with React/React Native and our custom cross-platform component library.

1 Like

Oh, wow, … kudos to you and the workload you handle then, @logan ! I’m probably not stating anything new to you by saying that is so not sustainable. Judging from recent activity levels on this forum, I have the impression Nova adoption is picking up steam, but I daresay either you or the interest in Nova will inevitably burn out if Panic cannot find a way to assign more resources to it, esp. re. extensibility …

Oops, sorry, @logan, I guess we shouldn’t be burdening you with so many requests then… you must be already working 20 hours/day (I’m also counting the time spent on the forums, GitHub, etc. answering questions/requests).

I sincerely hope that you guys are able to get a handful of developers to help you out. Let me second @kopischke’s impression — we can only ‘watch from outside’, of course, and have no clue about the real numbers, but I have certainly noticed that after half a year or so, Nova seems to have more extensions & themes than Coda 1 & 2 ever did… and many of those get updated (i.e. they’re not ‘one-shot’ attempts that have quickly been abandoned).

As this thread shows, there are already many extensions for the same language (addressing different issues). This is good. It means lots of people happy to try new things out, even starting from scratch, because one can always do better!

Last but not least, during the Beta testing, I remember seeing a comment from someone at Panic which really captured my attention: the notion that many programmers (especially those not conditioned at the workplace by a uniformly imposed workspace) are moving away from all-in-one IDEs — behemoths that will consume every available resource on the computer and then some — and, instead, pick light-weight editors, pushing all IDE-y things into plugins/external tools, and relying on editors supporting some form of integration with those tools. This might just be a fad, a trend, something in fashion right now, but the truth is that if this trend persists, you nailed it. And I’m not really surprised that even Microsoft figured out the same thing — pushing a much lighter VS Code as an alternative to the full-blown Visual Studio.

As mentioned early, I have no experience in developing things for the Apple ecosystem. One of the main reasons for that is Xcode. Again, I have personally nothing against Apple’s free IDE — except, of course, that it’s insanely heavy. A few times in the past I launched it only to edit a .plist or similar bit of configuration file that Xcode opens by default and makes a good job of doing of displaying it. Before I installed any other editor, Xcode even insisted on opening JavaScript files. This would drag the Mac down to the point that it was impossible to work with. I used a free version of Sublime Text for some time just to quickly edit files with some syntax highlighting (as opposed to opening JavaScript on, say, TextEditor…); I toyed around with one or another editor… until I got Coda :rofl: I remember that I was a bit sceptic with the overall concept of ‘Projects’, because, well, I thought that I would be getting a huge amount of overhead until I was able to type my first line of code.

It turns out that Panic’s philosophy — best illustrated in Nova, of course — relies on an old Unix aphorism: don’t try to do everything in a monolithic application, but, instead, spread out the load to several bits, each of which doing just one or two things very well (and quite quickly!), and focus on how to interact with those loose bits.

In other words, Emacs.

:rofl: :rofl: :rofl: :rofl: :rofl: :rofl:

Ok, jokes besides — no, I don’t want to start another vi vs.Emacs war! — my point here is that, for many decades, old-time programmers using 80x25 VT100 terminals faced the same issues, and the solution was to have an editor which can optionally run all sorts of extensions and external tools, and which was designed for being extended and expanded that way. I’d claim that Nova follows that concept, too (sure, it’s not the only one — as said, it’s a current trend), but, instead of 80x25 VT100 CRTs, we have Macs with GUIs on Retina displays :slight_smile: (well, and JavaScript is perhaps easier to learn than LISP…).

Nova is fast enough for me to use it to do a quick edit on some kind of text file. TextEdit is faster, but usually lacks a lot of functionality; but, as a curiosity, I do have GNU Emacs installed on my Mac as well (old habits die hard…), and it’s just slightly faster at opening a file from scratch than Nova. Emacs is well-known for its overhead (compared to so many millions of text editors out there), and on my old, mid-2014 PowerBook, the slight delay in launching all that LISP is perceptible (naturally enough, I mostly use other alternatives that are much faster than good old rms Emacs…). What I mean is — nice work, guys, Nova is really quite fast at doing its job, in spite of all the overhead for being a graphical editor-cum-extensions.

But I’m completely out of topic and will shut up now. I just wanted to give a heads-up, and hope that Panic’s board is willing to get a few extra hands to help out @logan

At the end of the day, tooltips in raw Markdown fall pretty low on my priority list. And as a whole, I’ll bet the bug/feature priorities expressed in this forum are not representative with the feedback Panic gets via other channels. Whatever development methodology you pick, at some point you end up with a prioritized list of stuff to do. I’d love a peak at that list for Nova, just to see where my pet feature requests are, but as someone whose day job is writing commercial software, I’m familiar assorted reasons why complete transparency isn’t realistic.

Ultimately I have to agree with you, there certainly are other priorities.

Also — who knows? — one day Apple might even release an upgrade to their API, allowing Markdown to be directly converted into attributed strings, rendering the issue moot (and having wasted @logan’s time in searching for a solution and hand-crafting it)… so we ought to pester Apple instead, they have vastly more resources, human and otherwise, and if they managed to give developers a method to convert HTML directly into attributed strings, it shouldn’t be hard to do it for Markdown as well — considering that ‘bare bones’ Markdown is orders of magnitude simpler to parse than HTML.

Then again, I remember that it was Panic which found out about bugs on the text-rendering mechanism provided by Apple’s SDKs, and after grumbling with Apple to fix the bugs they had uncovered, instead of waiting for a fix, Panic simply went ahead and rewrote their own text renderer… so sometimes we cannot rely solely on Apple :slight_smile:

But on a scale of importance, I’d rate this feature 3 out of 10. Nice and convenient to have, sure, especially when comparing with the competition (as well as compared to some of the extensions which do not rely on a Language Server), but it’s not a critical functionality.

Just wanted to update folks here—I’ve implemented support for basic Markdown rendering for Nova 6 for the LSP hovers. If the LSP you’re wrapping declares support for it, we should render it automatically. Nova 6 is planned to be released sometime this week.

It should cover things like headings, lists, strong/em, etc. No tables support for now, but so far I haven’t come across anything in that realm that’s using it. We can always look into extending it further if needed.

3 Likes

That is awesome, @logan, I’ll be looking forward to that! My LSP extension is suffering from the same problem. :slight_smile:

1 Like

I have to say, @logan, you’re simply a marvel! I hope that Panic pays you your weight in gold — every week :wink: You’d still be underpaid.

Thanks very much for that! I’m really, really looking forward to Nova 6 now! :smiley:

2 Likes

As the original poster for this topic I know I’ve been absent a while but the discussions in this thread are really constructive.

I certainly wasn’t expecting a response from Panic to say they’ve implemented a markdown parser - such fantastic news and can’t wait to test out the feature! Thanks @logan :slightly_smiling_face:

1 Like

Here’s a little snapshot of it working with my LSP extension:

1 Like

Apparently, that was grounded in fact.

2 Likes

I’m running up against this again. I’m looking at improving the documentation rendering in GitHub - typescript-language-server/typescript-language-server: TypeScript & JavaScript Language Server, and the experience in Nova is pretty bad.

The markdown:
```typescript
constructor Dot(x: number, y: number, width: number): Dot
```

Create a dot.

* _@param_ ``` x ``` — The x value.
* _@param_ ``` y ``` — The y value.
* _@param_ ``` width ``` — The width of the dot, in pixels.
* _@example_ — testing
  ```typescript
  new Dot(x, y, z)
  ```
Nova's rendering:

Note the odd paddings, the lack of a break above the code block in the last <li>, the additional bullet after that, and the lack of syntax highlighting.

Here's what it renders like in this forum:
constructor Dot(x: number, y: number, width: number): Dot

Create a dot.

  • @param x — The x value.
  • @param y — The y value.
  • @param width — The width of the dot, in pixels.
  • @example — testing
    new Dot(x, y, z)
    

I can get syntax highlighting if I use MarkedString (which is what the server currently uses), but that type is now deprecated in the language server protocol in favor of MarkedContent, so I don’t want to keep using it.

Nova also only renders the first of the set of MarkedStrings returned in the onHover response.

1 Like

Interesting…

If I return this MarkedString: { language: 'markdown', value: '...' }, the code fence’s syntax highlighting works. (In some ways, this is more readable than the native markdown rendering).

1 Like