Command to cycle between paragraph and task

The Task: Cycle state command is helpful, but I’d like a command that cycles between:

paragraph → list item → task

What would be the best way for me to write it? Asking because I notice that the Task: Cycle state command is a part of the Typescript source.

If I understand correctly you want a o write a command which cycles the line between item/task/heading?

If that’s so, you just need to separate the current line from its prefix and change the prefix. I’ve done something similar a while ago to cycle through the heading levels # → ## → ### …etc

Here is the post for inspiration:

is this what you meant?

command.define {
  name = "Line: Toggle paragraph / list / task",
  key = "Ctrl-Alt-Tab",
  run = function()
    local line = editor.getCurrentLine()
    if not line or not line.textWithCursor then return end

    local s = line.textWithCursor

    -- task → paragraph
    if s:match("^%s*%- %[[ xX]%] ") then
      s = s:gsub("^%s*%- %[[ xX]%] ", "", 1)

    -- list → task
    elseif s:match("^%s*%- ") then
      s = s:gsub("^%s*%- ", "- [ ] ", 1)

    -- paragraph → list
    elseif s:match("%S") then
      s = "- " .. s
    else
      return
    end

    editor.replaceRange(line.from, line.to, s, true)
  end
}

1 Like

This is exactly it, thank you very much @Mr.Red!

Two small follow up questions:

  • any idea how I can define the key to be control+enter? I tried Ctrl-CR as GPT insisted, but that doesn’t work
  • IS there an advantage to having this function be in javascript/typescript?

It should be "Ctrl-Enter", but that doesn’t work. "Ctrl-Alt-Enter" does work though.

1 Like

I think the issue with “Ctrl-Enter” is that is already assigned by the system to: “Navigate: To This Page” and depending on the priority your script is running before the system commands. So your shortcut definition might be overwritten by the core command.

You can try two things, change your script’s priority so it’s executed after the core functions. Or change the shortcut key for the “Navigate: To This Page” command.

I definitely wouldn’t use JS for this. Space-lua would always be my first choice. I use JS only where I need some DOM manipulation where lua can’t keep up.

This works:


-- first we disable the Shortcut of the system command which already has the Keys assigned
command.update {
  name = "Navigate: To This Page",
  key = "",
  mac = ""
}

-- and then define a command with the new shortcut keys
command.define {
  name = "Line: Toggle paragraph / list / task",
  key = "Ctrl-Enter",
  mac = "Cmd-Enter",
  run = function()

--    ... your code here ...

  end
}
1 Like

Ah, understood. Works beautifully, thanks a ton @Mr.Red !

1 Like

For anyone who’s following along, two small tweaks to @Mr.Red 's solution to better fit my own use case:

  • Preserved space at the beginning so it works for indents
  • For my use case, I removed it from cycling through a paragraph. It now toggles between task and list, but if it was a paragraph, then does a one time conversion to list (YMMV)

-- first we disable the Shortcut of the system command which already has the Keys assigned
command.update {
  name = "Navigate: To This Page",
  key = "",
  mac = ""
}

-- and then define a command with the new shortcut keys
command.define {
  name = "Line: Toggle paragraph / list / task",
  key = "Ctrl-Enter",
  run = function()
    local line = editor.getCurrentLine()
    if not line or not line.textWithCursor then return end

    local s = line.textWithCursor

    -- task → list
    if s:match("^(%s*)%- %[[ xX]%] ") then
      s = s:gsub("^(%s*)%- %[[ xX]%] ", "%1- ", 1)
    
    -- list → task
    elseif s:match("^(%s*)%- ") then
      s = s:gsub("^(%s*)%- ", "%1- [ ] ", 1)
    
    -- paragraph → list (one-time)
    elseif s:match("%S") then
      s = "- " .. s
    
    -- empty / whitespace-only line
    else
      return
    end

    editor.replaceRange(line.from, line.to, s, true)
  end
}

1 Like