diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbEditorState.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbEditorState.java index fd771c618d80..f858071dfda4 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbEditorState.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbEditorState.java @@ -20,7 +20,7 @@ import com.intellij.openapi.fileEditor.FileEditorStateLevel; final class IpnbEditorState implements FileEditorState{ private final transient long myDocumentModificationStamp; // should not be serialized - private int mySelectedIndex; + private int mySelectedIndex = 0; private int mySelectedTop; public IpnbEditorState(final long modificationStamp, int selectedComponentIndex, int top) { @@ -34,9 +34,8 @@ final class IpnbEditorState implements FileEditorState{ if (!(o instanceof IpnbEditorState)) return false; final IpnbEditorState state = (IpnbEditorState)o; - - return myDocumentModificationStamp == state.myDocumentModificationStamp && mySelectedIndex == state.mySelectedIndex && - mySelectedTop == state.mySelectedTop; + if (mySelectedIndex == -1) return true; + return myDocumentModificationStamp == state.myDocumentModificationStamp; } public int getSelectedTop() { diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbFileEditor.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbFileEditor.java index 0ad692bbd685..cc49f6e0285d 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbFileEditor.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbFileEditor.java @@ -1,11 +1,11 @@ package org.jetbrains.plugins.ipnb.editor; import com.google.common.collect.Lists; -import com.intellij.AppTopics; import com.intellij.codeHighlighting.BackgroundEditorHighlighter; import com.intellij.icons.AllIcons; import com.intellij.ide.structureView.StructureViewBuilder; import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.*; import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager; @@ -63,27 +63,15 @@ public class IpnbFileEditor extends UserDataHolderBase implements FileEditor, Te public IpnbFileEditor(Project project, final VirtualFile vFile) { - project.getMessageBus().connect(this).subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() { - @Override - public void beforeAllDocumentsSaving() { - if (!new File(vFile.getPath()).exists()) return; - final IpnbFilePanel filePanel = myIpnbFilePanel; - if (filePanel != null) { - IpnbParser.saveIpnbFile(filePanel); - vFile.refresh(false, false); - } - - } - }); - project.getMessageBus().connect(this).subscribe(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER, new FileEditorManagerListener.Before.Adapter() { @Override public void beforeFileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { if (!new File(file.getPath()).exists()) return; IpnbParser.saveIpnbFile(myIpnbFilePanel); - file.refresh(false, false); + file.refresh(false, false); } }); + myFile = vFile; myName = vFile.getName(); @@ -94,7 +82,7 @@ public class IpnbFileEditor extends UserDataHolderBase implements FileEditor, Te myEditorPanel.setBackground(IpnbEditorUtil.getBackground()); myIpnbFilePanel = createIpnbEditorPanel(project, vFile); - + Disposer.register(this, myIpnbFilePanel); final JPanel controlPanel = createControlPanel(); myEditorPanel.add(controlPanel, BorderLayout.NORTH); myScrollPane = ScrollPaneFactory.createScrollPane(myIpnbFilePanel); @@ -156,7 +144,9 @@ public class IpnbFileEditor extends UserDataHolderBase implements FileEditor, Te } }); final IpnbPanel selectedCell = myIpnbFilePanel.getSelectedCell(); - updateCellTypeCombo(selectedCell); + if (selectedCell != null) { + updateCellTypeCombo(selectedCell); + } controlPanel.add(myCellTypeCombo); final MatteBorder border = BorderFactory.createMatteBorder(0, 0, 1, 0, JBColor.GRAY); controlPanel.setBorder(border); @@ -329,7 +319,7 @@ public class IpnbFileEditor extends UserDataHolderBase implements FileEditor, Te }); } - private void updateCellTypeCombo(IpnbPanel ipnbPanel) { + private void updateCellTypeCombo(@NotNull final IpnbPanel ipnbPanel) { if (ipnbPanel instanceof IpnbHeadingPanel) { final IpnbHeadingCell cell = ((IpnbHeadingPanel)ipnbPanel).getCell(); final int level = cell.getLevel(); @@ -370,7 +360,9 @@ public class IpnbFileEditor extends UserDataHolderBase implements FileEditor, Te final int index = getIpnbFilePanel().getSelectedIndex(); final IpnbEditablePanel cell = getIpnbFilePanel().getSelectedCell(); final int top = cell != null ? cell.getTop() : 0; - return new IpnbEditorState(-1, index, top); + final Document document = FileDocumentManager.getInstance().getCachedDocument(myFile); + long modificationStamp = document != null ? document.getModificationStamp() : myFile.getModificationStamp(); + return new IpnbEditorState(modificationStamp, index, top); } @Override diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbAddCellAboveAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbAddCellAboveAction.java index 457818532e6f..382e5b7bf36f 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbAddCellAboveAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbAddCellAboveAction.java @@ -4,6 +4,8 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.fileEditor.FileEditor; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor; @@ -22,6 +24,16 @@ public class IpnbAddCellAboveAction extends AnAction { } public void addCell(@NotNull final IpnbFilePanel ipnbFilePanel) { - ipnbFilePanel.createAndAddCell(false); + CommandProcessor.getInstance().executeCommand(ipnbFilePanel.getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + ipnbFilePanel.createAndAddCell(false); + ipnbFilePanel.saveToFile(); + } + }); + } + }, "Ipnb.createAndAddCell", new Object()); + } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbAddCellBelowAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbAddCellBelowAction.java index 5c088f233151..0154fec7047e 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbAddCellBelowAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbAddCellBelowAction.java @@ -4,6 +4,8 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.fileEditor.FileEditor; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor; @@ -22,6 +24,15 @@ public class IpnbAddCellBelowAction extends AnAction { } public static void addCell(@NotNull final IpnbFilePanel ipnbFilePanel) { - ipnbFilePanel.createAndAddCell(true); + CommandProcessor.getInstance().executeCommand(ipnbFilePanel.getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + ipnbFilePanel.createAndAddCell(true); + ipnbFilePanel.saveToFile(); + } + }); + } + }, "Ipnb.createAndAddCell", new Object()); } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCodeCellAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCodeCellAction.java index e2d222aee059..f34fa77bcecc 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCodeCellAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCodeCellAction.java @@ -31,6 +31,7 @@ public class IpnbCodeCellAction extends AnAction { public void changeTypeToCode(@NotNull final IpnbFileEditor editor) { final IpnbFilePanel filePanel = editor.getIpnbFilePanel(); final IpnbEditablePanel selectedCell = filePanel.getSelectedCell(); + if (selectedCell == null) return; final IpnbEditableCell cell = selectedCell.getCell(); final List cells = filePanel.getIpnbFile().getCells(); diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCutCellAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCutCellAction.java index 834d572a89b8..7cecdebf833b 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCutCellAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCutCellAction.java @@ -5,6 +5,8 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.fileEditor.FileEditor; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor; @@ -26,8 +28,15 @@ public class IpnbCutCellAction extends AnAction { } public static void cutCell(@NotNull final IpnbFilePanel ipnbFilePanel) { - ipnbFilePanel.cutCell(); - ipnbFilePanel.revalidate(); - ipnbFilePanel.repaint(); + CommandProcessor.getInstance().executeCommand(ipnbFilePanel.getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + ipnbFilePanel.cutCell(); + ipnbFilePanel.saveToFile(); + } + }); + } + }, "Ipnb.cutCell", new Object()); } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbDeleteCellAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbDeleteCellAction.java index a1ade7f40dc3..2457be2a1c3b 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbDeleteCellAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbDeleteCellAction.java @@ -5,6 +5,8 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.fileEditor.FileEditor; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor; @@ -21,13 +23,20 @@ public class IpnbDeleteCellAction extends AnAction { final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context); if (editor instanceof IpnbFileEditor) { final IpnbFilePanel component = ((IpnbFileEditor)editor).getIpnbFilePanel(); - cutCell(component); + deleteCell(component); } } - public void cutCell(@NotNull final IpnbFilePanel ipnbFilePanel) { - ipnbFilePanel.deleteSelectedCell(); - ipnbFilePanel.revalidate(); - ipnbFilePanel.repaint(); + public static void deleteCell(@NotNull final IpnbFilePanel ipnbFilePanel) { + CommandProcessor.getInstance().executeCommand(ipnbFilePanel.getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + ipnbFilePanel.deleteSelectedCell(); + ipnbFilePanel.saveToFile(); + } + }); + } + }, "Ipnb.deleteCell", new Object()); } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbHeadingCellActionBase.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbHeadingCellActionBase.java index c1a1492690c9..52ff77b5b02c 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbHeadingCellActionBase.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbHeadingCellActionBase.java @@ -34,6 +34,7 @@ public class IpnbHeadingCellActionBase extends AnAction { public void changeTypeToHeading(@NotNull final IpnbFileEditor editor) { final IpnbFilePanel filePanel = editor.getIpnbFilePanel(); final IpnbEditablePanel selectedCell = filePanel.getSelectedCell(); + if (selectedCell == null) return; final IpnbEditableCell cell = selectedCell.getCell(); final List cells = filePanel.getIpnbFile().getCells(); diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMarkdownCellAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMarkdownCellAction.java index ed9455d6b120..a057ab7a422d 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMarkdownCellAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMarkdownCellAction.java @@ -29,6 +29,7 @@ public class IpnbMarkdownCellAction extends AnAction { public void changeTypeToMarkdown(@NotNull final IpnbFileEditor editor) { final IpnbFilePanel filePanel = editor.getIpnbFilePanel(); final IpnbEditablePanel selectedCell = filePanel.getSelectedCell(); + if (selectedCell == null) return; final IpnbEditableCell cell = selectedCell.getCell(); final List cells = filePanel.getIpnbFile().getCells(); diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMoveCellDownAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMoveCellDownAction.java index 618863a7f486..924891372ce5 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMoveCellDownAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMoveCellDownAction.java @@ -4,6 +4,8 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.fileEditor.FileEditor; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor; @@ -17,7 +19,17 @@ public class IpnbMoveCellDownAction extends AnAction { final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context); if (editor instanceof IpnbFileEditor) { final IpnbFilePanel ipnbFilePanel = ((IpnbFileEditor)editor).getIpnbFilePanel(); - ipnbFilePanel.moveCell(true); + CommandProcessor.getInstance().executeCommand(ipnbFilePanel.getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + ipnbFilePanel.moveCell(true); + ipnbFilePanel.saveToFile(); + } + }); + } + }, "Ipnb.moveCell", new Object()); + } } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMoveCellUpAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMoveCellUpAction.java index 6821ad53fe60..e3df036a5040 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMoveCellUpAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbMoveCellUpAction.java @@ -4,6 +4,8 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.fileEditor.FileEditor; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor; @@ -16,8 +18,18 @@ public class IpnbMoveCellUpAction extends AnAction { final DataContext context = event.getDataContext(); final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context); if (editor instanceof IpnbFileEditor) { - final IpnbFilePanel filePanel = ((IpnbFileEditor)editor).getIpnbFilePanel(); - filePanel.moveCell(false); + final IpnbFilePanel ipnbFilePanel = ((IpnbFileEditor)editor).getIpnbFilePanel(); + CommandProcessor.getInstance().executeCommand(ipnbFilePanel.getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + ipnbFilePanel.moveCell(false); + ipnbFilePanel.saveToFile(); + } + }); + } + }, "Ipnb.moveCell", new Object()); + } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbPasteCellAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbPasteCellAction.java index 4d654594296a..deb560650aa1 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbPasteCellAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbPasteCellAction.java @@ -5,6 +5,8 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.fileEditor.FileEditor; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor; @@ -26,6 +28,16 @@ public class IpnbPasteCellAction extends AnAction { } public static void pasteCell(@NotNull final IpnbFilePanel ipnbFilePanel) { - ipnbFilePanel.pasteCell(); + CommandProcessor.getInstance().executeCommand(ipnbFilePanel.getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + ipnbFilePanel.pasteCell(); + ipnbFilePanel.saveToFile(); + } + }); + } + }, "Ipnb.pasteCell", new Object()); + } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbRunCellBaseAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbRunCellBaseAction.java index 72ff742a421e..d3043bcde6e4 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbRunCellBaseAction.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbRunCellBaseAction.java @@ -19,6 +19,7 @@ public abstract class IpnbRunCellBaseAction extends AnAction { final int index = ipnbFilePanel.getSelectedIndex(); if (ipnbFilePanel.getIpnbPanels().size()-1 == index) { ipnbFilePanel.createAndAddCell(true); + ipnbFilePanel.saveToFile(); } ipnbFilePanel.selectNext(cell); } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbFilePanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbFilePanel.java index 021e162401d6..2719b9bfbbe3 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbFilePanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbFilePanel.java @@ -1,19 +1,31 @@ package org.jetbrains.plugins.ipnb.editor.panels; import com.google.common.collect.Lists; +import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.DataProvider; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.event.DocumentAdapter; +import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.VerticalFlowLayout; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; import com.intellij.ui.JBColor; +import com.intellij.util.Alarm; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil; import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor; +import org.jetbrains.plugins.ipnb.editor.actions.IpnbCutCellAction; +import org.jetbrains.plugins.ipnb.editor.actions.IpnbDeleteCellAction; +import org.jetbrains.plugins.ipnb.editor.actions.IpnbPasteCellAction; import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodePanel; import org.jetbrains.plugins.ipnb.format.IpnbFile; import org.jetbrains.plugins.ipnb.format.IpnbParser; @@ -30,16 +42,19 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { +public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider, Disposable { + private static final Logger LOG = Logger.getInstance(IpnbFilePanel.class); + private final DocumentAdapter myDocumentListener; + private Document myDocument; private IpnbFile myIpnbFile; - private Project myProject; @NotNull private IpnbFileEditor myParent; + @NotNull private final VirtualFile myVirtualFile; @NotNull private final IpnbFileEditor.CellSelectionListener myListener; private final List myIpnbPanels = Lists.newArrayList(); - private IpnbEditablePanel mySelectedCell; + @Nullable private IpnbEditablePanel mySelectedCell; private IpnbEditablePanel myBufferPanel; private int myIncrement = 10; private int myInitialSelection = 0; @@ -50,37 +65,61 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { super(new VerticalFlowLayout(VerticalFlowLayout.TOP, 100, 5, true, false)); myProject = project; myParent = parent; + myVirtualFile = vFile; myListener = listener; setBackground(IpnbEditorUtil.getBackground()); - ApplicationManager.getApplication().invokeLater(new Runnable() { + final Alarm alarm = new Alarm(); + myDocumentListener = new DocumentAdapter() { + public void documentChanged(final DocumentEvent e) { + alarm.cancelAllRequests(); + alarm.addRequest(new MySynchronizeRequest(), 10, ModalityState.stateForComponent(IpnbFilePanel.this)); + } + }; + myDocument = myParent.getEditor().getDocument(); + myDocument.addDocumentListener(myDocumentListener); + + alarm.addRequest(new Runnable() { @Override public void run() { - try { - myIpnbFile = IpnbParser.parseIpnbFile(vFile); - if (myIpnbFile.getCells().isEmpty()) { - final IpnbCodeCell cell = new IpnbCodeCell("python", new String[]{""}, null, new ArrayList()); - myIpnbFile.addCell(cell, 0); + readFromFile(true); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + updateCellSelection(e); } - layoutFile(); - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - updateCellSelection(e); - } - }); - setFocusable(true); - - } - catch (IOException e) { - Messages.showErrorDialog(project, e.getMessage(), "Can't open " + vFile.getPath()); - } + }); + setFocusable(true); } - }); + }, 10, ModalityState.stateForComponent(this)); UIUtil.requestFocus(this); } + public void dispose() { + myDocument.removeDocumentListener(myDocumentListener); + } + + private void readFromFile(boolean showError) { + try { + myIpnbPanels.clear(); + mySelectedCell = null; + myIpnbFile = IpnbParser.parseIpnbFile(myDocument, myVirtualFile.getPath()); + if (myIpnbFile.getCells().isEmpty()) { + final IpnbCodeCell cell = new IpnbCodeCell("python", new String[]{""}, null, new ArrayList()); + myIpnbFile.addCell(cell, 0); + saveToFile(); + } + } catch (IOException e) { + if (showError) + Messages.showErrorDialog(getProject(), e.getMessage(), "Can't open " + myVirtualFile.getPath()); + else + LOG.error(e.getMessage(), "Can't open " + myVirtualFile.getPath()); + + } + layoutFile(); + } + public List getIpnbPanels() { return myIpnbPanels; } @@ -119,8 +158,7 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { } } - public void createAndAddCell(boolean below) { - removeAll(); + public void createAndAddCell(final boolean below) { final IpnbCodeCell cell = new IpnbCodeCell("python", new String[]{""}, null, new ArrayList()); final IpnbCodePanel codePanel = new IpnbCodePanel(myProject, myParent, cell); @@ -128,7 +166,6 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { } private void addCell(@NotNull final IpnbEditablePanel panel, boolean below) { - removeAll(); final IpnbEditablePanel selectedCell = getSelectedCell(); int index = myIpnbPanels.indexOf(selectedCell); if (below) { @@ -137,16 +174,7 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { final IpnbEditableCell cell = panel.getCell(); myIpnbFile.addCell(cell, index); myIpnbPanels.add(index, panel); - - for (IpnbPanel comp : myIpnbPanels) { - add(comp); - } - add(createEmptyPanel()); - setSelectedCell(panel); - requestFocus(); - revalidate(); - repaint(); } public void cutCell() { @@ -171,7 +199,10 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { if (down) { deleteSelectedCell(); - addCell(selectedCell, true); + final IpnbEditableCell cell = selectedCell.getCell(); + myIpnbFile.addCell(cell, index + 1); + myIpnbPanels.add(index + 1, selectedCell); + setSelectedCell(selectedCell); } else { final IpnbEditablePanel siblingPanel = myIpnbPanels.get(siblingIndex); @@ -183,65 +214,161 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { public void deleteSelectedCell() { final IpnbEditablePanel cell = getSelectedCell(); - deleteCell(cell); + if (cell != null) + deleteCell(cell); } - private void deleteCell(@NotNull final IpnbEditablePanel cell) { - selectNextOrPrev(cell); + private boolean deleteCell(@NotNull final IpnbEditablePanel cell) { final int index = myIpnbPanels.indexOf(cell); - if (index < 0) return; + if (index < 0) return true; myIpnbPanels.remove(index); myIpnbFile.removeCell(index); + if (myIpnbFile.getCells().isEmpty()) { + final IpnbCodeCell newCell = new IpnbCodeCell("python", new String[]{""}, null, new ArrayList()); + final IpnbCodePanel codePanel = new IpnbCodePanel(myProject, myParent, newCell); + addCell(codePanel, false); + } + return false; + } - remove(cell); - if (myIpnbPanels.isEmpty()) { - createAndAddCell(true); + public void saveToFile() { + final String oldText = myDocument.getText(); + final String newText = IpnbParser.newDocumentText(this); + try { + final ReplaceInfo replaceInfo = findFragmentToChange(oldText, newText); + if (replaceInfo.getStartOffset() != -1) { + myDocument.replaceString(replaceInfo.getStartOffset(), replaceInfo.getEndOffset(), replaceInfo.getReplacement()); + } + } + catch (Exception e) { + myDocument.replaceString(0, oldText.length(), newText); } } + public static final class ReplaceInfo { + private final int myStartOffset; + private final int myEndOffset; + private final String myReplacement; + + public ReplaceInfo(final int startOffset, final int endOffset, final String replacement) { + myStartOffset = startOffset; + myEndOffset = endOffset; + myReplacement = replacement; + } + + public int getStartOffset() { + return myStartOffset; + } + + public int getEndOffset() { + return myEndOffset; + } + + public String getReplacement() { + return myReplacement; + } + } + + public static ReplaceInfo findFragmentToChange(final String oldText, final String newText) { + if (oldText.equals(newText)) { + return new ReplaceInfo(-1, -1, null); + } + + final int oldLength = oldText.length(); + final int newLength = newText.length(); + + int startOffset = 0; + while ( + startOffset < oldLength && startOffset < newLength && + oldText.charAt(startOffset) == newText.charAt(startOffset) + ) { + startOffset++; + } + + int endOffset = oldLength; + while (true) { + if (endOffset <= startOffset) { + break; + } + final int idxInNew = newLength - (oldLength - endOffset) - 1; + if (idxInNew < startOffset) { + break; + } + + final char c1 = oldText.charAt(endOffset - 1); + final char c2 = newText.charAt(idxInNew); + if (c1 != c2) { + break; + } + endOffset--; + } + + return new ReplaceInfo(startOffset, endOffset, newText.substring(startOffset, newLength - (oldLength - endOffset))); + } + + private class MySynchronizeRequest implements Runnable { + + public void run() { + final Project project = getProject(); + if (project.isDisposed()) { + return; + } + PsiDocumentManager.getInstance(project).commitDocument(myDocument); + final IpnbEditablePanel selectedCell = getSelectedCell(); + final int index = myIpnbPanels.indexOf(selectedCell); + myInitialSelection = index >= 0 && index < myIpnbPanels.size() ? index : myIpnbPanels.size() - 1; + removeAll(); + readFromFile(false); + } + } + + public void copyCell() { myBufferPanel = getSelectedCell(); } public void pasteCell() { if (myBufferPanel == null) return; - removeAll(); final IpnbEditablePanel editablePanel = (IpnbEditablePanel)myBufferPanel.clone(); addCell(editablePanel, true); } public void replaceComponent(@NotNull final IpnbEditablePanel from, @NotNull final IpnbCell cell) { - removeAll(); - final int index = myIpnbPanels.indexOf(from); - IpnbEditablePanel panel; - if (cell instanceof IpnbCodeCell) { - panel = new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)cell); - } - else if (cell instanceof IpnbMarkdownCell) { - panel = new IpnbMarkdownPanel((IpnbMarkdownCell)cell); - } - else if (cell instanceof IpnbHeadingCell) { - panel = new IpnbHeadingPanel((IpnbHeadingCell)cell); - } - else { - throw new UnsupportedOperationException(cell.getClass().toString()); - } - if (index >= 0) { - myIpnbPanels.remove(index); - myIpnbPanels.add(index, panel); - } - for (IpnbPanel comp : myIpnbPanels) { - add(comp); - } - add(createEmptyPanel()); + CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + final int index = myIpnbPanels.indexOf(from); + IpnbEditablePanel panel; + if (cell instanceof IpnbCodeCell) { + panel = new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)cell); + } + else if (cell instanceof IpnbMarkdownCell) { + panel = new IpnbMarkdownPanel((IpnbMarkdownCell)cell); + } + else if (cell instanceof IpnbHeadingCell) { + panel = new IpnbHeadingPanel((IpnbHeadingCell)cell); + } + else { + throw new UnsupportedOperationException(cell.getClass().toString()); + } + if (index >= 0) { + myIpnbFile.removeCell(index); + myIpnbFile.addCell(cell, index); + myIpnbPanels.remove(index); + myIpnbPanels.add(index, panel); + } + + if (from instanceof IpnbCodePanel) { + panel.switchToEditing(); + } + setSelectedCell(panel); + saveToFile(); + } + }); + } + }, "Ipnb.changeCellType", new Object()); - if (from instanceof IpnbCodePanel) { - panel.switchToEditing(); - } - setSelectedCell(panel); - remove(from); - revalidate(); - repaint(); } private void addComponent(@NotNull final IpnbEditablePanel comp) { @@ -287,20 +414,26 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { } } else if (e.getKeyCode() == KeyEvent.VK_DELETE) { - if (!mySelectedCell.isEditing()) - deleteCell(mySelectedCell); + if (!mySelectedCell.isEditing()) { + IpnbDeleteCellAction.deleteCell(this); + } } - else if (e.getKeyCode() == KeyEvent.VK_X && e.getModifiers() == InputEvent.CTRL_MASK) { - if (!mySelectedCell.isEditing()) - cutCell(); - } - else if (e.getKeyCode() == KeyEvent.VK_C && e.getModifiers() == InputEvent.CTRL_MASK) { - if (!mySelectedCell.isEditing()) - copyCell(); - } - else if (e.getKeyCode() == KeyEvent.VK_V && e.getModifiers() == InputEvent.CTRL_MASK) { - if (!mySelectedCell.isEditing()) - pasteCell(); + else if (e.getModifiers() == InputEvent.CTRL_MASK) { + if (e.getKeyCode() == KeyEvent.VK_X) { + if (!mySelectedCell.isEditing()) { + IpnbCutCellAction.cutCell(this); + } + } + else if (e.getKeyCode() == KeyEvent.VK_C) { + if (!mySelectedCell.isEditing()) { + copyCell(); + } + } + else if (e.getKeyCode() == KeyEvent.VK_V) { + if (!mySelectedCell.isEditing()) { + IpnbPasteCellAction.pasteCell(this); + } + } } else { getParent().dispatchEvent(e); @@ -435,4 +568,8 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider { } return null; } + + public Project getProject() { + return myProject; + } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodePanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodePanel.java index 13a4eea57cce..dfcece89eaa4 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodePanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodePanel.java @@ -1,13 +1,10 @@ package org.jetbrains.plugins.ipnb.editor.panels.code; import com.google.common.collect.Lists; -import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -30,7 +27,7 @@ public class IpnbCodePanel extends IpnbEditablePanel { private IpnbCodeSourcePanel myCodeSourcePanel; private final List myOutputPanels = Lists.newArrayList(); - public IpnbCodePanel(@NotNull final Project project, final IpnbFileEditor parent, @NotNull final IpnbCodeCell cell) { + public IpnbCodePanel(@NotNull final Project project, @NotNull final IpnbFileEditor parent, @NotNull final IpnbCodeCell cell) { super(cell, new BorderLayout()); myProject = project; myParent = parent; @@ -40,7 +37,6 @@ public class IpnbCodePanel extends IpnbEditablePanel { } public IpnbFileEditor getFileEditor() { - assert myParent != null; return myParent; } @@ -68,13 +64,6 @@ public class IpnbCodePanel extends IpnbEditablePanel { c.gridwidth = 1; myCodeSourcePanel = new IpnbCodeSourcePanel(myProject, this, myCell); - if (myParent != null) - Disposer.register(myParent, new Disposable() { - @Override - public void dispose() { - EditorFactory.getInstance().releaseEditor(myCodeSourcePanel.getEditor()); - } - }); addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.In, myCodeSourcePanel, c); c.gridx = 1; diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodeSourcePanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodeSourcePanel.java index 28892d9876cb..e3c710f86b73 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodeSourcePanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodeSourcePanel.java @@ -15,15 +15,16 @@ */ package org.jetbrains.plugins.ipnb.editor.panels.code; -import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.Disposable; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; import com.intellij.ui.Gray; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil; -import org.jetbrains.plugins.ipnb.editor.actions.IpnbRunCellAction; -import org.jetbrains.plugins.ipnb.editor.actions.IpnbRunCellInplaceAction; +import org.jetbrains.plugins.ipnb.editor.actions.IpnbRunCellBaseAction; import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditorPanel; import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel; import org.jetbrains.plugins.ipnb.editor.panels.IpnbPanel; @@ -73,7 +74,12 @@ public class IpnbCodeSourcePanel extends IpnbPanel imp else { myEditor = IpnbEditorUtil.createPythonCodeEditor(myProject, this); } - + Disposer.register(myParent.getFileEditor(), new Disposable() { + @Override + public void dispose() { + EditorFactory.getInstance().releaseEditor(myEditor); + } + }); final JComponent component = myEditor.getComponent(); final JComponent contentComponent = myEditor.getContentComponent(); @@ -100,12 +106,10 @@ public class IpnbCodeSourcePanel extends IpnbPanel imp UIUtil.requestFocus(getIpnbCodePanel().getFileEditor().getIpnbFilePanel()); } else if (keyCode == KeyEvent.VK_ENTER && InputEvent.CTRL_DOWN_MASK == e.getModifiersEx()) { - final IpnbRunCellInplaceAction action = (IpnbRunCellInplaceAction)ActionManager.getInstance().getAction("IpnbRunCellInplaceAction"); - action.runCell(ipnbFilePanel, false); + IpnbRunCellBaseAction.runCell(ipnbFilePanel, false); } else if (keyCode == KeyEvent.VK_ENTER && InputEvent.SHIFT_DOWN_MASK == e.getModifiersEx()) { - final IpnbRunCellAction action = (IpnbRunCellAction)ActionManager.getInstance().getAction("IpnbRunCellAction"); - action.runCell(ipnbFilePanel, true); + IpnbRunCellBaseAction.runCell(ipnbFilePanel, true); } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbParser.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbParser.java index f94dacce6577..0f1f001dc3b0 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbParser.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbParser.java @@ -4,8 +4,7 @@ import com.google.common.collect.Lists; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.editor.Document; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel; import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel; @@ -31,13 +30,13 @@ public class IpnbParser { } @NotNull - public static IpnbFile parseIpnbFile(@NotNull String fileText, String path) throws IOException { - IpnbFileRaw rawFile = gson.fromJson(fileText, IpnbFileRaw.class); + public static IpnbFile parseIpnbFile(@NotNull final CharSequence fileText, String path) throws IOException { + IpnbFileRaw rawFile = gson.fromJson(fileText.toString(), IpnbFileRaw.class); if (rawFile == null) return new IpnbFile(new IpnbFileRaw(), Lists.newArrayList(), path); List cells = new ArrayList(); final IpnbWorksheet[] worksheets = rawFile.worksheets; for (IpnbWorksheet worksheet : worksheets) { - final IpnbCellRaw[] rawCells = worksheet.cells; + final List rawCells = worksheet.cells; for (IpnbCellRaw rawCell : rawCells) { cells.add(rawCell.createCell()); } @@ -46,14 +45,19 @@ public class IpnbParser { } @NotNull - public static IpnbFile parseIpnbFile(@NotNull VirtualFile virtualFile) throws IOException { - final String fileText = new String(virtualFile.contentsToByteArray(), CharsetToolkit.UTF8); - return parseIpnbFile(fileText, virtualFile.getPath()); + public static IpnbFile parseIpnbFile(@NotNull Document document, @NotNull final String path) throws IOException { + return parseIpnbFile(document.getImmutableCharSequence(), path); } public static void saveIpnbFile(@NotNull final IpnbFilePanel ipnbPanel) { + final String json = newDocumentText(ipnbPanel); + if (json == null) return; + writeToFile(ipnbPanel.getIpnbFile().getPath(), json); + } + + public static String newDocumentText(@NotNull final IpnbFilePanel ipnbPanel) { final IpnbFile ipnbFile = ipnbPanel.getIpnbFile(); - if (ipnbFile == null) return; + if (ipnbFile == null) return null; for (IpnbEditablePanel panel : ipnbPanel.getIpnbPanels()) { if (panel.isModified()) { panel.updateCellSource(); @@ -62,14 +66,14 @@ public class IpnbParser { final IpnbFileRaw fileRaw = ipnbFile.getRawFile(); final IpnbWorksheet worksheet = new IpnbWorksheet(); - final ArrayList cellRaws = new ArrayList(); for (IpnbCell cell: ipnbFile.getCells()) { - cellRaws.add(IpnbCellRaw.fromCell(cell)); + worksheet.cells.add(IpnbCellRaw.fromCell(cell)); } - worksheet.cells = cellRaws.toArray(new IpnbCellRaw[cellRaws.size()]); fileRaw.worksheets = new IpnbWorksheet[]{worksheet}; - final String json = gson.toJson(fileRaw); - final String path = ipnbFile.getPath(); + return gson.toJson(fileRaw); + } + + private static void writeToFile(@NotNull final String path, @NotNull final String json) { final File file = new File(path); FileWriter writer = null; try { @@ -96,8 +100,9 @@ public class IpnbParser { } private static class IpnbWorksheet { - IpnbCellRaw[] cells; + List cells = new ArrayList(); } + private static class IpnbCellRaw { String cell_type; Integer level;