mirror of
https://github.com/obsqrbtz/clrsync.git
synced 2026-04-09 04:29:04 +03:00
287 lines
9.3 KiB
C++
287 lines
9.3 KiB
C++
#include "autocomplete.hpp"
|
|
#include "colors.hpp"
|
|
#include "imgui.h"
|
|
#include <algorithm>
|
|
|
|
namespace clrsync::gui::widgets
|
|
{
|
|
|
|
autocomplete_widget::autocomplete_widget()
|
|
: m_selected_index(0), m_show_autocomplete(false), m_dismissed(false), m_dismiss_brace_pos(-1)
|
|
{
|
|
m_bg_color = ImVec4(0.12f, 0.12f, 0.15f, 0.98f);
|
|
m_border_color = ImVec4(0.4f, 0.4f, 0.45f, 1.0f);
|
|
m_selected_color = ImVec4(0.25f, 0.45f, 0.75f, 0.9f);
|
|
m_text_color = ImVec4(0.85f, 0.85f, 0.9f, 1.0f);
|
|
m_selected_text_color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
|
m_dim_text_color = ImVec4(0.6f, 0.6f, 0.7f, 1.0f);
|
|
}
|
|
|
|
void autocomplete_widget::update_suggestions(const TextEditor& editor,
|
|
const std::vector<std::string>& available_keys,
|
|
const std::vector<std::string>& available_formats)
|
|
{
|
|
m_suggestions.clear();
|
|
|
|
auto cursor = editor.GetCursorPosition();
|
|
std::string line = editor.GetCurrentLineText();
|
|
int col = cursor.mColumn;
|
|
|
|
// Check if inside '{'
|
|
int brace_pos = -1;
|
|
for (int i = col - 1; i >= 0; --i)
|
|
{
|
|
if (i < (int)line.length())
|
|
{
|
|
if (line[i] == '{')
|
|
{
|
|
brace_pos = i;
|
|
break;
|
|
}
|
|
else if (line[i] == '}' || line[i] == ' ' || line[i] == '\t')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (brace_pos < 0)
|
|
{
|
|
m_show_autocomplete = false;
|
|
m_dismissed = false;
|
|
return;
|
|
}
|
|
|
|
if (m_dismissed)
|
|
{
|
|
bool should_reset_dismissal = false;
|
|
|
|
if (cursor.mLine != m_dismiss_position.mLine || brace_pos != m_dismiss_brace_pos ||
|
|
abs(cursor.mColumn - m_dismiss_position.mColumn) > 3)
|
|
{
|
|
should_reset_dismissal = true;
|
|
}
|
|
|
|
if (should_reset_dismissal)
|
|
{
|
|
m_dismissed = false;
|
|
}
|
|
else
|
|
{
|
|
m_show_autocomplete = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_prefix = line.substr(brace_pos + 1, col - brace_pos - 1);
|
|
m_start_pos = TextEditor::Coordinates(cursor.mLine, brace_pos + 1);
|
|
|
|
size_t dot_pos = m_prefix.find('.');
|
|
|
|
if (dot_pos != std::string::npos)
|
|
{
|
|
std::string color_key = m_prefix.substr(0, dot_pos);
|
|
std::string format_prefix = m_prefix.substr(dot_pos + 1);
|
|
|
|
bool valid_key = std::find(available_keys.begin(), available_keys.end(), color_key) != available_keys.end();
|
|
|
|
if (valid_key)
|
|
{
|
|
for (const auto &fmt : available_formats)
|
|
{
|
|
if (format_prefix.empty() || fmt.find(format_prefix) == 0 ||
|
|
fmt.find(format_prefix) != std::string::npos)
|
|
{
|
|
m_suggestions.push_back(color_key + "." + fmt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const auto& key : available_keys)
|
|
{
|
|
if (m_prefix.empty() || key.find(m_prefix) == 0 ||
|
|
key.find(m_prefix) != std::string::npos)
|
|
{
|
|
m_suggestions.push_back(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::sort(m_suggestions.begin(), m_suggestions.end(),
|
|
[this](const std::string &a, const std::string &b) {
|
|
bool a_prefix = a.find(m_prefix) == 0;
|
|
bool b_prefix = b.find(m_prefix) == 0;
|
|
if (a_prefix != b_prefix)
|
|
return a_prefix;
|
|
return a < b;
|
|
});
|
|
|
|
m_show_autocomplete = !m_suggestions.empty();
|
|
if (m_show_autocomplete && m_selected_index >= (int)m_suggestions.size())
|
|
{
|
|
m_selected_index = 0;
|
|
}
|
|
}
|
|
|
|
void autocomplete_widget::render(const ImVec2& editor_pos, TextEditor& editor)
|
|
{
|
|
if (!m_show_autocomplete || m_suggestions.empty())
|
|
return;
|
|
|
|
float line_height = ImGui::GetTextLineHeightWithSpacing();
|
|
float char_width = ImGui::GetFontSize() * 0.5f;
|
|
auto cursor = editor.GetCursorPosition();
|
|
|
|
const float line_number_width = 50.0f;
|
|
|
|
ImVec2 popup_pos;
|
|
popup_pos.x = editor_pos.x + line_number_width + (m_start_pos.mColumn * char_width);
|
|
popup_pos.y = editor_pos.y + ((cursor.mLine + 1) * line_height);
|
|
|
|
ImGui::SetNextWindowPos(popup_pos, ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(300, 0));
|
|
|
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
|
|
ImGuiWindowFlags_NoFocusOnAppearing |
|
|
ImGuiWindowFlags_AlwaysAutoResize;
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 2));
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, m_bg_color);
|
|
ImGui::PushStyleColor(ImGuiCol_Border, m_border_color);
|
|
|
|
if (ImGui::Begin("##autocomplete", nullptr, flags))
|
|
{
|
|
ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color);
|
|
if (m_prefix.find('.') != std::string::npos)
|
|
ImGui::Text("Formats");
|
|
else
|
|
ImGui::Text("Color Keys");
|
|
ImGui::PopStyleColor();
|
|
ImGui::Separator();
|
|
|
|
int max_items = std::min((int)m_suggestions.size(), 8);
|
|
|
|
for (int i = 0; i < max_items; ++i)
|
|
{
|
|
const auto &suggestion = m_suggestions[i];
|
|
bool is_selected = (i == m_selected_index);
|
|
|
|
if (is_selected)
|
|
{
|
|
ImVec4 selected_hover = m_selected_color;
|
|
selected_hover.w = std::min(selected_hover.w + 0.1f, 1.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_Header, m_selected_color);
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, selected_hover);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, m_selected_text_color);
|
|
}
|
|
else
|
|
{
|
|
ImVec4 normal_bg = m_bg_color;
|
|
normal_bg.w = 0.5f;
|
|
ImVec4 hover_bg = m_selected_color;
|
|
hover_bg.w = 0.3f;
|
|
ImGui::PushStyleColor(ImGuiCol_Header, normal_bg);
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hover_bg);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, m_text_color);
|
|
}
|
|
|
|
std::string display_text = " " + suggestion;
|
|
|
|
if (ImGui::Selectable(display_text.c_str(), is_selected, ImGuiSelectableFlags_None, ImVec2(0, 0)))
|
|
{
|
|
auto start = m_start_pos;
|
|
auto end = editor.GetCursorPosition();
|
|
editor.SetSelection(start, end);
|
|
editor.Delete();
|
|
editor.InsertText(suggestion + "}");
|
|
m_show_autocomplete = false;
|
|
m_dismissed = false;
|
|
}
|
|
|
|
ImGui::PopStyleColor(3);
|
|
|
|
if (is_selected && ImGui::IsWindowAppearing())
|
|
{
|
|
ImGui::SetScrollHereY();
|
|
}
|
|
}
|
|
|
|
if (m_suggestions.size() > 8)
|
|
{
|
|
ImGui::Separator();
|
|
ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color);
|
|
ImGui::Text(" +%d more", (int)m_suggestions.size() - 8);
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
ImGui::PushStyleColor(ImGuiCol_Text, m_dim_text_color);
|
|
ImGui::Text(" Tab/Enter: accept | Esc: dismiss");
|
|
ImGui::PopStyleColor();
|
|
}
|
|
ImGui::End();
|
|
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar(3);
|
|
}
|
|
|
|
void autocomplete_widget::apply_palette(const core::palette& palette)
|
|
{
|
|
m_bg_color = palette_color(palette, "surface", "background");
|
|
m_bg_color.w = 0.98f;
|
|
m_border_color = palette_color(palette, "border", "surface_variant");
|
|
m_selected_color = palette_color(palette, "accent", "surface_variant");
|
|
m_text_color = palette_color(palette, "on_surface", "foreground");
|
|
m_selected_text_color = palette_color(palette, "on_surface", "foreground");
|
|
m_dim_text_color = palette_color(palette, "on_surface_variant", "editor_inactive");
|
|
}
|
|
|
|
bool autocomplete_widget::handle_input(TextEditor& editor)
|
|
{
|
|
if (!m_show_autocomplete || m_suggestions.empty())
|
|
return false;
|
|
|
|
bool handled = false;
|
|
|
|
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
|
|
{
|
|
m_selected_index = (m_selected_index + 1) % (int)m_suggestions.size();
|
|
handled = true;
|
|
}
|
|
else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
|
|
{
|
|
m_selected_index = (m_selected_index - 1 + (int)m_suggestions.size()) % (int)m_suggestions.size();
|
|
handled = true;
|
|
}
|
|
else if (ImGui::IsKeyPressed(ImGuiKey_Tab) || ImGui::IsKeyPressed(ImGuiKey_Enter))
|
|
{
|
|
if (m_selected_index >= 0 && m_selected_index < (int)m_suggestions.size())
|
|
{
|
|
auto start = m_start_pos;
|
|
auto end = editor.GetCursorPosition();
|
|
editor.SetSelection(start, end);
|
|
editor.Delete();
|
|
editor.InsertText(m_suggestions[m_selected_index] + "}");
|
|
m_show_autocomplete = false;
|
|
m_dismissed = false;
|
|
}
|
|
handled = true;
|
|
}
|
|
else if (ImGui::IsKeyPressed(ImGuiKey_Escape))
|
|
{
|
|
m_dismissed = true;
|
|
m_dismiss_position = editor.GetCursorPosition();
|
|
m_dismiss_brace_pos = -1;
|
|
m_show_autocomplete = false;
|
|
handled = true;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
} // namespace clrsync::gui::widgets
|