mirror of
https://github.com/arduino/Arduino.git
synced 2024-11-28 09:24:14 +01:00
Clean up sketch loading
Previously, the Sketch constructor called its `load()` function, which called the `SketchData.load()` function to load files and then `Editor.sketchLoaded()` to initialize the GUI with the loaded files. When external editing was enabled, `Sketch.load()` was called again when activating the Arduino app, to reload the entire sketch. With this commit, the `Sketch.load()` function is removed, and `SketchData.load()` is called from the SketchData constructor. Instead of Sketch calling `Editor.sketchLoaded()`, that method is renamed to `createTabs()` and called by `Editor.HandleOpenInternal()` directly after creating the Sketch object. Handling of external editor mode has also changed. When the Arduino application is activated, instead of fully reloading the sketch (through the now-absent `Sketch.load()` method), the new `SketchData.reload()` method is called to reload the list of files in the sketch. If it changed, all tabs are re-created. If not, only the current tab is reloaded. When the user switches from one tab to another, that tab is also reloaded. This ensures that the visible tab is always up-to-date, without needlessly reloading all tabs all the time. When external editing mode is enabled or disabled, all tabs are reloaded too, to make sure they are up-to-date. When re-creating all tabs, no attempt is made to preserve the currently selected tab. Since adding or removing files happens rarely, this should not be a problem. When files are changed, the currently selected tab is implicitly preserved (because the tab is reloaded, not recreated). The caret (and thus scroll) position is preserved by temporarily changing the caret update policy, so the caret does not move while the text is swapped out. This happens in `EditorTab.setText()` now, so other callers can also profit from it. To support checking for a changed list of files in `SketchData.reload()`, a `SketchCode.equals()` method is added, that just checks if the filenames are equal. Additionally, the loading of the file list for a sketch has now moved from `SketchData.load()` to `SketchData.listSketchFiles()`, so `reload()` can also use it. At the same time, this loading is greatly simplified by using a sorted Set and `FileUtils.listFiles()`. In external editor mode, to ensure that during compilation the version from disk is always used instead of the in-memory version, EditorTab detaches itself from its SketchCode, so SketchCode has no access to the (possibly outdated) in-memory contents of the file.
This commit is contained in:
parent
283ccc150d
commit
8725bb1ec4
@ -608,11 +608,11 @@ public class Base {
|
||||
activeEditor.rebuildRecentSketchesMenu();
|
||||
if (PreferencesData.getBoolean("editor.external")) {
|
||||
try {
|
||||
int previousCaretPosition = activeEditor.getCurrentTab().getTextArea().getCaretPosition();
|
||||
activeEditor.getSketch().load(true);
|
||||
if (previousCaretPosition < activeEditor.getCurrentTab().getText().length()) {
|
||||
activeEditor.getCurrentTab().getTextArea().setCaretPosition(previousCaretPosition);
|
||||
}
|
||||
// If the list of files on disk changed, recreate the tabs for them
|
||||
if (activeEditor.getSketch().reload())
|
||||
activeEditor.createTabs();
|
||||
else // Let the current tab know it was activated, so it can reload
|
||||
activeEditor.getCurrentTab().activated();
|
||||
} catch (IOException e) {
|
||||
System.err.println(e);
|
||||
}
|
||||
|
@ -1602,6 +1602,7 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
redoAction.updateRedoState();
|
||||
updateTitle();
|
||||
header.rebuild();
|
||||
getCurrentTab().activated();
|
||||
|
||||
// This must be run in the GUI thread
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
@ -1653,7 +1654,11 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void sketchLoaded(Sketch sketch) {
|
||||
/**
|
||||
* Create tabs for each of the current sketch's files, removing any existing
|
||||
* tabs.
|
||||
*/
|
||||
public void createTabs() {
|
||||
tabs.clear();
|
||||
currentTabIndex = -1;
|
||||
tabs.ensureCapacity(sketch.getCodeCount());
|
||||
@ -1665,6 +1670,7 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
selectTab(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1980,9 +1986,8 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
Base.showWarning(tr("Error"), tr("Could not create the sketch."), e);
|
||||
return false;
|
||||
}
|
||||
createTabs();
|
||||
|
||||
header.rebuild();
|
||||
updateTitle();
|
||||
// Disable untitled setting from previous document, if any
|
||||
untitled = false;
|
||||
|
||||
|
@ -45,6 +45,7 @@ import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Element;
|
||||
import javax.swing.text.PlainDocument;
|
||||
import javax.swing.undo.UndoManager;
|
||||
import javax.swing.text.DefaultCaret;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
|
||||
@ -70,6 +71,8 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage {
|
||||
protected RTextScrollPane scrollPane;
|
||||
protected SketchCode code;
|
||||
protected boolean modified;
|
||||
/** Is external editing mode currently enabled? */
|
||||
protected boolean external;
|
||||
|
||||
/**
|
||||
* Create a new EditorTab
|
||||
@ -101,13 +104,13 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage {
|
||||
RSyntaxDocument document = createDocument(contents);
|
||||
this.textarea = createTextArea(document);
|
||||
this.scrollPane = createScrollPane(this.textarea);
|
||||
code.setStorage(this);
|
||||
applyPreferences();
|
||||
add(this.scrollPane, BorderLayout.CENTER);
|
||||
|
||||
RUndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor);
|
||||
document.addUndoableEditListener(undo);
|
||||
textarea.setUndoManager(undo);
|
||||
code.setStorage(this);
|
||||
}
|
||||
|
||||
private RSyntaxDocument createDocument(String contents) {
|
||||
@ -279,19 +282,29 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage {
|
||||
scrollPane.setFoldIndicatorEnabled(PreferencesData.getBoolean("editor.code_folding"));
|
||||
scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers"));
|
||||
|
||||
// apply the setting for 'use external editor'
|
||||
if (PreferencesData.getBoolean("editor.external")) {
|
||||
// disable line highlight and turn off the caret when disabling
|
||||
textarea.setBackground(Theme.getColor("editor.external.bgcolor"));
|
||||
textarea.setHighlightCurrentLine(false);
|
||||
textarea.setEditable(false);
|
||||
|
||||
} else {
|
||||
textarea.setBackground(Theme.getColor("editor.bgcolor"));
|
||||
textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight"));
|
||||
textarea.setEditable(true);
|
||||
// apply the setting for 'use external editor', but only if it changed
|
||||
if (external != PreferencesData.getBoolean("editor.external")) {
|
||||
external = !external;
|
||||
if (external) {
|
||||
// disable line highlight and turn off the caret when disabling
|
||||
textarea.setBackground(Theme.getColor("editor.external.bgcolor"));
|
||||
textarea.setHighlightCurrentLine(false);
|
||||
textarea.setEditable(false);
|
||||
// Detach from the code, since we are no longer the authoritative source
|
||||
// for file contents.
|
||||
code.setStorage(null);
|
||||
// Reload, in case the file contents already changed.
|
||||
reload();
|
||||
} else {
|
||||
textarea.setBackground(Theme.getColor("editor.bgcolor"));
|
||||
textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight"));
|
||||
textarea.setEditable(true);
|
||||
code.setStorage(this);
|
||||
// Reload once just before disabling external mode, to ensure we have
|
||||
// the latest contents.
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
// apply changes to the font size for the editor
|
||||
Font editorFont = scale(PreferencesData.getFont("editor.font"));
|
||||
textarea.setFont(editorFont);
|
||||
@ -306,7 +319,34 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage {
|
||||
document.setTokenMakerFactory(new ArduinoTokenMakerFactory(keywords));
|
||||
document.setSyntaxStyle(RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when this tab is made the current one, or when it is the current one
|
||||
* and the window is activated.
|
||||
*/
|
||||
public void activated() {
|
||||
// When external editing is enabled, reload the text whenever we get activated.
|
||||
if (external) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the contents of our file.
|
||||
*/
|
||||
private void reload() {
|
||||
String text;
|
||||
try {
|
||||
text = code.load();
|
||||
} catch (IOException e) {
|
||||
System.err.println(I18n.format("Warning: Failed to reload file: \"{0}\"",
|
||||
code.getFileName()));
|
||||
return;
|
||||
}
|
||||
setText(text);
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TextArea object for use (not recommended). This should only
|
||||
* be used in obscure cases that really need to hack the internals of the
|
||||
@ -343,7 +383,39 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage {
|
||||
* Replace the entire contents of this tab.
|
||||
*/
|
||||
public void setText(String what) {
|
||||
textarea.setText(what);
|
||||
// Set the caret update policy to NEVER_UPDATE while completely replacing
|
||||
// the current text. Normally, the caret tracks inserts and deletions, but
|
||||
// replacing the entire text will always make the caret end up at the end,
|
||||
// which isn't really useful. With NEVER_UPDATE, the caret will just keep
|
||||
// its absolute position (number of characters from the start), which isn't
|
||||
// always perfect, but the best we can do without making a diff of the old
|
||||
// and new text and some guesswork.
|
||||
// Note that we cannot use textarea.setText() here, since that first removes
|
||||
// text and then inserts the new text. Even with NEVER_UPDATE, the caret
|
||||
// always makes sure to stay valid, so first removing all text makes it
|
||||
// reset to 0. Also note that simply saving and restoring the caret position
|
||||
// will work, but then the scroll position might change in response to the
|
||||
// caret position.
|
||||
DefaultCaret caret = (DefaultCaret) textarea.getCaret();
|
||||
int policy = caret.getUpdatePolicy();
|
||||
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
|
||||
try {
|
||||
RSyntaxDocument doc = (RSyntaxDocument)textarea.getDocument();
|
||||
int oldLength = doc.getLength();
|
||||
// The undo manager already seems to group the insert and remove together
|
||||
// automatically, but better be explicit about it.
|
||||
textarea.getUndoManager().beginInternalAtomicEdit();
|
||||
try {
|
||||
doc.insertString(oldLength, what, null);
|
||||
doc.remove(0, oldLength);
|
||||
} catch (BadLocationException e) {
|
||||
System.err.println("Unexpected failure replacing text");
|
||||
} finally {
|
||||
textarea.getUndoManager().endInternalAtomicEdit();
|
||||
}
|
||||
} finally {
|
||||
caret.setUpdatePolicy(policy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,42 +65,8 @@ public class Sketch {
|
||||
public Sketch(Editor _editor, File file) throws IOException {
|
||||
editor = _editor;
|
||||
data = new SketchData(file);
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the list of files.
|
||||
* <P>
|
||||
* Generally this is only done once, rather than
|
||||
* each time a change is made, because otherwise it gets to be
|
||||
* a nightmare to keep track of what files went where, because
|
||||
* not all the data will be saved to disk.
|
||||
* <P>
|
||||
* This also gets called when the main sketch file is renamed,
|
||||
* because the sketch has to be reloaded from a different folder.
|
||||
* <P>
|
||||
* Another exception is when an external editor is in use,
|
||||
* in which case the load happens each time "run" is hit.
|
||||
*/
|
||||
private void load() throws IOException {
|
||||
load(false);
|
||||
}
|
||||
|
||||
protected void load(boolean forceUpdate) throws IOException {
|
||||
data.load();
|
||||
|
||||
// set the main file to be the current tab
|
||||
if (editor != null) {
|
||||
int current = editor.getCurrentTabIndex();
|
||||
if (current < 0)
|
||||
current = 0;
|
||||
editor.sketchLoaded(this);
|
||||
editor.selectTab(current);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean renamingCode;
|
||||
|
||||
/**
|
||||
@ -943,24 +909,6 @@ public class Sketch {
|
||||
public void prepare() throws IOException {
|
||||
// make sure the user didn't hide the sketch folder
|
||||
ensureExistence();
|
||||
|
||||
// TODO record history here
|
||||
//current.history.record(program, SketchHistory.RUN);
|
||||
|
||||
// if an external editor is being used, need to grab the
|
||||
// latest version of the code from the file.
|
||||
if (PreferencesData.getBoolean("editor.external")) {
|
||||
// history gets screwed by the open..
|
||||
//String historySaved = history.lastRecorded;
|
||||
//handleOpen(sketch);
|
||||
//history.lastRecorded = historySaved;
|
||||
|
||||
// nuke previous files and settings, just get things loaded
|
||||
load(true);
|
||||
}
|
||||
|
||||
// // handle preprocessing the main file's code
|
||||
// return build(tempBuildFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1288,6 +1236,9 @@ public class Sketch {
|
||||
return editor.untitled;
|
||||
}
|
||||
|
||||
public boolean reload() throws IOException {
|
||||
return data.reload();
|
||||
}
|
||||
|
||||
// .................................................................
|
||||
|
||||
|
@ -502,14 +502,13 @@ public class BaseNoGui {
|
||||
boolean success = false;
|
||||
try {
|
||||
// Editor constructor loads the sketch with handleOpenInternal() that
|
||||
// creates a new Sketch that, in trun, calls load() inside its constructor
|
||||
// creates a new Sketch that, in turn, builds a SketchData
|
||||
// inside its constructor.
|
||||
// This translates here as:
|
||||
// SketchData data = new SketchData(file);
|
||||
// File tempBuildFolder = getBuildFolder();
|
||||
// data.load();
|
||||
SketchData data = new SketchData(absoluteFile(parser.getFilenames().get(0)));
|
||||
File tempBuildFolder = getBuildFolder(data);
|
||||
data.load();
|
||||
|
||||
// Sketch.exportApplet()
|
||||
// - calls Sketch.prepare() that calls Sketch.ensureExistence()
|
||||
@ -556,7 +555,6 @@ public class BaseNoGui {
|
||||
// data.load();
|
||||
SketchData data = new SketchData(absoluteFile(path));
|
||||
File tempBuildFolder = getBuildFolder(data);
|
||||
data.load();
|
||||
|
||||
// Sketch.prepare() calls Sketch.ensureExistence()
|
||||
// Sketch.build(verbose) calls Sketch.ensureExistence() and set progressListener and, finally, calls Compiler.build()
|
||||
|
@ -91,7 +91,8 @@ public class SketchCode {
|
||||
|
||||
/**
|
||||
* Set an in-memory storage for this file's text, that will be queried
|
||||
* on compile, save, and whenever the text is needed.
|
||||
* on compile, save, and whenever the text is needed. null can be
|
||||
* passed to detach any attached storage.
|
||||
*/
|
||||
public void setStorage(TextStorage text) {
|
||||
this.storage = text;
|
||||
@ -201,6 +202,9 @@ public class SketchCode {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return (o instanceof SketchCode) && file.equals(((SketchCode) o).file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load this piece of code from a file and return the contents. This
|
||||
|
@ -6,6 +6,8 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import processing.app.helpers.FileUtils;
|
||||
|
||||
import static processing.app.I18n.tr;
|
||||
|
||||
public class SketchData {
|
||||
@ -53,7 +55,14 @@ public class SketchData {
|
||||
}
|
||||
};
|
||||
|
||||
SketchData(File file) {
|
||||
/**
|
||||
* Create a new SketchData object, and looks at the sketch directory
|
||||
* on disk to get populate the list of files in this sketch.
|
||||
*
|
||||
* @param file
|
||||
* The primary file for this sketch.
|
||||
*/
|
||||
SketchData(File file) throws IOException {
|
||||
primaryFile = file;
|
||||
|
||||
// get the name of the sketch by chopping .pde or .java
|
||||
@ -63,7 +72,9 @@ public class SketchData {
|
||||
name = mainFilename.substring(0, mainFilename.length() - suffixLength);
|
||||
|
||||
folder = new File(file.getParent());
|
||||
//System.out.println("sketch dir is " + folder);
|
||||
codeFolder = new File(folder, "code");
|
||||
dataFolder = new File(folder, "data");
|
||||
codes = listSketchFiles();
|
||||
}
|
||||
|
||||
static public File checkSketchFile(File file) {
|
||||
@ -90,68 +101,42 @@ public class SketchData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the list of files.
|
||||
* <p>
|
||||
* Generally this is only done once, rather than
|
||||
* each time a change is made, because otherwise it gets to be
|
||||
* a nightmare to keep track of what files went where, because
|
||||
* not all the data will be saved to disk.
|
||||
* <p>
|
||||
* This also gets called when the main sketch file is renamed,
|
||||
* because the sketch has to be reloaded from a different folder.
|
||||
* <p>
|
||||
* Another exception is when an external editor is in use,
|
||||
* in which case the load happens each time "run" is hit.
|
||||
* Reload the list of files. This checks the sketch directory on disk,
|
||||
* to see if any files were added or removed. This does *not* check
|
||||
* the contents of the files, just their presence.
|
||||
*
|
||||
* @return true when the list of files was changed, false when it was
|
||||
* not.
|
||||
*/
|
||||
protected void load() throws IOException {
|
||||
codeFolder = new File(folder, "code");
|
||||
dataFolder = new File(folder, "data");
|
||||
|
||||
// get list of files in the sketch folder
|
||||
String list[] = folder.list();
|
||||
if (list == null) {
|
||||
throw new IOException("Unable to list files from " + folder);
|
||||
public boolean reload() throws IOException {
|
||||
List<SketchCode> reloaded = listSketchFiles();
|
||||
if (!reloaded.equals(codes)) {
|
||||
codes = reloaded;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// reset these because load() may be called after an
|
||||
// external editor event. (fix for 0099)
|
||||
// codeDocs = new SketchCodeDoc[list.length];
|
||||
clearCodeDocs();
|
||||
// data.setCodeDocs(codeDocs);
|
||||
|
||||
for (String filename : list) {
|
||||
// Ignoring the dot prefix files is especially important to avoid files
|
||||
// with the ._ prefix on Mac OS X. (You'll see this with Mac files on
|
||||
// non-HFS drives, i.e. a thumb drive formatted FAT32.)
|
||||
if (filename.startsWith(".")) continue;
|
||||
|
||||
// Don't let some wacko name a directory blah.pde or bling.java.
|
||||
if (new File(folder, filename).isDirectory()) continue;
|
||||
|
||||
// figure out the name without any extension
|
||||
String base = filename;
|
||||
// now strip off the .pde and .java extensions
|
||||
for (String extension : EXTENSIONS) {
|
||||
if (base.toLowerCase().endsWith("." + extension)) {
|
||||
base = base.substring(0, base.length() - (extension.length() + 1));
|
||||
|
||||
// Don't allow people to use files with invalid names, since on load,
|
||||
// it would be otherwise possible to sneak in nasty filenames. [0116]
|
||||
if (BaseNoGui.isSanitaryName(base)) {
|
||||
File file = new File(folder, filename);
|
||||
addCode(new SketchCode(file, file.equals(primaryFile)));
|
||||
} else {
|
||||
System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), filename));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Scan this sketch's directory for files that should be loaded as
|
||||
* part of this sketch. Doesn't modify this SketchData instance, just
|
||||
* returns a filtered and sorted list of File objects ready to be
|
||||
* passed to the SketchCode constructor.
|
||||
*/
|
||||
private List<SketchCode> listSketchFiles() throws IOException {
|
||||
Set<SketchCode> result = new TreeSet<>(CODE_DOCS_COMPARATOR);
|
||||
for (File file : FileUtils.listFiles(folder, false, EXTENSIONS)) {
|
||||
if (BaseNoGui.isSanitaryName(file.getName())) {
|
||||
result.add(new SketchCode(file, file.equals(primaryFile)));
|
||||
} else {
|
||||
System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), file.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
if (getCodeCount() == 0)
|
||||
if (result.size() == 0)
|
||||
throw new IOException(tr("No valid code files found"));
|
||||
|
||||
// sort the entries at the top
|
||||
sortCode();
|
||||
return new ArrayList<>(result);
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
@ -225,10 +210,6 @@ public class SketchData {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void clearCodeDocs() {
|
||||
codes.clear();
|
||||
}
|
||||
|
||||
public File getFolder() {
|
||||
return folder;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user