├── .common.py.swp ├── __init.py__ ├── messages.json ├── Context.sublime-menu ├── messages ├── 1.0.2.md ├── 2.0.0.md ├── 1.0.3.md └── install.txt ├── Default (Windows).sublime-keymap ├── .gitignore ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── bookmarkWatcher.py ├── Main.sublime-menu ├── Commands.sublime-commands ├── common.py ├── ui.py ├── visibilityHandler.py ├── bookmark.py ├── README.md ├── LICENSE └── sublimebookmark.py /.common.py.swp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /__init.py__: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.txt" 3 | } -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "-" 4 | }, 5 | 6 | { 7 | "caption": "Add bookmark", 8 | "command": "sublime_bookmark", 9 | "args" : { "type" : "add" } 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /messages/1.0.2.md: -------------------------------------------------------------------------------- 1 | Sorry, this update breaks the save files, so all your bookmarks will be deleted :( 2 | 3 | I'll ensure that this *will not happen* in the future. Thanks for your cooperation, 4 | and sorry again! 5 | 6 | Yours humbly, 7 | ~bollu [https://github.com/bollu/] -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+shift+f2"], "command": "sublime_bookmark", "args" : { "type" : "add" } }, 3 | { "keys": ["f2"], "command": "sublime_bookmark", "args" : { "type" : "goto_next" } }, 4 | { "keys": ["shift+f2"], "command": "sublime_bookmark", "args" : { "type" : "goto_prev" } }, 5 | { "keys": ["ctrl+f2"], "command": "sublime_bookmark", "args" : { "type" : "toggle_line" } } 6 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+shift+f2"], "command": "sublime_bookmark", "args" : { "type" : "add" } }, 3 | { "keys": ["f2"], "command": "sublime_bookmark", "args" : { "type" : "goto_next" } }, 4 | { "keys": ["shift+f2"], "command": "sublime_bookmark", "args" : { "type" : "goto_previous" } }, 5 | { "keys": ["alt+f2"], "command": "sublime_bookmark", "args" : { "type" : "goto" } }, 6 | { "keys": ["ctrl+f2"], "command": "sublime_bookmark", "args" : { "type" : "toggle_line" } } 7 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["super+shift+f2"], "command": "sublime_bookmark", "args" : { "type" : "add" } }, 3 | { "keys": ["f2"], "command": "sublime_bookmark", "args" : { "type" : "goto_next" } }, 4 | { "keys": ["shift+f2"], "command": "sublime_bookmark", "args" : { "type" : "goto_previous" } }, 5 | { "keys": ["ctrl+f2"], "command": "sublime_bookmark", "args" : { "type" : "goto" } }, 6 | { "keys": ["super+f2"], "command": "sublime_bookmark", "args" : { "type" : "toggle_line" } } 7 | ] 8 | -------------------------------------------------------------------------------- /messages/2.0.0.md: -------------------------------------------------------------------------------- 1 | Sorry everybody, I've had a lot of college stuff to do and I haven't been able to work on 2 | this for a long time. 3 | 4 | However, I've taken the time to learn python and rewrite this from scratch. the codebase is sane, the features work, and most importantly - no more weirdness :) 5 | 6 | I'm sorry for all the trouble I've caused with the first two iterations - But I'm a newbie at python. This is not an excuse, I know, but is hopefully some form of apology. 7 | 8 | Please enjoy using the plugin, and as usual - contact me if you have any bugs / enhancements 9 | to [github](https://github.com/bollu/sublimeBookmark/issues?state=open). 10 | 11 | Thanks again! 12 | ~[bollu](https://github.com/bollu/) -------------------------------------------------------------------------------- /messages/1.0.3.md: -------------------------------------------------------------------------------- 1 | Projects can now coexist peacefully with bookmarks :) 2 | 3 | Next time you use sublimeBookmarks, it will only show bookmarks belonging to the project 4 | you were working on. 5 | 6 | Also, the bookmarks will be *wiped one _last_ time*. I'm really sorry about this. 7 | 8 | If you had created bookmarks without a project, then these bookmarks will always be shown. 9 | These settings can be changed by going to _Preferences->Package Settings->sublimeBookmarks->Settings (Default)_,copy-pasting the settings to _Preferences->Package Settings->sublimeBookmarks->Settings (User)_ and then changing them. 10 | 11 | Have fun, and as usual, report all bugs to [github](https://github.com/bollu/sublimeBookmark/issues?state=open). 12 | 13 | Thanks for using the plugin :) 14 | ~bollu [https://github.com/bollu/] -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | Thanks for installing *SublimeBookmarks*. I hope this plugin helps you :) 2 | 3 | To Use 4 | ====== 5 | 6 | Visit the github page for a quick intro. [https://github.com/bollu/sublimeBookmark] 7 | 8 | Press Ctrl+Shift+P (or Cmd+Shift+P depending on your OS). 9 | Use _Add Bookmark_ to add a bookmark. 10 | Use _Goto Bookmark_ to goto a bookmark 11 | Use _Remove Bookmark_ to remove a bookmark 12 | Use _Remove All Bookmarks_ to clear the list of bookmarks 13 | 14 | There are three modes to the plugin: 15 | 1) All bookmarks will be shown all the time - bookmarks will not be filtered based on the current project. For this mode, use the __Show All Bookmarks__ option. 16 | 17 | 2) Only bookmarks of the current project will be shown - bookmarks belonging to other projects are *NOT* shown in this mode. Use the Use the __Show Bookmarks In Current Project__ for this mode. 18 | 19 | 3) Only bookmarks in the current file is shown - irrespective of project. Use the __Show Bookmarks In Current File__ option for this mode. 20 | 21 | I hope you enjoy using the plugin as much as I enjoyed writing it :) 22 | 23 | Thanks again! 24 | ~(bollu)[https://github.com/bollu/] 25 | -------------------------------------------------------------------------------- /bookmarkWatcher.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | 4 | 5 | class bookmarkWatcher(sublime_plugin.EventListener): 6 | 7 | def on_activated_async(self, view): 8 | sublime.active_window().run_command("sublime_bookmark", 9 | {"type": "mark_buffer"}) 10 | sublime.active_window().run_command("sublime_bookmark", 11 | {"type": "move_bookmarks"}) 12 | 13 | def on_modified_async(self, view): 14 | sublime.active_window().run_command("sublime_bookmark", 15 | {"type": "move_bookmarks"}) 16 | 17 | def on_deactivated_async(self, view): 18 | sublime.active_window().run_command("sublime_bookmark", 19 | {"type": "mark_buffer"}) 20 | sublime.active_window().run_command("sublime_bookmark", 21 | {"type": "move_bookmarks"}) 22 | 23 | # Must be on close 24 | # not on pre close. on pre-close,the view still exists 25 | def on_close(self, view): 26 | sublime.active_window().run_command("sublime_bookmark", 27 | {"type": "update_temporary"}) 28 | 29 | def on_pre_save_async(self, view): 30 | pass 31 | # sublime.run_command("sublime_bookmark", {"type": "save_data" } ) 32 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | 2 | [ 3 | { 4 | "caption": "Edit", 5 | "mnemonic": "E", 6 | "id": "edit", 7 | "children": 8 | [ 9 | { 10 | "caption": "Code Folding", 11 | "id": "fold", 12 | "children": 13 | [ 14 | {"caption":"-"}, 15 | { "caption": "Refold", "command": "buffer_scroll_re_fold" } 16 | ] 17 | } 18 | ] 19 | }, 20 | { 21 | "caption": "Preferences", 22 | "mnemonic": "n", 23 | "id": "preferences", 24 | "children": 25 | [ 26 | {"caption":"-"}, 27 | { 28 | "caption": "Package Settings", 29 | "mnemonic": "P", 30 | "id": "package-settings", 31 | "children": 32 | [ 33 | { 34 | "caption": "Sublime Bookmarks", 35 | "children": 36 | [ 37 | { "caption": "-" }, 38 | { 39 | "command": "open_file", 40 | "args": { 41 | "file": "${packages}/Sublime Bookmarks/Default (OSX).sublime-keymap", 42 | "platform": "OSX" 43 | }, 44 | "caption": "Key Bindings – Default" 45 | }, 46 | { 47 | "command": "open_file", 48 | "args": { 49 | "file": "${packages}/Sublime Bookmarks/Default (Linux).sublime-keymap", 50 | "platform": "Linux" 51 | }, 52 | "caption": "Key Bindings – Default" 53 | }, 54 | { 55 | "command": "open_file", 56 | "args": { 57 | "file": "${packages}/Sublime Bookmarks/Default (Windows).sublime-keymap", 58 | "platform": "Windows" 59 | }, 60 | "caption": "Key Bindings – Default" 61 | }, 62 | { 63 | "command": "open_file", 64 | "args": { 65 | "file": "${packages}/User/Default.sublime-keymap" 66 | }, 67 | "caption": "Key Bindings – User" 68 | }, 69 | { "caption": "-" } 70 | ] 71 | } 72 | ] 73 | } 74 | ] 75 | } 76 | ] 77 | -------------------------------------------------------------------------------- /Commands.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | //Add a bookmark 3 | { 4 | "caption": "SublimeBookmarks: Add Bookmark", 5 | "command": "sublime_bookmark", 6 | "args" : { "type" : "add" } 7 | 8 | }, 9 | 10 | //Goto a specific bookmark 11 | { 12 | "caption": "SublimeBookmarks: Goto Bookmark", 13 | "command": "sublime_bookmark", 14 | "args" : { "type" : "goto" } 15 | }, 16 | //Remove a single bookmark 17 | { 18 | "caption": "SublimeBookmarks: Remove A Bookmark", 19 | "command": "sublime_bookmark", 20 | "args" : { "type" : "remove" } 21 | }, 22 | 23 | { 24 | "caption": "SublimeBookmarks: Goto Next Bookmark", 25 | "command": "sublime_bookmark", 26 | "args" : { "type" : "goto_next" } 27 | }, 28 | 29 | { 30 | "caption": "SublimeBookmarks: Goto Previous Bookmark", 31 | "command": "sublime_bookmark", 32 | "args" : { "type" : "goto_previous" } 33 | }, 34 | 35 | { 36 | "caption": "SublimeBookmarks: Remove All Bookmarks (Clear Bookmarks)", 37 | "command": "sublime_bookmark", 38 | "args" : { "type" : "remove_all" } 39 | }, 40 | { 41 | "caption": "SublimeBookmarks: Toggle Current Line", 42 | "command": "sublime_bookmark", 43 | "args" : { "type" : "toggle_line" } 44 | }, 45 | { 46 | "caption": "SublimeBookmarks: Show All Bookmarks ", 47 | "command": "sublime_bookmark", 48 | "args" : { "type" : "show_all_bookmarks" } 49 | }, 50 | 51 | { 52 | "caption": "SublimeBookmarks: Show Bookmarks Only In Current Project ", 53 | "command": "sublime_bookmark", 54 | "args" : { "type" : "show_project_bookmarks" } 55 | }, 56 | 57 | { 58 | "caption": "SublimeBookmarks: Show Bookmarks Only In Current File ", 59 | "command": "sublime_bookmark", 60 | "args" : { "type" : "show_file_bookmarks" } 61 | }, 62 | 63 | 64 | ] 65 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import os.path 3 | 4 | # if someone names their project this, we're boned 5 | NO_PROJECT = "___NO_PROJECT_PRESENT____" 6 | 7 | 8 | REGION_BASE_TAG = int(11001001000011111101) 9 | SETTINGS_NAME = "SublimeBookmarks.sublime-settings" 10 | 11 | VERSION = "2.0.0" 12 | 13 | 14 | def Log(string): 15 | if True: 16 | print(string) 17 | 18 | 19 | def getViewByBufferID(window, bufferID): 20 | for view in window.views(): 21 | if view.buffer_id() == int(bufferID): 22 | return view 23 | else: 24 | continue 25 | 26 | Log("NO VIEW. BUFFER ID: " + str(bufferID)) 27 | return None 28 | 29 | 30 | def getCurrentLineRegion(view): 31 | assert (len(view.sel()) > 0) 32 | selectedRegion = view.sel()[0] 33 | region = view.line(selectedRegion) 34 | 35 | return region 36 | 37 | 38 | def getCurrentProjectPath(window): 39 | projectPath = window.project_file_name() 40 | if projectPath is None or projectPath is "": 41 | projectPath = NO_PROJECT 42 | 43 | return projectPath 44 | 45 | 46 | # [HACK] I think? 47 | def getCurrentActiveGroup(window): 48 | # (viewGroup, viewIndex) = window.get_view_index(view) 49 | 50 | return 0 # viewGroup 51 | 52 | 53 | def isLineEmpty(line): 54 | return len(line.strip()) == 0 55 | 56 | 57 | def isViewTemporary(view): 58 | return (view is None) or (view.file_name() is None) 59 | 60 | 61 | def getSavePath(): 62 | # bookmark that represents the 63 | # file from which the panel was activated 64 | currentDir = os.path.dirname(sublime.packages_path()) 65 | return currentDir + '/sublimeBookmarks.pickle' 66 | 67 | 68 | # MESSAGES 69 | def MESSAGE_NoBookmarkToGoto(): 70 | sublime.status_message("SublimeBookmarks: NO ACCEPTABLE BOOKMARKS TO GOTO." 71 | "CHECK CURRENT MODE") 72 | 73 | 74 | def MESSAGE_BookmarkEmpty(): 75 | sublime.status_message("SublimeBookmarks: BOOKMARK EMPTY." 76 | "NOT CREATING BOOKMARK") 77 | -------------------------------------------------------------------------------- /ui.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | 4 | class OptionsSelector: 5 | 6 | def __init__(self, window, panelItems, onDone, onHighlight): 7 | self.window = window 8 | self.panelItems = deepcopy(panelItems) 9 | self.onDone = onDone 10 | self.onHighlight = onHighlight 11 | 12 | def start(self): 13 | startIndex = 0 14 | self.window.show_quick_panel(self.panelItems, 15 | self.onDone, 16 | 0, 17 | startIndex, 18 | self.onHighlight) 19 | 20 | 21 | class OptionsInput: 22 | 23 | def __init__(self, window, caption, initalText, onDone, onCancel): 24 | self.window = window 25 | self.caption = caption 26 | self.initalText = initalText 27 | self.onDone = onDone 28 | self.onCancel = onCancel 29 | 30 | def start(self): 31 | inputPanelView = self.window.show_input_panel(self.caption, 32 | self.initalText, 33 | self.onDone, 34 | None, 35 | self.onCancel) 36 | 37 | # select the text in the view so that 38 | # when the user types a new name, the old name is overwritten 39 | assert (len(inputPanelView.sel()) > 0) 40 | selectionRegion = inputPanelView.full_line(inputPanelView.sel()[0]) 41 | inputPanelView.sel().add(selectionRegion) 42 | 43 | 44 | def createBookmarkPanelItems(window, visibleBookmarks): 45 | def ellipsisStringEnd(string, length): 46 | # I have NO idea why the hell this would happen. But it's happening. 47 | if string is None: 48 | return "" 49 | else: 50 | if len(string) <= length: 51 | return string 52 | else: 53 | return string[0:length - 3] + '.' * 3 54 | 55 | def ellipsisStringBegin(string, length): 56 | if string is None: 57 | return "" 58 | else: 59 | if len(string) <= length: 60 | return string 61 | else: 62 | return '.' * 3 + string[len(string)+3-(length):len(string)] 63 | 64 | bookmarkItems = [] 65 | 66 | for bookmark in visibleBookmarks: 67 | bookmarkName = bookmark.getName() 68 | 69 | bookmarkLine = bookmark.getLineStr().lstrip() 70 | filepath = bookmark.getFilePath().lstrip() 71 | bookmarkFile = ellipsisStringBegin(filepath, 55) 72 | 73 | bookmarkItems.append([bookmarkName, bookmarkLine, bookmarkFile]) 74 | 75 | return bookmarkItems 76 | -------------------------------------------------------------------------------- /visibilityHandler.py: -------------------------------------------------------------------------------- 1 | from .common import * 2 | 3 | 4 | # whether all bookmarks (even unrelated) should be shown 5 | def SHOW_ALL_BOOKMARKS(): 6 | return "Show All Bookmarks" 7 | 8 | 9 | def SHOW_ONLY_PROJECT_BOOKMARKS(): 10 | return "Show Only Project Bookmarks" 11 | 12 | 13 | def SHOW_ONLY_FILE_BOOKMARKS(): 14 | return "Show Only File Bookmarks" 15 | 16 | 17 | def shouldShowBookmark(window, activeView, bookmark, bookmarkMode): 18 | # 1. there is no current project now. Show all bookmarks 19 | # 2. current project matches bookmark path 20 | def isValidProject(currentProjectPath, bookmarkProjectPath): 21 | return currentProjectPath == NO_PROJECT or \ 22 | currentProjectPath == bookmarkProjectPath 23 | 24 | currentFilePath = activeView.file_name() 25 | currentProjectPath = window.project_file_name() 26 | 27 | # free bookmarks can be shown. We don't need a criteria 28 | if bookmarkMode == SHOW_ALL_BOOKMARKS(): 29 | return True 30 | 31 | elif bookmarkMode == SHOW_ONLY_PROJECT_BOOKMARKS() and \ 32 | isValidProject(currentProjectPath, bookmark.getProjectPath()): 33 | return True 34 | 35 | elif bookmarkMode == SHOW_ONLY_FILE_BOOKMARKS() and \ 36 | bookmark.getFilePath() == currentFilePath: 37 | return True 38 | else: 39 | # There are no bookmarks in the current file 40 | return False 41 | 42 | return False 43 | 44 | 45 | def ___sortBookmarks(visibleBookmarks, currentFile): 46 | from collections import defaultdict 47 | 48 | def lineSortFn(bookmark): 49 | return bookmark.getLineNumber() 50 | 51 | def sortByLineNumber(bookmarks): 52 | return sorted(bookmarks, key=lineSortFn) 53 | 54 | fileBookmarks = defaultdict(list) 55 | sortedBookmarks = [] 56 | 57 | for bookmark in visibleBookmarks: 58 | filePath = bookmark.getFilePath() 59 | fileBookmarks[filePath].append(bookmark) 60 | 61 | # take all bookmarks in current file, 62 | # sort them, 63 | # remove the current file from the list of files 64 | currentFileBookmarks = fileBookmarks[currentFile] 65 | sortedBookmarks = sortedBookmarks + sortByLineNumber(currentFileBookmarks) 66 | 67 | del fileBookmarks[currentFile] 68 | 69 | # Iterate over all list of bookmarks in each file 70 | # sort them according to line number 71 | for bookmarkList in fileBookmarks.values(): 72 | sortedBookmarkList = sortByLineNumber(bookmarkList) 73 | sortedBookmarks = sortedBookmarks + sortedBookmarkList 74 | 75 | return sortedBookmarks 76 | 77 | 78 | def getVisibleBookmarks(bookmarks, window, activeView, bookmarkMode): 79 | visibleBookmarks = [] 80 | for bookmark in bookmarks: 81 | if shouldShowBookmark(window, activeView, bookmark, bookmarkMode): 82 | visibleBookmarks.append(bookmark) 83 | 84 | sortedBookmarks = ___sortBookmarks(visibleBookmarks, 85 | activeView.file_name()) 86 | return sortedBookmarks 87 | -------------------------------------------------------------------------------- /bookmark.py: -------------------------------------------------------------------------------- 1 | from .common import * 2 | 3 | 4 | class Bookmark: 5 | 6 | def __init__(self, uid, name, window, activeView): 7 | self.uid = int(uid) 8 | self.name = str(name) 9 | 10 | self.filePath = str(activeView.file_name()) 11 | self.projectPath = getCurrentProjectPath(window) 12 | 13 | region = getCurrentLineRegion(activeView) 14 | self.regionA = int(region.a) 15 | self.regionB = int(region.b) 16 | 17 | self.group = getCurrentActiveGroup(window) 18 | 19 | self.lineStr = activeView.substr(region) 20 | 21 | cursorPos = activeView.sel()[0].begin() 22 | self.lineNumber = activeView.rowcol(cursorPos)[0] 23 | 24 | self.bufferID = activeView.buffer_id() 25 | 26 | def getBufferID(self): 27 | return self.bufferID 28 | 29 | def getName(self): 30 | return self.name 31 | 32 | def getUid(self): 33 | return self.uid 34 | 35 | def getRegion(self): 36 | return sublime.Region(self.regionA, self.regionB) 37 | 38 | def getFilePath(self): 39 | return self.filePath 40 | 41 | def getProjectPath(self): 42 | return self.projectPath 43 | 44 | def getLineNumber(self): 45 | return self.lineNumber 46 | 47 | def getLineStr(self): 48 | return self.lineStr 49 | 50 | def getGroup(self): 51 | return self.group 52 | 53 | def setLineStr(self, newLineStr): 54 | self.lineStr = str(newLineStr) 55 | 56 | def setRegion(self, region): 57 | self.regionA = region.a 58 | self.regionB = region.b 59 | 60 | def setGroup(self, group): 61 | self.group = int(group) 62 | 63 | def isTemporary(self): 64 | return self.filePath == "None" 65 | 66 | def goto(self, window): 67 | view = getBookmarkView(window, self) 68 | assert (view is not None) 69 | 70 | region = self.getRegion() 71 | view.show_at_center(region) 72 | 73 | # move cursor to the middle of the bookmark's region 74 | bookmarkRegionMid = 0.5 * (region.begin() + region.end()) 75 | moveRegion = sublime.Region(bookmarkRegionMid, bookmarkRegionMid) 76 | view.sel().clear() 77 | view.sel().add(moveRegion) 78 | 79 | region = self.getRegion() 80 | 81 | line = view.substr(region) 82 | return isLineEmpty(line) 83 | 84 | # the bookmark is associated with the current view 85 | def isMyView(self, window, view): 86 | # I bloody hate python for this madness 87 | if view is None: 88 | return False 89 | 90 | bufferID = view.buffer_id() 91 | filePath = view.file_name() 92 | 93 | if self.isTemporary(): 94 | return self.getBufferID() == bufferID 95 | else: 96 | return self.getFilePath() == filePath 97 | 98 | # updates the bookmark's data 99 | # 1) moved region to cover whole line 100 | # 2) updates the group info 101 | # 3) updates the current line string 102 | def updateData(self, window, myView): 103 | regions = myView.get_regions(str(self.uid)) 104 | 105 | # the region is not loaded yet 106 | if len(regions) == 0: 107 | return 108 | 109 | lines = myView.split_by_newlines(regions[0]) 110 | 111 | region = lines[0] 112 | self.regionA = region.a 113 | self.regionB = region.b 114 | 115 | self.lineStr = myView.substr(region) 116 | self.group = window.get_view_index(myView)[0] 117 | 118 | 119 | def getBookmarkView(window, bookmark): 120 | view = None 121 | if bookmark.isTemporary(): 122 | # mimic behavior of open_file. so w have to focus the view too... 123 | view = getViewByBufferID(window, bookmark.getBufferID()) 124 | window.focus_view(view) 125 | else: 126 | view = window.open_file(bookmark.getFilePath()) 127 | 128 | assert view is not None 129 | return view 130 | 131 | 132 | def shouldRemoveTempBookmark(window, bookmark): 133 | assert(bookmark.isTemporary()) 134 | return getViewByBufferID(window, bookmark.getBufferID()) is None 135 | 136 | 137 | # I hate this function. 138 | def moveBookmarkToGroup(window, bookmark, group): 139 | def moveViewToGroup(window, view, group): 140 | (viewGroup, viewIndex) = window.get_view_index(view) 141 | 142 | # the view is not in the required group so move it 143 | # we have to move the view to the other group and give it a new index 144 | if group != viewGroup or viewGroup == -1 or viewIndex == -1: 145 | 146 | # SUBLIME_BUG 147 | # if the group the view is currently in has only one element 148 | # - i.e this view, sublime text goes crazy. 149 | # It closes our options selector. So, we have to create 150 | # a new file in the old group and *only then* move the view. 151 | if len(window.views_in_group(viewGroup)) == 1: 152 | window.focus_group(viewGroup) 153 | window.new_file() 154 | 155 | # if there are 0 views, then the moved view will have index 0 156 | # if there are n views, the last view will have index (n-1), and 157 | # so the new view will have index n 158 | newIndex = len(window.views_in_group(group)) 159 | # move the view to the highlighted group and assign a 160 | # correct index 161 | window.set_view_index(view, group, newIndex) 162 | 163 | # the view is in the right group, so chill 164 | else: 165 | pass 166 | 167 | view = None 168 | 169 | if bookmark.isTemporary(): 170 | view = getViewByBufferID(window, bookmark.getBufferID()) 171 | assert (view is not None) 172 | else: 173 | view = window.open_file(bookmark.getFilePath()) 174 | 175 | assert (view is not None) 176 | 177 | # move the bookmark's view to the correct group 178 | moveViewToGroup(window, view, group) 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SublimeBookmark [UNMAINTAINED] 2 | =============== 3 | 4 | a better bookmark system for SublimeText 5 | 6 | ![demo gif](http://i.imgur.com/gtjChPG.gif) 7 | 8 | 9 | Unmaintained 10 | ============ 11 | Unfortunately, being a college student doesn't leave me with time to fix the bugs that people seem to be having with 12 | this codebase. I do wish to rewrite this, since this was written when I was new to python. If **anyone** wishes to either 13 | 1. Take ownership of the package 14 | 2. Help me rewrite this 15 | 16 | Please do ping me, help is very appreciated `:)` 17 | 18 | 19 | 20 | Motivation 21 | ========== 22 | 23 | Let's face it: sublime text's bookmark system __sucks__. 24 | __\__ 25 | It doesn't support named bookmarks. It doesn't save bookmark statuses, and it is just terrible to use overall. 26 | __\__. 27 | 28 | This is a replacement for sublime text's bookmark functionality. It's slicker and easier to use, and has way more features. 29 | 30 | **Note**: As of now, This is only for **Sublime Text 3**. False! It's been ported. Unfortunately, I havent' implement live preview and project based bookmark sorting. I'm not sure how to port these features over to Sublime Text 2. If someone knows, please do contact me!d 31 | 32 | 33 | Features 34 | ======== 35 | 36 | * Named bookmarks 37 | * Bookmarks are saved across sessions 38 | * Goto any bookmark in the project 39 | * Add any number of bookmarks (not just 12). 40 | * Project based bookmarks (bookmarks are stored per-project and bookmarks can be navigated on a per-project basis) (Only for ST3) 41 | 42 | To Install 43 | ========== 44 | 45 | 46 | 47 | **With the Package Control plugin:** The easiest way to install SublimeBookmarks is through Package Control, which can be found at this site: http://wbond.net/sublime_packages/package_control 48 | 49 | Once you install Package Control, restart Sublime Text and bring up the Command Palette (``Command+Shift+P`` on OS X, ``Control+Shift+P`` on Linux/Windows). Select "Package Control: Install Package", wait while Package Control fetches the latest package list, then select SublimeBookmarks when the list appears. The advantage of using this method is that Package Control will automatically keep SublimeBookmarks up to date with the latest version. 50 | 51 | 52 | 53 | **Without Git:** Download the latest source from `GitHub ` and copy the whole directory into the Packages directory. 54 | 55 | **With Git:** Clone the repository in your Sublime Text Packages directory, located somewhere in user's "Home" directory:: 56 | 57 | git clone -b st3 https://github.com/bollu/sublimeBookmark.git 58 | 59 | To Use 60 | ====== 61 | 62 | ###Adding Bookmarks### 63 | 64 | Go to a line you wish to bookmark. Press ```ctrl + shift + P``` on Windows / Linux or ```cmd + shift + P``` on Mac and type ```SublimeBookmarks:Add Bookmark```. This opens up a panel where you can type the name of your bookmark. Once you're done naming your shiny new bookmark, hit ```Enter```. You should see a tiny yellow triangle next to your line. you're done! 65 | 66 | 67 | ###Accessing Bookmarks### 68 | Press ```ctrl + shift + P``` on Windows / Linux or ```cmd + shift + P``` on Mac and select ```SublimeBookmarks:Goto Bookmark```. This will bring up a list of all bookmarks. Type in the bookmark name you want to go to and press ```Enter``` to go to the bookmark 69 | 70 | 71 | ###Removing Bookmarks### 72 | Press ```ctrl + shift + P``` on Windows / Linux or ```cmd + shift + P``` on Mac and select ```Remove Bookmark```. Type the name of the bookmark you want to remove and press ```Enter```. This will remove the bookmark 73 | 74 | To remove _all_ bookmarks, select the option ```SublimeBookmarks:Remove All Bookmarks (Clear Bookmarks)```. This will clear _all bookmarks_. This _can not be undone_. 75 | 76 | 77 | 78 | ##Visibility Modes:## 79 | 80 | SublimeBookmarks has 3 visibility modes associated with it. 81 | The default mode is option 2. 82 | 83 | ###1) View all Bookmarks### 84 | 85 | This mode shows *all* bookmarks that have been created - irrespective of project or file information. 86 | 87 | 88 | To use this mode, Press ```ctrl + shift + P``` on Windows / Linux or ```cmd + shift + P``` on Mac and select ```SublimeBookmarks:Show All Bookmarks``` 89 | 90 | This will show __all__ bookmarks created 91 | 92 | 93 | ###2) View only Project Bookmarks### 94 | 95 | This mode only shows bookmarksthat belong to the *current project* - it will not show other bookmarks *at all* 96 | 97 | Press ```ctrl + shift + P``` on Windows / Linux or ```cmd + shift + P``` on Mac and select ```SublimeBookmarks:Show Only Bookmarks In Current Project``` 98 | 99 | This will only show bookmarks that belong to the current project. 100 | 101 | ###2) View only current file Bookmarks### 102 | 103 | This mode only shows bookmark that are present in the *current file*. 104 | 105 | Press ```ctrl + shift + P``` on Windows / Linux or ```cmd + shift + P``` on Mac and select ```SublimeBookmarks:Show Only Bookmarks In Current File``` 106 | 107 | This will only show bookmarks that belong to the current file. 108 | 109 | Notes / Addendum 110 | ================ 111 | 112 | ###Some TODO Stuff:### 113 | 114 | * Port to Sublime Text 2 (This is partially done. Unfortunately, I'm not able to implement live previews and project support. I don't really know how to port these to ST2. If someone does, please do contact me! ) 115 | * Add an option to only show bookmarks belonging to current project (Nope, it now fully supports project-based bookmark management!) 116 | 117 | 118 | ###To Help### 119 | 120 | Just fork my repo and send a pull request. I'll gladly accept :) 121 | 122 | I've spent quite some time writing this and making it bug-free. It would really help me if you'd chip in a little something :) I'm a student, so a little goes a long way. 123 | 124 | [![Support via Gittip](https://rawgithub.com/twolfson/gittip-badge/0.1.0/dist/gittip.png)](https://www.gittip.com/bollu/) 125 | 126 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /sublimebookmark.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import os.path 4 | from pickle import dump, load, UnpicklingError, PicklingError 5 | from copy import deepcopy 6 | 7 | 8 | from .common import * 9 | from .bookmark import * 10 | from .visibilityHandler import * 11 | from .ui import * 12 | 13 | 14 | BOOKMARKS = [] 15 | UID = None 16 | 17 | 18 | # list of bookmarks that have been deleted. 19 | # This is used to remove bookmarks' buffer highlights. 20 | # Without this, if a bookmark is removed, 21 | # when a file is revisited, 22 | # the buffer will still be marked. This will keep track of bookmarks 23 | # that have been removed. 24 | ERASED_BOOKMARKS = [] 25 | 26 | # whether all bookmarks (even unrelated) should be shown 27 | BOOKMARKS_MODE = SHOW_ALL_BOOKMARKS() 28 | 29 | 30 | def removeBookmark(bookmark): 31 | global BOOKMARKS 32 | global ERASED_BOOKMARKS 33 | ERASED_BOOKMARKS.append(deepcopy(bookmark)) 34 | BOOKMARKS.remove(bookmark) 35 | 36 | 37 | def addBookmark(bookmark): 38 | global BOOKMARKS 39 | BOOKMARKS.append(deepcopy(bookmark)) 40 | 41 | 42 | class SublimeBookmarkCommand(sublime_plugin.WindowCommand): 43 | 44 | def __init__(self, window): 45 | self.window = window 46 | self.activeGroup = self.window.active_group() 47 | self.activeView = self.window.active_view() 48 | 49 | global BOOKMARKS 50 | global UID 51 | 52 | BOOKMARKS = [] 53 | # Initialize to 0 54 | UID = 0 55 | 56 | # The bookmark to go to if the user cancels 57 | self.revertBookmark = None 58 | 59 | # Bookmarks that are being shown in the panel 60 | self.displayedBookmarks = None 61 | 62 | # The index used for goto next / goto previous 63 | # not a believer of dynamic typing 64 | self.global_bookmark_index = int(-1) 65 | 66 | currentDir = os.path.dirname(sublime.packages_path()) 67 | self.SAVE_PATH = currentDir + '/sublimeBookmarks.pickle' 68 | Log(currentDir) 69 | 70 | self._Load() 71 | 72 | def run(self, type, name=None): 73 | global BOOKMARKS_MODE 74 | 75 | self.activeGroup = self.window.active_group() 76 | self.activeView = self.window.active_view() 77 | 78 | # update bookmark positions. We need to do it anyway... 79 | self._UpdateBookmarkPosition() 80 | 81 | # delete any temp bookmarks that have been since destroyed 82 | self._UpdateTemporaryBookmarks() 83 | 84 | if type == "add": 85 | self._addBookmark(name) 86 | 87 | elif type == "goto": 88 | # on highlighting, goto the current bookmark 89 | # on option select, just center the selected item 90 | self._createBookmarkPanel(self._HilightDoneCallback, 91 | self._AutoMoveToBookmarkCallback) 92 | 93 | elif type == "remove": 94 | # on highlighting, goto the current bookmark 95 | # on option select, remove the selected item 96 | self._createBookmarkPanel(self._RemoveDoneCallback, 97 | self._AutoMoveToBookmarkCallback) 98 | 99 | elif type == "remove_all": 100 | self._removeAllBookmarks() 101 | 102 | elif type == "toggle_line": 103 | self._toggleCurrentLine() 104 | 105 | elif type == "goto_next": 106 | self._quickGoto(True) 107 | 108 | elif type == "goto_previous": 109 | self._quickGoto(False) 110 | 111 | # BOOKMARK MODES 112 | elif type == "show_all_bookmarks": 113 | BOOKMARKS_MODE = SHOW_ALL_BOOKMARKS() 114 | self._Save() 115 | # update buffer to show all bookmarks 116 | self._updateBufferStatus() 117 | 118 | elif type == "show_project_bookmarks": 119 | BOOKMARKS_MODE = SHOW_ONLY_PROJECT_BOOKMARKS() 120 | self._Save() 121 | # update buffer to show only project bookmarks 122 | self._updateBufferStatus() 123 | 124 | elif type == "show_file_bookmarks": 125 | BOOKMARKS_MODE = SHOW_ONLY_FILE_BOOKMARKS() 126 | self._Save() 127 | # update buffer to show only project bookmarks 128 | self._updateBufferStatus() 129 | 130 | # ASYNC OPERATIONS 131 | elif type == "mark_buffer": 132 | self._updateBufferStatus() 133 | 134 | elif type == "move_bookmarks": 135 | self._UpdateBookmarkPosition() 136 | 137 | elif type == "update_temporary": 138 | self._UpdateTemporaryBookmarks() 139 | 140 | def _createBookmarkPanel(self, onHighlight, onDone): 141 | 142 | def moveBookmarksToActiveGroup(activeGroup): 143 | # move all open bookmark tabs to one group 144 | # so that group switching does not occur. 145 | for bookmark in BOOKMARKS: 146 | moveBookmarkToGroup(self.window, bookmark, self.activeGroup) 147 | 148 | def createPanel(): 149 | bookmarkPanelItems = \ 150 | createBookmarkPanelItems(self.window, self.displayedBookmarks) 151 | 152 | # create a selection panel and launch it 153 | selector = OptionsSelector(self.window, 154 | bookmarkPanelItems, 155 | onHighlight, 156 | onDone) 157 | selector.start() 158 | 159 | return True 160 | 161 | # load all visible bookmarks 162 | self.displayedBookmarks = getVisibleBookmarks(BOOKMARKS, 163 | self.window, 164 | self.activeView, 165 | BOOKMARKS_MODE) 166 | 167 | # if no bookmarks are acceptable, don't show bookmarks 168 | if len(self.displayedBookmarks) == 0: 169 | return False 170 | 171 | # create a revert bookmark to go back if the user cancels 172 | self._createRevertBookmark(self.activeView) 173 | 174 | # move all active bookmarks to the currently active group 175 | moveBookmarksToActiveGroup(self.activeGroup) 176 | 177 | # create the selection panel 178 | panelCreated = createPanel() 179 | if not panelCreated: 180 | MESSAGE_NoBookmarkToGoto() 181 | return False 182 | 183 | # Event handlers 184 | def _addBookmark(self, name): 185 | if name is None: 186 | window = self.window 187 | view = window.active_view() 188 | region = getCurrentLineRegion(view) 189 | 190 | # copy whatever is on the line for the bookmark name 191 | initialText = view.substr(region).strip() 192 | 193 | input = OptionsInput(self.window, 194 | "Add Bookmark", 195 | initialText, 196 | self._AddBookmarkCallback, 197 | None) 198 | input.start() 199 | else: 200 | self._AddBookmarkCallback(name) 201 | 202 | def _removeAllBookmarks(self): 203 | global ERASED_BOOKMARKS 204 | global BOOKMARKS 205 | 206 | for bookmark in BOOKMARKS: # noqa 207 | # store erased bookmarks for delayed removal 208 | ERASED_BOOKMARKS.append(deepcopy(bookmark)) 209 | 210 | # yep. nuke em 211 | del BOOKMARKS 212 | BOOKMARKS = [] # noqa 213 | 214 | # update the buffer since we deleted a bookmark 215 | self._updateBufferStatus() 216 | # save to eternal storage 217 | self._Save() 218 | 219 | def _quickGoto(self, forward): 220 | # Gather appropriate bookmarks 221 | self.displayedBookmarks = getVisibleBookmarks(BOOKMARKS, 222 | self.window, 223 | self.activeView, 224 | BOOKMARKS_MODE) 225 | 226 | if 0 == len(self.displayedBookmarks): 227 | MESSAGE_NoBookmarkToGoto() 228 | return 229 | 230 | # increment or decrement 231 | if forward: 232 | self.global_bookmark_index = self.global_bookmark_index + 1 233 | else: 234 | self.global_bookmark_index = self.global_bookmark_index - 1 235 | 236 | # If we're pointing off the end, go to the first one instead 237 | if self.global_bookmark_index >= len(self.displayedBookmarks): 238 | self.global_bookmark_index = 0 239 | # If we're pointing off the beginning, go to the last one instead 240 | if self.global_bookmark_index < 0: 241 | self.global_bookmark_index = len(self.displayedBookmarks) - 1 242 | 243 | # Go there! 244 | bookmark = BOOKMARKS[self.global_bookmark_index] 245 | moveBookmarkToGroup(self.window, bookmark, self.activeGroup) 246 | self._AutoMoveToBookmarkCallback(self.global_bookmark_index) 247 | 248 | def _toggleCurrentLine(self): 249 | def getLineBookmark(window): 250 | currentFilePath = window.active_view().file_name() 251 | cursorRegion = getCurrentLineRegion(window.active_view()) 252 | 253 | for bookmark in BOOKMARKS: 254 | if bookmark.getFilePath() == currentFilePath and \ 255 | bookmark.getRegion().contains(cursorRegion): 256 | return bookmark 257 | 258 | # no bookmark 259 | return None 260 | 261 | bookmark = getLineBookmark(self.window) 262 | 263 | if bookmark is not None: 264 | global ERASED_BOOKMARKS 265 | global BOOKMARKS 266 | 267 | # add to list of erased bookmarks 268 | ERASED_BOOKMARKS.append(deepcopy(bookmark)) 269 | BOOKMARKS.remove(bookmark) 270 | 271 | self._updateBufferStatus() 272 | # [File IO] 273 | self._Save() 274 | else: 275 | region = getCurrentLineRegion(self.activeView) 276 | # copy whatever is on the line for the bookmark name 277 | name = self.activeView.substr(region).strip() 278 | 279 | self._AddBookmarkCallback(name) 280 | 281 | def _updateBufferStatus(self): 282 | # marks the given bookmark on the buffer 283 | def markBuffer(view, bookmark): 284 | uid = bookmark.getUid() 285 | region = bookmark.getRegion() 286 | view.add_regions(str(uid), [region], 287 | "text.plain", "bookmark", 288 | sublime.DRAW_NO_FILL | 289 | sublime.DRAW_EMPTY_AS_OVERWRITE) 290 | 291 | # unmarks the given bookmark on the buffer 292 | def unmarkBuffer(view, bookmark): 293 | uid = bookmark.getUid() 294 | view.erase_regions(str(uid)) 295 | 296 | if self.activeView is None: 297 | return 298 | 299 | # mark all bookmarks that are visible, and unmark invisible bookmarks 300 | for bookmark in BOOKMARKS: 301 | # if the bookmark should be shown 302 | # according to the current bookmark mode 303 | shouldShow = shouldShowBookmark(self.window, 304 | self.activeView, 305 | bookmark, BOOKMARKS_MODE) 306 | 307 | # only mark if we are in the right view. 308 | validContext = bookmark.isMyView(self.window, self.activeView) 309 | 310 | if validContext and shouldShow: 311 | markBuffer(self.activeView, bookmark) 312 | else: 313 | unmarkBuffer(self.activeView, bookmark) 314 | 315 | # unmark all erased bookmarks 316 | for bookmark in ERASED_BOOKMARKS: 317 | validContext = bookmark.isMyView(self.window, self.activeView) 318 | 319 | if validContext: 320 | unmarkBuffer(self.activeView, bookmark) 321 | 322 | # 1. move bookmarks 323 | # 2. update their regions when text is entered into the buffer 324 | def _UpdateBookmarkPosition(self): 325 | # this bookmark (might) have been changed 326 | # since it's in the current file 327 | # We're on a thread anyway so update it. 328 | for bookmark in BOOKMARKS: 329 | 330 | # if the activeView is the bookmark's view, update r 331 | # if bookmark.isMyView(self.window, self.activeView): 332 | if bookmark.isMyView(self.window, self.activeView) and not self.activeView.is_loading(): 333 | 334 | bookmark.updateData(self.window, self.activeView) 335 | 336 | # the bookmark is empty - it has no data in it. 337 | if bookmark.isEmpty(self.activeView): 338 | Log("EMPTY BOOKMARK. NAME: " + bookmark.getName()) 339 | removeBookmark(bookmark) 340 | 341 | # we've moved regions around so update the buffer 342 | self._updateBufferStatus() 343 | # we've moved bookmarks around and may also have deleted them. So, save 344 | self._Save() 345 | 346 | # check if the buffers associated 347 | # with temporary bookmark are still active or not, and remove 348 | # unnecessary bookmarks 349 | def _UpdateTemporaryBookmarks(self): 350 | for bookmark in BOOKMARKS: 351 | # if the bookmark is a temporary bookmark 352 | # and the bookmark has been deleted, remove the bookmark 353 | if bookmark.isTemporary() and \ 354 | shouldRemoveTempBookmark(self.window, bookmark): 355 | 356 | Log("BOOKMARK IS TEMP AND BUFFER HAS BEEN REMOVED." 357 | "REMOVING. " 358 | "BUFFER: %s | NAME: %s" % 359 | (bookmark.getBufferID(), bookmark.getName())) 360 | 361 | removeBookmark(bookmark) 362 | 363 | # helpers 364 | # creates a bookmark that keeps track of where we were before opening 365 | # n options menu. 366 | def _createRevertBookmark(self, activeView): 367 | # there's no file open. 368 | # return None 'cause there's no place to return TO 369 | if isViewTemporary(activeView): 370 | self.revertBookmark = None 371 | return 372 | 373 | uid = -1 374 | name = "" 375 | 376 | self.revertBookmark = Bookmark(uid, name, self.window, activeView) 377 | 378 | # goes to the revert bookmark 379 | def _gotoRevertBookmark(self): 380 | if self.revertBookmark is None: 381 | return 382 | self.revertBookmark.goto(self.window) 383 | self.revertBookmark = None 384 | 385 | def _restoreFiles(self): 386 | for bookmark in BOOKMARKS: 387 | moveBookmarkToGroup(self.window, bookmark, bookmark.getGroup()) 388 | 389 | # callbacks--------------------------------------------------- 390 | def _AddBookmarkCallback(self, name): 391 | 392 | global UID 393 | assert UID is not None 394 | 395 | myUID = UID 396 | myUID = REGION_BASE_TAG + myUID 397 | UID = UID + 1 398 | 399 | # get region and line data 400 | region = getCurrentLineRegion(self.activeView) 401 | lineStr = self.activeView.substr(region) 402 | 403 | # there's no content 404 | if isLineEmpty(lineStr): 405 | MESSAGE_BookmarkEmpty() 406 | return 407 | 408 | bookmark = Bookmark(UID, name, self.window, self.activeView) 409 | addBookmark(bookmark) 410 | 411 | self._updateBufferStatus() 412 | self._Save() 413 | 414 | # display highlighted bookmark 415 | def _AutoMoveToBookmarkCallback(self, index): 416 | assert index < len(self.displayedBookmarks) 417 | bookmark = self.displayedBookmarks[index] 418 | assert bookmark is not None 419 | 420 | # goto highlighted bookmark 421 | bookmark.goto(self.window) 422 | self._updateBufferStatus() 423 | 424 | # if the user canceled, go back to the original file 425 | def _HilightDoneCallback(self, index): 426 | 427 | # restore all files back to their original places 428 | self._restoreFiles() 429 | 430 | # if the user canceled, then goto the revert bookmark 431 | if index == -1: 432 | self._gotoRevertBookmark() 433 | 434 | # otherwise, goto the selected bookmark 435 | else: 436 | # now open the selected bookmark and scroll to bookmark 437 | self._AutoMoveToBookmarkCallback(index) 438 | 439 | # move the correct bookmark back to the active group - 440 | # since all fails including the bookmark have been restored, 441 | # we have to move the bookmark back 442 | # ARRGH! this is __so__ hacky :( 443 | bookmark = self.displayedBookmarks[index] 444 | moveBookmarkToGroup(self.window, bookmark, self.activeGroup) 445 | 446 | # IMPORTANT - not doing this will cause bookmark to think it is 447 | # still in it's previous group. 448 | bookmark.updateData(self.window, self.activeView) 449 | 450 | self._updateBufferStatus() 451 | 452 | # remove the selected bookmark or go back if user cancelled 453 | def _RemoveDoneCallback(self, index): 454 | # restore all files back to their original places 455 | self._restoreFiles() 456 | 457 | # if the user canceled, then goto the revert bookmark 458 | if index == -1: 459 | self._gotoRevertBookmark() 460 | # otherwise, goto the selected bookmark 461 | else: 462 | assert index < len(self.displayedBookmarks) 463 | bookmark = self.displayedBookmarks[index] 464 | assert bookmark is not None 465 | 466 | # goto the removed bookmark 467 | bookmark.goto(self.window) 468 | removeBookmark(bookmark) 469 | 470 | # decrement global_bookmark_index so 471 | # goto_next will not skip anything 472 | if index <= self.global_bookmark_index: 473 | self.global_bookmark_index = self.global_bookmark_index - 1 474 | 475 | self._updateBufferStatus() 476 | self._Save() 477 | 478 | # Save Load 479 | def _Load(self): 480 | global BOOKMARKS 481 | global BOOKMARKS_MODE 482 | global UID 483 | 484 | Log("LOADING BOOKMARKS") 485 | try: 486 | savefile = open(self.SAVE_PATH, "rb") 487 | 488 | saveVersion = load(savefile) 489 | 490 | if saveVersion != VERSION: 491 | raise UnpicklingError("version difference in files") 492 | 493 | BOOKMARKS_MODE = load(savefile) 494 | UID = load(savefile) 495 | BOOKMARKS = load(savefile) 496 | 497 | except (OSError, IOError, UnpicklingError, 498 | EOFError, BaseException) as e: 499 | print("\nEXCEPTION:------- ") 500 | print(e) 501 | print("\nUNABLE TO LOAD BOOKMARKS. NUKING LOAD FILE") 502 | # clear the load file :] 503 | open(self.SAVE_PATH, "wb").close() 504 | # if you can't load, try and save a "default" state 505 | self._Save() 506 | 507 | def _Save(self): 508 | global BOOKMARKS 509 | global BOOKMARKS_MODE 510 | global UID 511 | 512 | try: 513 | savefile = open(self.SAVE_PATH, "wb") 514 | 515 | dump(VERSION, savefile) 516 | dump(BOOKMARKS_MODE, savefile) 517 | dump(UID, savefile) 518 | dump(BOOKMARKS, savefile) 519 | 520 | savefile.close() 521 | except (OSError, IOError, PicklingError) as e: 522 | print(e) 523 | print("\nUNABLE TO SAVE BOOKMARKS. PLEASE CONTACT DEV") 524 | --------------------------------------------------------------------------------