diff --git a/CMakeLists.txt b/CMakeLists.txt index da710eb..f80481f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,10 @@ cmake_minimum_required (VERSION 2.8.4) set(project_name juci) -set(module juci_to_python_api) +#set(module juci_to_python_api) -#### TODO WINDOWS SUPPORT #### -set(bin_install_path "/usr/local/bin") -set(lib_install_path "/usr/local/lib/python2.7/dist-packages/") -##### +#set(lib_install_path "/usr/local/lib/python2.7/dist-packages/") project (${project_name}) -add_subdirectory("src") \ No newline at end of file +add_subdirectory("src") diff --git a/MINGW-packages/mingw-w64-gtksourceview3/0006-hack-convert-path-back-to-unix.patch b/MINGW-packages/mingw-w64-gtksourceview3/0006-hack-convert-path-back-to-unix.patch new file mode 100644 index 0000000..68f217f --- /dev/null +++ b/MINGW-packages/mingw-w64-gtksourceview3/0006-hack-convert-path-back-to-unix.patch @@ -0,0 +1,15 @@ +--- gtksourceview-3.13.90/configure.ac.orig 2014-08-22 00:42:28.532000000 +0400 ++++ gtksourceview-3.13.90/configure.ac 2014-08-22 00:43:00.621200000 +0400 +@@ -133,6 +133,12 @@ + AC_MSG_RESULT([$GLADE_CATALOG_DIR]) + AC_SUBST(GLADE_CATALOG_DIR)]) + ++case "$host" in ++ *-*-mingw*) ++ GLADE_CATALOG_DIR=`cygpath -u $GLADE_CATALOG_DIR` ++ ;; ++esac ++ + # i18N stuff + IT_PROG_INTLTOOL([0.40]) + AS_IF([test "$USE_NLS" = "yes"], diff --git a/MINGW-packages/mingw-w64-gtksourceview3/LICENSE b/MINGW-packages/mingw-w64-gtksourceview3/LICENSE new file mode 100644 index 0000000..e0f07ba --- /dev/null +++ b/MINGW-packages/mingw-w64-gtksourceview3/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013, Алексей +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +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. diff --git a/MINGW-packages/mingw-w64-gtksourceview3/PKGBUILD b/MINGW-packages/mingw-w64-gtksourceview3/PKGBUILD new file mode 100644 index 0000000..038333a --- /dev/null +++ b/MINGW-packages/mingw-w64-gtksourceview3/PKGBUILD @@ -0,0 +1,64 @@ +# Maintainer: Alexey Pavlov + +_realname=gtksourceview +pkgname="${MINGW_PACKAGE_PREFIX}-${_realname}3" +pkgver=3.12.0 +pkgrel=2 +pkgdesc="A text widget adding syntax highlighting and more to GNOME (mingw-w64)" +arch=('any') +url="http://www.gnome.org" +license=("LGPL") +makedepends=("${MINGW_PACKAGE_PREFIX}-gcc" + "${MINGW_PACKAGE_PREFIX}-pkg-config" + "${MINGW_PACKAGE_PREFIX}-gobject-introspection" + "${MINGW_PACKAGE_PREFIX}-glade" + "${MINGW_PACKAGE_PREFIX}-vala" + "intltool" + "gtk-doc") +depends=("${MINGW_PACKAGE_PREFIX}-gtk3" + "${MINGW_PACKAGE_PREFIX}-libxml2") +options=(!libtool strip staticlibs) +source=("http://ftp.gnome.org/pub/gnome/sources/gtksourceview/${pkgver%.*}/gtksourceview-${pkgver}.tar.xz" + 0006-hack-convert-path-back-to-unix.patch) +md5sums=('8850fc0aee4893668ede37a30ef05e85' + '324c9e3bb2e4fa2a4977653ce6ed6ef9') + +prepare() { + cd ${_realname}-${pkgver} + patch -p1 -i ${srcdir}/0006-hack-convert-path-back-to-unix.patch + + autoreconf -fi +} + +build() { + mkdir -p "${srcdir}/build-${MINGW_CHOST}" + cd "${srcdir}/build-${MINGW_CHOST}" + + mkdir -p docs/reference/html + cp -rf ../${_realname}-${pkgver}/docs/reference/html/* docs/reference/html + + DATADIRNAME=share \ + ../${_realname}-${pkgver}/configure \ + --prefix=${MINGW_PREFIX} \ + --build=${MINGW_CHOST} \ + --host=${MINGW_CHOST} \ + --enable-shared \ + --disable-static \ + --enable-glade-catalog + + LC_ALL=C make +} + +package() { + cd "${srcdir}/build-${MINGW_CHOST}" + make -j1 DESTDIR="${pkgdir}" install + + for ff in ${pkgdir}/${MINGW_PREFIX}/bin/libgtksourceview*.dll; do + local _reallib=$(basename $ff) + _reallib=${_reallib%.dll} + _reallib=${_reallib#lib} + sed -e "s|library=\"gtksourceview-3.0\"|library=\"${_reallib}\"|g" -i ${pkgdir}/${MINGW_PREFIX}/share/glade/catalogs/gtksourceview.xml + done + + install -Dm644 "${srcdir}/${_realname}-${pkgver}/COPYING" "${pkgdir}${MINGW_PREFIX}/share/licenses/${_realname}/COPYING" +} diff --git a/MINGW-packages/mingw-w64-gtksourceviewmm3/LICENSE b/MINGW-packages/mingw-w64-gtksourceviewmm3/LICENSE new file mode 100644 index 0000000..e0f07ba --- /dev/null +++ b/MINGW-packages/mingw-w64-gtksourceviewmm3/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013, Алексей +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +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. diff --git a/MINGW-packages/mingw-w64-gtksourceviewmm3/PKGBUILD b/MINGW-packages/mingw-w64-gtksourceviewmm3/PKGBUILD new file mode 100644 index 0000000..0d842a8 --- /dev/null +++ b/MINGW-packages/mingw-w64-gtksourceviewmm3/PKGBUILD @@ -0,0 +1,33 @@ +_realname=gtksourceviewmm3 +pkgname="${MINGW_PACKAGE_PREFIX}-${_realname}" +pkgver=3.12.0 +pkgrel=1 +pkgdesc="C++ bindings for gtksourceview3 (mingw-w64)" +arch=('any') +url="https://developer.gnome.org/gtksourceviewmm/" +license=("LGPL") +makedepends=("patch" "${MINGW_PACKAGE_PREFIX}-gcc" "${MINGW_PACKAGE_PREFIX}-pkg-config") +depends=("${MINGW_PACKAGE_PREFIX}-gtksourceview3=${pkgver}") +options=('staticlibs' 'strip') +source=("https://download.gnome.org/sources/gtksourceviewmm/${pkgver%.*}/gtksourceviewmm-${pkgver}.tar.xz") +sha256sums=("73939031bcc60e6ad31a315ec84b132deba15e5732de16e75fe424a619267ace") + +build() { + mkdir -p "${srcdir}/build-${MINGW_CHOST}" + cd "${srcdir}/build-${MINGW_CHOST}" + ../gtksourceviewmm-${pkgver}/configure \ + CXXFLAGS=-std=c++11 \ + --prefix=${MINGW_PREFIX} \ + --build=${MINGW_CHOST} \ + --host=${MINGW_CHOST} \ + --enable-shared \ + --enable-static \ + --disable-documentation + make +} + +package() { + cd "${srcdir}/build-${MINGW_CHOST}" + make DESTDIR="${pkgdir}" install + find "${pkgdir}${MINGW_PREFIX}" -name '*.def' -o -name '*.exp' | xargs -rtl1 rm +} \ No newline at end of file diff --git a/README.md b/README.md index 3823f43..8fb39cb 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,23 @@ # juCi++ ###### a lightweight C++-IDE with support for C++11 and C++14. ## About -A lot of IDEs struggle with good C++11/14 support. Therefore we -created juCi++. juCi++ is based of the compiler and will *always* -support new versions of C++. +Current IDEs struggle with C++ support due to the complexity of +the programming language. juCI++, however, is designed especially +towards libclang with speed in mind. ## Features -* Syntax highlighing (even C++11/14) -* Superfast autocomletion (even external libraries) -* Accurate refactoring across files -* Basic editor functionallity +* Fast and responsive +* Syntax highlighing (even C++11/14, and more than 100 other file types) +* C++ warnings and errors on the fly +* Fast C++ autocomletion (even external libraries) +* Tooltips showing type information and doxygen documentation +* Refactoring across files * Highlighting of similar types -* write your own plugins in python (limited atm) +* Spell checking depending on file context +* Basic editor functionallity +* Write your own plugins in python (disabled at the moment) ## Dependencies ## -Please install these dependencies on your system. -* libboost-python-dev * libboost-filesystem-dev * libboost-log-dev * libboost-test-dev @@ -23,20 +25,10 @@ Please install these dependencies on your system. * libboost-system-dev * libgtkmm-3.0-dev * libgtksourceview2.0-dev -* libgtksourceviewmm-3.0-dev -* libpython-dev +* libgtksourceviewmm-3.0-dev +* libaspell-dev * libclang-dev * [libclangmm](http://github.com/cppit/libclangmm/) -* cmake -* make -* clang or gcc (compiler) ## Installation ## -Quickstart: -```sh -$ cmake . -$ make -$ sudo make install -``` -See [installation guide](http://github.com/cppit/jucipp/blob/master/docs/install.md) for more details. - +See [installation guide](http://github.com/cppit/jucipp/blob/master/docs/install.md). diff --git a/docs/install.md b/docs/install.md index 345a815..ef2d95c 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,22 +1,90 @@ # juCi++ ## Installation guide ## -Before installation, please install libclangmm see [installation guide](http://github.com/cppit/libclangmm/blob/master/docs/install.md) for installation. -## Debian -First dependencies: +Before installation, please install libclangmm, see [installation guide](http://github.com/cppit/libclangmm/blob/master/docs/install.md). + +## Debian/Ubuntu ```sh -$ sudo apt-get install libboost-python-dev libboost-filesystem-dev libboost-log-dev libboost-test-dev -libboost-thread-dev libboost-system-dev libgtkmm-3.0-dev libgtksourceview2.0-dev libgtksourceviewmm-3.0-dev -libpython-dev libclang-dev make cmake gcc g++ +sudo apt-get install pkg-config libboost-system-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libgtkmm-3.0-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev ``` -Install the project: ```sh -$ git clone http://github.com/cppit/jucipp.git juci -$ cd juci -$ cmake . -$ make -$ sudo make install +git clone http://github.com/cppit/jucipp.git +cd jucipp +cmake . +make +sudo make install ``` + +## OS X with Homebrew (http://brew.sh/) +```sh +brew install pkg-config boost gtkmm3 gtksourceviewmm3 aspell +``` + +```sh +git clone https://github.com/cppit/jucipp.git +cd jucipp +cmake . +make +make install +``` + +##Windows with MSYS2 (https://msys2.github.io/) +Install dependencies(replace [arch] with i686 or x86_64 depending on your MSYS2 install): +```sh +pacman -S patch autoconf automake-wrapper mingw-w64-[arch]-gtkmm3 mingw-w64-[arch]-boost mingw-w64-[arch]-aspell mingw-w64-[arch]-aspell-en +``` + +Get juCi++ source: +```sh +git clone https://github.com/cppit/jucipp.git +cd jucipp +``` + +Compiling and installing gtksourceview3 and gtksourceviewmm3: +```sh +cd MINGW-packages/mingw-w64-gtksourceview3/ +makepkg-mingw -sLf +pacman -U mingw-w64-[arch]-gtksourceview3-3.12.0-2-any.pkg.tar.xz +cd ../mingw-w64-gtksourceviewmm3/ +makepkg-mingw -sLf +pacman -U mingw-w64-[arch]-gtksourceviewmm3-3.12.0-1-any.pkg.tar.xz +cd ../../ +``` + +Compile and install juCi++ source: +```sh +cmake -G"MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=/mingw[32 or 64] . +make +make install +``` + + + ## Run ```sh -$ juci +juci +``` + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f892eb..2d43bff 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ if(APPLE) set(CMAKE_MACOSX_RPATH 1) set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig") endif() + INCLUDE(FindPkgConfig) set(validation true) @@ -21,7 +22,7 @@ function(install_help APPLE UNIX WINDOWS) endif(APPLE) endif(UNIX) if(WINDOWS) - message("choco install ${WINDOWS}") + #message("choco install ${WINDOWS}") #Removed this for the time being endif(WINDOWS) endfunction(install_help) @@ -38,10 +39,16 @@ validate(${LCL_FOUND} "clangmm" "clangmm" "clangmm") find_package(LibClang) validate(${LIBCLANG_FOUND} "clang" "libclang-dev" "llvm") -find_package(PythonLibs 2.7) -validate(${PYTHONLIBS_FOUND} "python" "libpython-dev" "python") +#TODO: till clang is fixed on MSYS2 ((lib)clang.dll.a is missing): +if(MSYS) + set(LIBCLANG_LIBRARIES "${CMAKE_INSTALL_PREFIX}/bin/clang.dll") +endif() + +#find_package(PythonLibs 2.7) +#validate(${PYTHONLIBS_FOUND} "python" "libpython-dev" "python") -find_package(Boost 1.55 COMPONENTS python thread log system filesystem REQUIRED) +#find_package(Boost 1.55 COMPONENTS python thread log system filesystem REQUIRED) +find_package(Boost 1.55 COMPONENTS thread log system filesystem REQUIRED) validate(${Boost_FOUND} "boost" "libboost-all-dev" "boost") pkg_check_modules(GTKMM gtkmm-3.0) # The name GTKMM is set here for the variables abouve @@ -50,9 +57,9 @@ validate(${GTKMM_FOUND} "gtkmm" "libgtkmm-dev" "gtkmm") pkg_check_modules(GTKSVMM gtksourceviewmm-3.0) validate(${GTKSVMM_FOUND} "gtksvmm" "libgtksvmm-dev" "gtkmmsv") -if(${validation}) - add_executable(${project_name} - juci.h +find_package(ASPELL REQUIRED) + +set(source_files juci.h juci.cc menu.h menu.cc @@ -66,8 +73,8 @@ if(${validation}) sourcefile.cc window.cc window.h - api.h - api.cc +# api.h +# api.cc notebook.cc notebook.h entrybox.h @@ -75,7 +82,6 @@ if(${validation}) directories.h directories.cc terminal.h - terminal.cc tooltips.h tooltips.cc singletons.h @@ -83,31 +89,42 @@ if(${validation}) cmake.h cmake.cc) - add_library(${module} SHARED - api - api_ext) +if(MSYS) + list(APPEND source_files terminal_win.cc) +else() + list(APPEND source_files terminal.cc) +endif() + +if(${validation}) + add_executable(${project_name} ${source_files}) + +# add_library(${module} SHARED +# api +# api_ext) include_directories( ${Boost_INCLUDE_DIRS} - ${PYTHON_INCLUDE_DIRS} +# ${PYTHON_INCLUDE_DIRS} ${GTKMM_INCLUDE_DIRS} ${GTKSVMM_INCLUDE_DIRS} ${LCL_INCLUDE_DIRS} - ${LIBCLANG_INCLUDE_DIRS}) + ${LIBCLANG_INCLUDE_DIRS} + ${ASPELL_INCLUDE_DIR}) link_directories( ${GTKMM_LIBRARY_DIRS} ${GTKSVMM_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} - ${PYTHON_INCLUDE_DIRS} +# ${PYTHON_INCLUDE_DIRS} ${LCL_LIBRARY_DIRS} ${LIBCLANG_LIBRARY_DIRS}) - set_target_properties(${module} - PROPERTIES PREFIX "" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/") +# set_target_properties(${module} +# PROPERTIES PREFIX "" +# LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/") - target_link_libraries(${module} ${PYTHON_LIBRARIES} ${Boost_LIBRARIES}) +# target_link_libraries(${module} ${PYTHON_LIBRARIES} ${Boost_LIBRARIES}) +# target_link_libraries(${module} ${Boost_LIBRARIES}) target_link_libraries(${project_name} ${LIBCLANG_LIBRARIES} @@ -115,11 +132,14 @@ if(${validation}) ${GTKMM_LIBRARIES} ${GTKSVMM_LIBRARIES} ${Boost_LIBRARIES} - ${PYTHON_LIBRARIES}) + ${ASPELL_LIBRARIES} +# ${PYTHON_LIBRARIES} + ) - install(TARGETS ${project_name} ${module} - RUNTIME DESTINATION ${bin_install_path} - LIBRARY DESTINATION ${lib_install_path} +# install(TARGETS ${project_name} ${module} + install(TARGETS ${project_name} + RUNTIME DESTINATION bin +# LIBRARY DESTINATION ${lib_install_path} ) endif(${validation}) diff --git a/src/cmake.cc b/src/cmake.cc index d2ecfe6..d112a26 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -46,9 +46,25 @@ CMake::CMake(const boost::filesystem::path &path) { bool CMake::create_compile_commands(const boost::filesystem::path &path) { Singleton::terminal()->print("Creating "+path.string()+"/compile_commands.json\n"); - //TODO: Windows... - if(Singleton::terminal()->execute("cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", path)==EXIT_SUCCESS) + if(Singleton::terminal()->execute(Singleton::Config::terminal()->cmake_command+" . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", path)==EXIT_SUCCESS) { +#ifdef _WIN32 //Temporary fix to MSYS2's libclang + auto compile_commands_path=path; + compile_commands_path+="/compile_commands.json"; + auto compile_commands_file=juci::filesystem::read(compile_commands_path); + size_t pos=0; + while((pos=compile_commands_file.find("-I/", pos))!=std::string::npos) { + if(pos+3theme_name=cfg.get("gtk_theme.name"); Singleton::Config::window()->theme_variant=cfg.get("gtk_theme.variant"); - + Singleton::Config::terminal()->make_command=cfg.get("project.make_command"); + Singleton::Config::terminal()->cmake_command=cfg.get("project.cmake_command"); } void MainConfig::find_or_create_config_files() { @@ -45,13 +46,16 @@ void MainConfig::GenerateSource() { auto source_cfg = Singleton::Config::source(); auto source_json = cfg.get_child("source"); - source_cfg->tab_size = source_json.get("tab_size"); - source_cfg->tab_char = source_json.get("tab_char"); - for(unsigned c=0;ctab_size;c++) - source_cfg->tab+=source_cfg->tab_char; - - source_cfg->highlight_current_line = source_json.get_value("highlight_current_line"); - source_cfg->show_line_numbers = source_json.get_value("show_line_numbers"); + source_cfg->spellcheck_language = source_json.get("spellcheck_language"); + + source_cfg->default_tab_char = source_json.get("default_tab_char"); + source_cfg->default_tab_size = source_json.get("default_tab_size"); + source_cfg->auto_tab_char_and_size = source_json.get("auto_tab_char_and_size"); + + source_cfg->wrap_lines = source_json.get("wrap_lines"); + + source_cfg->highlight_current_line = source_json.get("highlight_current_line"); + source_cfg->show_line_numbers = source_json.get("show_line_numbers"); for (auto &i : source_json.get_child("clang_types")) source_cfg->clang_types[i.first] = i.second.get_value(); diff --git a/src/directories.cc b/src/directories.cc index 82d9dba..edf3ea7 100644 --- a/src/directories.cc +++ b/src/directories.cc @@ -3,7 +3,7 @@ #include "logging.h" #include "singletons.h" #include -#include "boost/algorithm/string.hpp" +#include #include //TODO: remove using namespace std; //TODO: remove @@ -12,78 +12,169 @@ namespace sigc { SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE } -Directories::Directories() { +Directories::Directories() : stop_update_thread(false) { DEBUG("adding treeview to scrolledwindow"); add(tree_view); set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); tree_store = Gtk::TreeStore::create(column_record); tree_view.set_model(tree_store); tree_view.append_column("", column_record.name); - tree_store->set_sort_column(0, Gtk::SortType::SORT_ASCENDING); + tree_store->set_sort_column(column_record.id, Gtk::SortType::SORT_ASCENDING); + tree_view.set_enable_search(true); //TODO: why does this not work in OS X? + tree_view.set_search_column(column_record.name); tree_view.signal_row_activated().connect([this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column){ INFO("Directory navigation"); auto iter = tree_store->get_iter(path); if (iter) { - Gtk::TreeModel::Row row = *iter; - std::string upath = Glib::ustring(row[column_record.path]); - boost::filesystem::path fs_path(upath); - if (boost::filesystem::is_directory(fs_path)) { - tree_view.row_expanded(path) ? tree_view.collapse_row(path) : tree_view.expand_row(path, false); - } else { - std::stringstream sstm; - sstm << row[column_record.path]; - if(on_row_activated) - on_row_activated(sstm.str()); + auto path_str=iter->get_value(column_record.path); + if(path_str!="") { + if (boost::filesystem::is_directory(boost::filesystem::path(path_str))) { + tree_view.row_expanded(path) ? tree_view.collapse_row(path) : tree_view.expand_row(path, false); + } else { + if(on_row_activated) + on_row_activated(path_str); + } } } }); + + tree_view.signal_test_expand_row().connect([this](const Gtk::TreeModel::iterator& iter, const Gtk::TreeModel::Path& path){ + if(iter->children().begin()->get_value(column_record.path)=="") { + update_mutex.lock(); + add_path(iter->get_value(column_record.path), *iter); + update_mutex.unlock(); + } + return false; + }); + tree_view.signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator& iter, const Gtk::TreeModel::Path& path){ + update_mutex.lock(); + last_write_times.erase(iter->get_value(column_record.path)); + update_mutex.unlock(); + auto children=iter->children(); + if(children) { + while(children) { + tree_store->erase(children.begin()); + } + auto child=tree_store->append(iter->children()); + child->set_value(column_record.name, std::string("(empty)")); + } + }); + + update_dispatcher.connect([this](){ + update_mutex.lock(); + for(auto &path: update_paths) { + if(last_write_times.count(path)>0) + add_path(path, last_write_times.at(path).first); + } + update_paths.clear(); + update_mutex.unlock(); + }); + + update_thread=std::thread([this](){ + while(!stop_update_thread) { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + update_mutex.lock(); + if(update_paths.size()==0) { + for(auto it=last_write_times.begin();it!=last_write_times.end();) { + try { + if(boost::filesystem::exists(it->first)) { //Added for older boost versions (no exception thrown) + if(it->second.secondfirst)) { + update_paths.emplace_back(it->first); + } + it++; + } + else + it=last_write_times.erase(it); + } + catch(const std::exception &e) { + it=last_write_times.erase(it); + } + } + if(update_paths.size()>0) + update_dispatcher(); + } + update_mutex.unlock(); + } + }); } -void Directories::open_folder(const boost::filesystem::path& dir_path) { - auto new_path=dir_path; - INFO("Open folder"); - if(new_path=="") { - if(current_path=="") - return; - new_path=current_path; - } +Directories::~Directories() { + stop_update_thread=true; + update_thread.join(); +} + +void Directories::open(const boost::filesystem::path& dir_path) { + if(dir_path=="") + return; - std::vector expanded_paths; - if(current_path==new_path) { - tree_view.map_expanded_rows([&expanded_paths](Gtk::TreeView* tree_view, const Gtk::TreeModel::Path& path){ - expanded_paths.emplace_back(path); - }); - } + INFO("Open folder"); tree_store->clear(); - - if(dir_path!="") - cmake=std::unique_ptr(new CMake(new_path)); + update_mutex.lock(); + last_write_times.clear(); + update_paths.clear(); + update_mutex.unlock(); + + cmake=std::unique_ptr(new CMake(dir_path)); auto project=cmake->get_functions_parameters("project"); if(project.size()>0 && project[0].second.size()>0) tree_view.get_column(0)->set_title(project[0].second[0]); else tree_view.get_column(0)->set_title(""); - add_paths(new_path, Gtk::TreeModel::Row(), 0); - - for(auto &path: expanded_paths) - tree_view.expand_row(path, false); + update_mutex.lock(); + add_path(dir_path, Gtk::TreeModel::Row()); + update_mutex.unlock(); - current_path=new_path; + current_path=dir_path; - if(selected_path!="") - select_path(selected_path); DEBUG("Folder opened"); } -void Directories::select_path(const boost::filesystem::path &path) { +void Directories::update() { + update_mutex.lock(); + for(auto &last_write_time: last_write_times) { + add_path(last_write_time.first, last_write_time.second.first); + } + update_mutex.unlock(); +} + +void Directories::select(const boost::filesystem::path &path) { + if(current_path=="") + return; + + if(path.string().substr(0, current_path.string().size())!=current_path.string()) + return; + + std::list paths; + boost::filesystem::path parent_path; + if(boost::filesystem::is_directory(path)) + parent_path=path; + else + parent_path=path.parent_path(); + paths.emplace_front(parent_path); + while(parent_path!=current_path) { + parent_path=parent_path.parent_path(); + paths.emplace_front(parent_path); + } + + for(auto &a_path: paths) { + tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator& iter){ + if(iter->get_value(column_record.path)==a_path.string()) { + update_mutex.lock(); + add_path(a_path, *iter); + update_mutex.unlock(); + return true; + } + return false; + }); + } + tree_store->foreach_iter([this, &path](const Gtk::TreeModel::iterator& iter){ if(iter->get_value(column_record.path)==path.string()) { auto tree_path=Gtk::TreePath(iter); tree_view.expand_to_path(tree_path); tree_view.set_cursor(tree_path); - selected_path=path; return true; } return false; @@ -106,37 +197,58 @@ bool Directories::ignored(std::string path) { return false; } -void Directories::add_paths(const boost::filesystem::path& dir_path, const Gtk::TreeModel::Row &parent, unsigned row_id) { - boost::filesystem::directory_iterator end_itr; - Gtk::TreeModel::Row child; - Gtk::TreeModel::Row row; - DEBUG(""); - // Fill the treeview - for(boost::filesystem::directory_iterator itr(dir_path);itr != end_itr;++itr) { - if (!ignored(itr->path().filename().string())) { - if (boost::filesystem::is_directory(itr->status())) { - if (boost::filesystem::canonical(itr->path()) > boost::filesystem::canonical(dir_path)) { // is child - child = *(tree_store->append(parent.children())); - std::string col_id("a"+itr->path().filename().string()); - child[column_record.id] = col_id; - child[column_record.name] = itr->path().filename().string(); - child[column_record.path] = itr->path().string(); - add_paths(itr->path(), child, row_id); - } else { - row = *(tree_store->append()); - std::string col_id("a"+itr->path().filename().string()); - row[column_record.path] = itr->path().string(); - row[column_record.id] = col_id; - row[column_record.name] = itr->path().filename().string(); - add_paths(itr->path(), parent, row_id); +void Directories::add_path(const boost::filesystem::path& dir_path, const Gtk::TreeModel::Row &parent) { + last_write_times[dir_path.string()]={parent, boost::filesystem::last_write_time(dir_path)}; + std::unique_ptr children; //Gtk::TreeNodeChildren is missing default constructor... + if(parent) + children=std::unique_ptr(new Gtk::TreeNodeChildren(parent.children())); + else + children=std::unique_ptr(new Gtk::TreeNodeChildren(tree_store->children())); + if(*children) { + if(children->begin()->get_value(column_record.path)=="") + tree_store->erase(children->begin()); + } + std::unordered_set not_deleted; + boost::filesystem::directory_iterator end_it; + for(boost::filesystem::directory_iterator it(dir_path);it!=end_it;it++) { + auto filename=it->path().filename().string(); + if (!ignored(filename)) { + bool already_added=false; + if(*children) { + for(auto &child: *children) { + if(child.get_value(column_record.name)==filename) { + not_deleted.emplace(filename); + already_added=true; + break; + } } - } else { // is a file - child = *(tree_store->append(parent.children())); - std::string col_id("b"+itr->path().filename().string()); - child[column_record.id] = col_id; - child[column_record.name] = itr->path().filename().string(); - child[column_record.path] = itr->path().string(); } + if(!already_added) { + auto child = tree_store->append(*children); + not_deleted.emplace(filename); + child->set_value(column_record.name, filename); + child->set_value(column_record.path, it->path().string()); + if (boost::filesystem::is_directory(*it)) { + child->set_value(column_record.id, "a"+filename); + auto grandchild=tree_store->append(child->children()); + grandchild->set_value(column_record.name, std::string("(empty)")); + } + else + child->set_value(column_record.id, "b"+filename); + } + } + } + if(*children) { + for(auto it=children->begin();it!=children->end();) { + if(not_deleted.count(it->get_value(column_record.name))==0) { + it=tree_store->erase(it); + } + else + it++; } } + if(!*children) { + auto child=tree_store->append(*children); + child->set_value(column_record.name, std::string("(empty)")); + } } diff --git a/src/directories.h b/src/directories.h index d8dcb77..c34e319 100644 --- a/src/directories.h +++ b/src/directories.h @@ -6,6 +6,9 @@ #include #include "boost/filesystem.hpp" #include "cmake.h" +#include +#include +#include class Directories : public Gtk::ScrolledWindow { public: @@ -22,26 +25,33 @@ public: add(name); add(path); } - Gtk::TreeModelColumn id; - Gtk::TreeModelColumn name; - Gtk::TreeModelColumn path; + Gtk::TreeModelColumn id; + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn path; }; Directories(); - void open_folder(const boost::filesystem::path& dir_path=""); - void select_path(const boost::filesystem::path &path); + ~Directories(); + void open(const boost::filesystem::path& dir_path=""); + void update(); + void select(const boost::filesystem::path &path); std::function on_row_activated; std::unique_ptr cmake; boost::filesystem::path current_path; private: - void add_paths(const boost::filesystem::path& dir_path, const Gtk::TreeModel::Row &row, unsigned depth); + void add_path(const boost::filesystem::path& dir_path, const Gtk::TreeModel::Row &row); bool ignored(std::string path); Gtk::TreeView tree_view; Glib::RefPtr tree_store; ColumnRecord column_record; - boost::filesystem::path selected_path; + std::unordered_map > last_write_times; + std::mutex update_mutex; + std::thread update_thread; + std::atomic stop_update_thread; + Glib::Dispatcher update_dispatcher; + std::vector update_paths; }; #endif // JUCI_DIRECTORIES_H_ diff --git a/src/files.h b/src/files.h index 4312334..44995f6 100644 --- a/src/files.h +++ b/src/files.h @@ -7,7 +7,17 @@ const std::string configjson = " },\n" " \"source\": {\n" " \"style\": \"juci-light\", //Use \"\" for default style, and for instance juci-dark together with dark gtk_theme variant. Styles from normal gtksourceview install: classic, cobalt, kate, oblivion, solarized-dark, solarized-light, tango\n" -" \"font\": \"Monospace\", //Use \"\" for default font, and for instance \"Monospace 12\" to also set size.\n" +#ifdef __APPLE__ +" \"font\": \"Menlo 11\", " +#else +#ifdef _WIN32 +" \"font\": \"Consolas\", " +#else +" \"font\": \"Monospace\", " +#endif +#endif +"//Use \"\" for default font, and for instance \"Monospace 12\" to also set size.\n" +" \"spellcheck_language\": \"en_US\", //Use \"\" to set language from your locale settings\n" " \"clang_types\": {\n" " \"8\": \"def:function\",\n" " \"21\": \"def:function\",\n" @@ -23,14 +33,17 @@ const std::string configjson = " \"702\": \"def:statement\",\n" " \"705\": \"def:comment\"\n" " },\n" -" \"tab_size\": 2,\n" -" \"tab_char\": \" \", //Use \"\\t\" for regular tab\n" +" \"auto_tab_char_and_size\": true, //Use false to always use default tab char and size\n" +" \"default_tab_char\": \" \", //Use \"\\t\" for regular tab\n" +" \"default_tab_size\": 2,\n" +" \"wrap_lines\": false,\n" " \"highlight_current_line\": true,\n" " \"show_line_numbers\": true\n" " },\n" " \"keybindings\": {\n" " \"new_file\": \"n\",\n" -" \"open_folder\": \"o\",\n" +" \"new_folder\": \"n\",\n" +" \"open_folder\": \"o\",\n" " \"open_file\": \"o\",\n" " \"save\": \"s\",\n" " \"save_as\": \"s\",\n" @@ -55,18 +68,17 @@ const std::string configjson = " \"force_kill_last_running\": \"Escape\"\n" " },\n" " \"project\": {\n" +#ifdef _WIN32 +" \"cmake_command\": \"cmake -G\\\"MSYS Makefiles\\\"\",\n" +#else +" \"cmake_command\": \"cmake\",\n" +#endif " \"make_command\": \"make\"\n" " },\n" " \"directoryfilter\": {\n" " \"ignore\": [\n" -" \"cmake\",\n" -" \"#\",\n" -" \"~\",\n" -" \".idea\",\n" -" \".so\"\n" " ],\n" " \"exceptions\": [\n" -" \"cmakelists.txt\"\n" " ]\n" " }\n" "}\n"; @@ -76,6 +88,7 @@ const std::string menuxml = " \n" " \n" " \n" +" \n" " \n" " \n" " \n" diff --git a/src/juci.cc b/src/juci.cc index 68623ea..6390d3d 100644 --- a/src/juci.cc +++ b/src/juci.cc @@ -45,7 +45,7 @@ void app::on_activate() { bool first_directory=true; for(auto &directory: directories) { if(first_directory) { - window->directories.open_folder(directory); + window->directories.open(directory); first_directory=false; } else { diff --git a/src/notebook.cc b/src/notebook.cc index 3bc8f0e..ad329ab 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -67,7 +67,7 @@ void Notebook::open(const boost::filesystem::path &file_path) { else Singleton::terminal()->print("Error: could not find project path for "+file_path.string()+"\n"); } - source_views.emplace_back(new Source::ClangView(file_path, project_path)); + source_views.emplace_back(new Source::ClangView(file_path, project_path, language)); } else source_views.emplace_back(new Source::GenericView(file_path, language)); @@ -120,7 +120,6 @@ bool Notebook::save(int page) { //TODO: recreate cmake even without directories open? if(view->file_path.filename()=="CMakeLists.txt") { if(directories.cmake && directories.cmake->project_path!="" && view->file_path.string().substr(0, directories.cmake->project_path.string().size())==directories.cmake->project_path.string() && CMake::create_compile_commands(directories.cmake->project_path)) { - directories.open_folder(); for(auto source_view: source_views) { if(auto source_clang_view=dynamic_cast(source_view)) { if(directories.cmake->project_path.string()==source_clang_view->project_path) { diff --git a/src/selectiondialog.cc b/src/selectiondialog.cc index 9965b98..8b73072 100644 --- a/src/selectiondialog.cc +++ b/src/selectiondialog.cc @@ -64,6 +64,7 @@ void SelectionDialogBase::add_row(const std::string& row, const std::string& too } void SelectionDialogBase::show() { + shown=true; move(); window->show_all(); } @@ -72,8 +73,9 @@ void SelectionDialogBase::hide() { window->hide(); if(tooltips) tooltips->hide(); - if(on_hide) + if(on_hide && shown) on_hide(); + shown=false; } void SelectionDialogBase::update_tooltips() { diff --git a/src/selectiondialog.h b/src/selectiondialog.h index abdd59b..e454a3c 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -31,6 +31,8 @@ protected: std::unique_ptr tooltips; std::unordered_map tooltip_texts; std::string last_row; +private: + bool shown=false; }; class SelectionDialog : public SelectionDialogBase { diff --git a/src/source.cc b/src/source.cc index 47f36d6..c4f43d4 100644 --- a/src/source.cc +++ b/src/source.cc @@ -4,12 +4,11 @@ #include #include "logging.h" #include -#include #include "singletons.h" #include #include +#include -#include //TODO: remove using namespace std; //TODO: remove namespace sigc { @@ -37,6 +36,8 @@ Glib::RefPtr Source::guess_language(const boost::filesystem::path ////////////// //// View //// ////////////// +AspellConfig* Source::View::spellcheck_config=NULL; + Source::View::View(const boost::filesystem::path &file_path): file_path(file_path) { set_smart_home_end(Gsv::SMART_HOME_END_BEFORE); get_source_buffer()->begin_not_undoable_action(); @@ -71,10 +72,202 @@ Source::View::View(const boost::filesystem::path &file_path): file_path(file_pat Singleton::terminal()->print("Error: Could not find gtksourceview style: "+Singleton::Config::source()->style+'\n'); } + if(Singleton::Config::source()->wrap_lines) + set_wrap_mode(Gtk::WrapMode::WRAP_CHAR); property_highlight_current_line() = Singleton::Config::source()->highlight_current_line; property_show_line_numbers() = Singleton::Config::source()->show_line_numbers; if(Singleton::Config::source()->font.size()>0) override_font(Pango::FontDescription(Singleton::Config::source()->font)); + + //Create tags for diagnostic warnings and errors: + auto scheme = get_source_buffer()->get_style_scheme(); + auto tag_table=get_buffer()->get_tag_table(); + auto style=scheme->get_style("def:warning"); + auto diagnostic_tag=get_source_buffer()->create_tag("def:warning"); + auto diagnostic_tag_underline=get_source_buffer()->create_tag("def:warning_underline"); + if(style && (style->property_foreground_set() || style->property_background_set())) { + Glib::ustring warning_property; + if(style->property_foreground_set()) { + warning_property=style->property_foreground().get_value(); + diagnostic_tag->property_foreground() = warning_property; + } + else if(style->property_background_set()) + warning_property=style->property_background().get_value(); + + diagnostic_tag_underline->property_underline()=Pango::Underline::UNDERLINE_ERROR; + auto tag_class=G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: + auto param_spec=g_object_class_find_property(tag_class, "underline-rgba"); + if(param_spec!=NULL) { + diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(warning_property)); + } + } + style=scheme->get_style("def:error"); + diagnostic_tag=get_source_buffer()->create_tag("def:error"); + diagnostic_tag_underline=get_source_buffer()->create_tag("def:error_underline"); + if(style && (style->property_foreground_set() || style->property_background_set())) { + Glib::ustring error_property; + if(style->property_foreground_set()) { + error_property=style->property_foreground().get_value(); + diagnostic_tag->property_foreground() = error_property; + } + else if(style->property_background_set()) + error_property=style->property_background().get_value(); + + diagnostic_tag_underline->property_underline()=Pango::Underline::UNDERLINE_ERROR; + auto tag_class=G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: + auto param_spec=g_object_class_find_property(tag_class, "underline-rgba"); + if(param_spec!=NULL) { + diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(error_property)); + } + } + //TODO: clear tag_class and param_spec? + + //Add tooltip foreground and background + style = scheme->get_style("def:note"); + auto note_tag=get_source_buffer()->create_tag("def:note_background"); + if(style->property_background_set()) { + note_tag->property_background()=style->property_background(); + } + note_tag=get_source_buffer()->create_tag("def:note"); + if(style->property_foreground_set()) { + note_tag->property_foreground()=style->property_foreground(); + } + + tab_char=Singleton::Config::source()->default_tab_char; + tab_size=Singleton::Config::source()->default_tab_size; + if(Singleton::Config::source()->auto_tab_char_and_size) { + auto tab_char_and_size=find_tab_char_and_size(); + if(tab_char_and_size.first!=0) { + if(tab_char!=tab_char_and_size.first || tab_size!=tab_char_and_size.second) { + std::string tab_str; + if(tab_char_and_size.first==' ') + tab_str=""; + else + tab_str=""; + Singleton::terminal()->print("Tab char and size for file "+file_path.string()+" set to: "+tab_str+", "+boost::lexical_cast(tab_char_and_size.second)+".\n"); + } + + tab_char=tab_char_and_size.first; + tab_size=tab_char_and_size.second; + } + } + for(unsigned c=0;cspellcheck_language.size()>0) + aspell_config_replace(spellcheck_config, "lang", Singleton::Config::source()->spellcheck_language.c_str()); + } + + spellcheck_possible_err=new_aspell_speller(spellcheck_config); + spellcheck_checker=NULL; + if (aspell_error_number(spellcheck_possible_err) != 0) + std::cerr << "Spell check error: " << aspell_error_message(spellcheck_possible_err) << std::endl; + else { + spellcheck_checker = to_aspell_speller(spellcheck_possible_err); + + auto tag=get_buffer()->create_tag("spellcheck_error"); + tag->property_underline()=Pango::Underline::UNDERLINE_ERROR; + + get_buffer()->signal_changed().connect([this](){ + auto iter=get_buffer()->get_insert()->get_iter(); + if(iter.backward_char()) { + auto context_iter=iter; + if(context_iter.backward_char()) { + if((spellcheck_all && !get_source_buffer()->iter_has_context_class(context_iter, "no-spell-check")) || get_source_buffer()->iter_has_context_class(context_iter, "comment") || get_source_buffer()->iter_has_context_class(context_iter, "string")) { + if(*iter==32 || *iter==45) { //Might have used space or - to split two words + auto first=iter; + auto second=iter; + if(first.backward_char() && second.forward_char()) { + get_buffer()->remove_tag_by_name("spellcheck_error", first, second); + spellcheck(first); + spellcheck(second); + } + } + else + spellcheck(iter); + } + } + } + }); + } + + set_tooltip_events(); +} + +void Source::View::set_tooltip_events() { + signal_motion_notify_event().connect([this](GdkEventMotion* event) { + if(on_motion_last_x!=event->x || on_motion_last_y!=event->y) { + delayed_tooltips_connection.disconnect(); + if(event->state==0) { + gdouble x=event->x; + gdouble y=event->y; + delayed_tooltips_connection=Glib::signal_timeout().connect([this, x, y]() { + Tooltips::init(); + Gdk::Rectangle rectangle(x, y, 1, 1); + if(source_readable) { + type_tooltips.show(rectangle); + diagnostic_tooltips.show(rectangle); + } + spellcheck_tooltips.show(rectangle); + return false; + }, 100); + } + type_tooltips.hide(); + diagnostic_tooltips.hide(); + spellcheck_tooltips.hide(); + } + on_motion_last_x=event->x; + on_motion_last_y=event->y; + return false; + }); + + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark) { + if(get_buffer()->get_has_selection() && mark->get_name()=="selection_bound") + delayed_tooltips_connection.disconnect(); + + if(mark->get_name()=="insert") { + delayed_tooltips_connection.disconnect(); + delayed_tooltips_connection=Glib::signal_timeout().connect([this]() { + Tooltips::init(); + Gdk::Rectangle rectangle; + get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); + int location_window_x, location_window_y; + buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_window_x, location_window_y); + rectangle.set_x(location_window_x-2); + rectangle.set_y(location_window_y); + rectangle.set_width(4); + if(source_readable) { + type_tooltips.show(rectangle); + diagnostic_tooltips.show(rectangle); + } + spellcheck_tooltips.show(rectangle); + return false; + }, 500); + type_tooltips.hide(); + diagnostic_tooltips.hide(); + spellcheck_tooltips.hide(); + } + }); + + signal_scroll_event().connect([this](GdkEventScroll* event) { + delayed_tooltips_connection.disconnect(); + type_tooltips.hide(); + diagnostic_tooltips.hide(); + spellcheck_tooltips.hide(); + return false; + }); + + signal_focus_out_event().connect([this](GdkEventFocus* event) { + delayed_tooltips_connection.disconnect(); + type_tooltips.hide(); + diagnostic_tooltips.hide(); + spellcheck_tooltips.hide(); + return false; + }); } void Source::View::search_occurrences_updated(GtkWidget* widget, GParamSpec* property, gpointer data) { @@ -86,6 +279,8 @@ void Source::View::search_occurrences_updated(GtkWidget* widget, GParamSpec* pro Source::View::~View() { g_clear_object(&search_context); g_clear_object(&search_settings); + + delete_aspell_speller(spellcheck_checker); } void Source::View::search_highlight(const std::string &text, bool case_sensitive, bool regex) { @@ -151,11 +346,10 @@ void Source::View::replace_all(const std::string &replacement) { void Source::View::paste() { Gtk::Clipboard::get()->request_text([this](const Glib::ustring& text){ - const std::regex spaces_regex(std::string("^(")+Singleton::Config::source()->tab_char+"*)(.*)$"); auto line=get_line_before_insert(); std::smatch sm; std::string prefix_tabs; - if(!get_buffer()->get_has_selection() && std::regex_match(line, sm, spaces_regex) && sm[2].str().size()==0) { + if(!get_buffer()->get_has_selection() && std::regex_match(line, sm, tabs_regex) && sm[2].str().size()==0) { prefix_tabs=sm[1].str(); Glib::ustring::size_type start_line=0; @@ -177,7 +371,7 @@ void Source::View::paste() { std::string line=text.substr(start_line, end_line-start_line); size_t tabs=0; for(auto chr: line) { - if(chr==Singleton::Config::source()->tab_char) + if(chr==tab_char) tabs++; else break; @@ -260,21 +454,19 @@ string Source::View::get_line_before_insert() { //Basic indentation bool Source::View::on_key_press_event(GdkEventKey* key) { get_source_buffer()->begin_user_action(); - auto config=Singleton::Config::source(); - const std::regex spaces_regex(std::string("^(")+config->tab_char+"*).*$"); //Indent as in next or previous line if(key->keyval==GDK_KEY_Return && key->state==0 && !get_buffer()->get_has_selection()) { auto insert_it=get_buffer()->get_insert()->get_iter(); int line_nr=insert_it.get_line(); auto line=get_line_before_insert(); std::smatch sm; - if(std::regex_match(line, sm, spaces_regex)) { + if(std::regex_match(line, sm, tabs_regex)) { if((line_nr+1)get_line_count()) { string next_line=get_line(line_nr+1); auto line_end_iter=get_buffer()->get_iter_at_line(line_nr+1); line_end_iter--; std::smatch sm2; - if(insert_it==line_end_iter && std::regex_match(next_line, sm2, spaces_regex)) { + if(insert_it==line_end_iter && std::regex_match(next_line, sm2, tabs_regex)) { if(sm2[1].str().size()>sm[1].str().size()) { get_source_buffer()->insert_at_cursor("\n"+sm2[1].str()); scroll_to(get_source_buffer()->get_insert()); @@ -297,7 +489,7 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { int line_end=selection_end.get_line(); for(int line=line_start;line<=line_end;line++) { Gtk::TextIter line_it = get_source_buffer()->get_iter_at_line(line); - get_source_buffer()->insert(line_it, config->tab); + get_source_buffer()->insert(line_it, tab); } get_source_buffer()->end_user_action(); return true; @@ -309,11 +501,11 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { int line_start=selection_start.get_line(); int line_end=selection_end.get_line(); - unsigned indent_left_steps=config->tab_size; + unsigned indent_left_steps=tab_size; for(int line_nr=line_start;line_nr<=line_end;line_nr++) { string line=get_line(line_nr); std::smatch sm; - if(std::regex_match(line, sm, spaces_regex) && sm[1].str().size()>0) { + if(std::regex_match(line, sm, tabs_regex) && sm[1].str().size()>0) { indent_left_steps=std::min(indent_left_steps, (unsigned)sm[1].str().size()); } else { @@ -339,12 +531,12 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { int line_nr=insert_it.get_line(); auto line=get_line_before_insert(); std::smatch sm; - if(std::regex_match(line, sm, spaces_regex) && sm[1].str().size()==line.size()) { + if(std::regex_match(line, sm, tabs_regex) && sm[1].str().size()==line.size()) { if((line_nr-1)>=0) { string previous_line=get_line(line_nr-1); std::smatch sm2; - if(std::regex_match(previous_line, sm2, spaces_regex)) { - if(line.size()==sm2[1].str().size() || line.size()==sm2[1].str().size()+config->tab_size || line.size()==sm2[1].str().size()-config->tab_size) { + if(std::regex_match(previous_line, sm2, tabs_regex)) { + if(line.size()==sm2[1].str().size() || line.size()==sm2[1].str().size()+tab_size || line.size()==sm2[1].str().size()-tab_size) { auto previous_line_end_it=insert_it; for(unsigned c=0;c=config->tab_size) { + if(line.size()>=tab_size) { auto insert_minus_tab_it=insert_it; - for(unsigned c=0;ctab_size;c++) + for(unsigned c=0;cerase(insert_minus_tab_it, insert_it); get_source_buffer()->end_user_action(); @@ -371,6 +563,134 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { return stop; } +bool Source::View::on_button_press_event(GdkEventButton *event) { + if(event->type==GDK_2BUTTON_PRESS) { + Gtk::TextIter start, end; + if(get_buffer()->get_selection_bounds(start, end)) { + auto iter=start; + while((*iter>=48 && *iter<=57) || (*iter>=65 && *iter<=90) || (*iter>=97 && *iter<=122) || *iter==95) { + start=iter; + if(!iter.backward_char()) + break; + } + while((*end>=48 && *end<=57) || (*end>=65 && *end<=90) || (*end>=97 && *end<=122) || *end==95) { + if(!end.forward_char()) + break; + } + get_buffer()->select_range(start, end); + return true; + } + } + + return Gsv::View::on_button_press_event(event); +} + +std::pair Source::View::find_tab_char_and_size() { + const std::regex indent_regex("^([ \t]+).*$"); + auto size=get_buffer()->get_line_count(); + std::unordered_map tab_chars; + std::unordered_map tab_sizes; + unsigned last_tab_size=0; + for(int c=0;c(str.size()-last_tab_size)); + if(tab_diff>0) { + unsigned tab_diff_unsigned=static_cast(tab_diff); + auto it_size=tab_sizes.find(tab_diff_unsigned); + if(it_size!=tab_sizes.end()) + it_size->second++; + else + tab_sizes[tab_diff_unsigned]=1; + } + + last_tab_size=str.size(); + + if(str.size()>0) { + auto it_char=tab_chars.find(str[0]); + if(it_char!=tab_chars.end()) + it_char->second++; + else + tab_chars[str[0]]=1; + } + } + } + char found_tab_char=0; + size_t occurences=0; + for(auto &tab_char: tab_chars) { + if(tab_char.second>occurences) { + found_tab_char=tab_char.first; + occurences=tab_char.second; + } + } + unsigned found_tab_size=0; + occurences=0; + for(auto &tab_size: tab_sizes) { + if(tab_size.second>occurences) { + found_tab_size=tab_size.first; + occurences=tab_size.second; + } + } + return {found_tab_char, found_tab_size}; +} + +void Source::View::spellcheck(Gtk::TextIter iter) { + auto start=iter; + auto end=iter; + + while((*iter>=48 && *iter<=57) || (*iter>=65 && *iter<=90) || (*iter>=97 && *iter<=122) || *iter==39 || *iter>=128) { + start=iter; + if(!iter.backward_char()) + break; + } + while((*end>=48 && *end<=57) || (*end>=65 && *end<=90) || (*end>=97 && *end<=122) || *iter==39 || *end>=128) { + if(!end.forward_char()) + break; + } + for(auto it=spellcheck_tooltips.begin();it!=spellcheck_tooltips.end();it++) { + if(it->start_mark->get_iter()==start || it->start_mark->get_iter()==it->end_mark->get_iter()) + it=spellcheck_tooltips.erase(it); + } + auto word=get_buffer()->get_text(start, end); + std::vector words; + if(word.size()>0) { + auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); + if(correct==0) { + get_buffer()->apply_tag_by_name("spellcheck_error", start, end); + + const AspellWordList *suggestions = aspell_speller_suggest(spellcheck_checker, word.data(), word.bytes()); + AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); + + auto words=std::make_shared(); + const char *word; + while ((word = aspell_string_enumeration_next(elements))!= NULL) { + if(words->size()==0) { + *words="Suggestions:\n"; + (*words)+=word; + } + else + (*words)+=std::string(", ")+word; + } + delete_aspell_string_enumeration(elements); + + if(words->size()>0) { + auto create_tooltip_buffer=[this, words]() { + auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), *words, "def:note"); + return tooltip_buffer; + }; + spellcheck_tooltips.emplace_back(create_tooltip_buffer, *this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); + } + } + else + get_buffer()->remove_tag_by_name("spellcheck_error", start, end); + } +} + + ///////////////////// //// GenericView //// ///////////////////// @@ -379,6 +699,7 @@ Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib: get_source_buffer()->set_language(language); Singleton::terminal()->print("Language for file "+file_path.string()+" set to "+language->get_name()+".\n"); } + spellcheck_all=true; auto completion=get_completion(); auto completion_words=Gsv::CompletionWords::create("", Glib::RefPtr()); completion_words->register_provider(get_buffer()); @@ -417,59 +738,7 @@ Source::View(file_path), project_path(project_path) { DEBUG("Style " + item.second + " not found in " + scheme->get_name()); } } - INFO("Tagtable filled"); - - //Create tags for diagnostic warnings and errors: - auto style=scheme->get_style("def:warning"); - auto diagnostic_tag=get_source_buffer()->create_tag("def:warning"); - auto diagnostic_tag_underline=get_source_buffer()->create_tag("def:warning_underline"); - if(style && (style->property_foreground_set() || style->property_background_set())) { - Glib::ustring warning_property; - if(style->property_foreground_set()) { - warning_property=style->property_foreground().get_value(); - diagnostic_tag->property_foreground() = warning_property; - } - else if(style->property_background_set()) - warning_property=style->property_background().get_value(); - - diagnostic_tag_underline->property_underline()=Pango::Underline::UNDERLINE_ERROR; - auto tag_class=G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: - auto param_spec=g_object_class_find_property(tag_class, "underline-rgba"); - if(param_spec!=NULL) { - diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(warning_property)); - } - } - style=scheme->get_style("def:error"); - diagnostic_tag=get_source_buffer()->create_tag("def:error"); - diagnostic_tag_underline=get_source_buffer()->create_tag("def:error_underline"); - if(style && (style->property_foreground_set() || style->property_background_set())) { - Glib::ustring error_property; - if(style->property_foreground_set()) { - error_property=style->property_foreground().get_value(); - diagnostic_tag->property_foreground() = error_property; - } - else if(style->property_background_set()) - error_property=style->property_background().get_value(); - - diagnostic_tag_underline->property_underline()=Pango::Underline::UNDERLINE_ERROR; - auto tag_class=G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: - auto param_spec=g_object_class_find_property(tag_class, "underline-rgba"); - if(param_spec!=NULL) { - diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(error_property)); - } - } - //TODO: clear tag_class and param_spec? - - //Add tooltip foreground and background - style = scheme->get_style("def:note"); - auto note_tag=get_source_buffer()->create_tag("def:note_background"); - if(style->property_background_set()) { - note_tag->property_background()=style->property_background(); - } - note_tag=get_source_buffer()->create_tag("def:note"); - if(style->property_foreground_set()) { - note_tag->property_foreground()=style->property_foreground(); - } + INFO("Tagtable filled"); parsing_in_progress=Singleton::terminal()->print_in_progress("Parsing "+file_path.string()); //GTK-calls must happen in main thread, so the parse_thread @@ -489,7 +758,7 @@ Source::View(file_path), project_path(project_path) { update_syntax(); update_diagnostics(); update_types(); - clang_readable=true; + source_readable=true; set_status(""); parsing_mutex.unlock(); INFO("Syntax updated"); @@ -506,16 +775,20 @@ Source::View(file_path), project_path(project_path) { start_reparse(); type_tooltips.hide(); diagnostic_tooltips.hide(); + spellcheck_tooltips.hide(); }); - get_buffer()->signal_mark_set().connect(sigc::mem_fun(*this, &Source::ClangViewParse::on_mark_set), false); + bracket_regex=std::regex(std::string("^(")+tab_char+"*).*\\{ *$"); + no_bracket_statement_regex=std::regex(std::string("^(")+tab_char+"*)(if|for|else if|catch|while) *\\(.*[^;}] *$"); + no_bracket_no_para_statement_regex=std::regex(std::string("^(")+tab_char+"*)(else|try|do) *$"); } void Source::ClangViewParse::init_parse() { type_tooltips.hide(); diagnostic_tooltips.hide(); + spellcheck_tooltips.hide(); get_buffer()->remove_all_tags(get_buffer()->begin(), get_buffer()->end()); - clang_readable=false; + source_readable=false; parse_thread_go=true; parse_thread_mapped=false; parse_thread_stop=false; @@ -579,10 +852,10 @@ std::map Source::ClangViewParse::get_buffer_map() cons void Source::ClangViewParse::start_reparse() { parse_thread_mapped=false; - clang_readable=false; + source_readable=false; delayed_reparse_connection.disconnect(); delayed_reparse_connection=Glib::signal_timeout().connect([this]() { - clang_readable=false; + source_readable=false; parse_thread_go=true; set_status("parsing..."); return false; @@ -608,6 +881,39 @@ std::vector Source::ClangViewParse::get_compilation_commands() { } if(file_path.extension()==".h") //TODO: temporary fix for .h-files (parse as c++) arguments.emplace_back("-xc++"); +#ifdef _WIN32 //Temporary fix to MSYS2's libclang + arguments.emplace_back("-IC:/msys32/mingw32/lib/gcc/i686-w64-mingw32/5.2.0/include"); + arguments.emplace_back("-IC:/msys32/mingw32//include"); + arguments.emplace_back("-IC:/msys32/mingw32/lib/gcc/i686-w64-mingw32/5.2.0/include-fixed"); + arguments.emplace_back("-IC:/msys32/mingw32/i686-w64-mingw32/include"); + arguments.emplace_back("-IC:/msys32/mingw32/include/c++/5.2.0"); + arguments.emplace_back("-IC:/msys32/mingw32/include/c++/5.2.0/i686-w64-mingw32"); + arguments.emplace_back("-IC:/msys32/mingw32/include/c++/5.2.0/backward"); + + arguments.emplace_back("-IC:/msys32/mingw64/lib/gcc/i686-w64-mingw32/5.2.0/include"); + arguments.emplace_back("-IC:/msys32/mingw64//include"); + arguments.emplace_back("-IC:/msys32/mingw64/lib/gcc/i686-w64-mingw32/5.2.0/include-fixed"); + arguments.emplace_back("-IC:/msys32/mingw64/i686-w64-mingw32/include"); + arguments.emplace_back("-IC:/msys32/mingw64/include/c++/5.2.0"); + arguments.emplace_back("-IC:/msys32/mingw64/include/c++/5.2.0/i686-w64-mingw32"); + arguments.emplace_back("-IC:/msys32/mingw64/include/c++/5.2.0/backward"); + + arguments.emplace_back("-IC:/msys64/mingw32/lib/gcc/i686-w64-mingw32/5.2.0/include"); + arguments.emplace_back("-IC:/msys64/mingw32//include"); + arguments.emplace_back("-IC:/msys64/mingw32/lib/gcc/i686-w64-mingw32/5.2.0/include-fixed"); + arguments.emplace_back("-IC:/msys64/mingw32/i686-w64-mingw32/include"); + arguments.emplace_back("-IC:/msys64/mingw32/include/c++/5.2.0"); + arguments.emplace_back("-IC:/msys64/mingw32/include/c++/5.2.0/i686-w64-mingw32"); + arguments.emplace_back("-IC:/msys64/mingw32/include/c++/5.2.0/backward"); + + arguments.emplace_back("-IC:/msys64/mingw64/lib/gcc/i686-w64-mingw32/5.2.0/include"); + arguments.emplace_back("-IC:/msys64/mingw64//include"); + arguments.emplace_back("-IC:/msys64/mingw64/lib/gcc/i686-w64-mingw32/5.2.0/include-fixed"); + arguments.emplace_back("-IC:/msys64/mingw64/i686-w64-mingw32/include"); + arguments.emplace_back("-IC:/msys64/mingw64/include/c++/5.2.0"); + arguments.emplace_back("-IC:/msys64/mingw64/include/c++/5.2.0/i686-w64-mingw32"); + arguments.emplace_back("-IC:/msys64/mingw64/include/c++/5.2.0/backward"); +#endif return arguments; } @@ -658,8 +964,24 @@ void Source::ClangViewParse::update_diagnostics() { auto diagnostics=clang_tu->get_diagnostics(); for(auto &diagnostic: diagnostics) { if(diagnostic.path==file_path.string()) { - auto start=get_buffer()->get_iter_at_line_index(diagnostic.offsets.first.line-1, diagnostic.offsets.first.index-1); - auto end=get_buffer()->get_iter_at_line_index(diagnostic.offsets.second.line-1, diagnostic.offsets.second.index-1); + auto start_line=get_line(diagnostic.offsets.first.line-1); //index is sometimes off the line + auto start_line_index=diagnostic.offsets.first.index-1; + if(start_line_index>=start_line.size()) { + if(start_line.size()==0) + start_line_index=0; + else + start_line_index=start_line.size()-1; + } + auto end_line=get_line(diagnostic.offsets.second.line-1); //index is sometimes off the line + auto end_line_index=diagnostic.offsets.second.index-1; + if(end_line_index>=end_line.size()) { + if(end_line.size()==0) + end_line_index=0; + else + end_line_index=end_line.size()-1; + } + auto start=get_buffer()->get_iter_at_line_index(diagnostic.offsets.first.line-1, start_line_index); + auto end=get_buffer()->get_iter_at_line_index(diagnostic.offsets.second.line-1, end_line_index); std::string diagnostic_tag_name; if(diagnostic.severity<=CXDiagnostic_Warning) diagnostic_tag_name="def:warning"; @@ -702,62 +1024,6 @@ void Source::ClangViewParse::update_types() { } } -bool Source::ClangViewParse::on_motion_notify_event(GdkEventMotion* event) { - delayed_tooltips_connection.disconnect(); - if(clang_readable && event->state==0) { - Gdk::Rectangle rectangle(event->x, event->y, 1, 1); - Tooltips::init(); - type_tooltips.show(rectangle); - diagnostic_tooltips.show(rectangle); - } - else { - type_tooltips.hide(); - diagnostic_tooltips.hide(); - } - - return Source::View::on_motion_notify_event(event); -} - -void Source::ClangViewParse::on_mark_set(const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark) { - if(get_buffer()->get_has_selection() && mark->get_name()=="selection_bound") - delayed_tooltips_connection.disconnect(); - - if(mark->get_name()=="insert") { - delayed_tooltips_connection.disconnect(); - delayed_tooltips_connection=Glib::signal_timeout().connect([this]() { - if(clang_readable) { - Gdk::Rectangle rectangle; - get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); - int location_window_x, location_window_y; - buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_window_x, location_window_y); - rectangle.set_x(location_window_x-2); - rectangle.set_y(location_window_y); - rectangle.set_width(4); - Tooltips::init(); - type_tooltips.show(rectangle); - diagnostic_tooltips.show(rectangle); - } - return false; - }, 500); - type_tooltips.hide(); - diagnostic_tooltips.hide(); - } -} - -bool Source::ClangViewParse::on_focus_out_event(GdkEventFocus* event) { - delayed_tooltips_connection.disconnect(); - type_tooltips.hide(); - diagnostic_tooltips.hide(); - return Source::View::on_focus_out_event(event); -} - -bool Source::ClangViewParse::on_scroll_event(GdkEventScroll* event) { - delayed_tooltips_connection.disconnect(); - type_tooltips.hide(); - diagnostic_tooltips.hide(); - return Source::View::on_scroll_event(event); -} - //Clang indentation //TODO: replace indentation methods with a better implementation or maybe use libclang bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { @@ -765,11 +1031,6 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { return Source::View::on_key_press_event(key); } get_source_buffer()->begin_user_action(); - auto config=Singleton::Config::source(); - const std::regex bracket_regex(std::string("^(")+config->tab_char+"*).*\\{ *$"); - const std::regex no_bracket_statement_regex(std::string("^(")+config->tab_char+"*)(if|for|else if|catch|while) *\\(.*[^;}] *$"); - const std::regex no_bracket_no_para_statement_regex(std::string("^(")+config->tab_char+"*)(else|try|do) *$"); - const std::regex spaces_regex(std::string("^(")+config->tab_char+"*).*$"); //Indent depending on if/else/etc and brackets if(key->keyval==GDK_KEY_Return && key->state==0) { @@ -780,18 +1041,30 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { if((line_nr+1)get_line_count()) { string next_line=get_line(line_nr+1); std::smatch sm2; - if(std::regex_match(next_line, sm2, spaces_regex)) { - if(sm2[1].str()==sm[1].str()+config->tab) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); + if(std::regex_match(next_line, sm2, tabs_regex)) { + if(sm2[1].str()==sm[1].str()+tab) { + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab); scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->end_user_action(); return true; } } - if(next_line!=sm[1].str()+"}") { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab+"\n"+sm[1].str()+"}"); + auto next_char=*get_buffer()->get_insert()->get_iter(); + auto next_line_with_end_bracket=sm[1].str()+"}"; + if(next_char!='}' && next_line.substr(0, next_line_with_end_bracket.size())!=next_line_with_end_bracket) { + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab+"\n"+sm[1].str()+"}"); auto insert_it = get_source_buffer()->get_insert()->get_iter(); - for(size_t c=0;ctab_size+sm[1].str().size();c++) + for(size_t c=0;cget_insert()); + get_source_buffer()->place_cursor(insert_it); + get_source_buffer()->end_user_action(); + return true; + } + else if(next_char=='}') { + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab+"\n"+sm[1].str()); + auto insert_it = get_source_buffer()->get_insert()->get_iter(); + for(size_t c=0;cget_insert()); get_source_buffer()->place_cursor(insert_it); @@ -799,7 +1072,7 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { return true; } else { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab); scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->end_user_action(); return true; @@ -807,21 +1080,21 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { } } else if(std::regex_match(line, sm, no_bracket_statement_regex)) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab); scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->end_user_action(); return true; } else if(std::regex_match(line, sm, no_bracket_no_para_statement_regex)) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab); scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->end_user_action(); return true; } - else if(std::regex_match(line, sm, spaces_regex)) { + else if(std::regex_match(line, sm, tabs_regex)) { std::smatch sm2; size_t line_nr=get_source_buffer()->get_insert()->get_iter().get_line(); - if(line_nr>0 && sm[1].str().size()>=config->tab_size) { + if(line_nr>0 && sm[1].str().size()>=tab_size) { string previous_line=get_line(line_nr-1); if(!std::regex_match(previous_line, sm2, bracket_regex)) { if(std::regex_match(previous_line, sm2, no_bracket_statement_regex)) { @@ -843,23 +1116,25 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { //Indent left when writing } on a new line else if(key->keyval==GDK_KEY_braceright) { string line=get_line_before_insert(); - if(line.size()>=config->tab_size) { + if(line.size()>=tab_size) { for(auto c: line) { - if(c!=config->tab_char) { + if(c!=tab_char) { + get_source_buffer()->insert_at_cursor("}"); get_source_buffer()->end_user_action(); - return Source::View::on_key_press_event(key); + return true; } } Gtk::TextIter insert_it = get_source_buffer()->get_insert()->get_iter(); Gtk::TextIter line_it = get_source_buffer()->get_iter_at_line(insert_it.get_line()); Gtk::TextIter line_plus_it=line_it; - for(unsigned c=0;ctab_size;c++) + for(unsigned c=0;cerase(line_it, line_plus_it); } + get_source_buffer()->insert_at_cursor("}"); get_source_buffer()->end_user_action(); - return Source::View::on_key_press_event(key); + return true; } get_source_buffer()->end_user_action(); @@ -898,6 +1173,14 @@ Source::ClangViewParse(file_path, project_path), autocomplete_cancel_starting(fa return false; }, false); + signal_focus_out_event().connect([this](GdkEventFocus* event) { + autocomplete_cancel_starting=true; + if(completion_dialog_shown) { + completion_dialog->hide(); + } + + return false; + }); } bool Source::ClangViewAutocomplete::on_key_press_event(GdkEventKey *key) { @@ -909,15 +1192,6 @@ bool Source::ClangViewAutocomplete::on_key_press_event(GdkEventKey *key) { return ClangViewParse::on_key_press_event(key); } -bool Source::ClangViewAutocomplete::on_focus_out_event(GdkEventFocus* event) { - autocomplete_cancel_starting=true; - if(completion_dialog_shown) { - completion_dialog->hide(); - } - - return Source::ClangViewParse::on_focus_out_event(event); -} - void Source::ClangViewAutocomplete::start_autocomplete() { if(!has_focus()) return; @@ -1030,16 +1304,17 @@ void Source::ClangViewAutocomplete::autocomplete() { }); std::shared_ptr > buffer_map=std::make_shared >(); - auto& buffer=(*buffer_map)[this->file_path.string()]; - buffer=get_buffer()->get_text(get_buffer()->begin(), get_buffer()->get_insert()->get_iter()); - auto iter = get_source_buffer()->get_insert()->get_iter(); + auto ustr=get_buffer()->get_text(); + auto iter=get_buffer()->get_insert()->get_iter(); auto line_nr=iter.get_line()+1; auto column_nr=iter.get_line_offset()+1; - while((buffer.back()>='a' && buffer.back()<='z') || (buffer.back()>='A' && buffer.back()<='Z') || (buffer.back()>='0' && buffer.back()<='9') || buffer.back()=='_') { - buffer.pop_back(); + auto pos=iter.get_offset()-1; + while(pos>=0 && ((ustr[pos]>='a' && ustr[pos]<='z') || (ustr[pos]>='A' && ustr[pos]<='Z') || (ustr[pos]>='0' && ustr[pos]<='9') || ustr[pos]=='_')) { + ustr.replace(pos, 1, " "); column_nr--; + pos--; } - buffer+="\n"; + (*buffer_map)[this->file_path.string()]=std::move(ustr); //TODO: does this work? set_status("autocomplete..."); if(autocomplete_thread.joinable()) autocomplete_thread.join(); @@ -1102,7 +1377,7 @@ Source::ClangViewAutocomplete(file_path, project_path) { }); get_token=[this]() -> std::string { - if(clang_readable) { + if(source_readable) { auto iter=get_buffer()->get_insert()->get_iter(); auto line=(unsigned)iter.get_line(); auto index=(unsigned)iter.get_line_index(); @@ -1120,7 +1395,7 @@ Source::ClangViewAutocomplete(file_path, project_path) { }; get_token_name=[this]() -> std::string { - if(clang_readable) { + if(source_readable) { auto iter=get_buffer()->get_insert()->get_iter(); auto line=(unsigned)iter.get_line(); auto index=(unsigned)iter.get_line_index(); @@ -1136,7 +1411,7 @@ Source::ClangViewAutocomplete(file_path, project_path) { }; tag_similar_tokens=[this](const std::string &usr){ - if(clang_readable) { + if(source_readable) { if(usr.size()>0 && last_similar_tokens_tagged!=usr) { get_buffer()->remove_tag(similar_tokens_tag, get_buffer()->begin(), get_buffer()->end()); auto offsets=clang_tokens->get_similar_token_offsets(usr); @@ -1154,7 +1429,7 @@ Source::ClangViewAutocomplete(file_path, project_path) { rename_similar_tokens=[this](const std::string &usr, const std::string &text) { size_t number=0; - if(clang_readable) { + if(source_readable) { auto offsets=clang_tokens->get_similar_token_offsets(usr); std::vector, Glib::RefPtr > > marks; for(auto &offset: offsets) { @@ -1177,14 +1452,18 @@ Source::ClangViewAutocomplete(file_path, project_path) { get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark){ if(mark->get_name()=="insert") { - auto usr=get_token(); - tag_similar_tokens(usr); + delayed_tag_similar_tokens_connection.disconnect(); + delayed_tag_similar_tokens_connection=Glib::signal_timeout().connect([this]() { + auto usr=get_token(); + tag_similar_tokens(usr); + return false; + }, 100); } }); get_declaration_location=[this](){ std::pair location; - if(clang_readable) { + if(source_readable) { auto iter=get_buffer()->get_insert()->get_iter(); auto line=(unsigned)iter.get_line(); auto index=(unsigned)iter.get_line_index(); @@ -1205,7 +1484,7 @@ Source::ClangViewAutocomplete(file_path, project_path) { }; goto_method=[this](){ - if(clang_readable) { + if(source_readable) { selection_dialog=std::unique_ptr(new SelectionDialog(*this, get_buffer()->create_mark(get_buffer()->get_insert()->get_iter()))); auto rows=std::make_shared >(); auto methods=clang_tokens->get_cxx_methods(); @@ -1227,7 +1506,12 @@ Source::ClangViewAutocomplete(file_path, project_path) { }; } -Source::ClangView::ClangView(const boost::filesystem::path &file_path, const boost::filesystem::path& project_path): ClangViewRefactor(file_path, project_path) { +Source::ClangView::ClangView(const boost::filesystem::path &file_path, const boost::filesystem::path& project_path, Glib::RefPtr language): ClangViewRefactor(file_path, project_path) { + if(language) { + get_source_buffer()->set_highlight_syntax(false); + get_source_buffer()->set_language(language); + } + do_delete_object.connect([this](){ if(delete_thread.joinable()) delete_thread.join(); diff --git a/src/source.h b/src/source.h index e76fb1e..95fa854 100644 --- a/src/source.h +++ b/src/source.h @@ -14,6 +14,8 @@ #include "tooltips.h" #include "selectiondialog.h" #include +#include +#include namespace Source { Glib::RefPtr guess_language(const boost::filesystem::path &file_path); @@ -22,9 +24,11 @@ namespace Source { public: std::string style; std::string font; - unsigned tab_size; - char tab_char; - std::string tab; + std::string spellcheck_language; + bool auto_tab_char_and_size; + char default_tab_char; + unsigned default_tab_size; + bool wrap_lines; bool highlight_current_line; bool show_line_numbers; std::unordered_map clang_types; @@ -73,16 +77,39 @@ namespace Source { std::function on_update_status; std::string status; protected: + bool source_readable; + Tooltips diagnostic_tooltips; + Tooltips type_tooltips; + Tooltips spellcheck_tooltips; + gdouble on_motion_last_x; + gdouble on_motion_last_y; + sigc::connection delayed_tooltips_connection; + void set_tooltip_events(); + void set_status(const std::string &status); std::string get_line(size_t line_number); std::string get_line_before_insert(); - + bool on_key_press_event(GdkEventKey* key); + bool on_button_press_event(GdkEventButton *event); + + std::pair find_tab_char_and_size(); + unsigned tab_size; + char tab_char; + std::string tab; + std::regex tabs_regex; + + bool spellcheck_all=false; private: GtkSourceSearchContext *search_context; GtkSourceSearchSettings *search_settings; static void search_occurrences_updated(GtkWidget* widget, GParamSpec* property, gpointer data); + + static AspellConfig* spellcheck_config; + AspellCanHaveError *spellcheck_possible_err; + AspellSpeller *spellcheck_checker; + void spellcheck(Gtk::TextIter iter); }; // class View class GenericView : public View { @@ -94,21 +121,22 @@ namespace Source { public: ClangViewParse(const boost::filesystem::path &file_path, const boost::filesystem::path& project_path); boost::filesystem::path project_path; + void start_reparse(); protected: void init_parse(); - void start_reparse(); bool on_key_press_event(GdkEventKey* key); - bool on_focus_out_event(GdkEventFocus* event); std::unique_ptr clang_tu; std::mutex parsing_mutex; std::unique_ptr clang_tokens; - bool clang_readable; sigc::connection delayed_reparse_connection; - sigc::connection delayed_tooltips_connection; std::shared_ptr parsing_in_progress; std::thread parse_thread; std::atomic parse_thread_stop; + + std::regex bracket_regex; + std::regex no_bracket_statement_regex; + std::regex no_bracket_no_para_statement_regex; private: std::map get_buffer_map() const; // inits the syntax highligthing on file open @@ -118,12 +146,7 @@ namespace Source { std::set last_syntax_tags; void update_diagnostics(); void update_types(); - Tooltips diagnostic_tooltips; - Tooltips type_tooltips; - bool on_motion_notify_event(GdkEventMotion* event); - void on_mark_set(const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark); - bool on_scroll_event(GdkEventScroll* event); static clang::Index clang_index; std::vector get_compilation_commands(); @@ -140,7 +163,6 @@ namespace Source { ClangViewAutocomplete(const boost::filesystem::path &file_path, const boost::filesystem::path& project_path); protected: bool on_key_press_event(GdkEventKey* key); - bool on_focus_out_event(GdkEventFocus* event); std::thread autocomplete_thread; private: void start_autocomplete(); @@ -163,13 +185,14 @@ namespace Source { private: Glib::RefPtr similar_tokens_tag; std::string last_similar_tokens_tagged; + sigc::connection delayed_tag_similar_tokens_connection; std::unique_ptr selection_dialog; bool renaming=false; }; class ClangView : public ClangViewRefactor { public: - ClangView(const boost::filesystem::path &file_path, const boost::filesystem::path& project_path); + ClangView(const boost::filesystem::path &file_path, const boost::filesystem::path& project_path, Glib::RefPtr language); void async_delete(); bool restart_parse(); private: diff --git a/src/sourcefile.cc b/src/sourcefile.cc index ae5780b..260a5fb 100644 --- a/src/sourcefile.cc +++ b/src/sourcefile.cc @@ -7,7 +7,7 @@ const size_t buffer_size=131072; //Only use on small files std::string juci::filesystem::read(const std::string &path) { std::stringstream ss; - std::ifstream input(path); + std::ifstream input(path, std::ofstream::binary); if(input) { ss << input.rdbuf(); input.close(); @@ -16,17 +16,22 @@ std::string juci::filesystem::read(const std::string &path) { } bool juci::filesystem::read(const std::string &path, Glib::RefPtr text_buffer) { - std::ifstream input(path); + std::ifstream input(path, std::ofstream::binary); if(input) { std::vector buffer(buffer_size); size_t read_length; + std::string buffer_str; while((read_length=input.read(&buffer[0], buffer_size).gcount())>0) { - auto ustr=Glib::ustring(std::string(&buffer[0], read_length)); - if(ustr.validate()) - text_buffer->insert_at_cursor(ustr); - else { - input.close(); - return false; + buffer_str+=std::string(&buffer[0], read_length); + if(buffer_str.back()>=0) { + auto ustr=Glib::ustring(buffer_str); + buffer_str=""; + if(ustr.validate()) + text_buffer->insert_at_cursor(ustr); + else { + input.close(); + return false; + } } } input.close(); @@ -38,7 +43,7 @@ bool juci::filesystem::read(const std::string &path, Glib::RefPtr juci::filesystem::read_lines(const std::string &path) { std::vector res; - std::ifstream input(path); + std::ifstream input(path, std::ofstream::binary); if (input) { do { res.emplace_back(); } while(getline(input, res.back())); } @@ -48,7 +53,7 @@ std::vector juci::filesystem::read_lines(const std::string &path) { //Only use on small files bool juci::filesystem::write(const std::string &path, const std::string &new_content) { - std::ofstream output(path); + std::ofstream output(path, std::ofstream::binary); if(output) output << new_content; else @@ -58,7 +63,7 @@ bool juci::filesystem::write(const std::string &path, const std::string &new_con } bool juci::filesystem::write(const std::string &path, Glib::RefPtr buffer) { - std::ofstream output(path); + std::ofstream output(path, std::ofstream::binary); if(output) { auto start_iter=buffer->begin(); auto end_iter=start_iter; diff --git a/src/terminal.cc b/src/terminal.cc index d47b9be..aed5906 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -8,57 +8,74 @@ #include //TODO: remove using namespace std; //TODO: remove -//TODO: Windows... //A working implementation of popen3, with all pipes getting closed properly. //TODO: Eidheim is going to publish this one on his github, along with example uses -pid_t popen3(const char *command, int &stdin, int &stdout, int &stderr) { +pid_t popen3(const std::string &command, const std::string &path, int *stdin_fd, int *stdout_fd, int *stderr_fd) { pid_t pid; - int p_stdin[2], p_stdout[2], p_stderr[2]; + int stdin_p[2], stdout_p[2], stderr_p[2]; - if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0 || pipe(p_stderr) != 0) { - close(p_stdin[0]); - close(p_stdout[0]); - close(p_stderr[0]); - close(p_stdin[1]); - close(p_stdout[1]); - close(p_stderr[1]); + if(stdin_fd!=nullptr && pipe(stdin_p)!=0) { + close(stdin_p[0]); + close(stdin_p[1]); return -1; } - + if(stdout_fd!=nullptr && pipe(stdout_p)!=0) { + if(stdin_fd!=nullptr) close(stdin_p[0]); + if(stdin_fd!=nullptr) close(stdin_p[1]); + close(stdout_p[0]); + close(stdout_p[1]); + return -1; + } + if(stderr_fd!=nullptr && pipe(stderr_p)!=0) { + if(stdin_fd!=nullptr) close(stdin_p[0]); + if(stdin_fd!=nullptr) close(stdin_p[1]); + if(stdout_fd!=nullptr) close(stdout_p[0]); + if(stdout_fd!=nullptr) close(stdout_p[1]); + close(stderr_p[0]); + close(stderr_p[1]); + return -1; + } + pid = fork(); if (pid < 0) { - close(p_stdin[0]); - close(p_stdout[0]); - close(p_stderr[0]); - close(p_stdin[1]); - close(p_stdout[1]); - close(p_stderr[1]); + if(stdin_fd!=nullptr) close(stdin_p[0]); + if(stdin_fd!=nullptr) close(stdin_p[1]); + if(stdout_fd!=nullptr) close(stdout_p[0]); + if(stdout_fd!=nullptr) close(stdout_p[1]); + if(stderr_fd!=nullptr) close(stderr_p[0]); + if(stderr_fd!=nullptr) close(stderr_p[1]); return pid; } else if (pid == 0) { - close(p_stdin[1]); - close(p_stdout[0]); - close(p_stderr[0]); - dup2(p_stdin[0], 0); - dup2(p_stdout[1], 1); - dup2(p_stderr[1], 2); + if(stdin_fd!=nullptr) close(stdin_p[1]); + if(stdout_fd!=nullptr) close(stdout_p[0]); + if(stderr_fd!=nullptr) close(stderr_p[0]); + if(stdin_fd!=nullptr) dup2(stdin_p[0], 0); + if(stdout_fd!=nullptr) dup2(stdout_p[1], 1); + if(stderr_fd!=nullptr) dup2(stderr_p[1], 2); setpgid(0, 0); //TODO: See here on how to emulate tty for colors: http://stackoverflow.com/questions/1401002/trick-an-application-into-thinking-its-stdin-is-interactive-not-a-pipe //TODO: One solution is: echo "command;exit"|script -q /dev/null - execl("/bin/sh", "sh", "-c", command, NULL); + std::string cd_path_and_command; + if(path!="") { + cd_path_and_command="cd \""+path+"\" && "+command; + } + else + cd_path_and_command=command; + execl("/bin/sh", "sh", "-c", cd_path_and_command.c_str(), NULL); perror("execl"); exit(EXIT_FAILURE); } - close(p_stdin[0]); - close(p_stdout[1]); - close(p_stderr[1]); + if(stdin_fd!=nullptr) close(stdin_p[0]); + if(stdout_fd!=nullptr) close(stdout_p[1]); + if(stderr_fd!=nullptr) close(stderr_p[1]); - stdin = p_stdin[1]; - stdout = p_stdout[0]; - stderr = p_stderr[0]; + if(stdin_fd!=nullptr) *stdin_fd = stdin_p[1]; + if(stdout_fd!=nullptr) *stdout_fd = stdout_p[0]; + if(stderr_fd!=nullptr) *stderr_fd = stderr_p[0]; return pid; } @@ -137,26 +154,18 @@ Terminal::Terminal() { } int Terminal::execute(const std::string &command, const boost::filesystem::path &path) { - std::string cd_path_and_command; - if(path!="") { - //TODO: Windows... - cd_path_and_command="cd \""+path.string()+"\" && "+command; - } - else - cd_path_and_command=command; - - int stdin, stdout, stderr; - auto pid=popen3(cd_path_and_command.c_str(), stdin, stdout, stderr); + int stdin_fd, stdout_fd, stderr_fd; + auto pid=popen3(command, path.string(), &stdin_fd, &stdout_fd, &stderr_fd); if (pid<=0) { async_print("Error: Failed to run command: " + command + "\n"); return -1; } else { - std::thread stderr_thread([this, stderr](){ + std::thread stderr_thread([this, stderr_fd](){ char buffer[1024]; ssize_t n; - while ((n=read(stderr, buffer, 1024)) > 0) { + while ((n=read(stderr_fd, buffer, 1024)) > 0) { std::string message; for(ssize_t c=0;c 0) { + while ((n=read(stdout_fd, buffer, 1024)) > 0) { std::string message; for(ssize_t c=0;c callback) { std::thread async_execute_thread([this, command, path, callback](){ - std::string cd_path_and_command; - if(path!="") { - - //TODO: Windows... - cd_path_and_command="cd \""+path.string()+"\" && "+command; - } - else - cd_path_and_command=command; - - int stdin, stdout, stderr; + int stdin_fd, stdout_fd, stderr_fd; async_executes_mutex.lock(); stdin_buffer.clear(); - auto pid=popen3(cd_path_and_command.c_str(), stdin, stdout, stderr); - async_executes.emplace_back(pid, stdin); + auto pid=popen3(command, path.string(), &stdin_fd, &stdout_fd, &stderr_fd); + async_executes.emplace_back(pid, stdin_fd); async_executes_mutex.unlock(); if (pid<=0) { @@ -211,27 +211,27 @@ void Terminal::async_execute(const std::string &command, const boost::filesystem callback(-1); } else { - std::thread stderr_thread([this, stderr](){ + std::thread stderr_thread([this, stderr_fd](){ char buffer[1024]; ssize_t n; - while ((n=read(stderr, buffer, 1024)) > 0) { + while ((n=read(stderr_fd, buffer, 1024)) > 0) { std::string message; for(ssize_t c=0;c 0) { + while ((n=read(stdout_fd, buffer, 1024)) > 0) { std::string message; for(ssize_t c=0;c bold_tag; std::mutex async_executes_mutex; +#ifdef _WIN32 + std::list > async_executes; +#else std::list > async_executes; - +#endif std::string stdin_buffer; }; diff --git a/src/terminal_win.cc b/src/terminal_win.cc new file mode 100644 index 0000000..e79014d --- /dev/null +++ b/src/terminal_win.cc @@ -0,0 +1,418 @@ +#include "terminal.h" +#include +#include "logging.h" +#include "singletons.h" +#include +#include + +#include //TODO: remove +using namespace std; //TODO: remove + +#define BUFSIZE 1024 + +HANDLE popen3(const std::string &command, const std::string &path, HANDLE *stdin_h, HANDLE *stdout_h, HANDLE *stderr_h) { + HANDLE g_hChildStd_IN_Rd = NULL; + HANDLE g_hChildStd_IN_Wr = NULL; + HANDLE g_hChildStd_OUT_Rd = NULL; + HANDLE g_hChildStd_OUT_Wr = NULL; + HANDLE g_hChildStd_ERR_Rd = NULL; + HANDLE g_hChildStd_ERR_Wr = NULL; + + SECURITY_ATTRIBUTES saAttr; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) + return NULL; + if(!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) { + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_IN_Wr); + return NULL; + } + if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) { + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_IN_Wr); + return NULL; + } + if(!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_IN_Wr); + CloseHandle(g_hChildStd_OUT_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + return NULL; + } + if (!CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr, 0)) { + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_IN_Wr); + CloseHandle(g_hChildStd_OUT_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + return NULL; + } + if(!SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0)) { + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_IN_Wr); + CloseHandle(g_hChildStd_OUT_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + CloseHandle(g_hChildStd_ERR_Rd); + CloseHandle(g_hChildStd_ERR_Wr); + return NULL; + } + + PROCESS_INFORMATION process_info; + STARTUPINFO siStartInfo; + + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = g_hChildStd_ERR_Wr; + siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; + siStartInfo.hStdInput = g_hChildStd_IN_Rd; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + char* path_ptr; + if(path=="") + path_ptr=NULL; + else { + path_ptr=new char[path.size()+1]; + std::strcpy(path_ptr, path.c_str()); + } + char* command_cstr=new char[command.size()+1]; + std::strcpy(command_cstr, command.c_str()); + BOOL bSuccess = CreateProcess(NULL, + command_cstr, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + path_ptr, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &process_info); // receives PROCESS_INFORMATION + + if(!bSuccess) { + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + CloseHandle(g_hChildStd_ERR_Wr); + return NULL; + } + else { + // Close handles to the child process and its primary thread. + // Some applications might keep these handles to monitor the status + // of the child process, for example. + + CloseHandle(process_info.hThread); + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + CloseHandle(g_hChildStd_ERR_Wr); + } + + *stdin_h=g_hChildStd_IN_Wr; + *stdout_h=g_hChildStd_OUT_Rd; + *stderr_h=g_hChildStd_ERR_Rd; + return process_info.hProcess; +} + +Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) { + waiting_print.connect([this](){ + Singleton::terminal()->async_print(line_nr-1, "."); + }); + start(start_msg); +} + +Terminal::InProgress::~InProgress() { + stop=true; + if(wait_thread.joinable()) + wait_thread.join(); +} + +void Terminal::InProgress::start(const std::string& msg) { + line_nr=Singleton::terminal()->print(msg+"...\n"); + wait_thread=std::thread([this](){ + size_t c=0; + while(!stop) { + if(c%100==0) + waiting_print(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + c++; + } + }); +} + +void Terminal::InProgress::done(const std::string& msg) { + if(!stop) { + stop=true; + Singleton::terminal()->async_print(line_nr-1, msg); + } +} + +void Terminal::InProgress::cancel(const std::string& msg) { + if(!stop) { + stop=true; + Singleton::terminal()->async_print(line_nr-1, msg); + } +} + +Terminal::Terminal() { + bold_tag=get_buffer()->create_tag(); + bold_tag->property_weight()=PANGO_WEIGHT_BOLD; + + signal_size_allocate().connect([this](Gtk::Allocation& allocation){ + auto iter=get_buffer()->end(); + if(iter.backward_char()) { + auto mark=get_buffer()->create_mark(iter); + scroll_to(mark, 0.0, 1.0, 1.0); + get_buffer()->delete_mark(mark); + } + }); + + async_print_dispatcher.connect([this](){ + async_print_strings_mutex.lock(); + if(async_print_strings.size()>0) { + for(auto &string_bold: async_print_strings) + print(string_bold.first, string_bold.second); + async_print_strings.clear(); + } + async_print_strings_mutex.unlock(); + }); + async_print_on_line_dispatcher.connect([this](){ + async_print_on_line_strings_mutex.lock(); + if(async_print_on_line_strings.size()>0) { + for(auto &line_string: async_print_on_line_strings) + print(line_string.first, line_string.second); + async_print_on_line_strings.clear(); + } + async_print_on_line_strings_mutex.unlock(); + }); +} + +//Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx +int Terminal::execute(const std::string &command, const boost::filesystem::path &path) { + HANDLE stdin_h, stdout_h, stderr_h; + + auto process=popen3(command, path.string(), &stdin_h, &stdout_h, &stderr_h); + if(process==NULL) { + async_print("Error: Failed to run command: " + command + "\n"); + return -1; + } + std::thread stderr_thread([this, stderr_h](){ + DWORD n; + CHAR buffer[BUFSIZE]; + for (;;) { + BOOL bSuccess = ReadFile(stderr_h, buffer, BUFSIZE, &n, NULL); + if(!bSuccess || n == 0) + break; + + std::string message; + for(DWORD c=0;c callback) { + std::thread async_execute_thread([this, command, path, callback](){ + HANDLE stdin_h, stdout_h, stderr_h; + + async_executes_mutex.lock(); + stdin_buffer.clear(); + auto process=popen3(command, path.string(), &stdin_h, &stdout_h, &stderr_h); + if(process==NULL) { + async_executes_mutex.unlock(); + async_print("Error: Failed to run command: " + command + "\n"); + if(callback) + callback(-1); + return; + } + async_executes.emplace_back(process, stdin_h); + async_executes_mutex.unlock(); + + std::thread stderr_thread([this, stderr_h](){ + DWORD n; + CHAR buffer[BUFSIZE]; + for (;;) { + BOOL bSuccess = ReadFile(stderr_h, buffer, BUFSIZE, &n, NULL); + if(!bSuccess || n == 0) + break; + + std::string message; + for(DWORD c=0;cfirst==process) { + async_executes.erase(it); + break; + } + } + stdin_buffer.clear(); + CloseHandle(process); + CloseHandle(stdin_h); + CloseHandle(stdout_h); + CloseHandle(stderr_h); + async_executes_mutex.unlock(); + + if(callback) + callback(exit_code); + }); + async_execute_thread.detach(); +} + +void Terminal::kill_last_async_execute(bool force) { + async_executes_mutex.lock(); + if(async_executes.size()>0) { + TerminateProcess(async_executes.back().first, 2); + } + async_executes_mutex.unlock(); +} + +void Terminal::kill_async_executes(bool force) { + async_executes_mutex.lock(); + for(auto &async_execute: async_executes) { + TerminateProcess(async_execute.first, 2); + } + async_executes_mutex.unlock(); +} + +int Terminal::print(const std::string &message, bool bold){ + INFO("Terminal: PrintMessage"); + if(bold) + get_buffer()->insert_with_tag(get_buffer()->end(), message, bold_tag); + else + get_buffer()->insert(get_buffer()->end(), message); + + auto iter=get_buffer()->end(); + if(iter.backward_char()) { + auto mark=get_buffer()->create_mark(iter); + scroll_to(mark, 0.0, 1.0, 1.0); + get_buffer()->delete_mark(mark); + } + + return get_buffer()->end().get_line(); +} + +void Terminal::print(int line_nr, const std::string &message){ + INFO("Terminal: PrintMessage at line " << line_nr); + auto iter=get_buffer()->get_iter_at_line(line_nr); + while(!iter.ends_line()) + iter++; + get_buffer()->insert(iter, message); +} + +std::shared_ptr Terminal::print_in_progress(std::string start_msg) { + std::shared_ptr in_progress=std::shared_ptr(new Terminal::InProgress(start_msg)); + return in_progress; +} + +void Terminal::async_print(const std::string &message, bool bold) { + async_print_strings_mutex.lock(); + bool dispatch=true; + if(async_print_strings.size()>0) + dispatch=false; + async_print_strings.emplace_back(message, bold); + async_print_strings_mutex.unlock(); + if(dispatch) + async_print_dispatcher(); +} + +void Terminal::async_print(int line_nr, const std::string &message) { + async_print_on_line_strings_mutex.lock(); + bool dispatch=true; + if(async_print_on_line_strings.size()>0) + dispatch=false; + async_print_on_line_strings.emplace_back(line_nr, message); + async_print_on_line_strings_mutex.unlock(); + if(dispatch) + async_print_on_line_dispatcher(); +} + +bool Terminal::on_key_press_event(GdkEventKey *event) { + async_executes_mutex.lock(); + if(async_executes.size()>0) { + get_buffer()->place_cursor(get_buffer()->end()); + auto unicode=gdk_keyval_to_unicode(event->keyval); + char chr=(char)unicode; + if(unicode>=32 && unicode<=126) { + stdin_buffer+=chr; + get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); + scroll_to(get_buffer()->get_insert()); + } + else if(event->keyval==GDK_KEY_BackSpace) { + if(stdin_buffer.size()>0 && get_buffer()->get_char_count()>0) { + auto iter=get_buffer()->end(); + iter--; + stdin_buffer.pop_back(); + get_buffer()->erase(iter, get_buffer()->end()); + scroll_to(get_buffer()->get_insert()); + } + } + else if(event->keyval==GDK_KEY_Return) { + stdin_buffer+='\n'; + DWORD written; + WriteFile(async_executes.back().second, stdin_buffer.c_str(), stdin_buffer.size(), &written, NULL); + //TODO: is this line needed? + get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); + scroll_to(get_buffer()->get_insert()); + stdin_buffer.clear(); + } + } + async_executes_mutex.unlock(); + return true; +} diff --git a/src/tooltips.h b/src/tooltips.h index 3d52c27..9330394 100644 --- a/src/tooltips.h +++ b/src/tooltips.h @@ -14,13 +14,13 @@ public: Gdk::Rectangle activation_rectangle; std::unique_ptr window; + Glib::RefPtr start_mark; + Glib::RefPtr end_mark; private: void wrap_lines(Glib::RefPtr text_buffer); std::function()> create_tooltip_buffer; std::unique_ptr tooltip_widget; - Glib::RefPtr start_mark; - Glib::RefPtr end_mark; Gtk::TextView& text_view; int tooltip_width, tooltip_height; }; diff --git a/src/window.cc b/src/window.cc index 11e4722..eb8585f 100644 --- a/src/window.cc +++ b/src/window.cc @@ -3,7 +3,7 @@ #include "singletons.h" #include "sourcefile.h" #include "config.h" -#include "api.h" +//#include "api.h" #include #include //TODO: remove @@ -30,7 +30,7 @@ Window::Window() : box(Gtk::ORIENTATION_VERTICAL), notebook(directories), compil add(box); generate_keybindings(); - PluginApi(&this->notebook, &this->menu); + //PluginApi(&this->notebook, &this->menu); create_menu(); box.pack_start(menu.get_widget(), Gtk::PACK_SHRINK); @@ -91,7 +91,10 @@ Window::Window() : box(Gtk::ORIENTATION_VERTICAL), notebook(directories), compil if(auto menu_item=dynamic_cast(menu.ui_manager->get_widget("/MenuBar/SourceMenu/SourceRename"))) menu_item->set_sensitive((bool)notebook.get_current_view()->rename_similar_tokens); - directories.select_path(notebook.get_current_view()->file_path); + directories.select(notebook.get_current_view()->file_path); + + if(auto source_view=dynamic_cast(notebook.get_current_view())) + source_view->start_reparse(); Singleton::status()->set_text(notebook.get_current_view()->status); } @@ -99,10 +102,6 @@ Window::Window() : box(Gtk::ORIENTATION_VERTICAL), notebook(directories), compil notebook.signal_page_removed().connect([this](Gtk::Widget* page, guint page_num) { entry_box.hide(); }); - - compile_success.connect([this](){ - directories.open_folder(); - }); INFO("Window created"); } // Window constructor @@ -112,20 +111,23 @@ void Window::create_menu() { menu.action_group->add(Gtk::Action::create("FileQuit", "Quit juCi++"), Gtk::AccelKey(menu.key_map["quit"]), [this]() { hide(); }); - menu.action_group->add(Gtk::Action::create("FileNewFile", "New file"), Gtk::AccelKey(menu.key_map["new_file"]), [this]() { - new_file_entry(); + menu.action_group->add(Gtk::Action::create("FileNewFile", "New File"), Gtk::AccelKey(menu.key_map["new_file"]), [this]() { + new_file_dialog(); + }); + menu.action_group->add(Gtk::Action::create("FileNewFolder", "New Folder"), Gtk::AccelKey(menu.key_map["new_folder"]), [this]() { + new_folder_dialog(); }); menu.action_group->add(Gtk::Action::create("FileNewProject", "New Project")); menu.action_group->add(Gtk::Action::create("FileNewProjectCpp", "C++"), [this]() { new_cpp_project_dialog(); }); - menu.action_group->add(Gtk::Action::create("FileOpenFile", "Open file"), Gtk::AccelKey(menu.key_map["open_file"]), [this]() { + menu.action_group->add(Gtk::Action::create("FileOpenFile", "Open File"), Gtk::AccelKey(menu.key_map["open_file"]), [this]() { open_file_dialog(); }); - menu.action_group->add(Gtk::Action::create("FileOpenFolder", "Open folder"), Gtk::AccelKey(menu.key_map["open_folder"]), [this]() { + menu.action_group->add(Gtk::Action::create("FileOpenFolder", "Open Folder"), Gtk::AccelKey(menu.key_map["open_folder"]), [this]() { open_folder_dialog(); }); - menu.action_group->add(Gtk::Action::create("FileSaveAs", "Save as"), Gtk::AccelKey(menu.key_map["save_as"]), [this]() { + menu.action_group->add(Gtk::Action::create("FileSaveAs", "Save As"), Gtk::AccelKey(menu.key_map["save_as"]), [this]() { save_file_dialog(); }); @@ -163,6 +165,7 @@ void Window::create_menu() { auto undo_manager = notebook.get_current_view()->get_source_buffer()->get_undo_manager(); if (undo_manager->can_undo()) { undo_manager->undo(); + notebook.get_current_view()->scroll_to(notebook.get_current_view()->get_buffer()->get_insert()); } } INFO("Done undo"); @@ -173,22 +176,23 @@ void Window::create_menu() { auto undo_manager = notebook.get_current_view()->get_source_buffer()->get_undo_manager(); if(undo_manager->can_redo()) { undo_manager->redo(); + notebook.get_current_view()->scroll_to(notebook.get_current_view()->get_buffer()->get_insert()); } } INFO("Done Redo"); }); - menu.action_group->add(Gtk::Action::create("SourceGotoLine", "Go to line"), Gtk::AccelKey(menu.key_map["source_goto_line"]), [this]() { + menu.action_group->add(Gtk::Action::create("SourceGotoLine", "Go to Line"), Gtk::AccelKey(menu.key_map["source_goto_line"]), [this]() { goto_line_entry(); }); - menu.action_group->add(Gtk::Action::create("SourceCenterCursor", "Center cursor"), Gtk::AccelKey(menu.key_map["source_center_cursor"]), [this]() { + menu.action_group->add(Gtk::Action::create("SourceCenterCursor", "Center Cursor"), Gtk::AccelKey(menu.key_map["source_center_cursor"]), [this]() { if(notebook.get_current_page()!=-1) { while(gtk_events_pending()) gtk_main_iteration(); notebook.get_current_view()->scroll_to(notebook.get_current_view()->get_buffer()->get_insert(), 0.0, 1.0, 0.5); } }); - menu.action_group->add(Gtk::Action::create("SourceGotoDeclaration", "Go to declaration"), Gtk::AccelKey(menu.key_map["source_goto_declaration"]), [this]() { + menu.action_group->add(Gtk::Action::create("SourceGotoDeclaration", "Go to Declaration"), Gtk::AccelKey(menu.key_map["source_goto_declaration"]), [this]() { if(notebook.get_current_page()!=-1) { if(notebook.get_current_view()->get_declaration_location) { auto location=notebook.get_current_view()->get_declaration_location(); @@ -202,7 +206,7 @@ void Window::create_menu() { } } }); - menu.action_group->add(Gtk::Action::create("SourceGotoMethod", "Go to method"), Gtk::AccelKey(menu.key_map["source_goto_method"]), [this]() { + menu.action_group->add(Gtk::Action::create("SourceGotoMethod", "Go to Method"), Gtk::AccelKey(menu.key_map["source_goto_method"]), [this]() { if(notebook.get_current_page()!=-1) { if(notebook.get_current_view()->goto_method) { notebook.get_current_view()->goto_method(); @@ -213,11 +217,10 @@ void Window::create_menu() { rename_token_entry(); }); - menu.action_group->add(Gtk::Action::create("ProjectCompileAndRun", "Compile And Run"), Gtk::AccelKey(menu.key_map["compile_and_run"]), [this]() { + menu.action_group->add(Gtk::Action::create("ProjectCompileAndRun", "Compile and Run"), Gtk::AccelKey(menu.key_map["compile_and_run"]), [this]() { if(notebook.get_current_page()==-1 || compiling) return; CMake cmake(notebook.get_current_view()->file_path); - directories.open_folder(); auto executables = cmake.get_functions_parameters("add_executable"); boost::filesystem::path executable_path; if(executables.size()>0 && executables[0].second.size()>0) { @@ -228,13 +231,10 @@ void Window::create_menu() { if(executable_path!="") { compiling=true; Singleton::terminal()->print("Compiling and running "+executable_path.string()+"\n"); - //TODO: Windows... auto project_path=cmake.project_path; Singleton::terminal()->async_execute(Singleton::Config::terminal()->make_command, cmake.project_path, [this, executable_path, project_path](int exit_code){ compiling=false; if(exit_code==EXIT_SUCCESS) { - compile_success(); - //TODO: Windows... auto executable_path_spaces_fixed=executable_path.string(); char last_char=0; for(size_t c=0;cfile_path); - directories.open_folder(); if(cmake.project_path!="") { compiling=true; Singleton::terminal()->print("Compiling project "+cmake.project_path.string()+"\n"); - //TODO: Windows... Singleton::terminal()->async_execute(Singleton::Config::terminal()->make_command, cmake.project_path, [this](int exit_code){ compiling=false; - if(exit_code==EXIT_SUCCESS) - compile_success(); }); } }); @@ -298,7 +294,7 @@ void Window::create_menu() { Singleton::terminal()->kill_last_async_execute(true); }); - menu.action_group->add(Gtk::Action::create("WindowCloseTab", "Close tab"), Gtk::AccelKey(menu.key_map["close_tab"]), [this]() { + menu.action_group->add(Gtk::Action::create("WindowCloseTab", "Close Tab"), Gtk::AccelKey(menu.key_map["close_tab"]), [this]() { notebook.close_current_page(); }); add_accel_group(menu.ui_manager->get_accel_group()); @@ -355,35 +351,62 @@ void Window::hide() { Gtk::Window::hide(); } -void Window::new_file_entry() { - entry_box.clear(); - entry_box.entries.emplace_back("untitled", [this](const std::string& content){ - std::string filename=content; - if(filename!="") { - if(directories.current_path!="" && !boost::filesystem::path(filename).is_absolute()) - filename=directories.current_path.string()+"/"+filename; - boost::filesystem::path p(filename); - if(boost::filesystem::exists(p)) { - Singleton::terminal()->print("Error: "+p.string()+" already exists.\n"); +void Window::new_file_dialog() { + Gtk::FileChooserDialog dialog("Please create a new file", Gtk::FILE_CHOOSER_ACTION_SAVE); + if(directories.current_path!="") + gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), directories.current_path.string().c_str()); + else + gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); + dialog.set_transient_for(*this); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Save", Gtk::RESPONSE_OK); + + int result = dialog.run(); + if(result==Gtk::RESPONSE_OK) { + boost::filesystem::path path = dialog.get_filename(); + if(path!="") { + if(boost::filesystem::exists(path)) { + Singleton::terminal()->print("Error: "+path.string()+" already exists.\n"); } else { - if(juci::filesystem::write(p)) { + if(juci::filesystem::write(path)) { if(directories.current_path!="") - directories.open_folder(); - notebook.open(boost::filesystem::canonical(p).string()); - Singleton::terminal()->print("New file "+p.string()+" created.\n"); + directories.update(); + notebook.open(path.string()); + Singleton::terminal()->print("New file "+path.string()+" created.\n"); } else - Singleton::terminal()->print("Error: could not create new file "+p.string()+".\n"); + Singleton::terminal()->print("Error: could not create new file "+path.string()+".\n"); } } - entry_box.hide(); - }); - auto entry_it=entry_box.entries.begin(); - entry_box.buttons.emplace_back("Create file", [this, entry_it](){ - entry_it->activate(); - }); - entry_box.show(); + } +} + +void Window::new_folder_dialog() { + auto time_now=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + Gtk::FileChooserDialog dialog("Please create a new folder", Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); + if(directories.current_path!="") + gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), directories.current_path.string().c_str()); + else + gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); + dialog.set_transient_for(*this); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Create", Gtk::RESPONSE_OK); + + int result = dialog.run(); + if(result==Gtk::RESPONSE_OK) { + boost::filesystem::path path=dialog.get_filename(); + if(boost::filesystem::last_write_time(path)>=time_now) { + if(directories.current_path!="") + directories.update(); + Singleton::terminal()->print("New folder "+path.string()+" created.\n"); + } + else + Singleton::terminal()->print("Error: "+path.string()+" already exists.\n"); + directories.select(path); + } } void Window::new_cpp_project_dialog() { @@ -393,9 +416,9 @@ void Window::new_cpp_project_dialog() { else gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); dialog.set_transient_for(*this); - - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("Select", Gtk::RESPONSE_OK); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Create", Gtk::RESPONSE_OK); int result = dialog.run(); if(result==Gtk::RESPONSE_OK) { @@ -420,7 +443,7 @@ void Window::new_cpp_project_dialog() { std::string cmakelists="cmake_minimum_required(VERSION 2.8)\n\nproject("+project_name+")\n\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++1y -Wall\")\n\nadd_executable("+project_name+" main.cpp)\n"; std::string cpp_main="#include \n\nusing namespace std;\n\nint main() {\n cout << \"Hello World!\" << endl;\n\n return 0;\n}\n"; if(juci::filesystem::write(cmakelists_path, cmakelists) && juci::filesystem::write(cpp_main_path, cpp_main)) { - directories.open_folder(project_path); + directories.open(project_path); notebook.open(cpp_main_path); Singleton::terminal()->print("C++ project "+project_name+" created.\n"); } @@ -436,15 +459,15 @@ void Window::open_folder_dialog() { else gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); dialog.set_transient_for(*this); - //Add response buttons the the dialog: - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("Select", Gtk::RESPONSE_OK); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Open", Gtk::RESPONSE_OK); int result = dialog.run(); if(result==Gtk::RESPONSE_OK) { std::string project_path=dialog.get_filename(); - directories.open_folder(project_path); + directories.open(project_path); } } @@ -456,28 +479,8 @@ void Window::open_file_dialog() { gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); dialog.set_transient_for(*this); dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); - - //Add response buttons the the dialog: - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("_Open", Gtk::RESPONSE_OK); - - //Add filters, so that only certain file types can be selected: - Glib::RefPtr filter_text = Gtk::FileFilter::create(); - filter_text->set_name("Text files"); - filter_text->add_mime_type("text/plain"); - dialog.add_filter(filter_text); - - Glib::RefPtr filter_cpp = Gtk::FileFilter::create(); - filter_cpp->set_name("C/C++ files"); - filter_cpp->add_mime_type("text/x-c"); - filter_cpp->add_mime_type("text/x-c++"); - filter_cpp->add_mime_type("text/x-c-header"); - dialog.add_filter(filter_cpp); - - Glib::RefPtr filter_any = Gtk::FileFilter::create(); - filter_any->set_name("Any files"); - filter_any->add_pattern("*"); - dialog.add_filter(filter_any); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Open", Gtk::RESPONSE_OK); int result = dialog.run(); @@ -492,10 +495,10 @@ void Window::save_file_dialog() { return; INFO("Save file dialog"); Gtk::FileChooserDialog dialog(*this, "Please choose a file", Gtk::FILE_CHOOSER_ACTION_SAVE); - gtk_file_chooser_set_filename((GtkFileChooser*)dialog.gobj(), notebook.get_current_view()->file_path.c_str()); + gtk_file_chooser_set_filename((GtkFileChooser*)dialog.gobj(), notebook.get_current_view()->file_path.string().c_str()); dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("_Save", Gtk::RESPONSE_OK); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Save", Gtk::RESPONSE_OK); int result = dialog.run(); if(result==Gtk::RESPONSE_OK) { @@ -506,7 +509,7 @@ void Window::save_file_dialog() { file << notebook.get_current_view()->get_buffer()->get_text(); file.close(); if(directories.current_path!="") - directories.open_folder(); + directories.update(); notebook.open(path); Singleton::terminal()->print("File saved to: " + notebook.get_current_view()->file_path.string()+"\n"); } diff --git a/src/window.h b/src/window.h index dbb08eb..b18ae0f 100644 --- a/src/window.h +++ b/src/window.h @@ -36,11 +36,11 @@ private: EntryBox entry_box; Menu menu; std::atomic compiling; - Glib::Dispatcher compile_success; void create_menu(); void hide(); - void new_file_entry(); + void new_file_dialog(); + void new_folder_dialog(); void new_cpp_project_dialog(); void open_folder_dialog(); void open_file_dialog();