Serenity Operating System
at portability 588 lines 23 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "CursorTool.h" 28#include "Editor.h" 29#include "EditorWrapper.h" 30#include "FindInFilesWidget.h" 31#include "FormEditorWidget.h" 32#include "FormWidget.h" 33#include "Locator.h" 34#include "Project.h" 35#include "TerminalWrapper.h" 36#include "WidgetTool.h" 37#include "WidgetTreeModel.h" 38#include <AK/StringBuilder.h> 39#include <LibCore/File.h> 40#include <LibGUI/AboutDialog.h> 41#include <LibGUI/Action.h> 42#include <LibGUI/ActionGroup.h> 43#include <LibGUI/Application.h> 44#include <LibGUI/BoxLayout.h> 45#include <LibGUI/Button.h> 46#include <LibGUI/CppSyntaxHighlighter.h> 47#include <LibGUI/FilePicker.h> 48#include <LibGUI/InputBox.h> 49#include <LibGUI/Label.h> 50#include <LibGUI/Menu.h> 51#include <LibGUI/MenuBar.h> 52#include <LibGUI/MessageBox.h> 53#include <LibGUI/Splitter.h> 54#include <LibGUI/StackWidget.h> 55#include <LibGUI/TabWidget.h> 56#include <LibGUI/TableView.h> 57#include <LibGUI/TextBox.h> 58#include <LibGUI/TextEditor.h> 59#include <LibGUI/ToolBar.h> 60#include <LibGUI/TreeView.h> 61#include <LibGUI/Widget.h> 62#include <LibGUI/Window.h> 63#include <stdio.h> 64#include <sys/wait.h> 65#include <unistd.h> 66 67NonnullRefPtrVector<EditorWrapper> g_all_editor_wrappers; 68RefPtr<EditorWrapper> g_current_editor_wrapper; 69 70String g_currently_open_file; 71OwnPtr<Project> g_project; 72RefPtr<GUI::Window> g_window; 73RefPtr<GUI::TreeView> g_project_tree_view; 74RefPtr<GUI::StackWidget> g_right_hand_stack; 75RefPtr<GUI::Splitter> g_text_inner_splitter; 76RefPtr<GUI::Widget> g_form_inner_container; 77RefPtr<FormEditorWidget> g_form_editor_widget; 78 79static RefPtr<GUI::TabWidget> s_action_tab_widget; 80 81void add_new_editor(GUI::Widget& parent) 82{ 83 auto wrapper = EditorWrapper::construct(); 84 if (s_action_tab_widget) { 85 parent.insert_child_before(wrapper, *s_action_tab_widget); 86 } else { 87 parent.add_child(wrapper); 88 } 89 g_current_editor_wrapper = wrapper; 90 g_all_editor_wrappers.append(wrapper); 91 wrapper->editor().set_focus(true); 92} 93 94enum class EditMode { 95 Text, 96 Form, 97}; 98 99void set_edit_mode(EditMode mode) 100{ 101 if (mode == EditMode::Text) { 102 g_right_hand_stack->set_active_widget(g_text_inner_splitter); 103 } else if (mode == EditMode::Form) { 104 g_right_hand_stack->set_active_widget(g_form_inner_container); 105 } 106} 107 108EditorWrapper& current_editor_wrapper() 109{ 110 ASSERT(g_current_editor_wrapper); 111 return *g_current_editor_wrapper; 112} 113 114Editor& current_editor() 115{ 116 return current_editor_wrapper().editor(); 117} 118 119static void build(TerminalWrapper&); 120static void run(TerminalWrapper&); 121void open_file(const String&); 122bool make_is_available(); 123 124int main(int argc, char** argv) 125{ 126 if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec unix fattr", nullptr) < 0) { 127 perror("pledge"); 128 return 1; 129 } 130 131 GUI::Application app(argc, argv); 132 133 if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec fattr", nullptr) < 0) { 134 perror("pledge"); 135 return 1; 136 } 137 138 Function<void()> update_actions; 139 140 g_window = GUI::Window::construct(); 141 g_window->set_rect(90, 90, 840, 600); 142 g_window->set_title("HackStudio"); 143 144 auto widget = GUI::Widget::construct(); 145 g_window->set_main_widget(widget); 146 147 widget->set_fill_with_background_color(true); 148 widget->set_layout(make<GUI::VerticalBoxLayout>()); 149 widget->layout()->set_spacing(0); 150 151 StringBuilder path; 152 path.append(getenv("PATH")); 153 if (path.length()) 154 path.append(":"); 155 path.append("/bin:/usr/bin:/usr/local/bin"); 156 setenv("PATH", path.to_string().characters(), true); 157 158 if (!make_is_available()) 159 GUI::MessageBox::show("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::InputType::OK, g_window); 160 161 if (chdir("/home/anon/little") < 0) { 162 perror("chdir"); 163 return 1; 164 } 165 g_project = Project::load_from_file("little.files"); 166 ASSERT(g_project); 167 168 auto toolbar = widget->add<GUI::ToolBar>(); 169 170 auto selected_file_names = [&] { 171 Vector<String> files; 172 g_project_tree_view->selection().for_each_index([&](const GUI::ModelIndex& index) { 173 files.append(g_project->model().data(index).as_string()); 174 }); 175 return files; 176 }; 177 178 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&) { 179 auto input_box = g_window->add<GUI::InputBox>("Enter name of new file:", "Add new file to project"); 180 if (input_box->exec() == GUI::InputBox::ExecCancel) 181 return; 182 auto filename = input_box->text_value(); 183 auto file = Core::File::construct(filename); 184 if (!file->open((Core::IODevice::OpenMode)(Core::IODevice::WriteOnly | Core::IODevice::MustBeNew))) { 185 GUI::MessageBox::show(String::format("Failed to create '%s'", filename.characters()), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, g_window); 186 return; 187 } 188 if (!g_project->add_file(filename)) { 189 GUI::MessageBox::show(String::format("Failed to add '%s' to project", filename.characters()), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, g_window); 190 // FIXME: Should we unlink the file here maybe? 191 return; 192 } 193 open_file(filename); 194 }); 195 196 auto add_existing_file_action = GUI::Action::create("Add existing file to project...", Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), [&](auto&) { 197 auto result = GUI::FilePicker::get_open_filepath("Add existing file to project"); 198 if (!result.has_value()) 199 return; 200 auto& filename = result.value(); 201 if (!g_project->add_file(filename)) { 202 GUI::MessageBox::show(String::format("Failed to add '%s' to project", filename.characters()), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, g_window); 203 return; 204 } 205 open_file(filename); 206 }); 207 208 auto delete_action = GUI::CommonActions::make_delete_action([&](const GUI::Action& action) { 209 (void)action; 210 211 auto files = selected_file_names(); 212 if (files.is_empty()) 213 return; 214 215 String message; 216 if (files.size() == 1) { 217 message = String::format("Really remove %s from the project?", FileSystemPath(files[0]).basename().characters()); 218 } else { 219 message = String::format("Really remove %d files from the project?", files.size()); 220 } 221 222 auto result = GUI::MessageBox::show( 223 message, 224 "Confirm deletion", 225 GUI::MessageBox::Type::Warning, 226 GUI::MessageBox::InputType::OKCancel, 227 g_window); 228 if (result == GUI::MessageBox::ExecCancel) 229 return; 230 231 for (auto& file : files) { 232 if (!g_project->remove_file(file)) { 233 GUI::MessageBox::show( 234 String::format("Removing file %s from the project failed.", file.characters()), 235 "Removal failed", 236 GUI::MessageBox::Type::Error, 237 GUI::MessageBox::InputType::OK, 238 g_window); 239 break; 240 } 241 } 242 }); 243 delete_action->set_enabled(false); 244 245 auto project_tree_view_context_menu = GUI::Menu::construct("Project Files"); 246 project_tree_view_context_menu->add_action(new_action); 247 project_tree_view_context_menu->add_action(add_existing_file_action); 248 project_tree_view_context_menu->add_action(delete_action); 249 250 auto outer_splitter = widget->add<GUI::HorizontalSplitter>(); 251 g_project_tree_view = outer_splitter->add<GUI::TreeView>(); 252 g_project_tree_view->set_model(g_project->model()); 253 g_project_tree_view->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 254 g_project_tree_view->set_preferred_size(140, 0); 255 256 g_project_tree_view->on_context_menu_request = [&](const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) { 257 if (index.is_valid()) { 258 project_tree_view_context_menu->popup(event.screen_position()); 259 } 260 }; 261 262 g_project_tree_view->on_selection_change = [&] { 263 delete_action->set_enabled(!g_project_tree_view->selection().is_empty()); 264 }; 265 266 g_right_hand_stack = outer_splitter->add<GUI::StackWidget>(); 267 268 g_form_inner_container = g_right_hand_stack->add<GUI::Widget>(); 269 g_form_inner_container->set_layout(make<GUI::HorizontalBoxLayout>()); 270 auto form_widgets_toolbar = g_form_inner_container->add<GUI::ToolBar>(Orientation::Vertical, 26); 271 form_widgets_toolbar->set_preferred_size(38, 0); 272 273 GUI::ActionGroup tool_actions; 274 tool_actions.set_exclusive(true); 275 276 auto cursor_tool_action = GUI::Action::create("Cursor", Gfx::Bitmap::load_from_file("/res/icons/widgets/Cursor.png"), [&](auto&) { 277 g_form_editor_widget->set_tool(make<CursorTool>(*g_form_editor_widget)); 278 }); 279 cursor_tool_action->set_checkable(true); 280 cursor_tool_action->set_checked(true); 281 tool_actions.add_action(cursor_tool_action); 282 283 form_widgets_toolbar->add_action(cursor_tool_action); 284 285 GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& reg) { 286 auto icon_path = String::format("/res/icons/widgets/G%s.png", reg.class_name().characters()); 287 auto action = GUI::Action::create(reg.class_name(), Gfx::Bitmap::load_from_file(icon_path), [&reg](auto&) { 288 g_form_editor_widget->set_tool(make<WidgetTool>(*g_form_editor_widget, reg)); 289 auto widget = reg.construct(); 290 g_form_editor_widget->form_widget().add_child(widget); 291 widget->set_relative_rect(30, 30, 30, 30); 292 g_form_editor_widget->model().update(); 293 }); 294 action->set_checkable(true); 295 action->set_checked(false); 296 tool_actions.add_action(action); 297 form_widgets_toolbar->add_action(move(action)); 298 }); 299 300 auto form_editor_inner_splitter = g_form_inner_container->add<GUI::HorizontalSplitter>(); 301 302 g_form_editor_widget = form_editor_inner_splitter->add<FormEditorWidget>(); 303 304 auto form_editing_pane_container = form_editor_inner_splitter->add<GUI::VerticalSplitter>(); 305 form_editing_pane_container->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 306 form_editing_pane_container->set_preferred_size(190, 0); 307 form_editing_pane_container->set_layout(make<GUI::VerticalBoxLayout>()); 308 309 auto add_properties_pane = [&](auto& text, auto pane_widget) { 310 auto wrapper = form_editing_pane_container->add<GUI::Widget>(); 311 wrapper->set_layout(make<GUI::VerticalBoxLayout>()); 312 auto label = wrapper->add<GUI::Label>(text); 313 label->set_fill_with_background_color(true); 314 label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 315 label->set_font(Gfx::Font::default_bold_font()); 316 label->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 317 label->set_preferred_size(0, 16); 318 wrapper->add_child(pane_widget); 319 }; 320 321 auto form_widget_tree_view = GUI::TreeView::construct(); 322 form_widget_tree_view->set_model(g_form_editor_widget->model()); 323 form_widget_tree_view->on_selection_change = [&] { 324 g_form_editor_widget->selection().disable_hooks(); 325 g_form_editor_widget->selection().clear(); 326 form_widget_tree_view->selection().for_each_index([&](auto& index) { 327 // NOTE: Make sure we don't add the FormWidget itself to the selection, 328 // since that would allow you to drag-move the FormWidget. 329 if (index.internal_data() != &g_form_editor_widget->form_widget()) 330 g_form_editor_widget->selection().add(*(GUI::Widget*)index.internal_data()); 331 }); 332 g_form_editor_widget->update(); 333 g_form_editor_widget->selection().enable_hooks(); 334 }; 335 336 g_form_editor_widget->selection().on_add = [&](auto& widget) { 337 form_widget_tree_view->selection().add(g_form_editor_widget->model().index_for_widget(widget)); 338 }; 339 g_form_editor_widget->selection().on_remove = [&](auto& widget) { 340 form_widget_tree_view->selection().remove(g_form_editor_widget->model().index_for_widget(widget)); 341 }; 342 g_form_editor_widget->selection().on_clear = [&] { 343 form_widget_tree_view->selection().clear(); 344 }; 345 346 add_properties_pane("Form widget tree:", form_widget_tree_view); 347 add_properties_pane("Widget properties:", GUI::TableView::construct()); 348 349 g_text_inner_splitter = g_right_hand_stack->add<GUI::VerticalSplitter>(); 350 g_text_inner_splitter->layout()->set_margins({ 0, 3, 0, 0 }); 351 add_new_editor(*g_text_inner_splitter); 352 353 auto switch_to_next_editor = GUI::Action::create("Switch to next editor", { Mod_Ctrl, Key_E }, [&](auto&) { 354 if (g_all_editor_wrappers.size() <= 1) 355 return; 356 Vector<EditorWrapper*> wrappers; 357 g_text_inner_splitter->for_each_child_of_type<EditorWrapper>([&](auto& child) { 358 wrappers.append(&child); 359 return IterationDecision::Continue; 360 }); 361 for (size_t i = 0; i < wrappers.size(); ++i) { 362 if (g_current_editor_wrapper.ptr() == wrappers[i]) { 363 if (i == wrappers.size() - 1) 364 wrappers[0]->editor().set_focus(true); 365 else 366 wrappers[i + 1]->editor().set_focus(true); 367 } 368 } 369 }); 370 371 auto switch_to_previous_editor = GUI::Action::create("Switch to previous editor", { Mod_Ctrl | Mod_Shift, Key_E }, [&](auto&) { 372 if (g_all_editor_wrappers.size() <= 1) 373 return; 374 Vector<EditorWrapper*> wrappers; 375 g_text_inner_splitter->for_each_child_of_type<EditorWrapper>([&](auto& child) { 376 wrappers.append(&child); 377 return IterationDecision::Continue; 378 }); 379 for (int i = wrappers.size() - 1; i >= 0; --i) { 380 if (g_current_editor_wrapper.ptr() == wrappers[i]) { 381 if (i == 0) 382 wrappers.last()->editor().set_focus(true); 383 else 384 wrappers[i - 1]->editor().set_focus(true); 385 } 386 } 387 }); 388 389 auto remove_current_editor_action = GUI::Action::create("Remove current editor", { Mod_Alt | Mod_Shift, Key_E }, [&](auto&) { 390 if (g_all_editor_wrappers.size() <= 1) 391 return; 392 auto wrapper = g_current_editor_wrapper; 393 switch_to_next_editor->activate(); 394 g_text_inner_splitter->remove_child(*wrapper); 395 g_all_editor_wrappers.remove_first_matching([&](auto& entry) { return entry == wrapper.ptr(); }); 396 update_actions(); 397 }); 398 399 auto save_action = GUI::Action::create("Save", { Mod_Ctrl, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), [&](auto&) { 400 if (g_currently_open_file.is_empty()) 401 return; 402 current_editor().write_to_file(g_currently_open_file); 403 }); 404 405 toolbar->add_action(new_action); 406 toolbar->add_action(add_existing_file_action); 407 toolbar->add_action(save_action); 408 toolbar->add_action(delete_action); 409 toolbar->add_separator(); 410 411 toolbar->add_action(GUI::CommonActions::make_cut_action([&](auto&) { current_editor().cut_action().activate(); })); 412 toolbar->add_action(GUI::CommonActions::make_copy_action([&](auto&) { current_editor().copy_action().activate(); })); 413 toolbar->add_action(GUI::CommonActions::make_paste_action([&](auto&) { current_editor().paste_action().activate(); })); 414 toolbar->add_separator(); 415 toolbar->add_action(GUI::CommonActions::make_undo_action([&](auto&) { current_editor().undo_action().activate(); })); 416 toolbar->add_action(GUI::CommonActions::make_redo_action([&](auto&) { current_editor().redo_action().activate(); })); 417 toolbar->add_separator(); 418 419 g_project_tree_view->on_activation = [&](auto& index) { 420 auto filename = g_project_tree_view->model()->data(index, GUI::Model::Role::Custom).to_string(); 421 open_file(filename); 422 }; 423 424 s_action_tab_widget = g_text_inner_splitter->add<GUI::TabWidget>(); 425 426 s_action_tab_widget->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 427 s_action_tab_widget->set_preferred_size(0, 24); 428 429 auto reveal_action_tab = [&](auto& widget) { 430 if (s_action_tab_widget->preferred_size().height() < 200) 431 s_action_tab_widget->set_preferred_size(0, 200); 432 s_action_tab_widget->set_active_widget(widget); 433 }; 434 435 auto hide_action_tabs = [&] { 436 s_action_tab_widget->set_preferred_size(0, 24); 437 }; 438 439 auto hide_action_tabs_action = GUI::Action::create("Hide action tabs", { Mod_Ctrl | Mod_Shift, Key_X }, [&](auto&) { 440 hide_action_tabs(); 441 }); 442 443 auto add_editor_action = GUI::Action::create("Add new editor", { Mod_Ctrl | Mod_Alt, Key_E }, [&](auto&) { 444 add_new_editor(*g_text_inner_splitter); 445 update_actions(); 446 }); 447 448 auto find_in_files_widget = s_action_tab_widget->add_tab<FindInFilesWidget>("Find in files"); 449 auto terminal_wrapper = s_action_tab_widget->add_tab<TerminalWrapper>("Console"); 450 451 auto locator = widget->add<Locator>(); 452 453 auto open_locator_action = GUI::Action::create("Open Locator...", { Mod_Ctrl, Key_K }, [&](auto&) { 454 locator->open(); 455 }); 456 457 auto menubar = make<GUI::MenuBar>(); 458 auto app_menu = GUI::Menu::construct("HackStudio"); 459 app_menu->add_action(save_action); 460 app_menu->add_action(GUI::CommonActions::make_quit_action([&](auto&) { 461 app.quit(); 462 })); 463 menubar->add_menu(move(app_menu)); 464 465 auto project_menu = GUI::Menu::construct("Project"); 466 project_menu->add_action(new_action); 467 project_menu->add_action(add_existing_file_action); 468 menubar->add_menu(move(project_menu)); 469 470 auto edit_menu = GUI::Menu::construct("Edit"); 471 edit_menu->add_action(GUI::Action::create("Find in files...", { Mod_Ctrl | Mod_Shift, Key_F }, [&](auto&) { 472 reveal_action_tab(find_in_files_widget); 473 find_in_files_widget->focus_textbox_and_select_all(); 474 })); 475 menubar->add_menu(move(edit_menu)); 476 477 auto stop_action = GUI::Action::create("Stop", Gfx::Bitmap::load_from_file("/res/icons/16x16/stop.png"), [&](auto&) { 478 terminal_wrapper->kill_running_command(); 479 }); 480 481 stop_action->set_enabled(false); 482 terminal_wrapper->on_command_exit = [&] { 483 stop_action->set_enabled(false); 484 }; 485 486 auto build_action = GUI::Action::create("Build", { Mod_Ctrl, Key_B }, Gfx::Bitmap::load_from_file("/res/icons/16x16/build.png"), [&](auto&) { 487 reveal_action_tab(terminal_wrapper); 488 build(terminal_wrapper); 489 stop_action->set_enabled(true); 490 }); 491 toolbar->add_action(build_action); 492 493 auto run_action = GUI::Action::create("Run", { Mod_Ctrl, Key_R }, Gfx::Bitmap::load_from_file("/res/icons/16x16/play.png"), [&](auto&) { 494 reveal_action_tab(terminal_wrapper); 495 run(terminal_wrapper); 496 stop_action->set_enabled(true); 497 }); 498 toolbar->add_action(run_action); 499 toolbar->add_action(stop_action); 500 501 auto build_menu = GUI::Menu::construct("Build"); 502 build_menu->add_action(build_action); 503 build_menu->add_action(run_action); 504 build_menu->add_action(stop_action); 505 menubar->add_menu(move(build_menu)); 506 507 auto view_menu = GUI::Menu::construct("View"); 508 view_menu->add_action(hide_action_tabs_action); 509 view_menu->add_action(open_locator_action); 510 view_menu->add_separator(); 511 view_menu->add_action(add_editor_action); 512 view_menu->add_action(remove_current_editor_action); 513 menubar->add_menu(move(view_menu)); 514 515 auto small_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/app-hack-studio.png"); 516 517 auto help_menu = GUI::Menu::construct("Help"); 518 help_menu->add_action(GUI::Action::create("About", [&](auto&) { 519 GUI::AboutDialog::show("HackStudio", small_icon, g_window); 520 })); 521 menubar->add_menu(move(help_menu)); 522 523 app.set_menubar(move(menubar)); 524 525 g_window->set_icon(small_icon); 526 527 g_window->show(); 528 529 update_actions = [&]() { 530 remove_current_editor_action->set_enabled(g_all_editor_wrappers.size() > 1); 531 }; 532 533 open_file("main.cpp"); 534 535 update_actions(); 536 return app.exec(); 537} 538 539void build(TerminalWrapper& wrapper) 540{ 541 wrapper.run_command("make"); 542} 543 544void run(TerminalWrapper& wrapper) 545{ 546 wrapper.run_command("make run"); 547} 548 549void open_file(const String& filename) 550{ 551 auto file = g_project->get_file(filename); 552 current_editor().set_document(const_cast<GUI::TextDocument&>(file->document())); 553 554 if (filename.ends_with(".cpp") || filename.ends_with(".h")) 555 current_editor().set_syntax_highlighter(make<GUI::CppSyntaxHighlighter>()); 556 557 if (filename.ends_with(".frm")) { 558 set_edit_mode(EditMode::Form); 559 } else { 560 set_edit_mode(EditMode::Text); 561 } 562 563 g_currently_open_file = filename; 564 g_window->set_title(String::format("%s - HackStudio", g_currently_open_file.characters())); 565 g_project_tree_view->update(); 566 567 current_editor_wrapper().filename_label().set_text(filename); 568 569 current_editor().set_focus(true); 570} 571 572bool make_is_available() 573{ 574 auto pid = fork(); 575 if (pid < 0) 576 return false; 577 578 if (!pid) { 579 int rc = execlp("make", "make", "--version", nullptr); 580 ASSERT(rc < 0); 581 perror("execl"); 582 exit(127); 583 } 584 585 int wstatus; 586 waitpid(pid, &wstatus, 0); 587 return WEXITSTATUS(wstatus) == 0; 588}