Add context help for PG functions.

Добавлен вызов контекстной помощи по именам функций Postgresql.
Для этого требуется:
1. в параметрах указать путь к html файлам документации.
2. выполнить скрипт _extract_func_help.pl для генерации файла _func.txt
3. разместить файл _func.txt в каталоге с документацией.

Для вызова помощи нужно выделить слово или выражение и нажать Ctrl+F1.
Можно просто нажать Ctrl+F1 и тогда для слова слева от курсора будет выведена справка.
Если слову соответсвует несколько функций они будут выведены в виде списка имен.

Для навигации в окне контекстной помощи:
 - закрыть окно или переместиться назад - правая кнопка мыши
 - выделить текст + правая кнопка - копировать выделение в буфер и закрыть окно.
 - нажатие ESC - закрытие окна.

Для отображения помощи используется  wxHtmlWindow https://docs.wxwidgets.org/latest/overview_html.html.
This commit is contained in:
lsv 2024-05-06 19:31:52 +05:00
parent 9f3cfacf96
commit 2d3f87edaa
9 changed files with 678 additions and 2 deletions

View file

@ -0,0 +1,139 @@
#ifndef FUNCTIONPGHELPER_H
#define FUNCTIONPGHELPER_H
#include <utils/sysSettings.h>
#include <wx/regex.h>
#include <map>
#include <vector>
#include <wx/stdpaths.h>
#include <wx/textfile.h>
#include <wx/filename.h>
extern sysSettings *settings;
class FunctionPGHelper
{
public:
FunctionPGHelper() {};
wxString getHelpString(wxString fnd, bool isPart = true) {
if (!isValid()) return wxEmptyString;
auto search = body.find(fnd);
wxString txt;
if (search != body.end())
txt = search->second;
else
{
std::vector<wxString> list;
int l = fnd.Len();
wxString b;
for (const auto& e : body) {
if (e.first.Len() > l && e.first.StartsWith(fnd)) {
list.push_back(e.first);
b = e.second;
}
}
if (list.size() == 1) txt = b;
else {
for (const auto& s : list) {
txt += wxString::Format("<a href=\"%s\">%s</a><br>", s, s);
}
}
}
//if (i == wxNOT_FOUND) return wxEmptyString;
return txt;
}
wxString getSqlCommandHelp(wxString fnd) {
wxUniChar sep = wxFileName::GetPathSeparator();
fnd.Replace(" ", "");
wxString f = wxFindFirstFile(path + sep+"sql-"+fnd+"*.html");
wxString last,txt;
int c = 0;
while (!f.empty())
{
f = f.AfterLast(sep);
last = f;
txt += wxString::Format("<a href=\"%s\">%s</a><br>", f, f);
f = wxFindNextFile();
c++;
}
if (last.empty()) {
return wxEmptyString;
}
else if (c==1) {
return getHelpFile(last);
}
else {
return txt;
}
}
wxString getHelpFile(wxString filename) {
wxString tempDir = path + wxFileName::GetPathSeparator() + filename;
if (!wxFileExists(tempDir)) return wxEmptyString;
wxTextFile tfile;
tfile.Open(tempDir);
// read the first line
wxString str, sbody;
sbody = tfile.GetFirstLine();
bool flag = true;
wxRegEx b("(<body .*?>)");
while (!tfile.Eof())
{
str = tfile.GetNextLine();
if (flag && b.Matches(str)) {
size_t start, len;
b.GetMatch(&start, &len, 0);
str = str.Mid(start + len);
sbody = "<body>";
flag = false;
}
sbody += str;
}
return sbody;
}
bool isValid() {
if (!isload) loadfile();
return isload;
}
private:
bool isload = false;
wxString path;
std::map<wxString, wxString> body;
void loadfile() {
if (isload) return;
body.clear();
path = settings->GetPgHelpPath();
wxString tempDir = path + "_func.txt";
//tempDir="C:\\Users\\lsv\\Source\\Repos\\wxHtmlhint\\1";
if (!wxFileExists(tempDir)) return;
wxTextFile tfile;
tfile.Open(tempDir);
// read the first line
wxString str, sbody;
wxString name = tfile.GetFirstLine();
//wxSortedArrayString names;
name = name.AfterFirst('#');
// read all lines one by one
// until the end of the file
while (!tfile.Eof())
{
str = tfile.GetNextLine();
if (str.Left(1) == '#') {
body.emplace(name, sbody);
sbody = "";
name = str.AfterFirst('#');
}
else sbody += str;
}
body.emplace(name, sbody);
isload = true;
};
};
#endif

128
include/utils/popuphelp.h Normal file
View file

@ -0,0 +1,128 @@
#ifndef POPUPHELP_H
#define POPUPHELP_H
#include "wx/popupwin.h"
#include <wx/html/htmlwin.h>
#include "wx/clipbrd.h"
#include "utils/FunctionPGHelper.h"
#include <wx/regex.h>
#include <map>
#include <vector>
class popuphelp :
public wxPopupTransientWindow
{
public:
//popuphelp(wxWindow* parent);
bool ProcessLeftDown(wxMouseEvent& event)
{
return false;
}
bool IsValid() {
return isvalid;
}
popuphelp(wxWindow* parent,wxString keyword, FunctionPGHelper *hhelper) : wxPopupTransientWindow(parent) {
SetSize(450,370);
this->hhelper = hhelper;
SetBackgroundColour(*wxBLACK);
htmlWindow = new wxHtmlWindow(this, -1, wxDefaultPosition,GetSize());
htmlWindow->SetRelatedStatusBar(0);
//htmlWindow->SetPage("<html><body><h1>TEST</h1><span fgcolor=\"#332233\">Set Page Works</span></body></hmtl>");
wxString txt = hhelper->getHelpString(keyword);
if (txt.IsEmpty()) {
txt = hhelper->getSqlCommandHelp(keyword);
if (txt.empty()) {
isvalid = false;
return;
}
}
SetPage(txt);
//wxSize sz= htmlWindow->GetSize();
//sz = htmlWindow->GetBestSize();
//htmlWindow->SetHTMLBackgroundImage(wxBitmapBundle::FromSVGFile("data/bg.svg", wxSize(65, 45)));
wxBoxSizer* topsizer;
topsizer = new wxBoxSizer(wxVERTICAL);
//htmlWindow->SetInitialSize(wxSize(htmlWindow->GetInternalRepresentation()->GetWidth(), htmlWindow->GetInternalRepresentation()->GetHeight()));
//SetSize(wxSize(300,150));
topsizer->Add(htmlWindow, 1, wxALL, 1);
//wxButton* bu1 = new wxButton(this, wxID_OK, _("OK"));
//bu1->SetDefault();
//topsizer->Add(bu1, 0, wxALL | wxALIGN_RIGHT, 15);
SetSizer(topsizer);
topsizer->Fit(this);
//this->Bind(wxEVT_HTML_CELL_CLICKED, [&](wxHtmlCellEvent& event) {
// wxHtmlCell* c = event.GetCell();
//
// wxString ctext=c->ConvertToText(NULL);
// ctext=htmlWindow->SelectionToText();
// wxString s = wxString::Format("cell = %s",ctext.c_str());
// wxMessageBox(s, "cell", wxOK | wxICON_INFORMATION);
// });
this->Bind(wxEVT_HTML_LINK_CLICKED, [&](wxHtmlLinkEvent& event) {
wxHtmlLinkInfo i = event.GetLinkInfo();
wxString name = i.GetHref();
wxString body=this->hhelper->getHelpString(name);
if (body.IsEmpty()) {
body = this->hhelper->getHelpFile(name);
}
SetPage(body);
//ctext=htmlWindow->SelectionToText();
//wxString s = wxString::Format("cell = %s",ctext.c_str());
});
htmlWindow->Bind(wxEVT_RIGHT_UP, [&](wxMouseEvent& event) {
wxString name;
//wxString body = this->hhelper->getHelpString(name);
wxString ctext = htmlWindow->SelectionToText();
if (!ctext.IsEmpty()) {
wxClipboardLocker clip;
if (!clip ||
!wxTheClipboard->AddData(new wxTextDataObject(ctext)))
{
}
Hide();
return;
}
this->SetPage("", true);
//ctext=htmlWindow->SelectionToText();
//wxString s = wxString::Format("cell = %s",ctext.c_str());
});
}
private:
bool isvalid = true;
wxHtmlWindow* htmlWindow;
FunctionPGHelper* hhelper;
std::vector<wxString> hist;
void SetPage(wxString innerbody,bool gethistory=false) {
wxString h;
int p = innerbody.Find("<body>");
if (p > -1) {
innerbody.Replace("<body>", "<html><body TEXT=\"#000000\" BGCOLOR=\"#FFFFA0\" LINK=\"#0000FF\" VLINK=\"#FF0000\" ALINK=\"#000088\">", false);
h = "" + innerbody + "";
} else
h = "<html><body TEXT=\"#000000\" BGCOLOR=\"#FFFFA0\" LINK=\"#0000FF\" VLINK=\"#FF0000\" ALINK=\"#000088\">" + innerbody + "</body></hmtl>";
if (gethistory) {
if (hist.size() < 2) {
Hide();
return;
}
hist.pop_back();
h = hist[hist.size()-1];
}
else {
hist.push_back(h);
}
htmlWindow->SetPage(h);
}
};
#endif