Skip to content
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

Support titles for links and images #42

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

bensyverson
Copy link

I didn't learn this until yesterday, but apparently stock Markdown supports titles in links and images:

Here is a [link](https://swiftbysundell.com/ "Good stuff here")

Same thing for images, meaning ![Pupper](foo.jpg "A cute puppy") should turn into:

<img src="foo.jpg" alt="Pupper" title="A cute puppy"/>

It's also supported in the link definition syntax, with any amount of same-line whitespace, and two other delimiters:

[sbs]: https://swiftbysundell.com     "Now powered by Publish"
[sbs]: https://swiftbysundell.com  'Now powered by Publish'
[sbs]: https://swiftbysundell.com (Now powered by Publish)

It even supports putting the title on the next line and using spaces for padding:

[sbs]: https://swiftbysundell.com
              "Now powered by Publish"

I took a very quick pass at implementing this functionality in Ink, and added a few tests to kick the tires. But I probably haven't spent enough time in the Ink codebase to do things 100% idiomatically. So please take or leave any of this!

Thanks @JohnSundell for creating such a powerful Markdown parser—I'm already customizing Ink via a Modifier in a Publish plug-in, which is a testament to how powerful this suite of tools really is!

@john-mueller
Copy link
Contributor

This is an awesome start! I haven't looked at the actual code yet, but spec tests are promising (see corresponding examples here).

Newly passing CommonMark tests are 166, 327, 481, 485, 504, 523, and 535.
Newly failing CommonMark tests are 179, 483, 487, 488, and 563.

It looks like the newly failing tests should be pretty easy to fix; I'll try to look at your code and give specifics tomorrow.

Thanks for working on this, @bensyverson!

@bensyverson
Copy link
Author

Nice, thanks @john-mueller ! I'll take a look at the newly failing tests too and poke around in the codebase. 🙏

Copy link
Contributor

@john-mueller john-mueller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With these tweaks, this PR passes 7 new CommonMark tests (166, 327, 481, 485, 504, 523, 535), and does not cause any CommonMark regressions.

All the changes, as well as an additional 4 tests to guard against regressions, are on the link-titles-fixup branch at https://github.com/john-mueller/Ink. I suggest merging these changes into your feature branch before merging this PR. If it would be easier, I can open a PR to add them to your feature branch.

Thanks again for adding this!

@@ -8,9 +8,31 @@ internal extension Character {
var isSameLineWhitespace: Bool {
isWhitespace && !isNewline
}

var isLegalInURL: Bool {
self != ")" && self != " "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self != ")" && self != " "
self != ")" && self != " " && self != "\n"

This helps fix the new failure of CommonMark test 487:
"The destination cannot contain line breaks" (spec)

@@ -19,20 +20,35 @@ internal struct Link: Fragment {

if reader.currentCharacter == "(" {
reader.advanceIndex()
let url = try reader.read(until: ")")
return Link(target: .url(url), text: text)
let url = try reader.readCharacters(matching: \.isLegalInURL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let url = try reader.readCharacters(matching: \.isLegalInURL)
let url = try? reader.readCharacters(matching: \.isLegalInURL)

This helps fix the new failure of CommonMark tests 483 and 563:
"Both the title and the destination may be omitted" (spec)
"Inline links also take precedence" (spec)

If both title and destination are missing, then this line encounters ")" immediately and throws, which ends link parsing. So instead of throwing, we assign nil to the url if it is not present, and use a default value of "" on line 33.

titleText = try reader.read(until: "\"")
}
try reader.read(")")
return Link(target: .url(url), text: text, title: titleText)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return Link(target: .url(url), text: text, title: titleText)
return Link(target: .url(url ?? ""), text: text, title: titleText)

Needed since url is now optional.

if reader.currentCharacter.isSameLineWhitespace {
try reader.readWhitespaces()
}
if let delimeter = TitleDelimeter(rawValue: reader.currentCharacter) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if let delimeter = TitleDelimeter(rawValue: reader.currentCharacter) {
if let delimeter = TitleDelimeter(rawValue: reader.currentCharacter) {
let index = reader.currentIndex

We capture the index at the start of the title parsing, in case there is an otherwise valid title followed by non-whitespace. If that happens, we rewind the reader to the beginning of what would have been the title, and set titleText = nil.

}
if let delimeter = TitleDelimeter(rawValue: reader.currentCharacter) {
reader.advanceIndex()
titleText = try reader.read(until: delimeter.closing)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
titleText = try reader.read(until: delimeter.closing)
titleText = try reader.read(until: delimeter.closing)
reader.discardWhitespaces()
if !reader.didReachEnd && !reader.currentCharacter.isWhitespace {
reader.moveToIndex(index)
titleText = nil
}

This helps fix the new failure of CommonMark test 179:
"No further non-whitespace characters may occur on the line." (spec & example)

@bensyverson
Copy link
Author

Amazing, thanks @john-mueller! Just merged in your changes 🥳

Copy link
Contributor

@john-mueller john-mueller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me!

@finestructure
Copy link
Contributor

Just wanted to chime in and alert you to PR #47 which I've just opened and which is somewhat related.

Some Markdown editors support width=400 height=300 attributes for images and it would be nice to roll both title and those into one single change perhaps.

PR #47 is already agnostic over what attributes it can handle. What it doesn't do is deal with quoting. I.e. while it currently does support title=foo, it won't handle title="foo bar" correctly.

I'm pretty sure with what's in this PR here my PR #47 could be extended (although I haven't looked at the changes in here in detail yet).

What are your thoughts to merge the two approaches?

@finestructure
Copy link
Contributor

finestructure commented Feb 22, 2020

Hold on, I misread the syntax for the title definition. It's not declared the same way as the width and height parameters, which are = pairs.

Still, supporting both title and = pairs at the same time would probably need some parsing coordination so that one doesn't clobber or impede the other.

@bensyverson
Copy link
Author

@finestructure Attributes on images/links would be fantastic—I've often wanted this for image sizes. I'll take a look and see if there's a nice clean way to merge the two!

@john-mueller
Copy link
Contributor

I left a note on #47 that is relevant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants