Add bookmarks service…

…for consumption by other packages.

This is nearly the simplest possible interface around the `bookmarks` package,
except that it will fire events when the number of bookmarks has changed.
This commit is contained in:
Andrew Dupont 2023-05-10 12:18:01 -07:00
parent 99fbca95be
commit 3ed145afb3
5 changed files with 104 additions and 16 deletions

View File

@ -0,0 +1,31 @@
class BookmarksProvider {
constructor(main) {
this.main = main
}
// Returns all bookmarks present in the given editor.
//
// Each bookmark tracks a buffer range and is represented by an instance of
// {DisplayMarker}.
//
// Will return an empty array if there are no bookmarks in the given editor.
//
// Keep in mind that a single bookmark can span multiple buffer rows and/or
// screen rows. Thus there isn't necessarily a 1:1 correlation between the
// number of bookmarks in the editor and the number of bookmark icons that
// the user will see in the gutter.
getBookmarksForEditor(editor) {
let instance = this.getInstanceForEditor(editor)
if (!instance) return null
return instance.getAllBookmarks()
}
// Returns the instance of the `Bookmarks` class that is responsible for
// managing bookmarks in the given editor.
getInstanceForEditor(editor) {
return this.main.editorsBookmarks.find(b => b.editor.id === editor.id)
}
}
module.exports = BookmarksProvider

View File

@ -1,12 +1,13 @@
const {CompositeDisposable} = require('atom')
const {CompositeDisposable, Emitter} = require('atom')
module.exports =
class Bookmarks {
static deserialize (editor, state) {
static deserialize(editor, state) {
return new Bookmarks(editor, editor.getMarkerLayer(state.markerLayerId))
}
constructor (editor, markerLayer) {
constructor(editor, markerLayer) {
this.emitter = new Emitter()
this.editor = editor
this.markerLayer = markerLayer || this.editor.addMarkerLayer({persistent: true})
this.decorationLayer = this.editor.decorateMarkerLayer(this.markerLayer, {type: 'line-number', class: 'bookmarked'})
@ -24,23 +25,23 @@ class Bookmarks {
this.disposables.add(this.editor.onDidDestroy(this.destroy.bind(this)))
}
destroy () {
destroy() {
this.deactivate()
this.markerLayer.destroy()
}
deactivate () {
deactivate() {
this.decorationLayer.destroy()
this.decorationLayerLine.destroy()
this.decorationLayerHighlight.destroy()
this.disposables.dispose()
}
serialize () {
serialize() {
return {markerLayerId: this.markerLayer.id}
}
toggleBookmark () {
toggleBookmark() {
for (const range of this.editor.getSelectedBufferRanges()) {
const bookmarks = this.markerLayer.findMarkers({intersectsRowRange: [range.start.row, range.end.row]})
if (bookmarks && bookmarks.length > 0) {
@ -52,19 +53,34 @@ class Bookmarks {
this.disposables.add(bookmark.onDidChange(({isValid}) => {
if (!isValid) {
bookmark.destroy()
// TODO: If N bookmarks are affected by a buffer change,
// `did-change-bookmarks` will be emitted N times. We could
// debounce this if we were willing to go async.
this.emitter.emit('did-change-bookmarks', this.getAllBookmarks())
}
}))
}
this.emitter.emit('did-change-bookmarks', this.getAllBookmarks())
}
}
clearBookmarks () {
getAllBookmarks() {
let markers = this.markerLayer.getMarkers()
return markers
}
onDidChangeBookmarks(callback) {
return this.emitter.on('did-change-bookmarks', callback)
}
clearBookmarks() {
for (const bookmark of this.markerLayer.getMarkers()) {
bookmark.destroy()
}
this.emitter.emit('did-change-bookmarks', [])
}
jumpToNextBookmark () {
jumpToNextBookmark() {
if (this.markerLayer.getMarkerCount() > 0) {
const bufferRow = this.editor.getLastCursor().getMarker().getStartBufferPosition().row
const markers = this.markerLayer.getMarkers().sort((a, b) => a.compare(b))
@ -76,7 +92,7 @@ class Bookmarks {
}
}
jumpToPreviousBookmark () {
jumpToPreviousBookmark() {
if (this.markerLayer.getMarkerCount() > 0) {
const bufferRow = this.editor.getLastCursor().getMarker().getStartBufferPosition().row
const markers = this.markerLayer.getMarkers().sort((a, b) => b.compare(a))
@ -88,7 +104,7 @@ class Bookmarks {
}
}
selectToNextBookmark () {
selectToNextBookmark() {
if (this.markerLayer.getMarkerCount() > 0) {
const bufferRow = this.editor.getLastCursor().getMarker().getStartBufferPosition().row
const markers = this.markerLayer.getMarkers().sort((a, b) => a.compare(b))
@ -103,7 +119,7 @@ class Bookmarks {
}
}
selectToPreviousBookmark () {
selectToPreviousBookmark() {
if (this.markerLayer.getMarkerCount() > 0) {
const bufferRow = this.editor.getLastCursor().getMarker().getStartBufferPosition().row
const markers = this.markerLayer.getMarkers().sort((a, b) => b.compare(a))

View File

@ -2,9 +2,10 @@ const {CompositeDisposable} = require('atom')
const Bookmarks = require('./bookmarks')
const BookmarksView = require('./bookmarks-view')
const BookmarksProvider = require('./bookmarks-provider')
module.exports = {
activate (bookmarksByEditorId) {
activate(bookmarksByEditorId) {
this.bookmarksView = null
this.editorsBookmarks = []
this.disposables = new CompositeDisposable()
@ -44,7 +45,7 @@ module.exports = {
})
},
deactivate () {
deactivate() {
if (this.bookmarksView != null) {
this.bookmarksView.destroy()
this.bookmarksView = null
@ -56,11 +57,16 @@ module.exports = {
this.disposables.dispose()
},
serialize () {
serialize() {
const bookmarksByEditorId = {}
for (let bookmarks of this.editorsBookmarks) {
bookmarksByEditorId[bookmarks.editor.id] = bookmarks.serialize()
}
return bookmarksByEditorId
},
provideBookmarks() {
this.bookmarksProvider ??= new BookmarksProvider(this)
return this.bookmarksProvider
}
}

View File

@ -10,5 +10,14 @@
},
"dependencies": {
"atom-select-list": "^0.7.0"
},
"providedServices": {
"bookmarks": {
"description": "Provides a list of bookmarks to any package that wants to know about them.",
"versions": {
"1.0.0": "provideBookmarks"
}
}
}
}

View File

@ -1,5 +1,5 @@
describe('Bookmarks package', () => {
let [workspaceElement, editorElement, editor, bookmarks] = []
let workspaceElement, editorElement, editor, bookmarks, provider
const bookmarkedRangesForEditor = editor => {
const decorationsById = editor.decorationsStateForScreenRowRange(0, editor.getLastScreenRow())
@ -18,6 +18,7 @@ describe('Bookmarks package', () => {
await atom.workspace.open('sample.js')
bookmarks = (await atom.packages.activatePackage('bookmarks')).mainModule
provider = bookmarks.bookmarksProvider
jasmine.attachToDOM(workspaceElement)
editor = atom.workspace.getActiveTextEditor()
@ -32,17 +33,28 @@ describe('Bookmarks package', () => {
expect(bookmarkedRangesForEditor(editor)).toEqual([])
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor)).toEqual([[[3, 10], [3, 10]]])
let marks = provider.getBookmarksForEditor(editor)
expect(marks.length).toBe(1)
expect(marks.map(m => m.getScreenRange())).toEqual(bookmarkedRangesForEditor(editor))
})
it('removes marker when toggled', () => {
let callback = jasmine.createSpy()
let instance = provider.getInstanceForEditor(editor)
instance.onDidChangeBookmarks(callback)
editor.setCursorBufferPosition([3, 10])
expect(bookmarkedRangesForEditor(editor).length).toBe(0)
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor).length).toBe(1)
expect(callback.callCount).toBe(1)
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor).length).toBe(0)
expect(callback.callCount).toBe(2)
})
})
@ -53,6 +65,8 @@ describe('Bookmarks package', () => {
expect(bookmarkedRangesForEditor(editor)).toEqual([])
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor)).toEqual([[[3, 10], [3, 10]], [[6, 11], [6, 11]]])
let instance = provider.getInstanceForEditor(editor)
expect(instance.getAllBookmarks().length).toBe(2)
})
it('removes multiple markers when toggled', () => {
@ -215,27 +229,39 @@ describe('Bookmarks package', () => {
})
it('clears all bookmarks', () => {
let callback = jasmine.createSpy()
let instance = provider.getInstanceForEditor(editor)
instance.onDidChangeBookmarks(callback)
editor.setCursorBufferPosition([3, 10])
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(callback.callCount).toBe(1)
editor.setCursorBufferPosition([5, 0])
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(callback.callCount).toBe(2)
atom.commands.dispatch(editorElement, 'bookmarks:clear-bookmarks')
expect(getBookmarkedLineNodes(editorElement).length).toBe(0)
expect(callback.callCount).toBe(3)
})
})
describe('when a bookmark is invalidated', () => {
it('creates a marker when toggled', () => {
let callback = jasmine.createSpy()
let instance = provider.getInstanceForEditor(editor)
instance.onDidChangeBookmarks(callback)
editor.setCursorBufferPosition([3, 10])
expect(bookmarkedRangesForEditor(editor).length).toBe(0)
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor).length).toBe(1)
expect(callback.callCount).toBe(1)
editor.setText('')
expect(bookmarkedRangesForEditor(editor).length).toBe(0)
expect(callback.callCount).toBe(2)
})
})