Atom Editor Background Image

Today spent some time to customize Atom user stylesheet to display the background image for editor.
Background image is a feature that I expected so much in either TextMate or Sublime.
Now finally I have it in Atom.

Finding the class for each html component is a little bit tricky
My theme is seti-ui.

Stylesheet to display background image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//.react is needed to avoid impact on mini-editor
.editor.react {
// Fix the color strip between gutter and the editing area.
background: #0e1112;
.scroll-view {
// Align the Background Image with Editing Area
padding-left: 0;
margin-left: 10px;
// I put the background image under ~/.atom/images
background: url('/Users/timnew/.atom/images/batou.jpg') no-repeat fixed;
// Stretch the image
// I tried to set it in with other background properties,
// but it doesn't work. Looks like a bug in Chrome.
background-size: cover;
}
.cursor-line {
// Set cursor-line semi-transparent to make background visible.
// Adjust the alpha value to fit your taste.
background: rgba(90,90,90,.4) !important;
}
.overlayer {
// Set overlayer semi-transparent to make background visible.
// Adjust the alpha value to fit your taste.
background-color: rgba(21, 23, 24, .7) !important;
}
}

Screenshots

Without TreeView

Without Tree View

With TreeView

With Tree View

Atom Editor

Atom is general purpose text editor brought to us by GitHub guys, which is empowered by the loved Node.js and Chrome.

Someone said Atom is just another clone of Sublime. Well, I don’t quite agree with the saying. I’d like to say Atom learnt a lot from Sublime.
And I’d like to explain why.

I think Sublime itself is a great text editor, powerful to use, easy to learn, ready to be hacked, matured community, tons of plug-ins… almost unlimited possibilities…

But on the other hand, I think, Sublime is too sticky to “Text”. Yes, it is a text editor, but it doesn’t mean everything in the editor could only be text-based.
Implementing custom UI in Sublime isn’t a easy job to do. Benefits from HTML based technologies, Atom has a lot more rich features, CSS3 effects, CSS3 Animations, SVG, and a lot more…

Along with Brackets, C9, Atom is the important candidates of next-gen editors. But among them, only Atom is a general purpose editor.

Since Atom is still in the eary stage, there are a number of flaws and issues in it. Besides these bugs, the biggest problem of Atom is performance.
Loading a several MB text file or thousands pages document will kill the editor in no time. (Atom blocks user to load file larger than 2MB.)

But still, these issues won’t block me from loving it. As a node.js and Ruby developer, it is an awesome companion to my work. ❤️

Pitfall in fs.watch: fs.watch fails when switch from TextMate to RubyMine

I’m writing a cake script that helps me to build the growlStyle bundle.
And I wish to my script can watch the change of the source file, and rebuild when file changed.
So I wrote the code as following:

Watching code change
1
2
3
4
5
files = fs.readdirSync getLocalPath('source')
for file in files
fs.watch file, ->
console.log "File changed, rebuilding..."
build()

The code works when I edits the code with TextMate, but fails when I uses RubyMine!

Super weird!

After half an hour debugging, I found the following interesting phenomena:

  • Given I’m using TextMate
    When I changed the file 1st time
    Then a ‘change’ event is captured
    When I changed the file 2nd time
    Then a ‘change’ event is captured
    When I changed the file 3rd time
    Then a ‘change’ event is captured

  • Given I’m using RubyMine
    When I change the file 1st time
    Then a ‘rename’ event is captured
    When I changed the file 2nd time
    Then no event is captured
    When I changed the file 3rd time
    Then no event is captured

From the result, we can easily find out that the script fails is because “change” event is not triggered as expected when using RubyMine.
And the reason of RubyMine’s “wried” behavior might be that RubyMine what to keep the file integrity so they “write” the file in an atomic way as following:

  1. RubyMine write the file content to a temp file
  2. RubyMine remove the original file
  3. RubyMine rename the temp file to original file

This workflow ensures that the content is fully written or not written. So in a word, RubyMine does not actually write the file, it actually replace the original file with another one, and the original one is removed or stored to some special location.

And on the other hand, according to Node.js document of fs.watch, node uses kqueue on Mac to implement this behavior.
And according to kqueue document, it uses file descriptor as identifier, and file descriptor is bound to the file itself rather than its path. So when the file is renamed, we will keep to track the file with new name. That’s why we lost the status of the file after the first ‘rename’ event.
And in our case, we actually wish to identify the file by file path rather than by ‘file descriptor’.

To solve this issue, we have 2 potential solutions:

  1. Also apply fs.watch to the directory that holds the source file besides of the source file itself.
    When the file is directly updated as TextMate does, the watcher on the file will raise the “change” event.
    When the file is atomically updated as RubyMine does, the watcher on the directory will raise 2 “rename” events.
    So theoretically, we could track the change of the file no matter how it is updated.

  2. Use the old fashioned fs.watchFile function, which tracks the change the with fs.stat.
    Comparing to fs.watch, fs.watchFile is less efficient because its polling mechanism, but it does track the file with file name rather than file descriptor. So it won’t be charmed by the fancy atomic writing.

Obviously, the 1st solution looks better than the 2nd one, because its uses the event rather than old-fashioned polling. Even document of fs.watchFile also says that try to use fs.watch instead of fs.watchFile when possible.

But actually it is kind of painful to write such code, since ‘rename’ event on the directory is not only triggered by the file update, it also can be triggered by adding file and removing file.

And the ‘rename’ event will be triggered twice when updating the file. Obviously we cannot rebuild the code when the first ‘rename’ event fired, or the build might fail because of the absence of the file. And we will trigger the build twice in a really short period of time.

So in fact, to solve our problem, the polling fs.watchFile is more useful, its old-fashion protected itself being charmed by the ‘fancy’ atomic file writing.

So finally, we got the following code:

fs.watchFile
1
2
3
4
5
6
7
8
9
10
11
runInWatch = (options, task) ->
action(options) unless options.watch
console.info "INFO: Watching..."
files = fs.readdirSync getLocalPath('source')
console.log '"Tracking files:'
for file in files
console.log "#{file}"
fs.watchFile getLocalPath('source', file), (current, previous) ->
unless current.mtime == previous.mtime
console.log "#{file} Changed..."
task(options)

HINT: Be careful about the differens of fs.watch and fs.watchFile:

  • The meaning of filename parameter
    The filename parameter of fs.watch is path sensitive, which accept ‘source.jade’ or ‘/path/to/source.jade’ The filename parameter of fs.watchFile isn’t path sensitive, which only accept ‘/path/to/source.jade’
  • Callback is invocation condition
    fs.watch invokes callback when the file is renamed or changed fs.watchFile invokes callback when the file is accessed, including write and read.
    So you need to compare the mtime of the fstat, file is changed when mtime changed.
  • Response time
    fs.watch uses event, which captures the “change” almost in realtime. fs.watchFile uses ‘polling’, which might differed for a period of time. By default, the maximum could be 5s.