xaizek / zograscope (License: AGPLv3 only) (since 2018-12-07)
Mainly a syntax-aware diff that also provides a number of additional tools.
<root> / tools / tui / tui.cpp (99dd62874afcc8f904380728eb29d76d60c14591) (5,058B) (mode 100644) [raw]
// Copyright (C) 2019 xaizek <xaizek@posteo.net>
//
// This file is part of zograscope.
//
// zograscope is free software: you can redistribute it and/or modify
// it under the terms of version 3 of the GNU Affero General Public License as
// published by the Free Software Foundation.
//
// zograscope is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with zograscope.  If not, see <http://www.gnu.org/licenses/>.

#include <iostream>
#include <sstream>
#include <vector>

#include "cursed/Init.hpp"
#include "cursed/Input.hpp"
#include "cursed/Label.hpp"
#include "cursed/Placeholder.hpp"
#include "cursed/Screen.hpp"
#include "cursed/Track.hpp"
#include "cursed/utils.hpp"

#include "vle/KeyDispatcher.hpp"
#include "vle/Mode.hpp"
#include "vle/Modes.hpp"

#include "tooling/Traverser.hpp"
#include "tooling/common.hpp"
#include "Highlighter.hpp"
#include "tree.hpp"

#include "FileRegistry.hpp"
#include "ViewManager.hpp"

static int run(const CommonArgs &args, Environment &env);

const char *const usage =
R"(Usage: zs-tui [options...] [paths...]

Paths can specify both files and directories.  When no path is specified, "." is
assumed.
)";

const int InputBufferWidth = 10;

int
main(int argc, char *argv[])
{
    int result;

    try {
        Environment env;
        env.setup({ argv + 1, argv + argc });

        CommonArgs args = env.getCommonArgs();
        if (args.help) {
            std::cout << usage
                      << "\n"
                      << "Options:\n";
            env.printOptions();
            return EXIT_SUCCESS;
        }
        result = run(args, env);

        env.teardown();
    } catch (const std::exception &e) {
        std::cerr << "ERROR: " << e.what() << '\n';
        result = EXIT_FAILURE;
    }

    return result;
}

// Runs the tool.  Returns exit code of the application.
static int
run(const CommonArgs &args, Environment &env)
{
    std::vector<std::string> paths = args.pos;
    if (paths.empty()) {
        paths.emplace_back(".");
    }

    Config &config = env.getConfig();
    FileRegistry registry(env);

    if (!Traverser(paths, args.lang, config,
                   [&](const std::string &path) {
                       return registry.addFile(path);
                   }).search()) {
        std::cerr << "No matching files were discovered.\n";
        return EXIT_FAILURE;
    }

    cursed::Init init;
    cursed::Input input(cursed::Keypad::Enabled);

    cursed::Format barBg;
    barBg.setBackground(cursed::Color::White);
    barBg.setForeground(60);
    barBg.setBold(true);
    barBg.setReversed(true);

    cursed::Format helpBg;
    helpBg.setBackground(239);

    cursed::Label title;
    title.setBackground(barBg);
    cursed::Label helpLine;
    helpLine.setBackground(helpBg);

    cursed::Label statusLine;
    statusLine.setBackground(barBg);
    cursed::Label inputBuf;
    inputBuf.setBackground(barBg);
    inputBuf.setFixedSize(InputBufferWidth, 1);

    cursed::Track bottomTrack(cursed::Orientation::Horizontal);
    bottomTrack.addItem(&statusLine);
    bottomTrack.addItem(&inputBuf);

    cursed::Placeholder viewPlaceholder;

    cursed::Track track;
    track.addItem(&title);
    track.addItem(&viewPlaceholder);
    track.addItem(&helpLine);
    track.addItem(&bottomTrack);

    ViewContext viewContext = {
        .registry = registry,
        .quit = false,
        .viewChanged = false,
        .lang = nullptr,
        .node = nullptr,
    };
    ViewManager viewManager(viewContext, viewPlaceholder);
    viewManager.push("files");

    auto viewChanged = [&]() {
        title.setText(L"[" + cursed::toWide(viewManager.getViewName()) + L"]");
        helpLine.setText(viewManager.getViewHelpLine());
        statusLine.setText(viewManager.getViewStatusLine());
        viewContext.viewChanged = false;
    };

    viewChanged();

    cursed::Screen screen;
    screen.replaceTopWidget(&track);
    screen.draw();

    vle::KeyDispatcher dispatcher;
    while (cursed::InputElement ie = input.read()) {
        if (ie.isTerminalResize()) {
            screen.resize();
            screen.draw();
            continue;
        }

        if (!ie.isFunctionalKey()) {
            dispatcher.dispatch(ie);

            if (viewContext.quit) {
                break;
            }

            if (viewContext.viewChanged) {
                viewChanged();
                screen.resize();
            }
        }

        statusLine.setText(viewManager.getViewStatusLine());

        std::wstring pendingInput = dispatcher.getPendingInput();
        int excess = pendingInput.size() - InputBufferWidth;
        if (excess > 0) {
            pendingInput.erase(0, excess);
        }
        inputBuf.setText(pendingInput);

        screen.draw();
    }
    return EXIT_SUCCESS;
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/zograscope

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/zograscope

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master