Skip to content

Commit

Permalink
ide: start work on multi-file support
Browse files Browse the repository at this point in the history
  • Loading branch information
JoepVanlier committed Dec 15, 2024
1 parent 7f35370 commit be2d9c9
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 127 deletions.
2 changes: 2 additions & 0 deletions cmake.plugin.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ function(add_new_target target_name is_synth)
"plugin/components/tokenizer.h"
"plugin/components/tokenizer_functions.h"
"plugin/components/tokenizer.cpp"
"plugin/components/ysfx_document.h"
"plugin/components/ysfx_document.cpp"
"plugin/utility/audio_processor_suspender.h"
"plugin/utility/functional_timer.h"
"plugin/utility/async_updater.cpp"
Expand Down
164 changes: 38 additions & 126 deletions plugin/components/ide_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,14 @@
#include "utility/functional_timer.h"
#include "tokenizer.h"
#include <algorithm>
#include "ysfx_document.h"

class YSFXCodeEditor : public juce::CodeEditorComponent
{
public:
YSFXCodeEditor(juce::CodeDocument& doc, juce::CodeTokeniser* tokenizer, std::function<bool(const juce::KeyPress&)> keyPressCallback): CodeEditorComponent(doc, tokenizer), m_keyPressCallback{keyPressCallback} {}

bool keyPressed(const juce::KeyPress &key) override
{
if (!m_keyPressCallback(key)) {
return juce::CodeEditorComponent::keyPressed(key);
} else {
return true;
};
}

private:
std::function<bool(const juce::KeyPress&)> m_keyPressCallback;
};

struct YsfxIDEView::Impl {
YsfxIDEView *m_self = nullptr;
ysfx_u m_fx;
juce::Time m_changeTime;
bool m_reloadDialogGuard = false;
std::unique_ptr<juce::CodeDocument> m_document;

std::vector<std::shared_ptr<YSFXCodeDocument>> m_documents;
std::unique_ptr<JSFXTokenizer> m_tokenizer;
std::unique_ptr<YSFXCodeEditor> m_editor;
std::unique_ptr<juce::TextButton> m_btnSave;
Expand All @@ -68,17 +51,14 @@ struct YsfxIDEView::Impl {
juce::Array<VariableUI> m_vars;
std::unique_ptr<juce::Timer> m_varsUpdateTimer;

juce::File m_file{};

bool m_forceUpdate{false};
int m_currentDocumentIndex{0};

//==========================================================================
void setupNewFx();
void saveCurrentFile();
void saveFile(juce::File filename);
void saveAs();
void search(juce::String text, bool reverse);
void checkFileForModifications();

//==========================================================================
void createUI();
Expand All @@ -91,8 +71,7 @@ YsfxIDEView::YsfxIDEView()
: m_impl(new Impl)
{
m_impl->m_self = this;

m_impl->m_document.reset(new juce::CodeDocument);
m_impl->m_documents.push_back(std::shared_ptr<YSFXCodeDocument>(new YSFXCodeDocument()));
m_impl->m_tokenizer.reset(new JSFXTokenizer());

m_impl->createUI();
Expand Down Expand Up @@ -122,7 +101,7 @@ void YsfxIDEView::setEffect(ysfx_t *fx, juce::Time timeStamp)
if (fx)
ysfx_add_ref(fx);

m_impl->m_changeTime = timeStamp;
(void) timeStamp;
m_impl->setupNewFx();
m_impl->m_btnSave->setEnabled(true);
}
Expand Down Expand Up @@ -150,7 +129,9 @@ void YsfxIDEView::focusOfChildComponentChanged(FocusChangeType cause)
juce::Component *focus = getCurrentlyFocusedComponent();

if (focus == m_impl->m_editor.get()) {
juce::Timer *timer = FunctionalTimer::create([this]() { m_impl->checkFileForModifications(); });
juce::Timer *timer = FunctionalTimer::create([this]() {
for (auto& document : m_impl->m_documents) document->checkFileForModifications();
});
m_impl->m_fileCheckTimer.reset(timer);
timer->startTimer(100);
}
Expand All @@ -168,24 +149,12 @@ void YsfxIDEView::Impl::setupNewFx()

if (!fx) {
//
m_document->replaceAllContent(juce::String{});
m_documents[0]->reset();
m_editor->setReadOnly(true);
}
else {
juce::File file{juce::CharPointer_UTF8{ysfx_get_file_path(fx)}};
if (file != juce::File{}) m_file = file;

{
juce::MemoryBlock memBlock;
if (m_file.loadFileAsData(memBlock)) {
juce::String newContent = memBlock.toString();
memBlock = {};
if (newContent != m_document->getAllContent()) {
m_document->replaceAllContent(newContent);
m_editor->moveCaretToTop(false);
}
}
}
m_documents[0]->loadFile(file);

m_vars.ensureStorageAllocated(64);

Expand Down Expand Up @@ -233,16 +202,15 @@ void YsfxIDEView::Impl::setupNewFx()
void YsfxIDEView::Impl::saveAs()
{
if (m_fileChooserActive) return;
if (m_currentDocumentIndex >= m_documents.size()) return;

juce::File initialPath;
if (m_file != juce::File{}) {
initialPath = m_file.getParentDirectory();
}
auto document = m_documents[m_currentDocumentIndex];
juce::File initialPath = document->getPath().getParentDirectory();

m_fileChooser.reset(new juce::FileChooser(TRANS("Choose filename to save JSFX to"), initialPath));
m_fileChooser->launchAsync(
juce::FileBrowserComponent::saveMode|juce::FileBrowserComponent::canSelectFiles,
[this](const juce::FileChooser &chooser) {
[this, document](const juce::FileChooser &chooser) {
juce::File chosenFile = chooser.getResult();
if (chosenFile != juce::File()) {
if (chosenFile.exists()) {
Expand All @@ -254,111 +222,55 @@ void YsfxIDEView::Impl::saveAs()
.withButton(TRANS("Yes"))
.withButton(TRANS("No"))
.withMessage(TRANS("File already exists! Overwrite?")),
[this, chosenFile](int result) {
[this, chosenFile, document](int result) {
if (result == 1) {
this->saveFile(chosenFile);
m_self->onFileSaved(chosenFile);
document->saveFile(chosenFile);
if (m_self->onFileSaved) m_self->onFileSaved(m_documents[0]->getPath());
};
}
);
} else {
saveFile(chosenFile);
m_self->onFileSaved(chosenFile);
document->saveFile(chosenFile);
if (m_self->onFileSaved) m_self->onFileSaved(m_documents[0]->getPath());
}
}
m_fileChooserActive = false;
}
);
}

void YsfxIDEView::Impl::saveFile(juce::File file)
{
const juce::String content = m_document->getAllContent();

bool success = file.replaceWithData(content.toRawUTF8(), content.getNumBytesAsUTF8());
if (!success) {
juce::AlertWindow::showAsync(
juce::MessageBoxOptions{}
.withParentComponent(m_self)
.withIconType(juce::MessageBoxIconType::WarningIcon)
.withTitle(TRANS("Error"))
.withButton(TRANS("OK"))
.withMessage(TRANS("Could not save the JSFX document.")),
nullptr);
return;
}

m_changeTime = juce::Time::getCurrentTime();

if (m_self->onFileSaved)
m_self->onFileSaved(file);
}

void YsfxIDEView::Impl::saveCurrentFile()
{
ysfx_t *fx = m_fx.get();
if (!fx)
return;

if (m_file.existsAsFile()) {
m_btnSave->setEnabled(false);
saveFile(m_file);
if (m_currentDocumentIndex >= m_documents.size()) return;

if (m_documents[m_currentDocumentIndex]->getPath().existsAsFile()) {
m_documents[m_currentDocumentIndex]->saveFile();
} else {
saveAs();
}
}

void YsfxIDEView::Impl::checkFileForModifications()
{
ysfx_t *fx = m_fx.get();
if (!fx)
return;
m_btnSave->setEnabled(false);

if (m_file == juce::File{})
return;

juce::Time newMtime = m_file.getLastModificationTime();
if (newMtime == juce::Time{})
return;

if (m_changeTime == juce::Time{} || newMtime > m_changeTime) {
m_changeTime = newMtime;

if (!m_reloadDialogGuard) {
m_reloadDialogGuard = true;

auto callback = [this](int result) {
m_reloadDialogGuard = false;
if (result != 0) {
if (m_self->onReloadRequested)
m_self->onReloadRequested(m_file);
}
};

juce::AlertWindow::showAsync(
juce::MessageBoxOptions{}
.withAssociatedComponent(m_self)
.withIconType(juce::MessageBoxIconType::QuestionIcon)
.withTitle(TRANS("Reload?"))
.withButton(TRANS("Yes"))
.withButton(TRANS("No"))
.withMessage(TRANS("The file has been modified outside this editor. Reload it?")),
callback);
}
}
if (m_self->onFileSaved)
m_self->onFileSaved(m_documents[0]->getPath());
}

void YsfxIDEView::Impl::search(juce::String text, bool reverse=false)
{
if (text.isNotEmpty())
{
auto currentPosition = juce::CodeDocument::Position(*m_document, m_editor->getCaretPosition());
if (m_currentDocumentIndex >= m_documents.size()) return;
std::shared_ptr document = m_documents[m_currentDocumentIndex];
auto currentPosition = juce::CodeDocument::Position(*document, m_editor->getCaretPosition());

auto chunk = [this, currentPosition](bool before) {
auto chunk = [this, currentPosition, document](bool before) {
if (before) {
return m_document->getTextBetween(juce::CodeDocument::Position(*m_document, 0), currentPosition.movedBy(-1));
return document->getTextBetween(juce::CodeDocument::Position(*document, 0), currentPosition.movedBy(-1));
} else {
return m_document->getTextBetween(currentPosition, juce::CodeDocument::Position(*m_document, m_document->getNumCharacters()));
return document->getTextBetween(currentPosition, juce::CodeDocument::Position(*document, document->getNumCharacters()));
}
};

Expand All @@ -370,21 +282,21 @@ void YsfxIDEView::Impl::search(juce::String text, bool reverse=false)

if (position == -1) {
// Not found at all -> stop
if (text.compare(m_document->getTextBetween(currentPosition.movedBy(- text.length()), currentPosition)) != 0) {
if (text.compare(document->getTextBetween(currentPosition.movedBy(- text.length()), currentPosition)) != 0) {
m_lblStatus->setText(TRANS("Didn't find search string ") + text, juce::NotificationType::dontSendNotification);
} else {
m_lblStatus->setText(TRANS("Didn't find other copies of search string ") + text, juce::NotificationType::dontSendNotification);
}
m_editor->grabKeyboardFocus();
return;
}
searchPosition = juce::CodeDocument::Position(*m_document, reverse ? currentPosition.getPosition() + position : position);
searchPosition = juce::CodeDocument::Position(*document, reverse ? currentPosition.getPosition() + position : position);
} else {
// Found it!
searchPosition = juce::CodeDocument::Position(*m_document, reverse ? position : currentPosition.getPosition() + position);
searchPosition = juce::CodeDocument::Position(*document, reverse ? position : currentPosition.getPosition() + position);
}

auto pos = juce::CodeDocument::Position(*m_document, searchPosition.getPosition());
auto pos = juce::CodeDocument::Position(*document, searchPosition.getPosition());
m_editor->grabKeyboardFocus();
m_editor->moveCaretTo(pos, false);
m_editor->moveCaretTo(pos.movedBy(text.length()), true);
Expand Down Expand Up @@ -434,7 +346,7 @@ void YsfxIDEView::Impl::createUI()
return false;
};

m_editor.reset(new YSFXCodeEditor(*m_document, m_tokenizer.get(), keyPressCallback));
m_editor.reset(new YSFXCodeEditor(*(m_documents[0]), m_tokenizer.get(), keyPressCallback));
m_self->addAndMakeVisible(*m_editor);
m_btnSave.reset(new juce::TextButton(TRANS("Save")));
m_btnSave->addShortcut(juce::KeyPress('s', juce::ModifierKeys::ctrlModifier, 0));
Expand Down
84 changes: 84 additions & 0 deletions plugin/components/ysfx_document.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "ysfx_document.h"
#include "modal_textinputbox.h"


YSFXCodeDocument::YSFXCodeDocument() : CodeDocument()
{
reset();
};

void YSFXCodeDocument::reset()
{
replaceAllContent(juce::String{});
}

void YSFXCodeDocument::loadFile(juce::File file)
{
if (file != juce::File{}) m_file = file;
if (!m_file.existsAsFile()) return;

{
juce::MemoryBlock memBlock;
if (m_file.loadFileAsData(memBlock)) {
juce::String newContent = memBlock.toString();
memBlock = {};
if (newContent != getAllContent()) {
replaceAllContent(newContent);
// m_editor->moveCaretToTop(false);
}
}
}
}

bool YSFXCodeDocument::loaded(void) {
return bool(m_file != juce::File{});
}

juce::File YSFXCodeDocument::getPath(void) {
return m_file;
}

void YSFXCodeDocument::checkFileForModifications()
{
if (m_file == juce::File{})
return;

juce::Time newMtime = m_file.getLastModificationTime();
if (newMtime == juce::Time{})
return;

if (m_changeTime == juce::Time{} || newMtime > m_changeTime) {
m_changeTime = newMtime;

if (!m_reloadDialogGuard) {
m_reloadDialogGuard = true;

auto callback = [this](int result) {
m_reloadDialogGuard = false;
if (result != 0) {
loadFile(m_file);
}
};

m_alertWindow.reset(show_option_window(TRANS("Reload?"), TRANS("The file ") + m_file.getFileNameWithoutExtension() + TRANS(" has been modified outside this editor. Reload it?"), std::vector<juce::String>{"Yes", "No"}, callback));
}
}
}

bool YSFXCodeDocument::saveFile(juce::File path)
{
const juce::String content = getAllContent();

if (path == juce::File{}) {
saveFile(m_file);
}

bool success = m_file.replaceWithData(content.toRawUTF8(), content.getNumBytesAsUTF8());
if (!success) {
m_alertWindow.reset(show_option_window(TRANS("Error"), TRANS("Could not save ") + m_file.getFileNameWithoutExtension() + TRANS("."), std::vector<juce::String>{"OK"}, [](int result){ (void) result; }));
return false;
}

m_changeTime = juce::Time::getCurrentTime();
return true;
}
Loading

0 comments on commit be2d9c9

Please sign in to comment.