mirror of
https://github.com/wassname/nvatom.git
synced 2026-07-11 00:40:46 +08:00
Use path watcher to monitor note changes. Fix #2
Refactor so that the note/directory parsing is done in separate classes.
This commit is contained in:
@@ -1,17 +1,22 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs'
|
||||
fsPlus = require 'fs-plus'
|
||||
{$, $$, SelectListView} = require 'atom-space-pen-views'
|
||||
NoteDirectory = require './note-directory'
|
||||
Note = require './note'
|
||||
|
||||
module.exports =
|
||||
class NotationalVelocityView extends SelectListView
|
||||
initialize: ->
|
||||
super
|
||||
@addClass('notational-velocity from-top overlay')
|
||||
@loadData()
|
||||
rootDirectory = atom.config.get('notational-velocity.directory')
|
||||
@noteDirectory = new NoteDirectory(rootDirectory, null, () => @updateNotes())
|
||||
@updateNotes()
|
||||
@prevFilterQuery = ''
|
||||
@prevCursorPosition = 0
|
||||
|
||||
updateNotes: () ->
|
||||
@notes = @noteDirectory.getNotes()
|
||||
@setItems(@notes)
|
||||
|
||||
selectItem: (filterQuery) ->
|
||||
if filterQuery.length == 0
|
||||
@prevCursorPosition = 0
|
||||
@@ -24,9 +29,8 @@ class NotationalVelocityView extends SelectListView
|
||||
|
||||
titleItem = null
|
||||
for titlePattern in titlePatterns
|
||||
titleItems = @items
|
||||
.filter (x) -> x.title.match(titlePattern) != null
|
||||
.sort (x, y) -> if x.modified.getTime() <= y.modified.getTime() then 1 else -1
|
||||
titleItems = @notes
|
||||
.filter (x) -> x.getTitle().match(titlePattern) != null
|
||||
titleItem = if titleItems.length > 0 then titleItems[0] else null
|
||||
if titleItem != null
|
||||
break
|
||||
@@ -36,74 +40,25 @@ class NotationalVelocityView extends SelectListView
|
||||
editor = @filterEditorView.model
|
||||
currCursorPosition = editor.getCursorBufferPosition().column
|
||||
if titleItem != null && @prevCursorPosition < currCursorPosition
|
||||
@prevFilterQuery = titleItem.title
|
||||
editor.setText(titleItem.title)
|
||||
editor.selectLeft(titleItem.title.length - filterQuery.length)
|
||||
@prevFilterQuery = titleItem.getTitle()
|
||||
editor.setText(titleItem.getTitle())
|
||||
editor.selectLeft(titleItem.getTitle().length - filterQuery.length)
|
||||
@prevCursorPosition = currCursorPosition
|
||||
|
||||
return titleItem
|
||||
|
||||
filter: (filterQuery) ->
|
||||
if filterQuery.length == 0
|
||||
return @items
|
||||
return @notes
|
||||
|
||||
queries = filterQuery.split(' ')
|
||||
.filter (x) -> x.length > 0
|
||||
.map (x) -> new RegExp(x, 'gi')
|
||||
contentItems = @items
|
||||
return @notes
|
||||
.filter (x) ->
|
||||
queries
|
||||
.map (q) -> q.test(x.filetext) || q.test(x.title)
|
||||
.map (q) -> q.test(x.getText()) || q.test(x.getTitle())
|
||||
.reduce (x, y) -> x && y
|
||||
return contentItems
|
||||
|
||||
getSubPath: (baseDir, dir)->
|
||||
ret = []
|
||||
fullDir = path.join(baseDir, dir)
|
||||
try
|
||||
filenameList = fs.readdirSync(fullDir)
|
||||
catch e
|
||||
return ret
|
||||
|
||||
for filename in filenameList
|
||||
filePath = path.join(dir, filename)
|
||||
fullPath = path.join(baseDir, filePath)
|
||||
try
|
||||
fileStat = fs.statSync(fullPath)
|
||||
catch e
|
||||
continue
|
||||
if fileStat.isDirectory()
|
||||
ret = ret.concat(@getSubPath(baseDir, filePath))
|
||||
else
|
||||
if !fsPlus.isMarkdownExtension(path.extname(filename))
|
||||
continue
|
||||
ret.push(filePath)
|
||||
return ret
|
||||
|
||||
loadData: ->
|
||||
@data = []
|
||||
|
||||
basedir = atom.config.get('notational-velocity.directory')
|
||||
|
||||
for filepath in @getSubPath(basedir, '')
|
||||
fullpath = path.join(basedir, filepath)
|
||||
filename = path.basename(filepath, path.extname(filepath))
|
||||
filetext = fs.readFileSync(fullpath, 'utf8')
|
||||
title = path.join(path.dirname(filepath), filename)
|
||||
modified = fs.statSync(fullpath).mtime
|
||||
|
||||
item = {
|
||||
'title': title,
|
||||
'modified': modified,
|
||||
'filetext': filetext,
|
||||
'filename': filename,
|
||||
'filepath': fullpath
|
||||
}
|
||||
@data.push(item)
|
||||
|
||||
@data = @data.sort (x, y) -> if x.modified.getTime() <= y.modified.getTime() then 1 else -1
|
||||
|
||||
@setItems(@data)
|
||||
|
||||
getFilterKey: ->
|
||||
'filetext'
|
||||
@@ -116,14 +71,13 @@ class NotationalVelocityView extends SelectListView
|
||||
@show()
|
||||
|
||||
viewForItem: (item) ->
|
||||
index = item.filetext.search /\n/
|
||||
content = item.filetext.slice(index, item.filetext.length)
|
||||
content = item.getText()[0...100]
|
||||
|
||||
$$ ->
|
||||
@li class: 'two-lines', =>
|
||||
@div class: 'primary-line', =>
|
||||
@span "#{item.title}"
|
||||
@div class: 'metadata', "#{item.modified.toLocaleDateString()}"
|
||||
@span "#{item.getTitle()}"
|
||||
@div class: 'metadata', "#{item.getModified().toLocaleDateString()}"
|
||||
@div class: 'secondary-line', "#{content}"
|
||||
|
||||
confirmSelection: ->
|
||||
@@ -135,7 +89,7 @@ class NotationalVelocityView extends SelectListView
|
||||
@cancel()
|
||||
|
||||
confirmed: (item) ->
|
||||
atom.workspace.open(item.filepath)
|
||||
atom.workspace.open(item.getFilePath())
|
||||
@cancel()
|
||||
|
||||
destroy: ->
|
||||
@@ -155,7 +109,7 @@ class NotationalVelocityView extends SelectListView
|
||||
@panel?.hide()
|
||||
|
||||
populateList: ->
|
||||
return unless @items?
|
||||
return unless @notes?
|
||||
|
||||
filterQuery = @getFilterQuery()
|
||||
filteredItems = @filter(filterQuery)
|
||||
@@ -176,7 +130,7 @@ class NotationalVelocityView extends SelectListView
|
||||
@selectItemView(@list.find("li:nth-child(#{n})"))
|
||||
|
||||
else
|
||||
@setError(@getEmptyMessage(@items.length, filteredItems.length))
|
||||
@setError(@getEmptyMessage(@notes.length, filteredItems.length))
|
||||
|
||||
schedulePopulateList: ->
|
||||
# We can skip it when we are just moving the position of the cursor.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs-plus'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
Note = require './note'
|
||||
|
||||
module.exports =
|
||||
class NoteDirectory
|
||||
constructor: (@filePath, @parent, @onChangeCallback) ->
|
||||
@directories = []
|
||||
@notes = []
|
||||
@updateMetadata()
|
||||
@watcher = pathWatcher.watch(@filePath, (event) => @onChange(event))
|
||||
|
||||
destroy: ->
|
||||
@notes.map (x) -> x.destroy()
|
||||
@directories.map (x) -> x.destroy()
|
||||
@watcher.close()
|
||||
|
||||
updateMetadata: ->
|
||||
@notes.map (x) -> x.destroy()
|
||||
@directories.map (x) -> x.destroy()
|
||||
|
||||
@directories = []
|
||||
@notes = []
|
||||
|
||||
try
|
||||
filenames = fs.readdirSync(@filePath)
|
||||
catch e
|
||||
return
|
||||
for filename in filenames
|
||||
@addChild(path.join(@filePath, filename))
|
||||
|
||||
addChild: (filePath) ->
|
||||
try
|
||||
fileStat = fs.statSync(filePath)
|
||||
catch e
|
||||
return
|
||||
if fileStat.isDirectory()
|
||||
@directories.push(new NoteDirectory(filePath, this, @onChangeCallback))
|
||||
else
|
||||
if fs.isMarkdownExtension(path.extname(filePath))
|
||||
@notes.push(new Note(filePath, this, @onChangeCallback))
|
||||
|
||||
getNotes: ->
|
||||
ret = []
|
||||
ret = ret.concat(@notes)
|
||||
for directory in @directories
|
||||
ret = ret.concat(directory.getNotes())
|
||||
if @parent is null
|
||||
ret.sort (x, y) -> if x.getModified().getTime() <= y.getModified().getTime() then 1 else -1
|
||||
return ret
|
||||
|
||||
onChange: (event) ->
|
||||
# For the case of rename and change, it will be handled in its parent.
|
||||
if event == 'change'
|
||||
@updateMetadata()
|
||||
if @onChangeCallback != null
|
||||
@onChangeCallback()
|
||||
@@ -0,0 +1,37 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
|
||||
module.exports =
|
||||
class Note
|
||||
constructor: (@filePath, @parent, @onChangeCallback) ->
|
||||
@updateMetadata()
|
||||
@updateText()
|
||||
@watcher = pathWatcher.watch(@filePath, (event) => @onChange(event))
|
||||
|
||||
destroy: ->
|
||||
@watcher.close()
|
||||
|
||||
updateMetadata: ->
|
||||
@modified = fs.statSync(@filePath).mtime
|
||||
relativePath = path.relative(atom.config.get('notational-velocity.directory'), @filePath)
|
||||
@title = path.join(
|
||||
path.dirname(relativePath),
|
||||
path.basename(relativePath, path.extname(relativePath))
|
||||
)
|
||||
|
||||
updateText: ->
|
||||
@text = fs.readFileSync(@filePath, 'utf8')
|
||||
|
||||
onChange: (event) ->
|
||||
# For the case of rename and change, it will be handled in its parent.
|
||||
if event == 'change' && fs.existsSync(@filePath)
|
||||
@updateMetadata()
|
||||
@updateText()
|
||||
if @onChangeCallback != null
|
||||
@onChangeCallback()
|
||||
|
||||
getTitle: -> @title
|
||||
getText: -> @text
|
||||
getModified: -> @modified
|
||||
getFilePath: -> @filePath
|
||||
+8
-1
@@ -13,6 +13,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-plus": "2.x",
|
||||
"atom-space-pen-views": "^2.0.3"
|
||||
"atom-space-pen-views": "^2.0.3",
|
||||
"pathwatcher": "^4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fs-plus": "2.x",
|
||||
"atom-space-pen-views": "^2.0.3",
|
||||
"pathwatcher": "^4.2",
|
||||
"temp": "~0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs-plus'
|
||||
temp = require 'temp'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
NoteDirectory = require '../lib/note-directory'
|
||||
Note = require '../lib/note'
|
||||
|
||||
describe 'NoteDirectory.getNotes', ->
|
||||
defaultDirectory = atom.config.get('notational-velocity.directory')
|
||||
tempDirectory = temp.mkdirSync('node-pathwatcher-directory')
|
||||
noteDirectory = null
|
||||
isCallbackCalled = false
|
||||
|
||||
# We don't want to let the mtimes of two consecutively create files are same.
|
||||
# This function ensures the mtime of the file created before calling this function to be always
|
||||
# smaller than to the mtime of the file created after calling this function.
|
||||
wait = ->
|
||||
timestampDirectory = temp.mkdirSync('node-pathwatcher-timestamp')
|
||||
filePath = path.join(timestampDirectory, 'temp')
|
||||
fs.writeFileSync(filePath, '.')
|
||||
mtimeOld = fs.statSync(filePath).mtime
|
||||
mtimeNew = mtimeOld
|
||||
while mtimeOld >= mtimeNew
|
||||
fs.writeFileSync(filePath, '.')
|
||||
mtimeNew = fs.statSync(filePath).mtime
|
||||
|
||||
beforeEach ->
|
||||
isCallbackCalled = false
|
||||
callback = => isCallbackCalled = true
|
||||
|
||||
atom.config.set('notational-velocity.directory', tempDirectory)
|
||||
|
||||
fs.writeFileSync(path.join(tempDirectory, 'Readme.md'), 'read me')
|
||||
wait()
|
||||
|
||||
fs.mkdirSync(path.join(tempDirectory, 'Car'))
|
||||
fs.writeFileSync(path.join(tempDirectory, 'Car', 'Mini.md'), 'mini')
|
||||
wait()
|
||||
|
||||
noteDirectory = new NoteDirectory(tempDirectory, null, callback)
|
||||
|
||||
afterEach ->
|
||||
noteDirectory.destroy()
|
||||
fs.unlinkSync(path.join(tempDirectory, 'Car', 'Mini.md'))
|
||||
fs.rmdirSync(path.join(tempDirectory, 'Car'))
|
||||
fs.unlinkSync(path.join(tempDirectory, 'Readme.md'))
|
||||
atom.config.set('notational-velocity.directory', defaultDirectory)
|
||||
|
||||
it 'gives a list of notes in the order so that the newest one comes first', ->
|
||||
notes = noteDirectory.getNotes()
|
||||
expect(notes.length).toEqual(2)
|
||||
expect(notes[0].getText()).toBe 'mini'
|
||||
expect(notes[1].getText()).toBe 'read me'
|
||||
expect(notes[0].getModified().getTime()).toBeGreaterThan(notes[1].getModified().getTime())
|
||||
|
||||
it 'changes its order when a note is changed', ->
|
||||
fs.writeFileSync(path.join(tempDirectory, 'Readme.md'), 'read me new')
|
||||
wait()
|
||||
|
||||
waitsFor -> isCallbackCalled
|
||||
runs ->
|
||||
notes = noteDirectory.getNotes()
|
||||
expect(notes[0].getText()).toBe 'read me new'
|
||||
expect(notes[1].getText()).toBe 'mini'
|
||||
|
||||
it 'changes its order when a note is created', ->
|
||||
fs.writeFileSync(path.join(tempDirectory, 'Car', 'Prius.md'), 'prius')
|
||||
wait()
|
||||
|
||||
waitsFor -> isCallbackCalled
|
||||
runs ->
|
||||
notes = noteDirectory.getNotes()
|
||||
expect(notes.length).toEqual(3)
|
||||
expect(notes[0].getText()).toBe 'prius'
|
||||
expect(notes[1].getText()).toBe 'mini'
|
||||
expect(notes[2].getText()).toBe 'read me'
|
||||
fs.unlinkSync(path.join(tempDirectory, 'Car', 'Prius.md'))
|
||||
|
||||
it 'changes its order when a note is deleted', ->
|
||||
fs.unlinkSync(path.join(tempDirectory, 'Car', 'Mini.md'))
|
||||
wait()
|
||||
|
||||
waitsFor -> isCallbackCalled
|
||||
runs ->
|
||||
notes = noteDirectory.getNotes()
|
||||
expect(notes.length).toEqual(1)
|
||||
expect(notes[0].getText()).toBe 'read me'
|
||||
# So that it won't spit an error in the teardown stage.
|
||||
fs.writeFileSync(path.join(tempDirectory, 'Car', 'Mini.md'), 'mini')
|
||||
|
||||
it 'changes its order when a note is renamed', ->
|
||||
oldPath = path.join(tempDirectory, 'Car', 'Mini.md')
|
||||
newPath = path.join(tempDirectory, 'Mini.md')
|
||||
fs.renameSync(oldPath, newPath)
|
||||
wait()
|
||||
|
||||
waitsFor -> isCallbackCalled
|
||||
runs ->
|
||||
notes = noteDirectory.getNotes()
|
||||
expect(notes.length).toEqual(2)
|
||||
expect(notes[0].getTitle()).toBe 'Mini'
|
||||
expect(notes[1].getTitle()).toBe 'Readme'
|
||||
# So that it won't spit an error in the teardown stage.
|
||||
fs.renameSync(newPath, oldPath)
|
||||
|
||||
it 'updates properly when a directory is created and a note is created inside it', ->
|
||||
fs.mkdirSync(path.join(tempDirectory, 'Food'))
|
||||
fs.writeFileSync(path.join(tempDirectory, 'Food', 'Milk.md'), 'milk')
|
||||
wait()
|
||||
waitsFor -> isCallbackCalled
|
||||
runs ->
|
||||
notes = noteDirectory.getNotes()
|
||||
expect(notes.length).toEqual(3)
|
||||
expect(notes[0].getText()).toBe 'milk'
|
||||
# So that it won't spit an error in the teardown stage.
|
||||
fs.unlinkSync(path.join(tempDirectory, 'Food', 'Milk.md'))
|
||||
fs.rmdirSync(path.join(tempDirectory, 'Food'))
|
||||
@@ -0,0 +1,36 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs-plus'
|
||||
temp = require 'temp'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
Note = require '../lib/note'
|
||||
|
||||
describe 'Note', ->
|
||||
defaultDirectory = atom.config.get('notational-velocity.directory')
|
||||
tempDirectory = temp.mkdirSync('node-pathwatcher-directory')
|
||||
tempFilePath = path.join(tempDirectory, 'Temp.md')
|
||||
|
||||
beforeEach ->
|
||||
atom.config.set('notational-velocity.directory', tempDirectory)
|
||||
fs.writeFileSync(tempFilePath, 'old')
|
||||
|
||||
afterEach ->
|
||||
fs.unlinkSync(tempFilePath)
|
||||
atom.config.set('notational-velocity.directory', defaultDirectory)
|
||||
|
||||
it 'creates a note', ->
|
||||
note = new Note(tempFilePath, null, null)
|
||||
expect(note.getTitle()).toBe 'Temp'
|
||||
expect(note.getText()).toBe 'old'
|
||||
expect(note.getFilePath()).toBe tempFilePath
|
||||
note.destroy()
|
||||
|
||||
it 'modifies a note', ->
|
||||
note = new Note(tempFilePath, null, null)
|
||||
expect(note.getText()).toBe 'old'
|
||||
|
||||
fs.writeFileSync(tempFilePath, 'new')
|
||||
oldModified = note.getModified()
|
||||
waitsFor -> oldModified != note.getModified()
|
||||
runs ->
|
||||
expect(note.getText()).toBe 'new'
|
||||
note.destroy()
|
||||
Reference in New Issue
Block a user