This repository has been archived by the owner on Sep 6, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Initial implementation of file save #16
Merged
Changes from 36 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
14375eb
Initial implementation of File > Save and dirty bit management
f048b02
Removed call to temp debug function
c4493d7
Added comments around our dirty bit management
8941cd9
Beginning implementation of prompt when closing dirty file
8b769ba
Merged with latest file read API
2a3832d
Merge branch 'open-file-from-tree' into save-file
5701dd6
Merge latest file api and open-file code
0d169b9
Implementing file close--prompting to save not yet implemented
33e8cfe
Added dialog when closing dirty file. Refactored dialog code to make …
72d998c
Made commands rely on $.Deferred() to handle asynchronicity, and upda…
ef8d96c
Added clarifying comment in FILE_CLOSE command
b91566c
Trying different indentation style for Deferred callbacks
8db07b7
Merge branch 'initial-fileio-work' into save-file
43a0b98
Added another reject() case to doOpen()
adb37d0
Hooked up to HTML file save API
3e7c89a
Renamed showDialog() to showModalDialog()
b01a80b
Refactored brackets.js to make the boot sequence cleaner, and pulled …
ab3951c
Merge latest save file API
da5f24d
Merge remote-tracking branch 'origin/initial-fileio-work' into save-file
b2df1a1
Merge remote-tracking branch 'origin/master' into save-file
9a91763
Merge remote-tracking branch 'origin/master' into save-file
dbf5170
Code review cleanup
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
/* | ||
* Copyright 2011 Adobe Systems Incorporated. All Rights Reserved. | ||
*/ | ||
|
||
/** | ||
* Handlers for commands related to file handling (opening, saving, etc.) | ||
*/ | ||
var FileCommandHandlers = (function() { | ||
// TODO: remove this and use the real exports variable when we switch to modules. | ||
var exports = {}; | ||
|
||
var _editor, _title, _currentFilePath, _currentTitlePath, | ||
_isDirty = false, | ||
_savedUndoPosition = 0; | ||
|
||
exports.init = function init(editor, title) { | ||
_editor = editor; | ||
_title = title; | ||
|
||
_editor.setOption("onChange", function() { | ||
updateDirty(); | ||
}); | ||
|
||
// Register global commands | ||
CommandManager.register(Commands.FILE_OPEN, handleFileOpen); | ||
CommandManager.register(Commands.FILE_SAVE, handleFileSave); | ||
CommandManager.register(Commands.FILE_CLOSE, handleFileClose); | ||
}; | ||
|
||
function updateDirty() { | ||
// If we've undone past the undo position at the last save, and there is no redo stack, | ||
// then we can never get back to a non-dirty state. | ||
var historySize = _editor.historySize(); | ||
if (historySize.undo < _savedUndoPosition && historySize.redo == 0) { | ||
_savedUndoPosition = -1; | ||
} | ||
var newIsDirty = (_editor.historySize().undo != _savedUndoPosition); | ||
if (_isDirty != newIsDirty) { | ||
_isDirty = newIsDirty; | ||
updateTitle(); | ||
} | ||
} | ||
|
||
function updateTitle() { | ||
_title.text( | ||
_currentTitlePath | ||
? (_currentTitlePath + (_isDirty ? " \u2022" : "")) | ||
: "Untitled" | ||
); | ||
} | ||
|
||
function handleFileOpen(fullPath) { | ||
// TODO: In the future, when we implement multiple open files, we won't close the previous file when opening | ||
// a new one. However, for now, since we only support a single open document, I'm pretending as if we're | ||
// closing the existing file first. This is so that I can put the code that checks for an unsaved file and | ||
// prompts the user to save it in the close command, where it belongs. When we implement multiple open files, | ||
// we can remove this here. | ||
var result; | ||
if (_currentFilePath) { | ||
result = new $.Deferred(); | ||
CommandManager | ||
.execute(Commands.FILE_CLOSE) | ||
.done(function() { | ||
doOpenWithOptionalPath(fullPath) | ||
.done(function() { | ||
result.resolve(); | ||
}) | ||
.fail(function() { | ||
result.reject(); | ||
}); | ||
}) | ||
.fail(function() { | ||
result.reject(); | ||
}); | ||
} | ||
else { | ||
result = doOpenWithOptionalPath(fullPath); | ||
} | ||
result.always(function() { | ||
_editor.focus(); | ||
}); | ||
return result; | ||
} | ||
|
||
function doOpenWithOptionalPath(fullPath) { | ||
if (!fullPath) { | ||
// Prompt the user with a dialog | ||
// TODO: we're relying on this to not be asynchronous--is that safe? | ||
NativeFileSystem.showOpenDialog(false, false, "Open File", ProjectManager.getProjectRoot().fullPath, | ||
["htm", "html", "js", "css"], function(files) { | ||
if (files.length > 0) { | ||
return doOpen(files[0]); | ||
} | ||
}); | ||
} | ||
else { | ||
return doOpen(fullPath); | ||
} | ||
} | ||
|
||
function doOpen(fullPath) { | ||
var result = new $.Deferred(); | ||
if (fullPath) { | ||
var reader = new NativeFileSystem.FileReader(); | ||
|
||
// TODO: we should implement something like NativeFileSystem.resolveNativeFileSystemURL() (similar | ||
// to what's in the standard file API) to get a FileEntry, rather than manually constructing it | ||
var fileEntry = new NativeFileSystem.FileEntry(fullPath); | ||
|
||
// TODO: it's weird to have to construct a FileEntry just to get a File. | ||
fileEntry.file(function(file) { | ||
reader.onload = function(event) { | ||
_currentFilePath = _currentTitlePath = fullPath; | ||
|
||
// TODO: have a real controller object for the editor | ||
_editor.setValue(event.target.result); | ||
_editor.clearHistory(); | ||
|
||
// In the main toolbar, show the project-relative path (if the file is inside the current project) | ||
// or the full absolute path (if it's not in the project). | ||
var projectRootPath = ProjectManager.getProjectRoot().fullPath; | ||
if (projectRootPath.length > 0 && projectRootPath.charAt(projectRootPath.length - 1) != "/") { | ||
projectRootPath += "/"; | ||
} | ||
if (fullPath.indexOf(projectRootPath) == 0) { | ||
_currentTitlePath = fullPath.slice(projectRootPath.length); | ||
if (_currentTitlePath.charAt(0) == '/') { | ||
_currentTitlePath = _currentTitlePath.slice(1); | ||
} | ||
} | ||
|
||
// Make sure we can't undo back to the previous content. | ||
_editor.clearHistory(); | ||
|
||
// This should be 0, but just to be safe... | ||
_savedUndoPosition = _editor.historySize().undo; | ||
updateDirty(); | ||
|
||
_editor.focus(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. handleFileOpen() has a result.always() handler that calls _editor.focus(). This one is probably not necessary. |
||
result.resolve(); | ||
}; | ||
|
||
reader.onerror = function(event) { | ||
// TODO: display meaningful error | ||
result.reject(); | ||
} | ||
|
||
reader.readAsText(file, "utf8"); | ||
}, | ||
function (error) { | ||
// TODO: display meaningful error | ||
result.reject(); | ||
}); | ||
} | ||
return result; | ||
} | ||
|
||
function handleFileSave() { | ||
var result = new $.Deferred(); | ||
if (_currentFilePath && _isDirty) { | ||
// TODO: we should implement something like NativeFileSystem.resolveNativeFileSystemURL() (similar | ||
// to what's in the standard file API) to get a FileEntry, rather than manually constructing it | ||
var fileEntry = new NativeFileSystem.FileEntry(_currentFilePath); | ||
|
||
fileEntry.createWriter( | ||
function(writer) { | ||
writer.onwrite = function() { | ||
_savedUndoPosition = _editor.historySize().undo; | ||
updateDirty(); | ||
result.resolve(); | ||
} | ||
writer.onerror = function() { | ||
result.reject(); | ||
} | ||
writer.write(_editor.getValue()); | ||
}, | ||
function(error) { | ||
// TODO: display meaningful error | ||
result.reject(); | ||
} | ||
); | ||
} | ||
else { | ||
result.resolve(); | ||
} | ||
result.always(function() { | ||
_editor.focus(); | ||
}); | ||
return result; | ||
} | ||
|
||
function handleFileClose() { | ||
if (_currentFilePath && _isDirty) { | ||
var result = new $.Deferred(); | ||
brackets.showModalDialog( | ||
brackets.DIALOG_ID_SAVE_CLOSE | ||
, brackets.strings.SAVE_CLOSE_TITLE | ||
, brackets.strings.format(brackets.strings.SAVE_CLOSE_MESSAGE, _currentTitlePath) | ||
).done(function(id) { | ||
if (id === brackets.DIALOG_BTN_CANCEL) { | ||
result.reject(); | ||
} | ||
else { | ||
if (id === brackets.DIALOG_BTN_OK) { | ||
CommandManager | ||
.execute(Commands.FILE_SAVE) | ||
.done(function() { | ||
doClose(); | ||
result.resolve(); | ||
}) | ||
.fail(function() { | ||
result.reject(); | ||
}); | ||
} | ||
else { | ||
// This is the "Don't Save" case--we can just go ahead and close the file. | ||
doClose(); | ||
result.resolve(); | ||
} | ||
} | ||
}); | ||
result.always(function() { | ||
_editor.focus(); | ||
}); | ||
return result; | ||
} | ||
else { | ||
doClose(); | ||
_editor.focus(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code path doesn't return a Deferred |
||
} | ||
} | ||
|
||
function doClose() { | ||
// TODO: When we implement multiple files being open, this will probably change to just | ||
// dispose of the editor for the current file (and will later change again if we choose to | ||
// limit the number of open editors). | ||
_editor.setValue(""); | ||
_editor.clearHistory(); | ||
_currentFilePath = _currentTitlePath = null; | ||
_savedUndoPosition = 0; | ||
_isDirty = false; | ||
updateTitle(); | ||
_editor.focus(); | ||
} | ||
|
||
return exports; | ||
})(); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* Copyright 2011 Adobe Systems Incorporated. All Rights Reserved. | ||
*/ | ||
|
||
/** | ||
* Manages the mapping of keyboard inputs to commands. | ||
*/ | ||
var KeyBindingManager = { | ||
/** | ||
* The currently installed keymap. | ||
*/ | ||
_keymap: null, | ||
|
||
/** | ||
* Install the specified keymap as the current keymap, overwriting the existing keymap. | ||
* | ||
* @param {KeyMap} keymap The keymap to install. | ||
*/ | ||
installKeymap: function(keymap) { | ||
this._keymap = keymap; | ||
}, | ||
|
||
/** | ||
* Process the keybinding for the current key. | ||
* | ||
* @param {string} A key-description string. | ||
* @return {boolean} true if the key was processed, false otherwise | ||
*/ | ||
handleKey: function(key) { | ||
if (this._keymap && this._keymap.map[key]) { | ||
CommandManager.execute(this._keymap.map[key]); | ||
return true; | ||
} | ||
return false; | ||
} | ||
}; | ||
|
||
/** class Keymap | ||
* | ||
* A keymap specifies how keys are mapped to commands. This currently just holds the map, but in future | ||
* it will likely be extended to include other metadata about the keymap. | ||
* | ||
* Keys are described by strings of the form "[modifier-modifier-...-]key", where modifier is one of | ||
* Ctrl, Alt, or Shift. If multiple modifiers are specified, they must be specified in that order | ||
* (i.e. "Ctrl-Alt-Shift-P" is legal, "Alt-Ctrl-Shift-P" is not). | ||
* (TODO: the above restriction is to simplify mapping--is it too onerous?) | ||
* -- Ctrl maps to Cmd on Mac. (This means that you can't specifically bind to the Ctrl key on Mac.) | ||
* -- Alt maps to the Option key on Mac. | ||
* -- Letters must be uppercase, but do not require Shift by default. To indicate that Shift must be held | ||
* down, you must specifically include Shift. | ||
* | ||
* @constructor | ||
* @param {map} map An object mapping key-description strings to command IDs. | ||
*/ | ||
var KeyMap = function(map) { | ||
if (map === undefined) { | ||
throw new Error("All parameters to the KeyMap constructor must be specified"); | ||
} | ||
this.map = map; | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_editor.clearHistory() is already done on line 117