Skip to content
This repository has been archived by the owner on Apr 2, 2024. It is now read-only.

Commit

Permalink
Rewrite style-line that preserve cursor position
Browse files Browse the repository at this point in the history
- Preserve cursor position after applied style
- Better toggle on task lists
  • Loading branch information
zhuochun committed Oct 15, 2018
1 parent 97e6405 commit 573e83e
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 110 deletions.
1 change: 1 addition & 0 deletions keymaps/sample-linux.cson
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
".platform-linux atom-text-editor:not([mini])":
"shift-ctrl-K": "markdown-writer:insert-link"
"shift-ctrl-I": "markdown-writer:insert-image"
"shift-ctrl-X": "markdown-writer:toggle-taskdone"
"ctrl-i": "markdown-writer:toggle-italic-text"
"ctrl-b": "markdown-writer:toggle-bold-text"
"ctrl-'": "markdown-writer:toggle-code-text"
Expand Down
1 change: 1 addition & 0 deletions keymaps/sample-osx.cson
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
".platform-darwin atom-text-editor:not([mini])":
"shift-cmd-K": "markdown-writer:insert-link"
"shift-cmd-I": "markdown-writer:insert-image"
"shift-cmd-X": "markdown-writer:toggle-taskdone"
"cmd-i": "markdown-writer:toggle-italic-text"
"cmd-b": "markdown-writer:toggle-bold-text"
"cmd-'": "markdown-writer:toggle-code-text"
Expand Down
1 change: 1 addition & 0 deletions keymaps/sample-win32.cson
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
".platform-win32 atom-text-editor:not([mini])":
"shift-ctrl-K": "markdown-writer:insert-link"
"shift-ctrl-I": "markdown-writer:insert-image"
"shift-ctrl-X": "markdown-writer:toggle-taskdone"
"ctrl-i": "markdown-writer:toggle-italic-text"
"ctrl-b": "markdown-writer:toggle-bold-text"
"ctrl-'": "markdown-writer:toggle-code-text"
Expand Down
81 changes: 58 additions & 23 deletions lib/commands/style-line.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,26 @@ class StyleLine
i: i + 1,
ul: config.get("templateVariables.ulBullet#{@editor.indentationForBufferRow(row)}") || config.get("templateVariables.ulBullet")

selection.cursor.setBufferPosition([row, 0])
selection.selectToEndOfLine()

if line = selection.getText()
if line = @editor.lineTextForBufferRow(row)
@toggleStyle(selection, line, data)
else
@insertEmptyStyle(selection, data)

# select the whole range, if selection contains multiple rows
selection.setBufferRange(range) if rows[0] != rows[1]

toggleStyle: (selection, line, data) ->
if @isStyleOn(line)
text = @removeStyle(line)
else
text = @addStyle(line, data)

selection.insertText(text)

insertEmptyStyle: (selection, data) ->
selection.insertText(utils.template(@style.before, data))
position = selection.cursor.getBufferPosition()
selection.insertText(utils.template(@style.after, data))
selection.cursor.setBufferPosition(position)

toggleStyle: (selection, line, data) ->
if @isStyleOn(line)
@removeStyle(selection, line, data)
else
@addStyle(selection, line, data)

# use regexMatchBefore/regexMatchAfter to match the string
isStyleOn: (text) ->
/// ^(\s*) # start with any spaces
Expand All @@ -69,22 +65,61 @@ class StyleLine
#{@style.regexMatchAfter} # style end
(\s*)$ ///i.test(text)

addStyle: (text, data) ->
before = utils.template(@style.before, data)
after = utils.template(@style.after, data)
addStyle: (selection, text, data) ->
# ["- [ ] body", "", "- [ ] ", "body", undefined, ""]
match = @getStylePattern().exec(text)
return unless match # ignore cases that not match, which shouldn't be
# ["- [ ] body", "", "- [ ] ", "-", "body", undefined, ""] (with capture=true)
if @style.captureBefore
data["captureBefore"] = match.splice(3, 1)[0] || data[@style.captureBefore]

# construct new before/after text
newBefore = utils.template(@style.before, data)
newAfter = utils.template(@style.after, data)
@applyStyle(selection, match, newBefore, newAfter)

removeStyle: (selection, text, data) ->
# ["- [ ] body", "", "- [ ] ", "body", undefined, ""]
match = @getStylePattern().exec(text)
if match
"#{match[1]}#{before}#{match[2]}#{after}#{match[3]}"
else
"#{before}#{after}"
return unless match # ignore cases that not match, which shouldn't be
# ["- [ ] body", "", "- [ ] ", "-", "body", undefined, ""] (with capture=true)
if @style.captureBefore
data["captureBefore"] = match.splice(3, 1)[0] || data[@style.captureBefore]

# construct new before/after text
newBefore = utils.template(@style.emptyBefore || "", data)
newAfter = utils.template(@style.emptyAfter || "", data)
@applyStyle(selection, match, newBefore, newAfter)

removeStyle: (text) ->
matches = @getStylePattern().exec(text)
return matches[1..].join("")
applyStyle: (selection, match, newBefore, newAfter) ->
position = selection.cursor.getBufferPosition()

# replace text in line
selection.cursor.setBufferPosition([position.row, 0])
selection.selectToEndOfLine()
selection.insertText("#{match[1]}#{newBefore}#{match[3]}#{newAfter}#{match[5]}")

# recover original position in the new text
m1 = match[1].length
m2 = (match[2] || "").length
m3 = match[3].length
m4 = (match[4] || "").length
# find new position
if position.column < m1
# no change
else if position.column < m1 + m2
position.column = m1 + newBefore.length # move to end of newBefore
else if position.column < m1 + m2 + m3
position.column += newBefore.length - m2
else if position.column < m1 + m2 + m3 + m4
position.column += m1 + m2 + m3 + newAfter.length # move to end of newAfter
else # at the end of line
position = selection.cursor.getBufferPosition()
# set cursor position
selection.cursor.setBufferPosition(position)

getStylePattern: ->
before = @style.regexBefore || utils.escapeRegExp(@style.before)
after = @style.regexAfter || utils.escapeRegExp(@style.after)

/// ^(\s*) (?:#{before})? (.*?) (?:#{after})? (\s*)$ ///i
/// ^(\s*) (#{before})? (.*?) (#{after})? (\s*)$ ///i
22 changes: 17 additions & 5 deletions lib/config.cson
Original file line number Diff line number Diff line change
Expand Up @@ -170,16 +170,28 @@ lineStyles:
regexMatchBefore: "(?:\\d+[\\.\\)]|[a-zA-Z]+[\\.\\)])\\s"
regexBefore: "(?:-|\\*|\\+|\\.|\\d+[\\.\\)]|[a-zA-Z]+[\\.\\)])\\s"
task:
before: "{ul} [ ] "
before: "{captureBefore} [ ] "
regexMatchBefore: "(?:-|\\*|\\+|\\d+[\\.\\)])\\s+\\[ ]\\s"
regexBefore: "(?:-|\\*|\\+|\\d+[\\.\\)]|[a-zA-Z]+[\\.\\)])\\s*(?:\\[[xX ]])?\\s"
regexBefore: "(-|\\*|\\+|\\d+[\\.\\)]|[a-zA-Z]+[\\.\\)])\\s*(?:\\[[xX ]])?\\s"
captureBefore: "ul"
taskdone:
before: "{ul} [x] "
before: "{captureBefore} [x] "
regexMatchBefore: "(?:-|\\*|\\+|\\d+[\\.\\)])\\s+\\[[xX]]\\s"
regexBefore: "(?:-|\\*|\\+|\\d+[\\.\\)]|[a-zA-Z]+[\\.\\)])\\s*(?:\\[[xX ]])?\\s"
regexBefore: "(-|\\*|\\+|\\d+[\\.\\)]|[a-zA-Z]+[\\.\\)])\\s*(?:\\[[xX ]])?\\s"
captureBefore: "ul"
emptyBefore: "{captureBefore} [ ] "
blockquote: before: "> "

# Image tag template
# Image tag template, available variables:
#
# - src: image path in local if copied or same as rawSrc
# - relativeFileSrc: image path relative to your edit file
# - relativeSiteSrc: image path relative to your open project
# - alt: alt text
# - width: width of image
# - height: height of image
# - align: alignment of image
#
imageTag: "![{alt}]({src})"
# Use relative path to image from the opened file
relativeImagePath: false
Expand Down
135 changes: 53 additions & 82 deletions spec/commands/style-line-spec.coffee
Original file line number Diff line number Diff line change
@@ -1,101 +1,60 @@
StyleLine = require "../../lib/commands/style-line"

describe "StyleLine", ->
describe ".isStyleOn", ->
it "check heading 1 exists", ->
cmd = new StyleLine("h1")
fixture = "# heading 1"
expect(cmd.isStyleOn(fixture)).toBe(true)

it "check heading 1 not exists", ->
cmd = new StyleLine("h1")
fixture = "## heading 1"
expect(cmd.isStyleOn(fixture)).toBe(false)

it "check ul exists", ->
cmd = new StyleLine("ul")
fixture = "* unordered list"
expect(cmd.isStyleOn(fixture)).toBe(true)
fixture = "- unordered list"
expect(cmd.isStyleOn(fixture)).toBe(true)

it "check ul not exists", ->
cmd = new StyleLine("ul")
fixture = "a normal list"
expect(cmd.isStyleOn(fixture)).toBe(false)
fixture = "0. ordered list"
expect(cmd.isStyleOn(fixture)).toBe(false)

describe ".addStyle", ->
it "applies heading 1 styles", ->
atom.config.set("markdown-writer.lineStyles.h1", before: "# ", after: " #")
cmd = new StyleLine("h1")
fixture = "## heading 1 ##"
expect(cmd.addStyle(fixture)).toBe("# heading 1 #")

it "applies heading 2 styles", ->
cmd = new StyleLine("h2")
fixture = "# heading 2"
expect(cmd.addStyle(fixture)).toBe("## heading 2")

it "applies blockquote styles", ->
cmd = new StyleLine("blockquote")
fixture = "blockquote"
expect(cmd.addStyle(fixture)).toBe("> blockquote")

it "applies unordered list template styles", ->
cmd = new StyleLine("ul")
fixture = " unordered line"
expect(cmd.addStyle(fixture, { ul: "*" })).toBe(" * unordered line")

it "applies ordered list template styles", ->
cmd = new StyleLine("ol")
fixture = "ordered line"
expect(cmd.addStyle(fixture, { i: 3 })).toBe("3. ordered line")

describe ".removeStyle", ->
it "applies heading 1 styles", ->
atom.config.set("markdown-writer.lineStyles.h1", before: "# ", after: " #")
cmd = new StyleLine("h1")
fixture = "# heading 1 #"
expect(cmd.removeStyle(fixture)).toBe("heading 1")

it "remove heading 3 styles", ->
cmd = new StyleLine("h3")
fixture = "### heading 3"
expect(cmd.removeStyle(fixture)).toBe("heading 3")

it "remove ol styles", ->
cmd = new StyleLine("ol")
fixture = "123. ordered list"
expect(cmd.removeStyle(fixture)).toBe("ordered list")

describe ".trigger", ->
editor = null

beforeEach ->
waitsForPromise -> atom.workspace.open("empty.markdown")
runs -> editor = atom.workspace.getActiveTextEditor()

it "insert empty blockquote style", ->
editor = null

beforeEach ->
waitsForPromise -> atom.workspace.open("empty.markdown")
runs -> editor = atom.workspace.getActiveTextEditor()

describe "blockquote", ->
it "insert empty blockquote", ->
new StyleLine("blockquote").trigger()
expect(editor.getText()).toBe("> ")
expect(editor.getCursorBufferPosition().column).toBe(2)

it "remove blockquote", ->
editor.setText("> blockquote")
editor.setCursorBufferPosition([0, 4])

new StyleLine("blockquote").trigger()
expect(editor.getText()).toBe("blockquote")
expect(editor.getCursorBufferPosition().column).toBe(2)

describe "headings", ->
it "apply heading 2", ->
editor.setText("# heading")
editor.setCursorBufferPosition([0, 3])

new StyleLine("h2").trigger()
expect(editor.getText()).toBe("## heading")
expect(editor.getCursorBufferPosition().column).toBe(10)
expect(editor.getCursorBufferPosition().column).toBe(4)

it "remove heading 3", ->
editor.setText("### heading")
editor.setCursorBufferPosition([0, 7])

new StyleLine("h3").trigger()
expect(editor.getText()).toBe("heading")
expect(editor.getCursorBufferPosition().column).toBe(3)

it "apply/remove heading 5", ->
atom.config.set("markdown-writer.lineStyles.h5", before: "##### ", after: " #####")

editor.setText("## heading")
editor.setCursorBufferPosition([0, 2]) # inside ##

new StyleLine("h5").trigger()
expect(editor.getText()).toBe("##### heading #####")
expect(editor.getCursorBufferPosition().column).toBe(6) # move to end of #

editor.setCursorBufferPosition([0, 16]) # close to the end of line

new StyleLine("h5").trigger()
expect(editor.getText()).toBe("heading")
expect(editor.getCursorBufferPosition().column).toBe(7)

describe "lists", ->
it "apply ordered/unordered list", ->
editor.setText("- list")

Expand All @@ -107,17 +66,29 @@ describe "StyleLine", ->
expect(editor.getText()).toBe("- list")
expect(editor.getCursorBufferPosition().column).toBe(6)

it "apply task/taskdone list", ->
it "apply task list", ->
editor.setText("task")

new StyleLine("task").trigger()
expect(editor.getText()).toBe("- [ ] task")

new StyleLine("taskdone").trigger()
expect(editor.getText()).toBe("- [x] task")
new StyleLine("task").trigger()
expect(editor.getText()).toBe("task")

it "apply task ol list", ->
editor.setText("1. task")

new StyleLine("task").trigger()
expect(editor.getText()).toBe("- [ ] task")
expect(editor.getText()).toBe("1. [ ] task")

new StyleLine("task").trigger()
expect(editor.getText()).toBe("task")

it "apply taskdone ol list", ->
editor.setText("1. [ ] task")

new StyleLine("taskdone").trigger()
expect(editor.getText()).toBe("1. [x] task")

new StyleLine("taskdone").trigger()
expect(editor.getText()).toBe("1. [ ] task")

0 comments on commit 573e83e

Please sign in to comment.