init.lua 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. -- bookmarks.lua
  2. -- Save and jump between named positions in files
  3. -- ============================================================================
  4. local bookmarks = {}
  5. -- Configuration
  6. bookmarks.config = {
  7. enabled = true,
  8. max_bookmarks = 100,
  9. }
  10. -- Bookmarks storage: name -> {filepath, line, column}
  11. bookmarks.marks = {}
  12. -- Quick marks (single character, like Vim marks)
  13. bookmarks.quick_marks = {}
  14. -- Set a named bookmark at current position
  15. function bookmarks.set(name)
  16. if not name or name == "" then
  17. return {success = false, message = "Bookmark name required"}
  18. end
  19. local buffer = editor:current_buffer()
  20. local filepath = buffer.filepath or buffer.name
  21. local pos = editor:cursor_pos()
  22. bookmarks.marks[name] = {
  23. filepath = filepath,
  24. line = pos.line,
  25. column = pos.column,
  26. created = os.time(),
  27. }
  28. return {success = true, message = string.format("Bookmark '%s' set at %s:%d", name, filepath, pos.line + 1)}
  29. end
  30. -- Jump to a named bookmark
  31. function bookmarks.jump(name)
  32. if not name or name == "" then
  33. return {success = false, message = "Bookmark name required"}
  34. end
  35. local mark = bookmarks.marks[name]
  36. if not mark then
  37. return {success = false, message = "Bookmark not found: " .. name}
  38. end
  39. -- Check if we need to switch buffers
  40. local buffer = editor:current_buffer()
  41. local current_file = buffer.filepath or buffer.name
  42. if current_file ~= mark.filepath then
  43. -- Try to open the file
  44. local result = editor:execute_command("find-file", {mark.filepath})
  45. if not result.success then
  46. return {success = false, message = "Cannot open file: " .. mark.filepath}
  47. end
  48. end
  49. -- Move to the bookmark position
  50. editor:set_cursor(mark.line, mark.column)
  51. return {success = true, message = string.format("Jumped to '%s'", name)}
  52. end
  53. -- Delete a bookmark
  54. function bookmarks.delete(name)
  55. if not name or name == "" then
  56. return {success = false, message = "Bookmark name required"}
  57. end
  58. if not bookmarks.marks[name] then
  59. return {success = false, message = "Bookmark not found: " .. name}
  60. end
  61. bookmarks.marks[name] = nil
  62. return {success = true, message = "Deleted bookmark: " .. name}
  63. end
  64. -- List all bookmarks
  65. function bookmarks.list()
  66. local result = {}
  67. for name, mark in pairs(bookmarks.marks) do
  68. local display_file = mark.filepath:gsub("^" .. (os.getenv("HOME") or ""), "~")
  69. table.insert(result, {
  70. name = name,
  71. filepath = mark.filepath,
  72. display = string.format("%s: %s:%d", name, display_file, mark.line + 1),
  73. })
  74. end
  75. -- Sort by name
  76. table.sort(result, function(a, b) return a.name < b.name end)
  77. return result
  78. end
  79. -- Quick mark (single character, for quick navigation)
  80. function bookmarks.set_quick(char)
  81. if not char or #char ~= 1 then
  82. return {success = false, message = "Quick mark must be a single character"}
  83. end
  84. local buffer = editor:current_buffer()
  85. local filepath = buffer.filepath or buffer.name
  86. local pos = editor:cursor_pos()
  87. bookmarks.quick_marks[char] = {
  88. filepath = filepath,
  89. line = pos.line,
  90. column = pos.column,
  91. }
  92. return {success = true, message = string.format("Mark '%s' set", char)}
  93. end
  94. -- Jump to quick mark
  95. function bookmarks.jump_quick(char)
  96. if not char or #char ~= 1 then
  97. return {success = false, message = "Quick mark must be a single character"}
  98. end
  99. local mark = bookmarks.quick_marks[char]
  100. if not mark then
  101. return {success = false, message = "Mark not set: " .. char}
  102. end
  103. local buffer = editor:current_buffer()
  104. local current_file = buffer.filepath or buffer.name
  105. if current_file ~= mark.filepath then
  106. local result = editor:execute_command("find-file", {mark.filepath})
  107. if not result.success then
  108. return {success = false, message = "Cannot open file: " .. mark.filepath}
  109. end
  110. end
  111. editor:set_cursor(mark.line, mark.column)
  112. return {success = true, message = string.format("Jumped to mark '%s'", char)}
  113. end
  114. -- Setup function
  115. function bookmarks.setup(opts)
  116. opts = opts or {}
  117. for k, v in pairs(opts) do
  118. bookmarks.config[k] = v
  119. end
  120. if not bookmarks.config.enabled then
  121. return
  122. end
  123. -- Register commands
  124. editor:register_command("bookmark-set", "Set a named bookmark at current position", function(args)
  125. if #args == 0 then
  126. return {success = true, message = "Usage: bookmark-set <name>"}
  127. end
  128. return bookmarks.set(args[1])
  129. end, {}, true, "s")
  130. editor:register_command("bookmark-jump", "Jump to a named bookmark", function(args)
  131. if #args == 0 then
  132. -- List available bookmarks
  133. local list = bookmarks.list()
  134. if #list == 0 then
  135. return {success = true, message = "No bookmarks set"}
  136. end
  137. local displays = {}
  138. for _, b in ipairs(list) do
  139. table.insert(displays, b.display)
  140. end
  141. editor:set_echo_area(displays)
  142. return {success = true, message = ""}
  143. end
  144. return bookmarks.jump(args[1])
  145. end, {}, true, "s")
  146. editor:register_command("bookmark-delete", "Delete a bookmark", function(args)
  147. if #args == 0 then
  148. return {success = true, message = "Usage: bookmark-delete <name>"}
  149. end
  150. return bookmarks.delete(args[1])
  151. end, {}, true, "s")
  152. editor:register_command("bookmark-list", "List all bookmarks", function(args)
  153. local list = bookmarks.list()
  154. if #list == 0 then
  155. return {success = true, message = "No bookmarks set"}
  156. end
  157. local displays = {}
  158. for _, b in ipairs(list) do
  159. table.insert(displays, b.display)
  160. end
  161. editor:set_echo_area(displays)
  162. return {success = true, message = ""}
  163. end)
  164. -- Quick marks (Vim-style m<char> and '<char>)
  165. editor:register_command("mark-set", "Set a quick mark (single character)", function(args)
  166. if #args == 0 then
  167. return {success = true, message = "Usage: mark-set <char>"}
  168. end
  169. return bookmarks.set_quick(args[1])
  170. end, {}, true, "c")
  171. editor:register_command("mark-jump", "Jump to a quick mark", function(args)
  172. if #args == 0 then
  173. return {success = true, message = "Usage: mark-jump <char>"}
  174. end
  175. return bookmarks.jump_quick(args[1])
  176. end, {}, true, "c")
  177. -- Key bindings
  178. -- C-x r m - set bookmark (Emacs)
  179. -- C-x r b - jump to bookmark (Emacs)
  180. -- C-x r l - list bookmarks (Emacs)
  181. editor:bind_key("C-x r m", "bookmark-set", "Set bookmark")
  182. editor:bind_key("C-x r b", "bookmark-jump", "Jump to bookmark")
  183. editor:bind_key("C-x r l", "bookmark-list", "List bookmarks")
  184. -- Quick marks with F-keys for convenience
  185. editor:bind_key("F2", "mark-set", "Set quick mark")
  186. editor:bind_key("S-F2", "mark-jump", "Jump to quick mark")
  187. print("[bookmarks] Package loaded")
  188. end
  189. -- Auto-setup with defaults
  190. bookmarks.setup()
  191. return bookmarks