total rewrite

This commit is contained in:
Mark Hahn 2015-11-20 23:18:27 -08:00
parent d0db9301b1
commit ab4855173b
8 changed files with 293 additions and 231 deletions

View File

@ -1,4 +1,8 @@
### 2.0.0 - 11/20/15
Rewritten from scratch.
### 0.4.0 - 6/4/15
* supports lit CS, thanks @michaelforrest

View File

@ -1,25 +1,26 @@
# markdown-scroll-sync Atom editor package
Auto-scroll markdown-preview tab to match markdown source. See the project at [github](https://github.com/mark-hahn/markdown-scroll-sync).
Auto-scroll markdown-preview tab to match markdown source.
---
![Image inserted by Atom editor package auto-host-markdown-image](http://i.imgur.com/X3fVXdL.gif)
![mkdn-scrl-sync](https://cloud.githubusercontent.com/assets/811455/11317259/c5b9b0c2-8fdc-11e5-8f85-b7deefb525c5.gif)
---
### Usage:
### Note about version 2.x.x
There is no atom command or keybinding. There are no config settings.
This is a total rewrite of the package. This version is more accurate and smoother. And last, but not least, fewer bugs (grin).
The package starts on load and watches for when it should sync. It will automatically sync when there are two panes where one active tab is a markdown file and another active tab is the markdown preview for that file. At that point it will scroll the preview to match the markdown file's scroll position.
### Installation
When the markdown file is scrolled the preview is automatically scrolled to match. If the preview is scrolled then syncing is temporarily turned off until the main file is focused again. The markdown file is never automatically scrolled to match the preview.
Hello world.
Run `apm install markdown-scroll-sync` or use the settings page to find the package and install it.
### Usage
There is no atom command or keybinding. There are no config settings. Just open a markdown file, open the preview, and watch it work.
### License
Copyright Mark Hahn by MIT license.
### Advertisement.
See other packages by mark-hahn at https://atom.io/users/mark-hahn/packages?direction=desc&sort=downloads.

View File

@ -2,132 +2,74 @@
lib/main.coffee
###
$ = null
log = (args...) ->
console.log.apply console, ['markdown-scroll, main:'].concat args
SubAtom = require 'sub-atom'
class MarkdownScrlSync
activate: (state) ->
process.nextTick =>
# console.log 'markdown-scroll-sync: activate '
pathUtil = require 'path'
@roaster = require 'roaster'
{TextEditor} = require 'atom'
{$} = require 'space-pen'
SubAtom = require 'sub-atom'
@subs = new SubAtom
pathUtil = require 'path'
{TextEditor} = require 'atom'
@subs = new SubAtom
if not (prvwPkg = atom.packages.getLoadedPackage 'markdown-preview')
if not (prvwPkg = atom.packages.getLoadedPackage 'markdown-preview-plus')
console.log 'markdown-scroll-sync: markdown preview packages not found'
return
if not (prvwPkg = atom.packages.getLoadedPackage 'markdown-preview') and
not (prvwPkg = atom.packages.getLoadedPackage 'markdown-preview-plus')
log 'markdown preview package not found'
return
viewPath = pathUtil.join prvwPkg.path, 'lib/markdown-preview-view'
MarkdownPreviewView = require viewPath
@subs.add atom.workspace.observeActivePaneItem (editor) =>
isMarkdown = (editor)->
for name in ["GitHub Markdown", "CoffeeScript (Literate)"]
return true if editor.getGrammar().name is name
return false
if editor instanceof TextEditor and
editor.alive and
isMarkdown(editor)
@stopTracking()
for preview in atom.workspace.getPaneItems()
if preview instanceof MarkdownPreviewView and
preview.editor is editor
@startTracking editor, preview
break
null
startTracking: (editor, preview) ->
editorView = atom.views.getView editor
if not (shadow = editorView.shadowRoot) then return
$lines = $ shadow.querySelector '.lines'
viewPath = pathUtil.join prvwPkg.path, 'lib/markdown-preview-view'
MarkdownPreviewView = require viewPath
lastTopRow = lastBotRow = null
@scrollInterval = setInterval =>
if not editor.alive
@subs.add atom.workspace.observeActivePaneItem (editor) =>
isMarkdown = (editor)->
for name in ["GitHub Markdown", "CoffeeScript (Literate)"]
return true if editor.getGrammar().name is name
return false
if editor instanceof TextEditor and
editor.alive and
isMarkdown(editor)
@stopTracking()
return
topRow = Math.min()
botRow = Math.max()
$lines.find('.line[data-screen-row]').each (idx, ele) =>
row = $(ele).attr 'data-screen-row'
topRow = Math.min topRow, row
botRow = Math.max botRow, row
endPos = editor.screenPositionForBufferPosition(editor.getBuffer().getEndPosition()).row
if botRow isnt lastBotRow and
botRow >= endPos - 1
preview.scrollToBottom()
# console.log 'bottom', botRow, endPos
else if topRow isnt lastTopRow
# console.log 'topRow', topRow
try
bufPos = editor.bufferPositionForScreenPosition [topRow+1, 0]
catch e
console.log 'markdown-scroll-sync: error in bufferPositionForScreenPosition',
{editor, topRow, e}
return
mdBeforeTopLine = editor.getTextInBufferRange [[0,0], bufPos]
@scroll preview, mdBeforeTopLine
# fs.writeFileSync 'C:\\atom\\markdown-scroll-sync\\mdBeforeTopLine.md', mdBeforeTopLine
lastTopRow = topRow
lastBotRow = botRow
, 300
walkDOM: (node) ->
node = node.firstChild
while node
# console.log 'nodeName', node.nodeName, node.nodeType
# if node.nodeType is 3 then console.log node.data
if node.nodeType in [1,8]
@resultNode = node
--@numEles
if @numEles <= 0 then return
if node.nodeName.toLowerCase().indexOf('atom-') < 0 then @walkDOM node
# else debugger
if @numEles <= 0 then return
node = node.nextSibling
scroll: (preview, text) ->
@roaster text, {}, (err, html) =>
# fs.writeFileSync 'C:\\atom\\markdown-scroll-sync\\htmlBeforeTopLine.html', html
@numEles = 0
regex = new RegExp '<([^\\/][a-z]*).*?>', 'g'
while (match = regex.exec html)
if match[1].toLowerCase() isnt 'code'
@numEles++
# console.log 'before walkDOM', @numEles
if (@resultNode = preview[0])
@walkDOM @resultNode
@resultNode.scrollIntoView()
# console.log 'walkDOM done', @resultNode
for previewView in atom.workspace.getPaneItems()
if previewView instanceof MarkdownPreviewView and
previewView.editor is editor
@startTracking editor, previewView
break
null
startTracking: (@editor, previewView) ->
@editorView = atom.views.getView @editor
@previewEle = previewView.element
@chrHgt = @editor.getLineHeightInPixels()
@lastScrnRow = null
@lastChrOfs = 0
@setMap()
@chkScroll 'init'
@subs2 = new SubAtom
@subs2.add @editor .onDidStopChanging => @setMap(); @chkScroll 'changed'
@subs2.add @editor .onDidChangeCursorPosition => @chkScroll 'cursorMoved'
@subs2.add @editorView.onDidChangeScrollTop => @chkScroll 'newtop'
@subs2.add @editor .onDidDestroy => @stopTracking()
stopTracking: ->
if @scrollInterval
clearInterval @scrollInterval
@scrollInterval = null
@subs2.dispose() if @subs2
@subs2 = null
deactivate: ->
@stopTracking()
@subs.dispose()
mix = (mixinName) ->
mixin = require './' + mixinName
for key in Object.keys mixin
MarkdownScrlSync.prototype[key] = mixin[key]
mix 'map'
mix 'scroll'
mix 'utils'
module.exports = new MarkdownScrlSync

92
lib/map.coffee Normal file
View File

@ -0,0 +1,92 @@
###
lib/map.coffee
###
log = (args...) ->
console.log.apply console, ['markdown-scroll, map:'].concat args
module.exports =
setMap: (getVis = yes) ->
start = Date.now()
timings = {}
if getVis
@getVisTopHgtBot()
timings['getVisTopHgtBot'] = Date.now() - start; start = Date.now()
@nodes = []
wlkr = document.createTreeWalker @previewEle, NodeFilter.SHOW_TEXT, null, yes
while (node = wlkr.nextNode())
text = node.textContent
if not /\w+/.test text then continue
[top, hgt, bot] = @getEleTopHgtBot node.parentNode, no
@nodes.push [top, bot, null, null, text, null]
timings['tree walk'] = Date.now() - start; start = Date.now()
nodePtr = 0
for bufRow in [0..@editor.getLastBufferRow()]
line = @editor.lineTextForBufferRow bufRow
if not (matches = line.match /[a-z0-9-\s]+/ig) then continue
maxLen = 0
target = null
for match in matches when /\w+/.test match
match = match.replace /^\s+|\s+$/g, ''
if match.length > maxLen
maxLen = match.length
target = match
if target
nodeMatch = null
for node, idx in @nodes[nodePtr...]
if node[4].includes target
if nodeMatch then nodeMatch = 'dup'; break
nodeMatch = node
idxMatch = idx
if not nodeMatch or nodeMatch is 'dup' then continue
{start:{row:topRow},end:{row:botRow}} =
@editor.screenRangeForBufferRange [[bufRow, 0],[bufRow, 9e9]]
nodeMatch[2] = topRow
nodeMatch[3] = botRow
nodeMatch[5] = target # DEBUG
nodePtr = idxMatch
timings['node match'] = Date.now() - start; start = Date.now()
@map = [[0,0,0,0]]
@lastTopPix = @lastBotPix = @lastTopRow = @lastBotRow = 0
firstNode = yes
addNodeToMap = (node) =>
[topPix, botPix, topRow, botRow] = node
if topPix < @lastBotPix or
topRow <= @lastBotRow
@lastTopPix = Math.min topPix, @lastTopPix
@lastBotPix = Math.max botPix, @lastBotPix
@lastTopRow = Math.min topRow, @lastTopRow
@lastBotRow = Math.max botRow, @lastBotRow
@map[@map.length - 1] =
[@lastTopPix, @lastBotPix, @lastTopRow, @lastBotRow]
else
if firstNode
@map[0][1] = topPix
@map[0][3] = Math.max 0, topRow - 1
@map.push [@lastTopPix = topPix,
@lastBotPix = botPix,
@lastTopRow = topRow,
@lastBotRow = botRow]
firstNode = no
for node in @nodes when node[2] isnt null
addNodeToMap node
botRow = @editor.getLastScreenRow()
topRow = Math.min botRow, @lastBotRow + 1
addNodeToMap [@lastBotPix, @previewEle.scrollHeight, topRow, botRow]
@nodes = null
# timings['map merge'] = Date.now() - start; start = Date.now()
# str = ''
# for k, v of timings then str += ' ' + k + ': ' + v
# log 'timings', str

78
lib/scroll.coffee Normal file
View File

@ -0,0 +1,78 @@
###
lib/scroll.coffee
###
log = (args...) ->
console.log.apply console, ['markdown-scroll, scroll:'].concat args
args[0]
module.exports =
chkScroll: (eventType, auto) ->
if @scrollTimeout
clearTimeout @scrollTimeout
@scrollTimeout = null
if not @editor.alive then @stopTracking(); return
if eventType isnt 'changed'
@getVisTopHgtBot()
if @scrnTopOfs isnt @lastScrnTopOfs or
@scrnBotOfs isnt @lastScrnBotOfs or
@previewTopOfs isnt @lastPvwTopOfs or
@previewBotOfs isnt @lastPvwBotOfs
@lastScrnTopOfs = @scrnTopOfs
@lastScrnBotOfs = @scrnBotOfs
@lastPvwTopOfs = @previewTopOfs
@lastPvwBotOfs = @previewBotOfs
@setMap no
switch eventType
when 'init'
cursorOfs = @editor.getCursorScreenPosition().row * @chrHgt
if @scrnTopOfs <= cursorOfs <= @scrnBotOfs
@setScroll cursorOfs
else @setScroll @scrnTopOfs
when 'changed', 'cursorMoved'
@setScroll @editor.getCursorScreenPosition().row * @chrHgt
@ignoreScrnScrollUntil = Date.now() + 500
when 'newtop'
if @ignoreScrnScrollUntil and
Date.now() < @ignoreScrnScrollUntil then break
@ignoreScrnScrollUntil = null
scrollFrac = @scrnTopOfs / (@scrnScrollHgt - @scrnHeight)
@setScroll @scrnTopOfs + (@scrnHeight * scrollFrac)
if not auto
@scrollTimeout = setTimeout (=> @chkScroll 'newtop', yes), 300
setScroll: (scrnPosPix) ->
scrnPosPix = Math.max 0, scrnPosPix
lastMapping = null
for mapping, idx in @map
[topPix, botPix, topRow, botRow] = mapping
if (topRow * @chrHgt) <= scrnPosPix < ((botRow+1) * @chrHgt) or
idx is @map.length - 1
row1 = topRow
row2 = botRow + 1
pix1 = topPix
pix2 = botPix
break
else
lastMapping ?= mapping
lastBotPix = lastMapping[1]
lastBotRow = lastMapping[3] + 1
if (lastBotRow * @chrHgt) <= scrnPosPix < (topRow * @chrHgt)
row1 = lastBotRow
row2 = topRow
pix1 = lastBotPix
pix2 = topPix
break
lastMapping = mapping
spanFrac = (scrnPosPix - (row1 * @chrHgt)) / ((row2 - row1) * @chrHgt)
visOfs = scrnPosPix - @scrnTopOfs
pvwPosPix = pix1 + ((pix2 - pix1) * spanFrac)
@previewEle.scrollTop = pvwPosPix - visOfs

42
lib/utils.coffee Normal file
View File

@ -0,0 +1,42 @@
###
lib/utils.coffee
###
log = (args...) ->
console.log.apply console, ['markdown-scroll, utils:'].concat args
module.exports =
getVisTopHgtBot: ->
{top: @edtTopBnd, bottom: edtBotBnd} = @editorView.getBoundingClientRect()
lineEles = @editorView.shadowRoot.querySelectorAll '.lines .line[data-screen-row]'
lines = []
for lineEle in lineEles
{top: lineTopBnd} = lineEle.getBoundingClientRect()
lines.push [+lineEle.getAttribute('data-screen-row'), lineTopBnd]
if lines.length is 0
log 'no visible lines in editor'
@scrnTopOfs = @scrnBotOfs = @pvwTopB = @previewTopOfs = @previewBotOfs = 0
return
lines.sort()
for refLine in lines
if refLine[1] >= @edtTopBnd then break
[refRow, refTopBnd] = refLine
@scrnTopOfs = (refRow * @chrHgt) - (refTopBnd - @edtTopBnd)
@scrnHeight = edtBotBnd - @edtTopBnd
@scrnBotOfs = @scrnTopOfs + @scrnHeight
botScrnScrollRow = @editor.clipScreenPosition([9e9, 9e9]).row
@scrnScrollHgt = (botScrnScrollRow + 1) * @chrHgt
{top: @pvwTopBnd, bottom: pvwBotBnd} = @previewEle.getBoundingClientRect()
@previewTopOfs = @previewEle.scrollTop
@previewBotOfs = @previewTopOfs + (pvwBotBnd - @pvwTopBnd)
getEleTopHgtBot: (ele, scrn = yes) ->
{top:eleTopBnd, bottom: eleBotBnd} = ele.getBoundingClientRect()
top = if scrn then @scrnTopOfs + (eleTopBnd - @edtTopBnd) \
else @previewTopOfs + (eleTopBnd - @pvwTopBnd)
hgt = eleBotBnd - eleTopBnd
bot = top + hgt
[top, hgt, bot]

View File

@ -1,16 +1,20 @@
{
"name": "markdown-scroll-sync",
"main": "./lib/main",
"version": "0.4.2",
"version": "2.0.0",
"description": "Auto-scroll markdown-preview tab to match markdown source",
"repository": "https://github.com/mark-hahn/markdown-scroll-sync",
"license": "MIT",
"engines": {
"atom": ">0.180.0"
"atom": ">1.0.0"
},
"keywords": [
"markdown",
"preview",
"scroll",
"sync"
],
"dependencies": {
"space-pen": "*",
"sub-atom": "*",
"roaster": "^1.1.2"
"sub-atom": "*"
}
}

101
test.md
View File

@ -1,101 +0,0 @@
# AR/VR Report
* This report try to explain the definition of AR and VR
* Those concepts are very young.
* Their definitions is still very vague.
* Just trying to better understand the meaning of those words.
* I gather here thoughts i had recently about those new realities.
it clarifies those concepts for me.
# Difference between AR and VR
* here those difference are based on current usage of those terms.
As their definitions are vague, the difference 2 terms is likely to
become more precise as we are moving along.
### Various Fields to Consider
Here are various fields on which we can compare and classify current AR and VR
technology. It may be a good idea to classify each existing devices.
It will helps clarify all those concepts
Here are possible fields:
* the content they are displaying
* the technology used
* to describe it
* is it available now ?
* if not yet available, how far fetch it is ?
* it goes into the feasibility of the product
* How to interact with this reality? Joypad, depth sensors like kinect or
leap motion. Keyboard. Issue of opaque display... You don't see your hands
so simple thing like topping on a keyboard is directly very hard when
you wear and oculus
* Is it mobile? Gear vr, Google cardboard are mobile. Oculus is not. Leap
motion is not. Likely his to be in the future?
* How to display it. Where is the screen for the user. Is there a single
screens?
Or two, one for each eye. Is it see thru display. Is it more normal opaque
screen. Can user perceive depth
* Relation between screen position and the latency issue to fool the brain
* VR implies to fool the brain about very primal things about balance.
The famous latency issue.
The one that makes you disy, sea sick and potentially throw up.
Believe me, after that, you don't want to do it again...
## Content Topic
* AR and VR differs greatly when it comes to the topic of their content
**About Virtual Reality**
* VR is about transporting the user in a virtual reality
* This reality is different to the normal world surrounding the user
* the user must deeply immersed in this world. the normal
world must disapears.
### Old notes
* Virtual reality is about an other reality. Not like the actual reality that
surround you.
* Virtual reality is close to the scope of entertainment because it can
display any reality even the fixtuous ones. it goes from the realty
displayed in game. Even GtaV.
* We can imagine how the actual movie industry could use vR to display a
new kind of movie
* Augmented reality is about modifying our reality.
* The current works we live in.
* It is a way more profound change.
* AR is more about utilities. VR is more about entertainment
* What reality is displayed? What is the type of content. Is it purely
virtual or is it our current reality with a little bit on top
## Interaction
### Type of inputs available
* touch screen
* any mobile devices, phone, or tablets
* depth sensor
* leap motion, kinect, tango
* device orientation
* device location
* precision: gps is crap
* using markers
* keyboard
### Product Available
* kinect
* leap motion
* various joystick: joypad, other ?
* device orientation
## Mobility
* Real important when it comes to application
* a mobile device provides more freedom
* You can use it in the street potentially
* To have wires conecting you to a computer limits your freedom
* It is especially important in AR/VR because they are about giving
freedom to access new spaces
* to have wireless devices make a great difference
## Do You put this one all the time or only some times with a specific purpose
* how long are they used
* it goes to confort, weight
* it goes into privacy
* if a personn wears them in a restaurant, people around the users got some reasonable expectations of privacy
* when a personns wears them at work, people got other expectations of privacy