diff --git a/DevTools/HackStudio/CMakeLists.txt b/DevTools/HackStudio/CMakeLists.txt index 2199e1b28c..7b2b28b1f2 100644 --- a/DevTools/HackStudio/CMakeLists.txt +++ b/DevTools/HackStudio/CMakeLists.txt @@ -1,13 +1,9 @@ set(SOURCES + CodeDocument.cpp CursorTool.cpp - Git/DiffViewer.cpp - Git/GitWidget.cpp - Git/GitFilesModel.cpp - Git/GitRepo.cpp - Git/GitFilesView.cpp Debugger/BacktraceModel.cpp - Debugger/Debugger.cpp Debugger/DebugInfoWidget.cpp + Debugger/Debugger.cpp Debugger/DisassemblyModel.cpp Debugger/DisassemblyWidget.cpp Debugger/RegistersModel.cpp @@ -17,8 +13,13 @@ set(SOURCES FindInFilesWidget.cpp FormEditorWidget.cpp FormWidget.cpp + Git/DiffViewer.cpp + Git/GitFilesModel.cpp + Git/GitFilesView.cpp + Git/GitRepo.cpp + Git/GitWidget.cpp + HackStudioWidget.cpp Locator.cpp - main.cpp ProcessStateWidget.cpp Project.cpp ProjectFile.cpp @@ -26,7 +27,7 @@ set(SOURCES Tool.cpp WidgetTool.cpp WidgetTreeModel.cpp - CodeDocument.cpp + main.cpp ) serenity_bin(HackStudio) diff --git a/DevTools/HackStudio/Debugger/DebugInfoWidget.h b/DevTools/HackStudio/Debugger/DebugInfoWidget.h index bf8c7825ad..db432522a6 100644 --- a/DevTools/HackStudio/Debugger/DebugInfoWidget.h +++ b/DevTools/HackStudio/Debugger/DebugInfoWidget.h @@ -30,10 +30,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include diff --git a/DevTools/HackStudio/EditorWrapper.cpp b/DevTools/HackStudio/EditorWrapper.cpp index 91786b65ef..c02816a9eb 100644 --- a/DevTools/HackStudio/EditorWrapper.cpp +++ b/DevTools/HackStudio/EditorWrapper.cpp @@ -66,11 +66,11 @@ EditorWrapper::EditorWrapper(BreakpointChangeCallback breakpoint_change_callback }; m_editor->on_focus = [this] { - g_current_editor_wrapper = this; + set_current_editor_wrapper(this); }; m_editor->on_open = [](String path) { - g_open_file(path); + open_file(path); }; m_editor->on_breakpoint_change = move(breakpoint_change_callback); diff --git a/DevTools/HackStudio/FindInFilesWidget.cpp b/DevTools/HackStudio/FindInFilesWidget.cpp index e00d1a6ca6..764274a3a7 100644 --- a/DevTools/HackStudio/FindInFilesWidget.cpp +++ b/DevTools/HackStudio/FindInFilesWidget.cpp @@ -95,7 +95,7 @@ public: return {}; } - virtual void update() override { } + virtual void update() override {} virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& = GUI::ModelIndex()) const override { return create_index(row, column, &m_matches.at(row)); } private: @@ -105,7 +105,7 @@ private: static RefPtr find_in_files(const StringView& text) { Vector matches; - g_project->for_each_text_file([&](auto& file) { + project().for_each_text_file([&](auto& file) { auto matches_in_file = file.document().find_all(text); for (auto& range : matches_in_file) { auto whole_line_range = file.document().range_for_entire_line(range.start().line()); diff --git a/DevTools/HackStudio/FindInFilesWidget.h b/DevTools/HackStudio/FindInFilesWidget.h index abe214d55a..ce2d28bed1 100644 --- a/DevTools/HackStudio/FindInFilesWidget.h +++ b/DevTools/HackStudio/FindInFilesWidget.h @@ -26,6 +26,8 @@ #pragma once +#include +#include #include namespace HackStudio { @@ -33,7 +35,7 @@ namespace HackStudio { class FindInFilesWidget final : public GUI::Widget { C_OBJECT(FindInFilesWidget) public: - virtual ~FindInFilesWidget() override { } + virtual ~FindInFilesWidget() override {} void focus_textbox_and_select_all(); diff --git a/DevTools/HackStudio/HackStudio.h b/DevTools/HackStudio/HackStudio.h index c66121ee7a..9216bf3c2c 100644 --- a/DevTools/HackStudio/HackStudio.h +++ b/DevTools/HackStudio/HackStudio.h @@ -35,10 +35,10 @@ namespace HackStudio { GUI::TextEditor& current_editor(); void open_file(const String&); - -extern RefPtr g_current_editor_wrapper; -extern Function g_open_file; -extern OwnPtr g_project; -extern String g_currently_open_file; +RefPtr current_editor_wrapper(); +void open_file(const String&); +Project& project(); +String currently_open_file(); +void set_current_editor_wrapper(RefPtr); } diff --git a/DevTools/HackStudio/HackStudioWidget.cpp b/DevTools/HackStudio/HackStudioWidget.cpp new file mode 100644 index 0000000000..bcd299e9ab --- /dev/null +++ b/DevTools/HackStudio/HackStudioWidget.cpp @@ -0,0 +1,899 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2020, Itamar S. + * Copyright (c) 2020, the SerenityOS developers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "HackStudioWidget.h" +#include "CursorTool.h" +#include "Debugger/DebugInfoWidget.h" +#include "Debugger/Debugger.h" +#include "Debugger/DisassemblyWidget.h" +#include "Editor.h" +#include "EditorWrapper.h" +#include "FindInFilesWidget.h" +#include "FormEditorWidget.h" +#include "FormWidget.h" +#include "Git/DiffViewer.h" +#include "Git/GitWidget.h" +#include "HackStudio.h" +#include "HackStudioWidget.h" +#include "Locator.h" +#include "Project.h" +#include "TerminalWrapper.h" +#include "WidgetTool.h" +#include "WidgetTreeModel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HackStudio { + +HackStudioWidget::HackStudioWidget(const String& path_to_project) +{ + set_fill_with_background_color(true); + set_layout(); + layout()->set_spacing(2); + + open_project(path_to_project); + + auto& toolbar_container = add(); + + auto& outer_splitter = add(); + create_project_tree_view(outer_splitter); + m_project_tree_view_context_menu = create_project_tree_view_context_menu(); + + m_right_hand_splitter = outer_splitter.add(); + m_right_hand_stack = m_right_hand_splitter->add(); + create_form_editor(*m_right_hand_stack); + + m_diff_viewer = m_right_hand_stack->add(); + + m_editors_splitter = m_right_hand_stack->add(); + m_editors_splitter->layout()->set_margins({ 0, 3, 0, 0 }); + add_new_editor(*m_editors_splitter); + + m_switch_to_next_editor = create_switch_to_next_editor_action(); + m_switch_to_previous_editor = create_switch_to_previous_editor_action(); + + m_remove_current_editor_action = create_remove_current_editor_action(); + m_open_action = create_open_action(); + m_save_action = create_save_action(); + + create_action_tab(*m_right_hand_splitter); + + m_add_editor_action = create_add_editor_action(); + m_add_terminal_action = create_add_terminal_action(); + m_remove_current_terminal_action = create_remove_current_terminal_action(); + + m_locator = add(); + + m_terminal_wrapper->on_command_exit = [this] { + m_stop_action->set_enabled(false); + }; + + m_build_action = create_build_action(); + m_run_action = create_run_action(); + m_stop_action = create_stop_action(); + m_debug_action = create_debug_action(); + + initialize_debugger(); + + create_toolbar(toolbar_container); +} + +void HackStudioWidget::update_actions() +{ + auto is_remove_terminal_enabled = [this]() { + auto widget = m_action_tab_widget->active_widget(); + if (!widget) + return false; + if (StringView { "TerminalWrapper" } != widget->class_name()) + return false; + if (!reinterpret_cast(widget)->user_spawned()) + return false; + return true; + }; + + m_remove_current_editor_action->set_enabled(m_all_editor_wrappers.size() > 1); + m_remove_current_terminal_action->set_enabled(is_remove_terminal_enabled()); +} + +void HackStudioWidget::on_action_tab_change() +{ + update_actions(); + auto git_widget = m_action_tab_widget->active_widget(); + if (!git_widget) + return; + if (StringView { "GitWidget" } != git_widget->class_name()) + return; + reinterpret_cast(git_widget)->refresh(); +} + +void HackStudioWidget::open_project(String filename) +{ + LexicalPath lexical_path(filename); + if (chdir(lexical_path.dirname().characters()) < 0) { + perror("chdir"); + exit(1); + } + m_project = Project::load_from_file(filename); + ASSERT(m_project); + if (m_project_tree_view) { + m_project_tree_view->set_model(m_project->model()); + m_project_tree_view->toggle_index(m_project_tree_view->model()->index(0, 0)); + m_project_tree_view->update(); + } + if (Debugger::is_initialized()) { + Debugger::the().reset_breakpoints(); + } +} + +Vector HackStudioWidget::selected_file_names() const +{ + Vector files; + m_project_tree_view->selection().for_each_index([&](const GUI::ModelIndex& index) { + files.append(index.data().as_string()); + }); + return files; +} + +void HackStudioWidget::open_file(const String& filename) +{ + auto project_file = m_project->get_file(filename); + if (project_file) { + current_editor().set_document(const_cast(project_file->document())); + current_editor().set_mode(GUI::TextEditor::Editable); + } else { + auto external_file = ProjectFile::construct_with_name(filename); + current_editor().set_document(const_cast(external_file->document())); + current_editor().set_mode(GUI::TextEditor::ReadOnly); + } + + if (filename.ends_with(".cpp") || filename.ends_with(".h")) + current_editor().set_syntax_highlighter(make()); + else if (filename.ends_with(".js")) + current_editor().set_syntax_highlighter(make()); + else if (filename.ends_with(".ini")) + current_editor().set_syntax_highlighter(make()); + else + current_editor().set_syntax_highlighter(nullptr); + + if (filename.ends_with(".frm")) { + set_edit_mode(EditMode::Form); + } else { + set_edit_mode(EditMode::Text); + } + + m_currently_open_file = filename; + window()->set_title(String::format("%s - HackStudio", m_currently_open_file.characters())); + m_project_tree_view->update(); + + current_editor_wrapper().filename_label().set_text(filename); + + current_editor().set_focus(true); +} + +EditorWrapper& HackStudioWidget::current_editor_wrapper() +{ + ASSERT(m_current_editor_wrapper); + return *m_current_editor_wrapper; +} + +GUI::TextEditor& HackStudioWidget::current_editor() +{ + return current_editor_wrapper().editor(); +} + +void HackStudioWidget::set_edit_mode(EditMode mode) +{ + if (mode == EditMode::Text) { + m_right_hand_stack->set_active_widget(m_editors_splitter); + } else if (mode == EditMode::Form) { + m_right_hand_stack->set_active_widget(m_form_inner_container); + } else if (mode == EditMode::Diff) { + m_right_hand_stack->set_active_widget(m_diff_viewer); + } else { + ASSERT_NOT_REACHED(); + } + m_right_hand_stack->active_widget()->update(); +} + +NonnullRefPtr HackStudioWidget::create_project_tree_view_context_menu() +{ + m_open_selected_action = create_open_selected_action(); + m_new_action = create_new_action(); + m_add_existing_file_action = create_add_existing_file_action(); + m_delete_action = create_delete_action(); + auto project_tree_view_context_menu = GUI::Menu::construct("Project Files"); + project_tree_view_context_menu->add_action(*m_open_selected_action); + // TODO: Rename, cut, copy, duplicate with new name, show containing folder ... + project_tree_view_context_menu->add_separator(); + project_tree_view_context_menu->add_action(*m_new_action); + project_tree_view_context_menu->add_action(*m_add_existing_file_action); + project_tree_view_context_menu->add_action(*m_delete_action); + return project_tree_view_context_menu; +} + +NonnullRefPtr HackStudioWidget::create_new_action() +{ + return GUI::Action::create("Add new file to project...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"), [this](const GUI::Action&) { + String filename; + if (GUI::InputBox::show(filename, window(), "Enter name of new file:", "Add new file to project") != GUI::InputBox::ExecOK) + return; + auto file = Core::File::construct(filename); + if (!file->open((Core::IODevice::OpenMode)(Core::IODevice::WriteOnly | Core::IODevice::MustBeNew))) { + GUI::MessageBox::show(window(), String::format("Failed to create '%s'", filename.characters()), "Error", GUI::MessageBox::Type::Error); + return; + } + if (!m_project->add_file(filename)) { + GUI::MessageBox::show(window(), String::format("Failed to add '%s' to project", filename.characters()), "Error", GUI::MessageBox::Type::Error); + // FIXME: Should we unlink the file here maybe? + return; + } + m_project_tree_view->toggle_index(m_project_tree_view->model()->index(0, 0)); + open_file(filename); + }); +} + +NonnullRefPtr HackStudioWidget::create_open_selected_action() +{ + + auto open_selected_action = GUI::Action::create("Open", [this](const GUI::Action&) { + auto files = selected_file_names(); + for (auto& file : files) + open_file(file); + }); + open_selected_action->set_enabled(true); + return open_selected_action; +} + +NonnullRefPtr HackStudioWidget::create_add_existing_file_action() +{ + return GUI::Action::create("Add existing file to project...", Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), [this](auto&) { + auto result = GUI::FilePicker::get_open_filepath(window(), "Add existing file to project"); + if (!result.has_value()) + return; + auto& filename = result.value(); + if (!m_project->add_file(filename)) { + GUI::MessageBox::show(window(), String::format("Failed to add '%s' to project", filename.characters()), "Error", GUI::MessageBox::Type::Error); + return; + } + m_project_tree_view->toggle_index(m_project_tree_view->model()->index(0, 0)); + open_file(filename); + }); +} + +NonnullRefPtr HackStudioWidget::create_delete_action() +{ + + auto delete_action = GUI::CommonActions::make_delete_action([this](const GUI::Action&) { + auto files = selected_file_names(); + if (files.is_empty()) + return; + + String message; + if (files.size() == 1) { + message = String::format("Really remove %s from the project?", LexicalPath(files[0]).basename().characters()); + } else { + message = String::format("Really remove %d files from the project?", files.size()); + } + + auto result = GUI::MessageBox::show(window(), + message, + "Confirm deletion", + GUI::MessageBox::Type::Warning, + GUI::MessageBox::InputType::OKCancel); + if (result == GUI::MessageBox::ExecCancel) + return; + + for (auto& file : files) { + if (!m_project->remove_file(file)) { + GUI::MessageBox::show(window(), + String::format("Removing file %s from the project failed.", file.characters()), + "Removal failed", + GUI::MessageBox::Type::Error); + break; + } + } + }); + delete_action->set_enabled(false); + return delete_action; +} + +void HackStudioWidget::add_new_editor(GUI::Widget& parent) +{ + auto wrapper = EditorWrapper::construct(Debugger::on_breakpoint_change); + if (m_action_tab_widget) { + parent.insert_child_before(wrapper, *m_action_tab_widget); + } else { + parent.add_child(wrapper); + } + m_current_editor_wrapper = wrapper; + m_all_editor_wrappers.append(wrapper); + wrapper->editor().set_focus(true); +} + +NonnullRefPtr HackStudioWidget::create_switch_to_next_editor_action() +{ + return GUI::Action::create("Switch to next editor", { Mod_Ctrl, Key_E }, [this](auto&) { + if (m_all_editor_wrappers.size() <= 1) + return; + Vector wrappers; + m_editors_splitter->for_each_child_of_type([this, &wrappers](auto& child) { + wrappers.append(&child); + return IterationDecision::Continue; + }); + for (size_t i = 0; i < wrappers.size(); ++i) { + if (m_current_editor_wrapper.ptr() == wrappers[i]) { + if (i == wrappers.size() - 1) + wrappers[0]->editor().set_focus(true); + else + wrappers[i + 1]->editor().set_focus(true); + } + } + }); +} + +NonnullRefPtr HackStudioWidget::create_switch_to_previous_editor_action() +{ + return GUI::Action::create("Switch to previous editor", { Mod_Ctrl | Mod_Shift, Key_E }, [this](auto&) { + if (m_all_editor_wrappers.size() <= 1) + return; + Vector wrappers; + m_editors_splitter->for_each_child_of_type([this, &wrappers](auto& child) { + wrappers.append(&child); + return IterationDecision::Continue; + }); + for (int i = wrappers.size() - 1; i >= 0; --i) { + if (m_current_editor_wrapper.ptr() == wrappers[i]) { + if (i == 0) + wrappers.last()->editor().set_focus(true); + else + wrappers[i - 1]->editor().set_focus(true); + } + } + }); +} + +NonnullRefPtr HackStudioWidget::create_remove_current_editor_action() +{ + return GUI::Action::create("Remove current editor", { Mod_Alt | Mod_Shift, Key_E }, [this](auto&) { + if (m_all_editor_wrappers.size() <= 1) + return; + auto wrapper = m_current_editor_wrapper; + m_switch_to_next_editor->activate(); + m_editors_splitter->remove_child(*wrapper); + m_all_editor_wrappers.remove_first_matching([&wrapper](auto& entry) { return entry == wrapper.ptr(); }); + update_actions(); + }); +} + +NonnullRefPtr HackStudioWidget::create_open_action() +{ + return GUI::Action::create("Open project...", { Mod_Ctrl | Mod_Shift, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), [this](auto&) { + auto open_path = GUI::FilePicker::get_open_filepath(window(), "Open project"); + if (!open_path.has_value()) + return; + open_project(open_path.value()); + open_file(m_project->default_file()); + update_actions(); + }); +} + +NonnullRefPtr HackStudioWidget::create_save_action() +{ + return GUI::Action::create("Save", { Mod_Ctrl, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), [this](auto&) { + if (m_currently_open_file.is_empty()) + return; + + current_editor().write_to_file(m_currently_open_file); + + if (m_git_widget->initialized()) + m_git_widget->refresh(); + }); +} + +NonnullRefPtr HackStudioWidget::create_remove_current_terminal_action() +{ + return GUI::Action::create("Remove current Terminal", { Mod_Alt | Mod_Shift, Key_T }, [this](auto&) { + auto widget = m_action_tab_widget->active_widget(); + if (!widget) + return; + if (strcmp(widget->class_name(), "TerminalWrapper") != 0) + return; + auto terminal = reinterpret_cast(widget); + if (!terminal->user_spawned()) + return; + + m_action_tab_widget->remove_tab(*terminal); + update_actions(); + }); +} + +NonnullRefPtr HackStudioWidget::create_add_editor_action() +{ + return GUI::Action::create("Add new editor", { Mod_Ctrl | Mod_Alt, Key_E }, + Gfx::Bitmap::load_from_file("/res/icons/16x16/app-text-editor.png"), + [this](auto&) { + add_new_editor(*m_editors_splitter); + update_actions(); + }); +} + +NonnullRefPtr HackStudioWidget::create_add_terminal_action() +{ + return GUI::Action::create("Add new Terminal", { Mod_Ctrl | Mod_Alt, Key_T }, + Gfx::Bitmap::load_from_file("/res/icons/16x16/app-terminal.png"), + [this](auto&) { + auto& terminal = m_action_tab_widget->add_tab("Terminal"); + reveal_action_tab(terminal); + update_actions(); + terminal.terminal()->set_focus(true); + }); +} + +void HackStudioWidget::reveal_action_tab(GUI::Widget& widget) +{ + if (m_action_tab_widget->preferred_size().height() < 200) + m_action_tab_widget->set_preferred_size(0, 200); + m_action_tab_widget->set_active_widget(&widget); +} + +NonnullRefPtr HackStudioWidget::create_debug_action() +{ + return GUI::Action::create("Debug", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-run.png"), [this](auto&) { + if (m_project->type() != ProjectType::Cpp) { + GUI::MessageBox::show(window(), String::format("Cannot debug current project type", get_project_executable_path().characters()), "Error", GUI::MessageBox::Type::Error); + return; + } + if (!GUI::FilePicker::file_exists(get_project_executable_path())) { + GUI::MessageBox::show(window(), String::format("Could not find file: %s. (did you build the project?)", get_project_executable_path().characters()), "Error", GUI::MessageBox::Type::Error); + return; + } + if (Debugger::the().session()) { + GUI::MessageBox::show(window(), "Debugger is already running", "Error", GUI::MessageBox::Type::Error); + return; + } + Debugger::the().set_executable_path(get_project_executable_path()); + m_debugger_thread = adopt(*new LibThread::Thread(Debugger::start_static)); + m_debugger_thread->start(); + }); +} + +void HackStudioWidget::initialize_debugger() +{ + Debugger::initialize( + [this](const PtraceRegisters& regs) { + ASSERT(Debugger::the().session()); + const auto& debug_session = *Debugger::the().session(); + auto source_position = debug_session.debug_info().get_source_position(regs.eip); + if (!source_position.has_value()) { + dbg() << "Could not find source position for address: " << (void*)regs.eip; + return Debugger::HasControlPassedToUser::No; + } + + Core::EventLoop::main().post_event( + *window(), + make( + [this, source_position, ®s](auto&) { + m_current_editor_in_execution = get_editor_of_file(source_position.value().file_path); + m_current_editor_in_execution->editor().set_execution_position(source_position.value().line_number - 1); + m_debug_info_widget->update_state(*Debugger::the().session(), regs); + m_debug_info_widget->set_debug_actions_enabled(true); + m_disassembly_widget->update_state(*Debugger::the().session(), regs); + HackStudioWidget::reveal_action_tab(*m_debug_info_widget); + })); + Core::EventLoop::wake(); + + return Debugger::HasControlPassedToUser::Yes; + }, + [this]() { + Core::EventLoop::main().post_event(*window(), make([this](auto&) { + m_debug_info_widget->set_debug_actions_enabled(false); + if (m_current_editor_in_execution) { + m_current_editor_in_execution->editor().clear_execution_position(); + } + })); + Core::EventLoop::wake(); + }, + [this]() { + Core::EventLoop::main().post_event(*window(), make([this](auto&) { + m_debug_info_widget->program_stopped(); + m_disassembly_widget->program_stopped(); + HackStudioWidget::hide_action_tabs(); + GUI::MessageBox::show(window(), "Program Exited", "Debugger", GUI::MessageBox::Type::Information); + })); + Core::EventLoop::wake(); + }); +} + +String HackStudioWidget::get_full_path_of_serenity_source(const String& file) +{ + auto path_parts = LexicalPath(file).parts(); + ASSERT(path_parts[0] == ".."); + path_parts.remove(0); + StringBuilder relative_path_builder; + relative_path_builder.join("/", path_parts); + constexpr char SERENITY_LIBS_PREFIX[] = "/usr/src/serenity"; + LexicalPath serenity_sources_base(SERENITY_LIBS_PREFIX); + return String::format("%s/%s", serenity_sources_base.string().characters(), relative_path_builder.to_string().characters()); +} + +NonnullRefPtr HackStudioWidget::get_editor_of_file(const String& file_name) +{ + + String file_path = file_name; + + // TODO: We can probably do a more specific condition here, something like + // "if (file.starts_with("../Libraries/") || file.starts_with("../AK/"))" + if (file_name.starts_with("../")) { + file_path = get_full_path_of_serenity_source(file_name); + } + + open_file(file_path); + return current_editor_wrapper(); +} + +String HackStudioWidget::get_project_executable_path() const +{ + // e.g /my/project.hsp => /my/project + // TODO: Perhaps a Makefile rule for getting the value of $(PROGRAM) would be better? + return m_project->path().substring(0, m_project->path().index_of(".").value()); +} + +void HackStudioWidget::build(TerminalWrapper& wrapper) +{ + if (m_project->type() == ProjectType::JavaScript && m_currently_open_file.ends_with(".js")) + wrapper.run_command(String::format("js -A %s", m_currently_open_file.characters())); + else + wrapper.run_command("make"); +} + +void HackStudioWidget::run(TerminalWrapper& wrapper) +{ + if (m_project->type() == ProjectType::JavaScript && m_currently_open_file.ends_with(".js")) + wrapper.run_command(String::format("js %s", m_currently_open_file.characters())); + else + wrapper.run_command("make run"); +} + +void HackStudioWidget::hide_action_tabs() +{ + m_action_tab_widget->set_preferred_size(0, 24); +}; + +Project& HackStudioWidget::project() +{ + return *m_project; +} + +void HackStudioWidget::set_current_editor_wrapper(RefPtr editor_wrapper) +{ + m_current_editor_wrapper = editor_wrapper; +} + +void HackStudioWidget::create_project_tree_view(GUI::Widget& parent) +{ + m_project_tree_view = parent.add(); + m_project_tree_view->set_model(m_project->model()); + m_project_tree_view->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); + m_project_tree_view->set_preferred_size(140, 0); + m_project_tree_view->toggle_index(m_project_tree_view->model()->index(0, 0)); + + m_project_tree_view->on_context_menu_request = [this](const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) { + if (index.is_valid()) { + m_project_tree_view_context_menu->popup(event.screen_position(), m_open_selected_action); + } + }; + + m_project_tree_view->on_selection_change = [this] { + m_open_selected_action->set_enabled(!m_project_tree_view->selection().is_empty()); + m_delete_action->set_enabled(!m_project_tree_view->selection().is_empty()); + }; + + m_project_tree_view->on_activation = [this](auto& index) { + auto filename = index.data(GUI::ModelRole::Custom).to_string(); + open_file(filename); + }; +} + +void HackStudioWidget::create_form_editor(GUI::Widget& parent) +{ + m_form_inner_container = parent.add(); + m_form_inner_container->set_layout(); + auto& form_widgets_toolbar = m_form_inner_container->add(Orientation::Vertical, 26); + form_widgets_toolbar.set_preferred_size(38, 0); + + GUI::ActionGroup tool_actions; + tool_actions.set_exclusive(true); + + auto cursor_tool_action = GUI::Action::create_checkable("Cursor", Gfx::Bitmap::load_from_file("/res/icons/hackstudio/Cursor.png"), [this](auto&) { + m_form_editor_widget->set_tool(make(*m_form_editor_widget)); + }); + cursor_tool_action->set_checked(true); + tool_actions.add_action(cursor_tool_action); + + form_widgets_toolbar.add_action(cursor_tool_action); + + GUI::WidgetClassRegistration::for_each([&, this](const GUI::WidgetClassRegistration& reg) { + auto icon_path = String::format("/res/icons/hackstudio/G%s.png", reg.class_name().characters()); + auto action = GUI::Action::create_checkable(reg.class_name(), Gfx::Bitmap::load_from_file(icon_path), [®, this](auto&) { + m_form_editor_widget->set_tool(make(*m_form_editor_widget, reg)); + auto widget = reg.construct(); + m_form_editor_widget->form_widget().add_child(widget); + widget->set_relative_rect(30, 30, 30, 30); + m_form_editor_widget->model().update(); + }); + action->set_checked(false); + tool_actions.add_action(action); + form_widgets_toolbar.add_action(move(action)); + }); + + auto& form_editor_inner_splitter = m_form_inner_container->add(); + + m_form_editor_widget = form_editor_inner_splitter.add(); + + auto& form_editing_pane_container = form_editor_inner_splitter.add(); + form_editing_pane_container.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); + form_editing_pane_container.set_preferred_size(190, 0); + form_editing_pane_container.set_layout(); + + auto add_properties_pane = [&](auto& text, auto& pane_widget) { + auto& wrapper = form_editing_pane_container.add(); + wrapper.set_layout(); + auto& label = wrapper.add(text); + label.set_fill_with_background_color(true); + label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + label.set_font(Gfx::Font::default_bold_font()); + label.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); + label.set_preferred_size(0, 16); + wrapper.add_child(pane_widget); + }; + + m_form_widget_tree_view = GUI::TreeView::construct(); + m_form_widget_tree_view->set_model(m_form_editor_widget->model()); + m_form_widget_tree_view->on_selection_change = [this] { + m_form_editor_widget->selection().disable_hooks(); + m_form_editor_widget->selection().clear(); + m_form_widget_tree_view->selection().for_each_index([this](auto& index) { + // NOTE: Make sure we don't add the FormWidget itself to the selection, + // since that would allow you to drag-move the FormWidget. + if (index.internal_data() != &m_form_editor_widget->form_widget()) + m_form_editor_widget->selection().add(*(GUI::Widget*)index.internal_data()); + }); + m_form_editor_widget->update(); + m_form_editor_widget->selection().enable_hooks(); + }; + + m_form_editor_widget->selection().on_add = [this](auto& widget) { + m_form_widget_tree_view->selection().add(m_form_editor_widget->model().index_for_widget(widget)); + }; + m_form_editor_widget->selection().on_remove = [this](auto& widget) { + m_form_widget_tree_view->selection().remove(m_form_editor_widget->model().index_for_widget(widget)); + }; + m_form_editor_widget->selection().on_clear = [this] { + m_form_widget_tree_view->selection().clear(); + }; + + add_properties_pane("Form widget tree:", *m_form_widget_tree_view); + add_properties_pane("Widget properties:", *GUI::TableView::construct()); +} + +void HackStudioWidget::create_toolbar(GUI::Widget& parent) +{ + auto& toolbar = parent.add(); + toolbar.add_action(*m_new_action); + toolbar.add_action(*m_add_existing_file_action); + toolbar.add_action(*m_save_action); + toolbar.add_action(*m_delete_action); + toolbar.add_separator(); + + toolbar.add_action(GUI::CommonActions::make_cut_action([this](auto&) { current_editor().cut_action().activate(); })); + toolbar.add_action(GUI::CommonActions::make_copy_action([this](auto&) { current_editor().copy_action().activate(); })); + toolbar.add_action(GUI::CommonActions::make_paste_action([this](auto&) { current_editor().paste_action().activate(); })); + toolbar.add_separator(); + toolbar.add_action(GUI::CommonActions::make_undo_action([this](auto&) { current_editor().undo_action().activate(); })); + toolbar.add_action(GUI::CommonActions::make_redo_action([this](auto&) { current_editor().redo_action().activate(); })); + toolbar.add_separator(); + + toolbar.add_action(*m_build_action); + toolbar.add_separator(); + + toolbar.add_action(*m_run_action); + toolbar.add_action(*m_stop_action); + toolbar.add_separator(); + + toolbar.add_action(*m_debug_action); +} + +NonnullRefPtr HackStudioWidget::create_build_action() +{ + return GUI::Action::create("Build", { Mod_Ctrl, Key_B }, Gfx::Bitmap::load_from_file("/res/icons/16x16/build.png"), [this](auto&) { + reveal_action_tab(*m_terminal_wrapper); + build(*m_terminal_wrapper); + m_stop_action->set_enabled(true); + }); +} + +NonnullRefPtr HackStudioWidget::create_run_action() +{ + return GUI::Action::create("Run", { Mod_Ctrl, Key_R }, Gfx::Bitmap::load_from_file("/res/icons/16x16/program-run.png"), [this](auto&) { + reveal_action_tab(*m_terminal_wrapper); + run(*m_terminal_wrapper); + m_stop_action->set_enabled(true); + }); +} + +void HackStudioWidget::create_action_tab(GUI::Widget& parent) +{ + m_action_tab_widget = parent.add(); + + m_action_tab_widget->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); + m_action_tab_widget->set_preferred_size(0, 24); + m_action_tab_widget->on_change = [this](auto&) { + on_action_tab_change(); + + static bool first_time = true; + if (!first_time) + m_action_tab_widget->set_preferred_size(0, 200); + first_time = false; + }; + + m_find_in_files_widget = m_action_tab_widget->add_tab("Find in files"); + m_terminal_wrapper = m_action_tab_widget->add_tab("Build", false); + m_debug_info_widget = m_action_tab_widget->add_tab("Debug"); + m_disassembly_widget = m_action_tab_widget->add_tab("Disassembly"); + m_git_widget = m_action_tab_widget->add_tab("Git", LexicalPath(m_project->root_directory())); + m_git_widget->set_view_diff_callback([this](const auto& original_content, const auto& diff) { + m_diff_viewer->set_content(original_content, diff); + set_edit_mode(EditMode::Diff); + }); +} + +void HackStudioWidget::create_app_menubar(GUI::MenuBar& menubar) +{ + auto& app_menu = menubar.add_menu("HackStudio"); + app_menu.add_action(*m_open_action); + app_menu.add_action(*m_save_action); + app_menu.add_separator(); + app_menu.add_action(GUI::CommonActions::make_quit_action([this](auto&) { + GUI::Application::the()->quit(); + })); +} + +void HackStudioWidget::create_project_menubar(GUI::MenuBar& menubar) +{ + auto& project_menu = menubar.add_menu("Project"); + project_menu.add_action(*m_new_action); + project_menu.add_action(*m_add_existing_file_action); +} + +void HackStudioWidget::create_edit_menubar(GUI::MenuBar& menubar) +{ + auto& edit_menu = menubar.add_menu("Edit"); + edit_menu.add_action(GUI::Action::create("Find in files...", { Mod_Ctrl | Mod_Shift, Key_F }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find.png"), [this](auto&) { + reveal_action_tab(*m_find_in_files_widget); + m_find_in_files_widget->focus_textbox_and_select_all(); + })); +} + +void HackStudioWidget::create_build_menubar(GUI::MenuBar& menubar) +{ + auto& build_menu = menubar.add_menu("Build"); + build_menu.add_action(*m_build_action); + build_menu.add_separator(); + build_menu.add_action(*m_run_action); + build_menu.add_action(*m_stop_action); + build_menu.add_separator(); + build_menu.add_action(*m_debug_action); +} + +void HackStudioWidget::create_view_menubar(GUI::MenuBar& menubar) +{ + auto hide_action_tabs_action = GUI::Action::create("Hide action tabs", { Mod_Ctrl | Mod_Shift, Key_X }, [this](auto&) { + hide_action_tabs(); + }); + auto open_locator_action = GUI::Action::create("Open Locator...", { Mod_Ctrl, Key_K }, [this](auto&) { + m_locator->open(); + }); + + auto& view_menu = menubar.add_menu("View"); + view_menu.add_action(hide_action_tabs_action); + view_menu.add_action(open_locator_action); + view_menu.add_separator(); + view_menu.add_action(*m_add_editor_action); + view_menu.add_action(*m_remove_current_editor_action); + view_menu.add_action(*m_add_terminal_action); + view_menu.add_action(*m_remove_current_terminal_action); +} + +void HackStudioWidget::create_help_menubar(GUI::MenuBar& menubar) +{ + auto& help_menu = menubar.add_menu("Help"); + help_menu.add_action(GUI::Action::create("About", [this](auto&) { + GUI::AboutDialog::show("HackStudio", Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"), window()); + })); +} + +NonnullRefPtr HackStudioWidget::create_stop_action() +{ + auto action = GUI::Action::create("Stop", Gfx::Bitmap::load_from_file("/res/icons/16x16/program-stop.png"), [this](auto&) { + m_terminal_wrapper->kill_running_command(); + }); + + action->set_enabled(false); + return action; +} + +void HackStudioWidget::initialize_menubar(GUI::MenuBar& menubar) +{ + create_app_menubar(menubar); + create_project_menubar(menubar); + create_edit_menubar(menubar); + create_build_menubar(menubar); + create_view_menubar(menubar); + create_help_menubar(menubar); +} + +} diff --git a/DevTools/HackStudio/HackStudioWidget.h b/DevTools/HackStudio/HackStudioWidget.h new file mode 100644 index 0000000000..ec1f918b3d --- /dev/null +++ b/DevTools/HackStudio/HackStudioWidget.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2020, Itamar S. + * Copyright (c) 2020, the SerenityOS developers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Debugger/DebugInfoWidget.h" +#include "Debugger/DisassemblyWidget.h" +#include "EditorWrapper.h" +#include "FindInFilesWidget.h" +#include "FormEditorWidget.h" +#include "Git/DiffViewer.h" +#include "Git/GitWidget.h" +#include "Locator.h" +#include "Project.h" +#include "TerminalWrapper.h" +#include +#include +#include + +namespace HackStudio { + +class HackStudioWidget : public GUI::Widget { + C_OBJECT(HackStudioWidget) + +public: + void open_file(const String& filename); + + Vector selected_file_names() const; + + void update_actions(); + Project& project(); + GUI::TextEditor& current_editor(); + EditorWrapper& current_editor_wrapper(); + void set_current_editor_wrapper(RefPtr); + + String currently_open_file() const { return m_currently_open_file; } + void initialize_menubar(GUI::MenuBar&); + +private: + static String get_full_path_of_serenity_source(const String& file); + + HackStudioWidget(const String& path_to_project); + void open_project(String filename); + + enum class EditMode { + Text, + Form, + Diff, + }; + + void set_edit_mode(EditMode); + + NonnullRefPtr create_project_tree_view_context_menu(); + NonnullRefPtr create_new_action(); + NonnullRefPtr create_open_selected_action(); + NonnullRefPtr create_add_existing_file_action(); + NonnullRefPtr create_delete_action(); + NonnullRefPtr create_switch_to_next_editor_action(); + NonnullRefPtr create_switch_to_previous_editor_action(); + NonnullRefPtr create_remove_current_editor_action(); + NonnullRefPtr create_open_action(); + NonnullRefPtr create_save_action(); + NonnullRefPtr create_add_editor_action(); + NonnullRefPtr create_add_terminal_action(); + NonnullRefPtr create_remove_current_terminal_action(); + NonnullRefPtr create_debug_action(); + NonnullRefPtr create_build_action(); + NonnullRefPtr create_run_action(); + NonnullRefPtr create_stop_action(); + + void add_new_editor(GUI::Widget& parent); + NonnullRefPtr get_editor_of_file(const String& file_name); + String get_project_executable_path() const; + + void on_action_tab_change(); + void reveal_action_tab(GUI::Widget&); + void initialize_debugger(); + + void create_project_tree_view(GUI::Widget& parent); + void create_form_editor(GUI::Widget& parent); + void create_toolbar(GUI::Widget& parent); + void create_action_tab(GUI::Widget& parent); + void create_app_menubar(GUI::MenuBar&); + void create_project_menubar(GUI::MenuBar&); + void create_edit_menubar(GUI::MenuBar&); + void create_build_menubar(GUI::MenuBar&); + void create_view_menubar(GUI::MenuBar&); + void create_help_menubar(GUI::MenuBar&); + + void run(TerminalWrapper& wrapper); + void build(TerminalWrapper& wrapper); + + void hide_action_tabs(); + + NonnullRefPtrVector m_all_editor_wrappers; + RefPtr m_current_editor_wrapper; + + String m_currently_open_file; + OwnPtr m_project; + RefPtr m_project_tree_view; + RefPtr m_right_hand_splitter; + RefPtr m_right_hand_stack; + RefPtr m_editors_splitter; + RefPtr m_form_inner_container; + RefPtr m_form_editor_widget; + RefPtr m_form_widget_tree_view; + RefPtr m_diff_viewer; + RefPtr m_git_widget; + RefPtr m_project_tree_view_context_menu; + RefPtr m_action_tab_widget; + RefPtr m_terminal_wrapper; + RefPtr m_locator; + RefPtr m_find_in_files_widget; + RefPtr m_debug_info_widget; + RefPtr m_disassembly_widget; + RefPtr m_debugger_thread; + RefPtr m_current_editor_in_execution; + + RefPtr m_new_action; + RefPtr m_open_selected_action; + RefPtr m_add_existing_file_action; + RefPtr m_delete_action; + RefPtr m_switch_to_next_editor; + RefPtr m_switch_to_previous_editor; + RefPtr m_remove_current_editor_action; + RefPtr m_open_action; + RefPtr m_save_action; + RefPtr m_add_editor_action; + RefPtr m_add_terminal_action; + RefPtr m_remove_current_terminal_action; + RefPtr m_stop_action; + RefPtr m_debug_action; + RefPtr m_build_action; + RefPtr m_run_action; +}; +} diff --git a/DevTools/HackStudio/Locator.cpp b/DevTools/HackStudio/Locator.cpp index 092cbb9b1a..dfdf6b7b88 100644 --- a/DevTools/HackStudio/Locator.cpp +++ b/DevTools/HackStudio/Locator.cpp @@ -175,7 +175,7 @@ void Locator::update_suggestions() { auto typed_text = m_textbox->text(); Vector suggestions; - g_project->for_each_text_file([&](auto& file) { + project().for_each_text_file([&](auto& file) { if (file.name().contains(typed_text)) suggestions.append(file.name()); }); diff --git a/DevTools/HackStudio/Project.cpp b/DevTools/HackStudio/Project.cpp index 416739f906..a2c7e72bd3 100644 --- a/DevTools/HackStudio/Project.cpp +++ b/DevTools/HackStudio/Project.cpp @@ -126,7 +126,7 @@ public: return m_project.m_file_icon; } if (role == GUI::ModelRole::Font) { - if (node->name == g_currently_open_file) + if (node->name == currently_open_file()) return Gfx::Font::default_bold_font(); return {}; } diff --git a/DevTools/HackStudio/main.cpp b/DevTools/HackStudio/main.cpp index ac82cda60e..0a10f45715 100644 --- a/DevTools/HackStudio/main.cpp +++ b/DevTools/HackStudio/main.cpp @@ -24,53 +24,17 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "CursorTool.h" -#include "Debugger/DebugInfoWidget.h" -#include "Debugger/Debugger.h" -#include "Debugger/DisassemblyWidget.h" -#include "Editor.h" -#include "EditorWrapper.h" -#include "FindInFilesWidget.h" -#include "FormEditorWidget.h" -#include "FormWidget.h" -#include "Git/DiffViewer.h" -#include "Git/GitWidget.h" #include "HackStudio.h" -#include "Locator.h" +#include "HackStudioWidget.h" #include "Project.h" -#include "TerminalWrapper.h" -#include "WidgetTool.h" -#include "WidgetTreeModel.h" #include #include #include #include #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -85,110 +49,15 @@ using namespace HackStudio; -namespace HackStudio { +static RefPtr s_window; +static RefPtr s_hack_studio_widget; -NonnullRefPtrVector g_all_editor_wrappers; -RefPtr g_current_editor_wrapper; -Function g_open_file; +static bool make_is_available(); +static void update_path_environment_variable(); +static String path_to_project(const String& path_argument_absolute_path); +static void open_default_project_file(const String& project_path); -String g_currently_open_file; -OwnPtr g_project; -RefPtr g_window; -RefPtr g_project_tree_view; -RefPtr g_right_hand_splitter; -RefPtr g_right_hand_stack; -RefPtr g_editors_splitter; -RefPtr g_form_inner_container; -RefPtr g_form_editor_widget; -RefPtr g_diff_viewer; -RefPtr g_git_widget; - -static RefPtr s_action_tab_widget; - -static void add_new_editor(GUI::Widget& parent) -{ - auto wrapper = EditorWrapper::construct(Debugger::on_breakpoint_change); - if (s_action_tab_widget) { - parent.insert_child_before(wrapper, *s_action_tab_widget); - } else { - parent.add_child(wrapper); - } - g_current_editor_wrapper = wrapper; - g_all_editor_wrappers.append(wrapper); - wrapper->editor().set_focus(true); -} - -enum class EditMode { - Text, - Form, - Diff, -}; - -static void set_edit_mode(EditMode mode) -{ - if (mode == EditMode::Text) { - g_right_hand_stack->set_active_widget(g_editors_splitter); - } else if (mode == EditMode::Form) { - g_right_hand_stack->set_active_widget(g_form_inner_container); - } else if (mode == EditMode::Diff) { - g_right_hand_stack->set_active_widget(g_diff_viewer); - } else { - ASSERT_NOT_REACHED(); - } - g_right_hand_stack->active_widget()->update(); -} - -static void build(TerminalWrapper&); -static void run(TerminalWrapper&); -void open_project(String); -void open_file(const String&); -bool make_is_available(); - -static EditorWrapper& current_editor_wrapper() -{ - ASSERT(g_current_editor_wrapper); - return *g_current_editor_wrapper; -} - -GUI::TextEditor& current_editor() -{ - return current_editor_wrapper().editor(); -} - -static String get_full_path_of_serenity_source(const String& file) -{ - auto path_parts = LexicalPath(file).parts(); - ASSERT(path_parts[0] == ".."); - path_parts.remove(0); - StringBuilder relative_path_builder; - relative_path_builder.join("/", path_parts); - constexpr char SERENITY_LIBS_PREFIX[] = "/usr/src/serenity"; - LexicalPath serenity_sources_base(SERENITY_LIBS_PREFIX); - return String::format("%s/%s", serenity_sources_base.string().characters(), relative_path_builder.to_string().characters()); -} - -static NonnullRefPtr get_editor_of_file(const String& file) -{ - String file_path = file; - - // TODO: We can probably do a more specific condition here, something like - // "if (file.starts_with("../Libraries/") || file.starts_with("../AK/"))" - if (file.starts_with("../")) { - file_path = get_full_path_of_serenity_source(file); - } - - open_file(file_path); - return current_editor_wrapper(); -} - -static String get_project_executable_path() -{ - // e.g /my/project.hsp => /my/project - // TODO: Perhaps a Makefile rule for getting the value of $(PROGRAM) would be better? - return g_project->path().substring(0, g_project->path().index_of(".").value()); -} - -static int main_impl(int argc, char** argv) +int main(int argc, char** argv) { if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec unix fattr thread", nullptr) < 0) { perror("pledge"); @@ -202,28 +71,15 @@ static int main_impl(int argc, char** argv) return 1; } - Function update_actions; - Function on_action_tab_change; + s_window = GUI::Window::construct(); + s_window->resize(840, 600); + s_window->set_title("HackStudio"); + s_window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-hack-studio.png")); - g_window = GUI::Window::construct(); - g_window->resize(840, 600); - g_window->set_title("HackStudio"); - - auto& widget = g_window->set_main_widget(); - - widget.set_fill_with_background_color(true); - widget.set_layout(); - widget.layout()->set_spacing(2); - - StringBuilder path; - path.append(getenv("PATH")); - if (path.length()) - path.append(":"); - path.append("/bin:/usr/bin:/usr/local/bin"); - setenv("PATH", path.to_string().characters(), true); + update_path_environment_variable(); if (!make_is_available()) - GUI::MessageBox::show(g_window, "The 'make' command is not available. You probably want to install the binutils, gcc, and make ports from the root of the Serenity repository.", "Error", GUI::MessageBox::Type::Error); + GUI::MessageBox::show(s_window, "The 'make' command is not available. You probably want to install the binutils, gcc, and make ports from the root of the Serenity repository.", "Error", GUI::MessageBox::Type::Error); const char* path_argument = nullptr; Core::ArgsParser args_parser; @@ -231,610 +87,22 @@ static int main_impl(int argc, char** argv) args_parser.parse(argc, argv); auto argument_absolute_path = Core::File::real_path_for(path_argument); - if (argument_absolute_path.ends_with(".hsp")) - open_project(argument_absolute_path); - else - open_project("/home/anon/Source/little/little.hsp"); - - auto& toolbar_container = widget.add(); - auto& toolbar = toolbar_container.add(); - - auto selected_file_names = [&] { - Vector files; - g_project_tree_view->selection().for_each_index([&](const GUI::ModelIndex& index) { - files.append(index.data().as_string()); - }); - return files; - }; - - auto new_action = GUI::Action::create("Add new file to project...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"), [&](const GUI::Action&) { - String filename; - if (GUI::InputBox::show(filename, g_window, "Enter name of new file:", "Add new file to project") != GUI::InputBox::ExecOK) - return; - auto file = Core::File::construct(filename); - if (!file->open((Core::IODevice::OpenMode)(Core::IODevice::WriteOnly | Core::IODevice::MustBeNew))) { - GUI::MessageBox::show(g_window, String::format("Failed to create '%s'", filename.characters()), "Error", GUI::MessageBox::Type::Error); - return; - } - if (!g_project->add_file(filename)) { - GUI::MessageBox::show(g_window, String::format("Failed to add '%s' to project", filename.characters()), "Error", GUI::MessageBox::Type::Error); - // FIXME: Should we unlink the file here maybe? - return; - } - g_project_tree_view->toggle_index(g_project_tree_view->model()->index(0, 0)); - open_file(filename); - }); - - auto open_selected_action = GUI::Action::create("Open", [&](const GUI::Action&) { - auto files = selected_file_names(); - for (auto& file : files) - open_file(file); - }); - open_selected_action->set_enabled(true); - - auto add_existing_file_action = GUI::Action::create("Add existing file to project...", Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), [&](auto&) { - auto result = GUI::FilePicker::get_open_filepath(g_window, "Add existing file to project"); - if (!result.has_value()) - return; - auto& filename = result.value(); - if (!g_project->add_file(filename)) { - GUI::MessageBox::show(g_window, String::format("Failed to add '%s' to project", filename.characters()), "Error", GUI::MessageBox::Type::Error); - return; - } - g_project_tree_view->toggle_index(g_project_tree_view->model()->index(0, 0)); - open_file(filename); - }); - - auto delete_action = GUI::CommonActions::make_delete_action([&](const GUI::Action&) { - auto files = selected_file_names(); - if (files.is_empty()) - return; - - String message; - if (files.size() == 1) { - message = String::format("Really remove %s from the project?", LexicalPath(files[0]).basename().characters()); - } else { - message = String::format("Really remove %d files from the project?", files.size()); - } - - auto result = GUI::MessageBox::show(g_window, - message, - "Confirm deletion", - GUI::MessageBox::Type::Warning, - GUI::MessageBox::InputType::OKCancel); - if (result == GUI::MessageBox::ExecCancel) - return; - - for (auto& file : files) { - if (!g_project->remove_file(file)) { - GUI::MessageBox::show(g_window, - String::format("Removing file %s from the project failed.", file.characters()), - "Removal failed", - GUI::MessageBox::Type::Error); - break; - } - } - }); - delete_action->set_enabled(false); - - auto project_tree_view_context_menu = GUI::Menu::construct("Project Files"); - project_tree_view_context_menu->add_action(open_selected_action); - // TODO: Rename, cut, copy, duplicate with new name, show containing folder ... - project_tree_view_context_menu->add_separator(); - project_tree_view_context_menu->add_action(new_action); - project_tree_view_context_menu->add_action(add_existing_file_action); - project_tree_view_context_menu->add_action(delete_action); - - auto& outer_splitter = widget.add(); - g_project_tree_view = outer_splitter.add(); - g_project_tree_view->set_model(g_project->model()); - g_project_tree_view->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); - g_project_tree_view->set_preferred_size(140, 0); - g_project_tree_view->toggle_index(g_project_tree_view->model()->index(0, 0)); - - g_project_tree_view->on_context_menu_request = [&](const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) { - if (index.is_valid()) { - project_tree_view_context_menu->popup(event.screen_position(), open_selected_action); - } - }; - - g_project_tree_view->on_selection_change = [&] { - open_selected_action->set_enabled(!g_project_tree_view->selection().is_empty()); - delete_action->set_enabled(!g_project_tree_view->selection().is_empty()); - }; - - g_right_hand_splitter = outer_splitter.add(); - g_right_hand_stack = g_right_hand_splitter->add(); - - g_form_inner_container = g_right_hand_stack->add(); - g_form_inner_container->set_layout(); - auto& form_widgets_toolbar = g_form_inner_container->add(Orientation::Vertical, 26); - form_widgets_toolbar.set_preferred_size(38, 0); - - GUI::ActionGroup tool_actions; - tool_actions.set_exclusive(true); - - auto cursor_tool_action = GUI::Action::create_checkable("Cursor", Gfx::Bitmap::load_from_file("/res/icons/hackstudio/Cursor.png"), [&](auto&) { - g_form_editor_widget->set_tool(make(*g_form_editor_widget)); - }); - cursor_tool_action->set_checked(true); - tool_actions.add_action(cursor_tool_action); - - form_widgets_toolbar.add_action(cursor_tool_action); - - GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& reg) { - auto icon_path = String::format("/res/icons/hackstudio/G%s.png", reg.class_name().characters()); - auto action = GUI::Action::create_checkable(reg.class_name(), Gfx::Bitmap::load_from_file(icon_path), [®](auto&) { - g_form_editor_widget->set_tool(make(*g_form_editor_widget, reg)); - auto widget = reg.construct(); - g_form_editor_widget->form_widget().add_child(widget); - widget->set_relative_rect(30, 30, 30, 30); - g_form_editor_widget->model().update(); - }); - action->set_checked(false); - tool_actions.add_action(action); - form_widgets_toolbar.add_action(move(action)); - }); - - auto& form_editor_inner_splitter = g_form_inner_container->add(); - - g_form_editor_widget = form_editor_inner_splitter.add(); - - auto& form_editing_pane_container = form_editor_inner_splitter.add(); - form_editing_pane_container.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); - form_editing_pane_container.set_preferred_size(190, 0); - form_editing_pane_container.set_layout(); - - auto add_properties_pane = [&](auto& text, auto pane_widget) { - auto& wrapper = form_editing_pane_container.add(); - wrapper.set_layout(); - auto& label = wrapper.add(text); - label.set_fill_with_background_color(true); - label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - label.set_font(Gfx::Font::default_bold_font()); - label.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); - label.set_preferred_size(0, 16); - wrapper.add_child(pane_widget); - }; - - auto form_widget_tree_view = GUI::TreeView::construct(); - form_widget_tree_view->set_model(g_form_editor_widget->model()); - form_widget_tree_view->on_selection_change = [&] { - g_form_editor_widget->selection().disable_hooks(); - g_form_editor_widget->selection().clear(); - form_widget_tree_view->selection().for_each_index([&](auto& index) { - // NOTE: Make sure we don't add the FormWidget itself to the selection, - // since that would allow you to drag-move the FormWidget. - if (index.internal_data() != &g_form_editor_widget->form_widget()) - g_form_editor_widget->selection().add(*(GUI::Widget*)index.internal_data()); - }); - g_form_editor_widget->update(); - g_form_editor_widget->selection().enable_hooks(); - }; - - g_form_editor_widget->selection().on_add = [&](auto& widget) { - form_widget_tree_view->selection().add(g_form_editor_widget->model().index_for_widget(widget)); - }; - g_form_editor_widget->selection().on_remove = [&](auto& widget) { - form_widget_tree_view->selection().remove(g_form_editor_widget->model().index_for_widget(widget)); - }; - g_form_editor_widget->selection().on_clear = [&] { - form_widget_tree_view->selection().clear(); - }; - - add_properties_pane("Form widget tree:", form_widget_tree_view); - add_properties_pane("Widget properties:", GUI::TableView::construct()); - - g_diff_viewer = g_right_hand_stack->add(); - - g_editors_splitter = g_right_hand_stack->add(); - g_editors_splitter->layout()->set_margins({ 0, 3, 0, 0 }); - add_new_editor(*g_editors_splitter); - - auto switch_to_next_editor = GUI::Action::create("Switch to next editor", { Mod_Ctrl, Key_E }, [&](auto&) { - if (g_all_editor_wrappers.size() <= 1) - return; - Vector wrappers; - g_editors_splitter->for_each_child_of_type([&](auto& child) { - wrappers.append(&child); - return IterationDecision::Continue; - }); - for (size_t i = 0; i < wrappers.size(); ++i) { - if (g_current_editor_wrapper.ptr() == wrappers[i]) { - if (i == wrappers.size() - 1) - wrappers[0]->editor().set_focus(true); - else - wrappers[i + 1]->editor().set_focus(true); - } - } - }); - - auto switch_to_previous_editor = GUI::Action::create("Switch to previous editor", { Mod_Ctrl | Mod_Shift, Key_E }, [&](auto&) { - if (g_all_editor_wrappers.size() <= 1) - return; - Vector wrappers; - g_editors_splitter->for_each_child_of_type([&](auto& child) { - wrappers.append(&child); - return IterationDecision::Continue; - }); - for (int i = wrappers.size() - 1; i >= 0; --i) { - if (g_current_editor_wrapper.ptr() == wrappers[i]) { - if (i == 0) - wrappers.last()->editor().set_focus(true); - else - wrappers[i - 1]->editor().set_focus(true); - } - } - }); - - auto remove_current_editor_action = GUI::Action::create("Remove current editor", { Mod_Alt | Mod_Shift, Key_E }, [&](auto&) { - if (g_all_editor_wrappers.size() <= 1) - return; - auto wrapper = g_current_editor_wrapper; - switch_to_next_editor->activate(); - g_editors_splitter->remove_child(*wrapper); - g_all_editor_wrappers.remove_first_matching([&](auto& entry) { return entry == wrapper.ptr(); }); - update_actions(); - }); - - auto open_action = GUI::Action::create("Open project...", { Mod_Ctrl | Mod_Shift, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), [&](auto&) { - auto open_path = GUI::FilePicker::get_open_filepath(g_window, "Open project"); - if (!open_path.has_value()) - return; - open_project(open_path.value()); - open_file(g_project->default_file()); - update_actions(); - }); - - auto save_action = GUI::Action::create("Save", { Mod_Ctrl, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), [&](auto&) { - if (g_currently_open_file.is_empty()) - return; - - current_editor().write_to_file(g_currently_open_file); - - if (g_git_widget->initialized()) - g_git_widget->refresh(); - }); - - toolbar.add_action(new_action); - toolbar.add_action(add_existing_file_action); - toolbar.add_action(save_action); - toolbar.add_action(delete_action); - toolbar.add_separator(); - - toolbar.add_action(GUI::CommonActions::make_cut_action([&](auto&) { current_editor().cut_action().activate(); })); - toolbar.add_action(GUI::CommonActions::make_copy_action([&](auto&) { current_editor().copy_action().activate(); })); - toolbar.add_action(GUI::CommonActions::make_paste_action([&](auto&) { current_editor().paste_action().activate(); })); - toolbar.add_separator(); - toolbar.add_action(GUI::CommonActions::make_undo_action([&](auto&) { current_editor().undo_action().activate(); })); - toolbar.add_action(GUI::CommonActions::make_redo_action([&](auto&) { current_editor().redo_action().activate(); })); - toolbar.add_separator(); - - g_project_tree_view->on_activation = [&](auto& index) { - auto filename = index.data(GUI::ModelRole::Custom).to_string(); - open_file(filename); - }; - - s_action_tab_widget = g_right_hand_splitter->add(); - - s_action_tab_widget->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); - s_action_tab_widget->set_preferred_size(0, 24); - - s_action_tab_widget->on_change = [&](auto&) { - on_action_tab_change(); - - static bool first_time = true; - if (!first_time) - s_action_tab_widget->set_preferred_size(0, 200); - first_time = false; - }; - - auto reveal_action_tab = [&](auto& widget) { - if (s_action_tab_widget->preferred_size().height() < 200) - s_action_tab_widget->set_preferred_size(0, 200); - s_action_tab_widget->set_active_widget(&widget); - }; - - auto hide_action_tabs = [&] { - s_action_tab_widget->set_preferred_size(0, 24); - }; - - auto hide_action_tabs_action = GUI::Action::create("Hide action tabs", { Mod_Ctrl | Mod_Shift, Key_X }, [&](auto&) { - hide_action_tabs(); - }); - - auto add_editor_action = GUI::Action::create("Add new editor", { Mod_Ctrl | Mod_Alt, Key_E }, - Gfx::Bitmap::load_from_file("/res/icons/16x16/app-text-editor.png"), - [&](auto&) { - add_new_editor(*g_editors_splitter); - update_actions(); - }); - - auto add_terminal_action = GUI::Action::create("Add new Terminal", { Mod_Ctrl | Mod_Alt, Key_T }, - Gfx::Bitmap::load_from_file("/res/icons/16x16/app-terminal.png"), - [&](auto&) { - auto& terminal = s_action_tab_widget->add_tab("Terminal"); - reveal_action_tab(terminal); - update_actions(); - terminal.terminal()->set_focus(true); - }); - - auto remove_current_terminal_action = GUI::Action::create("Remove current Terminal", { Mod_Alt | Mod_Shift, Key_T }, [&](auto&) { - auto widget = s_action_tab_widget->active_widget(); - if (!widget) - return; - if (strcmp(widget->class_name(), "TerminalWrapper") != 0) - return; - auto terminal = reinterpret_cast(widget); - if (!terminal->user_spawned()) - return; - - s_action_tab_widget->remove_tab(*terminal); - update_actions(); - }); - - auto& find_in_files_widget = s_action_tab_widget->add_tab("Find in files"); - auto& terminal_wrapper = s_action_tab_widget->add_tab("Build", false); - auto& debug_info_widget = s_action_tab_widget->add_tab("Debug"); - auto& disassembly_widget = s_action_tab_widget->add_tab("Disassembly"); - g_git_widget = s_action_tab_widget->add_tab("Git", LexicalPath(g_project->root_directory())); - g_git_widget->set_view_diff_callback([](const auto& original_content, const auto& diff) { - g_diff_viewer->set_content(original_content, diff); - set_edit_mode(EditMode::Diff); - }); - - auto& locator = widget.add(); - - auto open_locator_action = GUI::Action::create("Open Locator...", { Mod_Ctrl, Key_K }, [&](auto&) { - locator.open(); - }); auto menubar = GUI::MenuBar::construct(); - auto& app_menu = menubar->add_menu("HackStudio"); - app_menu.add_action(open_action); - app_menu.add_action(save_action); - app_menu.add_separator(); - app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { - app->quit(); - })); + s_hack_studio_widget = s_window->set_main_widget(path_to_project(argument_absolute_path)); - auto& project_menu = menubar->add_menu("Project"); - project_menu.add_action(new_action); - project_menu.add_action(add_existing_file_action); + s_hack_studio_widget->initialize_menubar(menubar); + app->set_menubar(menubar); - auto& edit_menu = menubar->add_menu("Edit"); - edit_menu.add_action(GUI::Action::create("Find in files...", { Mod_Ctrl | Mod_Shift, Key_F }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find.png"), [&](auto&) { - reveal_action_tab(find_in_files_widget); - find_in_files_widget.focus_textbox_and_select_all(); - })); + s_window->show(); - auto stop_action = GUI::Action::create("Stop", Gfx::Bitmap::load_from_file("/res/icons/16x16/program-stop.png"), [&](auto&) { - terminal_wrapper.kill_running_command(); - }); + open_default_project_file(argument_absolute_path); + s_hack_studio_widget->update_actions(); - stop_action->set_enabled(false); - terminal_wrapper.on_command_exit = [&] { - stop_action->set_enabled(false); - }; - - auto build_action = GUI::Action::create("Build", { Mod_Ctrl, Key_B }, Gfx::Bitmap::load_from_file("/res/icons/16x16/build.png"), [&](auto&) { - reveal_action_tab(terminal_wrapper); - build(terminal_wrapper); - stop_action->set_enabled(true); - }); - toolbar.add_action(build_action); - toolbar.add_separator(); - - auto run_action = GUI::Action::create("Run", { Mod_Ctrl, Key_R }, Gfx::Bitmap::load_from_file("/res/icons/16x16/program-run.png"), [&](auto&) { - reveal_action_tab(terminal_wrapper); - run(terminal_wrapper); - stop_action->set_enabled(true); - }); - - RefPtr debugger_thread; - auto debug_action = GUI::Action::create("Debug", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-run.png"), [&](auto&) { - if (g_project->type() != ProjectType::Cpp) { - GUI::MessageBox::show(g_window, String::format("Cannot debug current project type", get_project_executable_path().characters()), "Error", GUI::MessageBox::Type::Error); - return; - } - if (!GUI::FilePicker::file_exists(get_project_executable_path())) { - GUI::MessageBox::show(g_window, String::format("Could not find file: %s. (did you build the project?)", get_project_executable_path().characters()), "Error", GUI::MessageBox::Type::Error); - return; - } - if (Debugger::the().session()) { - GUI::MessageBox::show(g_window, "Debugger is already running", "Error", GUI::MessageBox::Type::Error); - return; - } - Debugger::the().set_executable_path(get_project_executable_path()); - debugger_thread = adopt(*new LibThread::Thread(Debugger::start_static)); - debugger_thread->start(); - }); - - toolbar.add_separator(); - toolbar.add_action(debug_action); - - RefPtr current_editor_in_execution; - Debugger::initialize( - [&](const PtraceRegisters& regs) { - ASSERT(Debugger::the().session()); - const auto& debug_session = *Debugger::the().session(); - auto source_position = debug_session.debug_info().get_source_position(regs.eip); - if (!source_position.has_value()) { - dbg() << "Could not find source position for address: " << (void*)regs.eip; - return Debugger::HasControlPassedToUser::No; - } - - Core::EventLoop::main().post_event( - *g_window, - make( - [&, source_position](auto&) { - current_editor_in_execution = get_editor_of_file(source_position.value().file_path); - current_editor_in_execution->editor().set_execution_position(source_position.value().line_number - 1); - debug_info_widget.update_state(*Debugger::the().session(), regs); - debug_info_widget.set_debug_actions_enabled(true); - disassembly_widget.update_state(*Debugger::the().session(), regs); - reveal_action_tab(debug_info_widget); - })); - Core::EventLoop::wake(); - - return Debugger::HasControlPassedToUser::Yes; - }, - [&]() { - Core::EventLoop::main().post_event(*g_window, make([&](auto&) { - debug_info_widget.set_debug_actions_enabled(false); - if (current_editor_in_execution) { - current_editor_in_execution->editor().clear_execution_position(); - } - })); - Core::EventLoop::wake(); - }, - [&]() { - Core::EventLoop::main().post_event(*g_window, make([&](auto&) { - debug_info_widget.program_stopped(); - disassembly_widget.program_stopped(); - hide_action_tabs(); - GUI::MessageBox::show(g_window, "Program Exited", "Debugger", GUI::MessageBox::Type::Information); - })); - Core::EventLoop::wake(); - }); - - auto& build_menu = menubar->add_menu("Build"); - build_menu.add_action(build_action); - build_menu.add_separator(); - build_menu.add_action(run_action); - build_menu.add_action(stop_action); - build_menu.add_separator(); - build_menu.add_action(debug_action); - - auto& view_menu = menubar->add_menu("View"); - view_menu.add_action(hide_action_tabs_action); - view_menu.add_action(open_locator_action); - view_menu.add_separator(); - view_menu.add_action(add_editor_action); - view_menu.add_action(remove_current_editor_action); - view_menu.add_action(add_terminal_action); - view_menu.add_action(remove_current_terminal_action); - - auto& help_menu = menubar->add_menu("Help"); - help_menu.add_action(GUI::Action::create("About", [&](auto&) { - GUI::AboutDialog::show("HackStudio", Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"), g_window); - })); - - app->set_menubar(move(menubar)); - - g_window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-hack-studio.png")); - - g_window->show(); - - update_actions = [&]() { - auto is_remove_terminal_enabled = []() { - auto widget = s_action_tab_widget->active_widget(); - if (!widget) - return false; - if (StringView { "TerminalWrapper" } != widget->class_name()) - return false; - if (!reinterpret_cast(widget)->user_spawned()) - return false; - return true; - }; - - remove_current_editor_action->set_enabled(g_all_editor_wrappers.size() > 1); - remove_current_terminal_action->set_enabled(is_remove_terminal_enabled()); - }; - - on_action_tab_change = [&]() { - update_actions(); - auto git_widget = s_action_tab_widget->active_widget(); - if (!git_widget) - return; - if (StringView { "GitWidget" } != git_widget->class_name()) - return; - reinterpret_cast(git_widget)->refresh(); - }; - - g_open_file = open_file; - - if (!argument_absolute_path.is_empty() && !argument_absolute_path.ends_with(".hsp")) - open_file(argument_absolute_path); - else - open_file(g_project->default_file()); - - update_actions(); return app->exec(); } -void build(TerminalWrapper& wrapper) -{ - if (g_project->type() == ProjectType::JavaScript && g_currently_open_file.ends_with(".js")) - wrapper.run_command(String::format("js -A %s", g_currently_open_file.characters())); - else - wrapper.run_command("make"); -} - -void run(TerminalWrapper& wrapper) -{ - if (g_project->type() == ProjectType::JavaScript && g_currently_open_file.ends_with(".js")) - wrapper.run_command(String::format("js %s", g_currently_open_file.characters())); - else - wrapper.run_command("make run"); -} - -void open_project(String filename) -{ - LexicalPath lexical_path(filename); - if (chdir(lexical_path.dirname().characters()) < 0) { - perror("chdir"); - exit(1); - } - g_project = Project::load_from_file(filename); - ASSERT(g_project); - if (g_project_tree_view) { - g_project_tree_view->set_model(g_project->model()); - g_project_tree_view->toggle_index(g_project_tree_view->model()->index(0, 0)); - g_project_tree_view->update(); - } - if (Debugger::is_initialized()) { - Debugger::the().reset_breakpoints(); - } -} - -void open_file(const String& filename) -{ - auto project_file = g_project->get_file(filename); - if (project_file) { - current_editor().set_document(const_cast(project_file->document())); - current_editor().set_mode(GUI::TextEditor::Editable); - } else { - auto external_file = ProjectFile::construct_with_name(filename); - current_editor().set_document(const_cast(external_file->document())); - current_editor().set_mode(GUI::TextEditor::ReadOnly); - } - - if (filename.ends_with(".cpp") || filename.ends_with(".h")) - current_editor().set_syntax_highlighter(make()); - else if (filename.ends_with(".js")) - current_editor().set_syntax_highlighter(make()); - else if (filename.ends_with(".ini")) - current_editor().set_syntax_highlighter(make()); - else - current_editor().set_syntax_highlighter(nullptr); - - if (filename.ends_with(".frm")) { - set_edit_mode(EditMode::Form); - } else { - set_edit_mode(EditMode::Text); - } - - g_currently_open_file = filename; - g_window->set_title(String::format("%s - HackStudio", g_currently_open_file.characters())); - g_project_tree_view->update(); - - current_editor_wrapper().filename_label().set_text(filename); - - current_editor().set_focus(true); -} - -bool make_is_available() +static bool make_is_available() { pid_t pid; const char* argv[] = { "make", "--version", nullptr }; @@ -852,9 +120,66 @@ bool make_is_available() return WEXITSTATUS(wstatus) == 0; } +static void update_path_environment_variable() +{ + StringBuilder path; + path.append(getenv("PATH")); + if (path.length()) + path.append(":"); + path.append("/bin:/usr/bin:/usr/local/bin"); + setenv("PATH", path.to_string().characters(), true); } -int main(int argc, char** argv) +static String path_to_project(const String& path_argument_absolute_path) { - return main_impl(argc, argv); + if (path_argument_absolute_path.ends_with(".hsp")) + return path_argument_absolute_path; + else + return "/home/anon/Source/little/little.hsp"; +} + +static void open_default_project_file(const String& project_path) +{ + if (!project_path.is_empty() && !project_path.ends_with(".hsp")) + open_file(project_path); + else + open_file(s_hack_studio_widget->project().default_file()); +} + +namespace HackStudio { + +GUI::TextEditor& current_editor() +{ + return s_hack_studio_widget->current_editor(); +} + +void open_file(const String& file_name) +{ + return s_hack_studio_widget->open_file(file_name); +} + +RefPtr current_editor_wrapper() +{ + if (!s_hack_studio_widget) + return nullptr; + return s_hack_studio_widget->current_editor_wrapper(); +} + +Project& project() +{ + return s_hack_studio_widget->project(); +} + +String currently_open_file() +{ + if (!s_hack_studio_widget) + return {}; + return s_hack_studio_widget->currently_open_file(); +} + +void set_current_editor_wrapper(RefPtr wrapper) +{ + s_hack_studio_widget->set_current_editor_wrapper(wrapper); +} + }