Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Markdownをリアルタイムに解析する

nakajijapan
August 31, 2018

 Markdownをリアルタイムに解析する

Markdownを解析する時にGitHubで探せば簡単にライブラリを何個も見つけることができます。しかし、をそれをリアルタイムとなるとなかなかみつけることができません。私は端末間で同期できるメモアプリを作成し、機能の一つに入力しながらMarkdown形式に色付けをする機能を実装しました。本トークではリアルタイムに文章を解析し、リッチな表現をどのようにして行っているのかを解説します。

https://fortee.jp/iosdc-japan-2018/proposal/a2e20820-d4f6-43e5-b34b-1b9e6fec7806

nakajijapan

August 31, 2018
Tweet

More Decks by nakajijapan

Other Decks in Technology

Transcript

  1. #iOSDC2018
    .BSLEPXOΛ
    ϦΞϧλΠϜʹ
    ղੳ͢Δ

    View Slide

  2. #iOSDC2018

    View Slide

  3. !OBLBKJKBQBO
    4PGUXBSF&OHJOFFS
    $".1'*3&J04%$4UB⒎/,+*OUFSOFU
    J04"OESPJENBD04

    View Slide

  4. View Slide

  5. .BSLEPXOΛ
    ϦΞϧλΠϜʹ
    ղੳ͢Δ

    View Slide

  6. .BSLEPXOΛ
    ϦΞϧλΠϜʹ
    ղੳ͢Δ
    ͖͔͚ͬ

    View Slide

  7. Izumo
    Markdown Note Tool

    View Slide

  8. J04
    NBD04

    View Slide

  9. ͖͔͚ͬ
    wࣗ෼ʹͱͬͯྑ͍ΤσΟλ͕ͳ͔ͬͨ
    wϦονͷදݱํ๏ʹෆຬ
    w৭͚ͩมΘͬͯ͘ΕΕ͹͍͍
    wಉظͯ͠ཉ͍͠

    View Slide

  10. ΍Γ͍ͨ͜ͱ

    View Slide

  11. wΧϥʔϦϯά
    wฤूิࠤ

    View Slide

  12. ΧϥʔϦϯά
    ߦ໨͸λΠτϧߦɺݟग़͠ɺڧௐɺΠϯ
    ϥΠϯදࣔɺϦετɺ50%0Ϧετɺը૾
    ଧͪফ͠ɺςʔϒϧɺਫฏઢɺҾ༻ɺίʔ
    υϒϩοΫɺҾ༻

    View Slide

  13. ฤूิࠤ
    wࣗಈΠϯσϯτ
    wλϒͰͷϦετҠಈ

    View Slide

  14. ௐࠪ

    View Slide

  15. IUUQTHJUIVCDPN
    TFBSDI
    RNBSLEPXOTXJGU

    View Slide

  16. View Slide

  17. wϦονͳදݱʹม׵ͯ͠͠·͏
    wશจΛಡΈࠐΉඞཁ͕͋Γ

    View Slide

  18. ࠷খݶͷൣғͰղੳ
    ॲཧΛߦ͍͍ͨ

    View Slide

  19. w͍ͭʁ
    wԿΛʁ
    wͲͷΑ͏ʹͯ͠ʁ

    View Slide

  20. ͍ͭʁ

    View Slide

  21. wςΩετ͕ೖྗ͞Εͨ
    wॳظԽ͞Εͨ
    wϑΥϯτ͕มߋ͞Εͨ

    View Slide

  22. wςΩετ͕ೖྗ͞Εͨ
    wॳظԽ͞Εͨ
    wϑΥϯτ͕มߋ͞Εͨ

    View Slide

  23. /45FYU4UPSBHF%FMFHBUF

    View Slide

  24. // MARK: - NSTextStorageDelegate
    extension TextViewController: NSTextStorageDelegate {

    func textStorage(_ textStorage: NSTextStorage, willProcessEditing editedMask:
    NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
    self.editedRange = editedRange
    }
    func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask:
    NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {

    }
    }

    View Slide

  25. ԿΛʁ

    View Slide

  26. ม׵͢Δ΂͖ྖҬ

    View Slide

  27. // MARK: - NSTextStorageDelegate
    extension TextViewController: NSTextStorageDelegate {

    func textStorage(_ textStorage: NSTextStorage, willProcessEditing editedMask:
    NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
    self.editedRange = editedRange
    }
    func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask:
    NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {

    }
    }

    View Slide

  28. // MARK: - NSTextStorageDelegate
    extension TextViewController: NSTextStorageDelegate {

    func textStorage(_ textStorage: NSTextStorage, willProcessEditing editedMask:
    NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
    self.editedRange = editedRange
    }
    func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask:
    NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {

    }
    }
    ⚠ฤूͨ͠ྖҬ

    View Slide

  29. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ

    View Slide

  30. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ

    View Slide

  31. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ

    View Slide

  32. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View Slide

  33. Ұؾʹෳ਺ߦ͕
    ฤू͞Εͨ৔߹
    Λߟྀ͢Δ

    View Slide

  34. ͲͷΑ͏ʹͯ͠ʁ

    View Slide

  35. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵

    View Slide

  36. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵
    ෳ਺ߦͷ৔߹͕͋Δ

    View Slide

  37. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵

    View Slide


  38. J04%$
    J04%$

    View Slide

  39. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ

    View Slide

  40. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ

    View Slide

  41. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ

    View Slide

  42. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View Slide

  43. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View Slide

  44. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    if let endRange = endNSRange.range(string: string) {
    lineRange = string.lineRange(for: endRange)
    } else {
    return nil
    }
    let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)!
    let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)!
    let lineNSRange = NSRange(lower..return lineNSRange
    }

    View Slide

  45. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    if let endRange = endNSRange.range(string: string) {
    lineRange = string.lineRange(for: endRange)
    } else {
    return nil
    }
    let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)!
    let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)!
    let lineNSRange = NSRange(lower..return lineNSRange
    }

    View Slide

  46. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    if let endRange = endNSRange.range(string: string) {
    lineRange = string.lineRange(for: endRange)
    } else {
    return nil
    }
    let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)!
    let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)!
    let lineNSRange = NSRange(lower..return lineNSRange
    }
    վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ

    View Slide

  47. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    if let endRange = endNSRange.range(string: string) {
    lineRange = string.lineRange(for: endRange)
    } else {
    return nil
    }
    let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)!
    let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)!
    let lineNSRange = NSRange(lower..return lineNSRange
    }
    վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ
    લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View Slide

  48. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    if let endRange = endNSRange.range(string: string) {
    lineRange = string.lineRange(for: endRange)
    } else {
    return nil
    }
    let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)!
    let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)!
    let lineNSRange = NSRange(lower..return lineNSRange
    }
    ର৅ͷߦશମΛऔಘ
    վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ

    View Slide

  49. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    if let endRange = endNSRange.range(string: string) {
    lineRange = string.lineRange(for: endRange)
    } else {
    return nil
    }
    let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)!
    let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)!
    let lineNSRange = NSRange(lower..return lineNSRange
    }
    ର৅ͷߦશମΛऔಘ
    վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ
    લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View Slide

  50. lineRange(for:)
    Returns the range of characters representing the line or lines
    containing a given range.
    func lineRange(for range: NSRange) -> NSRange


    View Slide

  51. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵

    View Slide

  52. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵ ίʔυϒϩοΫ͸શ
    ମΛݕࡧඞཁ͕͋Δ

    View Slide

  53. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵
    ޙ͸֘౰͢ΔจࣈྻΛந
    ग़ͯ͠ద੾ͳ৭ʹม׵͢
    Δ͚ͩɻ

    View Slide

  54. ࠷খݶͷൣғͰղ
    ੳ͢Δ͜ͱ͕େ੾

    View Slide

  55. View Slide

  56. #$%&

    View Slide

  57. ֆจࣈ
    w/44USJOHɺ/43BOHFͷར༻࣌
    w୯७ʹจࣈྻΛΧ΢ϯτͯ͠͸❌
    w65'Λҙࣝͨ͠จࣈྻૢ࡞Λ͢Δ

    View Slide

  58. View Slide


  59. 6' Y%% Y%&
    6OJDPEF 65'

    View Slide

  60. "཮".count // 1
    "཮".unicodeScalars.count // 1
    "཮".utf16.count // 1
    "཮".utf8.count // 3
    "".count // 1
    "".unicodeScalars.count // 1
    "".utf16.count // 2
    "".utf8.count // 4
    ")".count // 1
    ")".unicodeScalars.count // 2
    ")".utf16.count // 4
    ")".utf8.count // 8

    View Slide

  61. #$%&

    View Slide

  62. /44USJOH

    View Slide

  63. w಺෦తʹ͸65'Ͱό
    ΠτྻΛ؅ཧ
    /44USJOH

    View Slide

  64. let snowy = "❄ Let it snow! ☃"
    let nsrange = NSRange(location: 3, length: 12)
    if let range = Range(nsrange, in: snowy) {
    print(snowy[range])
    }
    // Prints "Let it snow!"

    View Slide

  65. NSRange(location: 0, length: line.utf16.count)

    View Slide

  66. 4USJOH

    View Slide

  67. wόΠτྻ͸Ӆณ
    4USJOH

    View Slide

  68. let snowy = "❄ Let it snow! ☃"
    print(snowy.prefix(5))
    // Prints "❄ Let"
    print(snowy.suffix(1))
    // Prints "☃"

    View Slide

  69. View Slide

  70. ೖྗิࠤ

    View Slide

  71. wΦʔτΠϯσϯτ
    wλϒͰͷҠಈ

    View Slide

  72. View Slide

  73. ΠϕϯτϋϯυϦϯά
    wλϒ
    wόοΫλϒ
    wվߦ

    View Slide

  74. J04NBD04

    View Slide

  75. w/45FYU7JFX
    w6*5FYU7JFX

    View Slide

  76. /45FYU7JFX

    View Slide

  77. View Slide

  78. ઐ༻ͷؔ਺
    ͕༻ҙ͞Ε͍ͯΔ

    View Slide

  79. class NSTextView {
    func insertTab(_ sender: Any?)
    func insertBacktab(_ sender: Any?)
    func insertNewline(_ sender: Any?)
    }
    public protocol NSTextInputClient {
    public func insertText(_ string: Any, replacementRange: NSRange)
    }
    طʹظ଴͞Εͨؔ਺͕༻ҙ͞Ε͍ͯΔ
    ΧελϚΠζͨ͠จࣈྻΛૠೖ͢Δ

    View Slide

  80. class TextView: NS {
    override func insertTab(_ sender: Any?) {
    var indent = ""
    let (spaces, lineRange) = getPrefixStringIfLineHasListTag(regex: TextRegex.insertTab)
    if useIndentWithSpaceKey {
    indent = String(repeating: " ", count: indentWidth)
    if !spaces.isEmpty {
    super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0))
    return
    }
    super.insertText(indent, replacementRange: self.rangeForUserTextChange)
    return
    }
    indent = "\t"
    if !spaces.isEmpty {
    super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0))
    return
    }
    super.insertTab(sender)
    }
    }

    View Slide

  81. class TextView: NS {
    override func insertTab(_ sender: Any?) {
    var indent = ""
    let (spaces, lineRange) = getPrefixStringIfLineHasListTag(regex: TextRegex.insertTab)
    if useIndentWithSpaceKey {
    indent = String(repeating: " ", count: indentWidth)
    if !spaces.isEmpty {
    super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0))
    return
    }
    super.insertText(indent, replacementRange: self.rangeForUserTextChange)
    return
    }
    indent = "\t"
    if !spaces.isEmpty {
    super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0))
    return
    }
    super.insertTab(sender)
    }
    }
    Πϯσϯτର৅ͱͳ
    Δઌ಄෦෼Λऔಘ

    View Slide

  82. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ
    લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ

    View Slide

  83. class TextView: NS {
    override func insertTab(_ sender: Any?) {
    var indent = ""
    let (spaces, lineRange) = getPrefixStringIfLineHasListTag(regex: TextRegex.insertTab)
    if useIndentWithSpaceKey {
    indent = String(repeating: " ", count: indentWidth)
    if !spaces.isEmpty {
    super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0))
    return
    }
    super.insertText(indent, replacementRange: self.rangeForUserTextChange)
    return
    }
    indent = "\t"
    if !spaces.isEmpty {
    super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0))
    return
    }
    super.insertTab(sender)
    }
    }
    ਌ॲཧʹ೚ͤΔ

    View Slide

  84. 6*5FYU7JFX

    View Slide

  85. View Slide

  86. class NSTextView {
    func insertTab(_ sender: Any?)
    func insertBacktab(_ sender: Any?)
    func insertNewline(_ sender: Any?)
    }
    public protocol NSTextInputClient {
    public func insertText(_ string: Any, replacementRange: NSRange)
    }
    ༻ҙ͞Ε͍ͯͳ͍ɻɻɻ

    View Slide

  87. class NSTextView {
    func insertTab(_ sender: Any?)
    func insertBacktab(_ sender: Any?)
    func insertNewline(_ sender: Any?)
    }
    public protocol NSTextInputClient {
    public func insertText(_ string: Any, replacementRange: NSRange)
    }
    • ΩʔϘʔυʹλϒ͸ແ͍
    • λϒϘλϯͰಉ౳ͷػೳΛ࣮૷

    View Slide

  88. insertTab insertBackTab

    View Slide

  89. ςΩετૠೖ࣌

    View Slide

  90. 6*5FYU7JFXͷࢠΫϥε
    let stringRange = lineRange.range(string: text)!
    text.insert(contentsOf: indent, at: stringRange.lowerBound)

    View Slide

  91. 6*5FYU7JFXͷࢠΫϥε
    let stringRange = lineRange.range(string: text)!
    text.insert(contentsOf: indent, at: stringRange.lowerBound)

    View Slide

  92. UITextViewͷࢠΫϥε
    let stringRange = lineRange.range(string: text)!
    text.insert(contentsOf: indent, at: stringRange.lowerBound)
    wFEJUFE3BOHFͰฤूൣғ͕શͯʹͳͬ
    ͯ͠·͏
    wUFYU%JE$IBOHF͸ίʔϧ͞Εͳ͍
    wظ଴͞ΕͨҐஔʹΧʔιϧ͕Ҡಈ͞Ε
    ͳ͍

    View Slide

  93. public protocol UIKeyInput : UITextInputTraits {
    public var hasText: Bool { get }
    public func insertText(_ text: String)
    public func deleteBackward()
    }

    View Slide

  94. func insertTab(rangeForTextChange: NSRange) {
    var indent = ""
    let (spaces, lineRange) = getPrefixStringIfLineHasList(regex: TextInputRegex.insertTab,
    rangeForTextChange: rangeForTextChange)
    if useIndentWithSpaceKey {
    indent = String(repeating: " ", count: indentWidth)
    if !spaces.isEmpty {
    selectedRange = NSRange(location: lineRange.location, length: 0)
    insertText(indent)
    selectedRange = NSRange(location: rangeForTextChange.location + indentWidth, length: 0)
    return
    }
    super.insertText(indent)
    return
    }
    indent = "\t"
    if !spaces.isEmpty {
    selectedRange = NSRange(location: lineRange.location, length: 0)
    insertText(indent)
    selectedRange = NSRange(location: rangeForTextChange.location + 1, length: 0)
    return
    }
    }

    View Slide

  95. func insertTab(rangeForTextChange: NSRange) {
    var indent = ""
    let (spaces, lineRange) = getPrefixStringIfLineHasList(regex: TextInputRegex.insertTab,
    rangeForTextChange: rangeForTextChange)
    if useIndentWithSpaceKey {
    indent = String(repeating: " ", count: indentWidth)
    if !spaces.isEmpty {
    selectedRange = NSRange(location: lineRange.location, length: 0)
    insertText(indent)
    selectedRange = NSRange(location: rangeForTextChange.location + indentWidth, length: 0)
    return
    }
    super.insertText(indent)
    return
    }
    indent = "\t"
    if !spaces.isEmpty {
    selectedRange = NSRange(location: lineRange.location, length: 0)
    insertText(indent)
    selectedRange = NSRange(location: rangeForTextChange.location + 1, length: 0)
    return
    }
    } Χʔιϧ͕ظ଴ͨ͠Ґஔʹ͍ͳ͍ͷͰઌ಄ʹҠಈɺૠೖޙɺ
    ૠೖޙͷҐஔʹҠಈ͢Δඞཁ͕͋Δɻ

    View Slide

  96. ·ͱΊ

    View Slide

  97. ·ͱΊ
    wղੳ͸ͳΔ΂͘࠷খݶͷൣғͰղੳ͢Δ

    View Slide

  98. ·ͱΊ
    wղੳ͸ͳΔ΂͘࠷খݶͷൣғͰߦ͏
    wจࣈίʔυΛҙࣝͨ͠จࣈྻૢ࡞
    w⚠/44USJOH /43BOHF

    View Slide

  99. ·ͱΊ
    wղੳ͸ͳΔ΂͘࠷খݶͷൣғͰߦ͏
    wจࣈίʔυΛҙࣝͨ͠จࣈྻૢ࡞
    w⚠/44USJOH /43BOHF
    wೖྗิࠤ࣌ͷJ04Ͱͷઃܭ
    wΧελϚΠζ͕ඞਢ

    View Slide

  100. 5IBOLT

    View Slide

  101. &OKPZ

    View Slide