パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
コード作成支援クラスの追加

KTxtEditの高機能化にあたりコード作成支援クラスを追加する。

コード作成支援クラス(TMyCodingAssistance)はScintillaコンポーネントにコード作成支援機能を実装する。インスタンスはコア実装クラス(TMyCoreImpl)に所有され、ファイル入出力クラス(TMyFileOpenSave)が設定するファイル名の拡張子に従い機能を選択する。

プログラミング手順

  1. コード作成支援クラス(TMyCodingAssistance)
  2. ファイル入出力クラス(TMyFileOpenSave)の修正
  3. コア実装クラス(TMyCoreImpl)の修正
  4. リポジトリ更新とビルドテスト

コード作成支援クラス(TMyCodingAssistance)

プロジェクトディレクトリにTMyCodingAssistance.hとTMyCodingAssistance.cppを作成して全ターゲットに加える。

  • コード作成支援とは、キーワード強調、自動インデント、括弧強調である。これらは折り返し表示設定と合わせてpimpl_の所有するTMyProgrammingLanguageBase派生(TMyProgrammingLanguageクラステンプレート特殊化)が行う。
  • SetProgrammingLanguageメンバ関数にファイル名を与えれば拡張子に従いTMyProgrammingLanguage特殊化を切り替える。各TMyProgrammingLanguage特殊化は対象言語に合わせたコード作成支援を行う。
    拡張子 対象言語 折り返し表示 コード作成支援
    c, cc, cp, cxx, cpp, c++, h, hh, hp, hxx, hpp, h++, tcc C++ オフ オン
    py, pyw Python オフ オン
    その他 - オン オフ
  • 折り返し表示設定とキーワード強調はTMyProgrammingLanguageクラステンプレート特殊化がコンストラクタからコールするInitメンバ関数で行う。
  • 自動インデントと括弧強調は状況検知を必要とする。pimpl_はScintillaイベントを捉えてTMyProgrammingLanguage特殊化のOnScintillaNotifyメンバ関数をコールして自動インデントと括弧強調を行う。C++とPythonは同じコードで冗長だが、将来の個別カスタマイズに備える。
メンバ関数 機能 返値 説明 仮引数 説明
TMyCodingAssistance コンストラクタ - - TMyScintilla* TMyScintillaコントロールへのポインタ
~TMyCodingAssistance デストラクタ - - - -
SetProgrammingLanguage コード作成支援の対象言語設定 - - const wxString& ファイル名
SetStyles フォント変更後のスタイル再設定 - - - -
OnScintillaNotify Scintillaイベントハンドラ - - SCNotification* SCNotification構造体へのポインタ

TMyCodingAssistance.h

#ifndef TMYCODINGASSISTANCE_H
#define TMYCODINGASSISTANCE_H
class TMyCodingAssistance
{
private:
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
public:
explicit TMyCodingAssistance(class TMyScintilla* ctrl);
~TMyCodingAssistance();
void SetProgrammingLanguage(const wxString& fileName=wxString{});
void SetStyles();
void OnScintillaNotify(class SCNotification* notification);
};
#endif // TMYCODINGASSISTANCE_H

TMyCodingAssistance.cpp

#include "wx_pch.h"
#include "TMyCodingAssistance.h"
#include "TMyScintilla.h"
#include <Scintilla.h>
#include <ILexer.h>
#include <Lexilla.h>
#include <SciLexer.h>
namespace
{
struct TMyProgrammingLanguageBase
{
enum class ID{NONE,CPP,PYTHON};
template<size_t sz> static bool FindId(const char* const (&extList)[sz],const char* ext)
{
return std::find_if(extList,extList+sz
,[ext](const char* aExt) {return std::strcmp(aExt,ext)==0;})
!=extList+sz;
}
static ID GetId(const wxString& fileName)
{
static const char* const cppExt[]={"c","cc","cp","cxx","cpp","c++","h","hh","hp","hxx","hpp","h++","tcc"};
static const char* const pythonExt[]={"py","pyw"};
auto ext=wxFileName{fileName}.GetExt().Lower();
if (FindId(cppExt,ext.mb_str())) {return ID::CPP;}
if (FindId(pythonExt,ext.mb_str())) {return ID::PYTHON;}
return ID::NONE;
}
static TMyProgrammingLanguageBase* Create(TMyScintilla* ctrl,const wxString& fileName);
virtual ~TMyProgrammingLanguageBase() {}
virtual void SetStyles()=0;
virtual void OnScintillaNotify(SCNotification* notification)=0;
};
template<TMyProgrammingLanguageBase::ID>
class TMyProgrammingLanguage:public TMyProgrammingLanguageBase
{
private:
TMyScintilla* const ctrl_;
void Init();
public:
TMyProgrammingLanguage(TMyScintilla* ctrl):ctrl_{ctrl} {Init();}
void SetStyles() override;
void OnScintillaNotify(SCNotification* notification) override;
};
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::NONE>::SetStyles();
// Without the above declaration the error saying "specialization of 'void ...::SetStyles() [with ...]' after instantiation"
// would take place on the line defining ...::SetStyles() below.
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::NONE>::Init()
{
ctrl_->SendMsg(SCI_SETILEXER,0,0);
ctrl_->SendMsg(SCI_CLEARDOCUMENTSTYLE,0,0);
ctrl_->SendMsg(SCI_SETWRAPMODE,SC_WRAP_WORD,0);
// If file is huge with wrap on, tons of idle events may cause trouble.
// https://sourceforge.net/p/scintilla/bugs/1299/
ctrl_->SendMsg(SCI_SETKEYWORDS,0,(LPARAM)"");
SetStyles();
}
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::NONE>::SetStyles()
{
ctrl_->SendMsg(SCI_STYLECLEARALL,0,0);
}
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::NONE>::OnScintillaNotify
(SCNotification* notification)
{
}
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::CPP>::SetStyles();
// Without the above declaration the error saying "specialization of 'void ...::SetStyles() [with ...]' after instantiation"
// would take place on the line defining ...::SetStyles() below.
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::CPP>::Init()
{
ctrl_->SendMsg(SCI_SETILEXER,0,(LPARAM)CreateLexer("cpp"));
ctrl_->SendMsg(SCI_CLEARDOCUMENTSTYLE,0,0);
ctrl_->SendMsg(SCI_SETWRAPMODE,SC_WRAP_NONE,0);
ctrl_->SendMsg(SCI_SETKEYWORDS,0,(LPARAM)
"alignas alignof asm auto bool break case catch char char8_t "
"char16_t char32_t class concept const consteval constexpr constinit "
"const_cast continue co_await co_return co_yield decltype default "
"delete do double dynamic_cast else enum explicit export extern false "
"float for friend goto if inline int long mutable namespace new "
"noexcept nullptr operator private protected public register "
"reinterpret_cast requires return short signed sizeof static "
"static_assert static_cast struct switch template this thread_local "
"throw true try typedef typeid typename union unsigned using virtual "
"void volatile wchar_t while");
SetStyles();
}
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::CPP>::SetStyles()
{
ctrl_->SendMsg(SCI_STYLECLEARALL,0,0);
ctrl_->SendMsg(SCI_STYLESETFORE,SCE_C_COMMENT,0x000080);
ctrl_->SendMsg(SCI_STYLESETFORE,SCE_C_COMMENTLINE,0x000080);
ctrl_->SendMsg(SCI_STYLESETFORE,SCE_C_COMMENTDOC,0x000080);
ctrl_->SendMsg(SCI_STYLESETFORE,SCE_C_COMMENTLINEDOC,0x000080);
ctrl_->SendMsg(SCI_STYLESETFORE,SCE_C_PREPROCESSOR,0x008000);
ctrl_->SendMsg(SCI_STYLESETBOLD,SCE_C_WORD,true);
ctrl_->SendMsg(SCI_STYLESETBOLD,STYLE_BRACELIGHT,true);
ctrl_->SendMsg(SCI_STYLESETBOLD,STYLE_BRACEBAD,true);
ctrl_->SendMsg(SCI_STYLESETBACK,STYLE_BRACEBAD,0x8080FF);
}
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::CPP>::OnScintillaNotify
(SCNotification* notification)
{
switch(notification->nmhdr.code)
{
case SCN_CHARADDED:
if (notification->ch=='\n')
{
auto curLine=ctrl_->SendMsg(SCI_LINEFROMPOSITION,ctrl_->SendMsg(SCI_GETCURRENTPOS,0,0),0);
auto buf=std::vector<char>(ctrl_->SendMsg(SCI_LINELENGTH,curLine-1,0));
ctrl_->SendMsg(SCI_GETLINE,curLine-1,(LPARAM)buf.data());
for (auto p=buf.begin();p!=buf.end();++p) {if (*p!=' '&&*p!='\t') {*p=0;break;}}
ctrl_->SendMsg(SCI_REPLACESEL,0,(LPARAM)buf.data());
}
break;
case SCN_UPDATEUI:
{
auto PosIsBraced=[this](auto p)
{
static const char braces[]="()[]{}";
static const char* const bracesEnd=braces+std::extent<decltype(braces)>::value-1;
return std::find(braces,bracesEnd,ctrl_->SendMsg(SCI_GETCHARAT,p,0))!=bracesEnd;
};
auto p1=ctrl_->SendMsg(SCI_GETCURRENTPOS,0,0)-1;
if (PosIsBraced(p1)||PosIsBraced(++p1))
{
auto p2=ctrl_->SendMsg(SCI_BRACEMATCH,p1,0);
if (p2!=-1) {ctrl_->SendMsg(SCI_BRACEHIGHLIGHT,p1,p2);}
else {ctrl_->SendMsg(SCI_BRACEBADLIGHT,p1,0);}
}
else
{
ctrl_->SendMsg(SCI_BRACEHIGHLIGHT,-1,-1);
}
}
break;
}
}
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::PYTHON>::SetStyles();
// Without the above declaration the error saying "specialization of 'void ...::SetStyles() [with ...]' after instantiation"
// would take place on the line defining ...::SetStyles() below.
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::PYTHON>::Init()
{
ctrl_->SendMsg(SCI_SETILEXER,0,(LPARAM)CreateLexer("python"));
ctrl_->SendMsg(SCI_CLEARDOCUMENTSTYLE,0,0);
ctrl_->SendMsg(SCI_SETWRAPMODE,SC_WRAP_NONE,0);
ctrl_->SendMsg(SCI_SETKEYWORDS,0,(LPARAM)
"False await else import pass None break except in raise True class "
"finally is return and continue for lambda try as def from nonlocal "
"while assert del global not with async elif if or yield");
SetStyles();
}
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::PYTHON>::SetStyles()
{
ctrl_->SendMsg(SCI_STYLECLEARALL,0,0);
ctrl_->SendMsg(SCI_STYLESETFORE,SCE_P_COMMENTLINE,0x000080);
ctrl_->SendMsg(SCI_STYLESETFORE,SCE_P_COMMENTBLOCK,0x000080);
ctrl_->SendMsg(SCI_STYLESETBOLD,SCE_P_WORD,true);
ctrl_->SendMsg(SCI_STYLESETBOLD,STYLE_BRACELIGHT,true);
ctrl_->SendMsg(SCI_STYLESETBOLD,STYLE_BRACEBAD,true);
ctrl_->SendMsg(SCI_STYLESETBACK,STYLE_BRACEBAD,0x8080FF);
}
template<> void TMyProgrammingLanguage<TMyProgrammingLanguageBase::ID::PYTHON>::OnScintillaNotify
(SCNotification* notification)
{
switch(notification->nmhdr.code)
{
case SCN_CHARADDED:
if (notification->ch=='\n')
{
auto curLine=ctrl_->SendMsg(SCI_LINEFROMPOSITION,ctrl_->SendMsg(SCI_GETCURRENTPOS,0,0),0);
auto buf=std::vector<char>(ctrl_->SendMsg(SCI_LINELENGTH,curLine-1,0));
ctrl_->SendMsg(SCI_GETLINE,curLine-1,(LPARAM)buf.data());
for (auto p=buf.begin();p!=buf.end();++p) {if (*p!=' '&&*p!='\t') {*p=0;break;}}
ctrl_->SendMsg(SCI_REPLACESEL,0,(LPARAM)buf.data());
}
break;
case SCN_UPDATEUI:
{
auto PosIsBraced=[this](auto p)
{
static const char braces[]="()[]{}";
static const char* const bracesEnd=braces+std::extent<decltype(braces)>::value-1;
return std::find(braces,bracesEnd,ctrl_->SendMsg(SCI_GETCHARAT,p,0))!=bracesEnd;
};
auto p1=ctrl_->SendMsg(SCI_GETCURRENTPOS,0,0)-1;
if (PosIsBraced(p1)||PosIsBraced(++p1))
{
auto p2=ctrl_->SendMsg(SCI_BRACEMATCH,p1,0);
if (p2!=-1) {ctrl_->SendMsg(SCI_BRACEHIGHLIGHT,p1,p2);}
else {ctrl_->SendMsg(SCI_BRACEBADLIGHT,p1,0);}
}
else
{
ctrl_->SendMsg(SCI_BRACEHIGHLIGHT,-1,-1);
}
}
break;
}
}
TMyProgrammingLanguageBase* TMyProgrammingLanguageBase::Create(TMyScintilla* ctrl,const wxString& fileName)
{
switch (GetId(fileName))
{
case ID::CPP:
return new TMyProgrammingLanguage<ID::CPP>{ctrl};
case ID::PYTHON:
return new TMyProgrammingLanguage<ID::PYTHON>{ctrl};
default:
return new TMyProgrammingLanguage<ID::NONE>{ctrl};
}
}
}
class TMyCodingAssistance::Impl
{
private:
TMyScintilla* const ctrl_;
std::unique_ptr<TMyProgrammingLanguageBase> programmingLanguage_;
public:
Impl(TMyScintilla* ctrl):ctrl_{ctrl}
,programmingLanguage_{TMyProgrammingLanguageBase::Create(ctrl_,wxString{})}
{}
void SetProgrammingLanguage(const wxString& fileName)
{
// Make TMyProgrammingLanguageBase descendants do nothing important in their destructor,
// because new one's constructor is called before old one's destructor. If it matters,
// please do reset(nullptr) beforehand.
programmingLanguage_.reset(TMyProgrammingLanguageBase::Create(ctrl_,fileName));
}
void SetStyles() {programmingLanguage_->SetStyles();}
void OnScintillaNotify(SCNotification* notification)
{programmingLanguage_->OnScintillaNotify(notification);}
};
TMyCodingAssistance::TMyCodingAssistance(TMyScintilla* ctrl):pimpl_{new Impl{ctrl}} {}
TMyCodingAssistance::~TMyCodingAssistance() {}
void TMyCodingAssistance::SetProgrammingLanguage(const wxString& fileName)
{pimpl_->SetProgrammingLanguage(fileName);}
void TMyCodingAssistance::SetStyles() {pimpl_->SetStyles();}
void TMyCodingAssistance::OnScintillaNotify(SCNotification* notification)
{pimpl_->OnScintillaNotify(notification);}

ファイル入出力クラス(TMyFileOpenSave)の修正

ファイル名変更で支援対象言語を変更させるためのwxEVT_MY_FILE_CHANGEイベントを追加する。

  • wxEVT_MY_FILE_CHANGEを定義する。
  • TMyFileOpenSave::ImplクラスにSetCurrentメンバ関数を追加してファイル名変更時にwxEVT_MY_FILE_CHANGEイベントを発生してwxCommandEventを送付する。
  • wxCommandEventインスタンスにはSetClientStringメンバ関数で変更されたファイル名フルパスを格納する。
  • ファイル名を保持するcurrent_メンバ変数への代入を全てSetCurrentコールに変更する。

TMyFileOpenSave.h(修正)

...
wxDECLARE_EVENT(wxEVT_MY_FILE_CHANGE,wxCommandEvent);
class TMyFileOpenSave final
{
...
};

TMyFileOpenSave.cpp(修正)

...
#include <fstream>
#include <list>
#include <set>
#include <algorithm>
wxDEFINE_EVENT(wxEVT_MY_FILE_CHANGE,wxCommandEvent);
...
class TMyFileOpenSave::Impl:public TMyOptionDialog::Observer
{
...
private:
void SetCurrent(const TMyFile& file)
{
current_=file;
auto evt=wxCommandEvent{wxEVT_MY_FILE_CHANGE,textCtrl_->GetId()};
evt.SetEventObject(textCtrl_);
evt.SetString(current_.path_);
wxPostEvent(textCtrl_,evt);
}
void Remember(const wxString& path,const TMyFileOption& option)
{
...
SetCurrent(recencies_.front());
Notify();
}
...
public:
...
void UnnameCurrent() {SetCurrent(GetUnnamed());}
...
};
...

コア実装クラス(TMyCoreImpl)の修正

コード作成支援機能を追加する。Schintillaイベントとファイル名変更イベントを捉えて適切な処理を行う。

  • "TMyCodingAssistance.h"と"MyUtility.h"をインクルードに追加する。
  • TMyCodingAssistanceインスタンス(codingAssistance_)をメンバに加える。イベント捕捉のためTMyEventHandlerManager(evtHandlerManager_)も加える。
  • wxEVT_MY_SCHINTILLA_NOTIFYとwxEVT_MY_FILE_CHANGEイベントハンドラを追加する。前者はcodingAssistance_へScintillaイベントを記述するSCNotificationへのポインタを送付し、後者はcodingAssistance_の支援対象言語を変更する。
  • Updateメンバ関数でフォント変更後のスタイル設定を行うためにcodingAssistance_.SetStyles()を追加する。

TMyCoreImpl.cpp(修正)

...
#include "version_macro.h"
#include "TMyOptionDialog.h"
#include "TMyFileOpenSave.h"
#include "TMyScintilla.h"
#include <Scintilla.h>
#include "TMyCodingAssistance.h"
#include "MyUtility.h"
class TMyCoreImpl::Impl:public TMyOptionDialog::Observer
{
private:
TMyScintilla* const textCtrl_;
TMyFileOpenSave fileOpenSave_;
TMyCodingAssistance codingAssistance_;
TMyEventHandlerManager evtHandlerManager_;
bool ConfirmIfModified() const
{
return !textCtrl_->SendMsg(SCI_GETMODIFY,0,0)
||wxMessageBox(_("The current file is modified. Do you want to discard?")
,MYAPPINFO_NAME,wxICON_QUESTION|wxYES_NO|wxNO_DEFAULT|wxCENTER)==wxYES;
}
void OnScintillaNotify(wxCommandEvent& evt)
{
if (auto* notification=static_cast<SCNotification*>(evt.GetClientData()))
{codingAssistance_.OnScintillaNotify(notification);}
}
void OnFileChange(wxCommandEvent& evt)
{
codingAssistance_.SetProgrammingLanguage(evt.GetString());
}
public:
Impl(wxWindow* parent)
:textCtrl_{new TMyScintilla{parent,wxID_ANY}}
,fileOpenSave_{textCtrl_}
,codingAssistance_{textCtrl_}
,evtHandlerManager_{textCtrl_}
{
Update(TMyOptionDialog::GetInstance());
evtHandlerManager_.Add(wxEVT_MY_SCHINTILLA_NOTIFY,&Impl::OnScintillaNotify,this);
evtHandlerManager_.Add(wxEVT_MY_FILE_CHANGE,&Impl::OnFileChange,this);
}
...
void Update(const TMyOptionDialog& subject) override
{
const auto& font=subject.GetTextCtrlFont();
textCtrl_->SendMsg(SCI_STYLESETFORE,STYLE_DEFAULT,subject.GetTextCtrlForegroundColor().GetRGB());
textCtrl_->SendMsg(SCI_STYLESETBACK,STYLE_DEFAULT,subject.GetTextCtrlBackgroundColor().GetRGB());
textCtrl_->SendMsg(SCI_STYLESETFONT,STYLE_DEFAULT,(LPARAM)(const char*)font.GetFaceName().utf8_str());
textCtrl_->SendMsg(SCI_STYLESETWEIGHT,STYLE_DEFAULT
,font.GetWeight()==wxFONTWEIGHT_NORMAL?SC_WEIGHT_NORMAL
:font.GetWeight()==wxFONTWEIGHT_LIGHT?SC_WEIGHT_SEMIBOLD:SC_WEIGHT_BOLD);// Handles 3 levels of weight at max.
textCtrl_->SendMsg(SCI_STYLESETITALIC,STYLE_DEFAULT,font.GetStyle()==wxFONTSTYLE_ITALIC);
textCtrl_->SendMsg(SCI_STYLESETSIZE,STYLE_DEFAULT,font.GetPointSize());
codingAssistance_.SetStyles();// SCI_STYLECLEARALL is called inside.
textCtrl_->SendMsg(SCI_SETMARGINWIDTHN,0
,textCtrl_->SendMsg(SCI_TEXTWIDTH,STYLE_LINENUMBER,(LPARAM)"_999999"));
}
...
};
...

リポジトリ更新とビルドテスト

Git for Windowsリポジトリを更新する。ビルド実行して動作を確認する。読み込んだファイルの拡張子に従いコード作成支援機能が自動設定される。

UMLクラス図

TMyCoreImplをルートとして示す。これはアプリケーションの一部にすぎないが変更はTMyCoreImpl内に留まり外部に波及しない。