Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] マクロの Python 対応 #1327

Closed
wants to merge 3 commits into from
Closed

Conversation

beru
Copy link
Contributor

@beru beru commented Jun 26, 2020

PR の目的

サクラエディタのマクロを Python で書けるようにする事が目的です。

カテゴリ

  • 機能追加

PR の背景

現状は WSH(JScript, VBScript)マクロ と PPAマクロが使えます。PPAマクロは 32bit 版のみ対応です。

PR のメリット

  • Python ユーザにとってマクロが書きやすくなります。
  • Python のエコシステムを間接的に使う事が出来ます。

PR のデメリット (トレードオフとかあれば)

  • Python 3.8 のみに対応していて、それ以外のバージョンに未対応です。
  • ビルド環境に Python 3.8 がインストールされていないとビルドに失敗してしまう作りになっています。
    • 手間を掛ければインストールされていない環境でもビルド可能な作りに出来るかもしれません…。
    • Python38.dll を遅延ロードするように設定したので、実行環境で Python 3.8 がインストールされていなくてもサクラエディタの起動は行えると思います。
  • 開発環境の設定ですが、Pythonのインストール先のパスが C:\Python38 固定になっています。
    • CMake では Python のインストール先を検出させる事が出来ますが。Visual Studio の場合は分からない為です。

仕様・動作説明

メニューの ツール > 名前を指定してマクロ実行 を選ぶとファイルダイアログが表示されますが、そこで拡張子が *.py の Python ファイルを選べるようにしました。

Python スクリプト側では import SakuraEditor と記述してそのモジュールの関数を使ってサクラエディタのコマンドやマクロ関数を呼び出す事が出来ます。

テスト内容

from time import time,ctime
import SakuraEditor

#SakuraEditor.FileNew()
#SakuraEditor.FileOpen("", 0, 0, "C:\\")
#SakuraEditor.GetFilename()
clipboard = SakuraEditor.GetClipboard()
SakuraEditor.AddTail("clipboard : " + clipboard)

print('Today is', ctime(time()))

開発時には上記のようなPythonスクリプトのファイルを指定して実行確認を行いました。

PR の影響範囲

マクロ関連

関連 issue, PR

参考資料

https://docs.python.org/3/extending/embedding.html

@beru beru added the enhancement ■機能追加 label Jun 26, 2020
@AppVeyorBot
Copy link

Build sakura 1.0.2862 failed (commit f50e760e0a by @beru)

@AppVeyorBot
Copy link

Build sakura 1.0.2863 failed (commit 3858512f3f by @beru)

@AppVeyorBot
Copy link

Build sakura 1.0.2864 failed (commit 27d554df39 by @beru)

@berryzplus
Copy link
Contributor

Pythonマクロの導入提案には賛成します。
PRの内容がかなり特殊なので、メンバーによる意味のあるレビューは現実的に不可能だと思います。
じゃ、どうするか?は別にissueを立てて考えたるのが良いかも知れないです。

ひとまず、このPRでWin32 Debugのビルドが失敗している原因は調べたほうがよいのかな・・・

@berryzplus
Copy link
Contributor

なお、参考情報ですが、サクラエディタは ActiveScripting に対応しているので、EmEditor同様にActiveScriptingに対応したスクリプトエンジンをインストールしさえすればソースコード変更なしにpythonスクリプトを使うことができるはずです。

http://emeditor.web.fc2.com/EmEditor_Macro_ActiveScript.html

標準のpythonじゃなくてactive pythonを入れないといかんのがネックですが・・・。

@beru
Copy link
Contributor Author

beru commented Jun 27, 2020

なお、参考情報ですが、サクラエディタは ActiveScripting に対応しているので、EmEditor同様にActiveScriptingに対応したスクリプトエンジンをインストールしさえすればソースコード変更なしにpythonスクリプトを使うことができるはずです。

http://emeditor.web.fc2.com/EmEditor_Macro_ActiveScript.html

標準のpythonじゃなくてactive pythonを入れないといかんのがネックですが・・・。

お、これ知らなかったです。サクラエディタは ActiveScripting に対応しているっていうのはソースコードだとどこら辺が相当するのか調べてみます。

ActiveStateは懐かしいですね。結構昔にWindows上で Perl を動かしたい時に ActivePerl を使ってました。

https://www.activestate.com/products/python/downloads/

を見ると、3.7 までしか対応していないのであんまりアクティブに最新版に追従していないのが残念なとこですね。

@beru
Copy link
Contributor Author

beru commented Jun 27, 2020

CWSH.h ファイルを見ると CWSHClient クラスのメンバーに IActiveScript* 型のメンバーがあるので多分これでActiveScriptingに対応出来てるんですね。ただ標準のPythonではないのでなんとなく嫌かも…。

@beru
Copy link
Contributor Author

beru commented Jun 27, 2020

Pythonマクロの導入提案には賛成します。
PRの内容がかなり特殊なので、メンバーによる意味のあるレビューは現実的に不可能だと思います。

Python/C API の知識はレビューに要ると思います。自分は今回初めて使ったんですが不明な事が多くて色々と調べました。

じゃ、どうするか?は別にissueを立てて考えたるのが良いかも知れないです。

Issue 立てて色々とコメントを交わしてもそれだけではこのPRのコードがレビュー出来るようになるとは思えませんが、Python導入の方針とかを落ち着いて議論して決めたいのであれば有用だと思います。

自分が今の実装で気にしている事ですが、暗黙的なリンクにしているのでビルド環境に Python のヘッダファイルや lib ファイルが存在しないとビルドに失敗します。この実装方法だと、Pythonを使わないからインストールはしていないという人がサクラエディタをビルドしようとした時にビルドがコケてしまうので問題になりそうです。

LoadLibrary, GetProcAddress を使う明示的なリンクにする書き方に変更する事でその問題は回避出来ますが、使っているシンボルの数が多かったりするので少し手間です。。でもやらないといけなそうですね。

ひとまず、このPRでWin32 Debugのビルドが失敗している原因は調べたほうがよいのかな・・・

現状 AppVeyor で Win32 のビルドは成功していて x64 のビルドが失敗しています。

https://ci.appveyor.com/project/sakuraeditor/sakura/builds/33769185

64bit の Python が C:\Python38-x64 にインストールされているので、それに対応すればビルド出来ると思います。

https://www.appveyor.com/docs/windows-images-software/#python

ただ AppVeyor で仮にビルド出来たとしても、Azure Pipelines でのビルドに失敗している問題は残ります。

@beru
Copy link
Contributor Author

beru commented Jun 27, 2020

あと今の実装で気にしている他の事ですが、利用する Python のバージョンが 3.8 固定になっているというのがあります。
現時点で最新版が 3.8.3 で近々 3.9 がリリースされそうですが、3.5, 3.6, 3.7 系の古いPythonをインストールして使っている人も当然いると思います。Python 2.7 に関しては過去の遺物という事で対応は省いて良いと思います。しかし 3.8 をインストールしていないと使えないというのは間口が少し狭い気がします。

limited API に限定して使用する事で複数のバージョンのPythonに対応出来そうですが、使えるAPIが制限されている分実装に躓く事が多いと思います。

https://docs.python.org/ja/3/c-api/stable.html

@berryzplus
Copy link
Contributor

CWSH.h ファイルを見ると CWSHClient クラスのメンバーに IActiveScript* 型のメンバーがあるので多分これでActiveScriptingに対応出来てるんですね。ただ標準のPythonではないのでなんとなく嫌かも…。

実装はここです。

CLSID ClassID;
if(CLSIDFromProgID(AEngine, &ClassID) != S_OK)

AEngineには L"JScript" みたいなスクリプトエンジン名を入れる仕様です。ここにシステムにインストールした python のスクリプトエンジンの識別名を入れることにより、次のコードでスクリプトエンジンのインスタンスが生成されます。

if(CoCreateInstance(ClassID, 0, CLSCTX_INPROC_SERVER, IID_IActiveScript, reinterpret_cast<void **>(&m_Engine)) != S_OK)
Error(LS(STR_ERR_CWSH02));

なんとなく嫌、は同意 😃

@k-takata
Copy link
Member

公式版のPythonのインストール先はレジストリから取得することが出来ます。
64bit: HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\3.8\InstallPath
32bit: HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Python\PythonCore\3.8-32\InstallPath

なお、VimもPythonインターフェースを持っていますが、使用するPythonのバージョンはコンパイル時には選択可能ですが、実行時には変更できません。
実行時にバージョンを選択できるようにするのであれば、Pythonのバージョンごとにwrapper DLLを用意して、sakuraはそのwrapper DLLを介して呼ぶようにするのがよいかも知れません。

@berryzplus
Copy link
Contributor

実行時にバージョンを選択できるようにするのであれば、Pythonのバージョンごとにwrapper DLLを用意して、sakuraはそのwrapper DLLを介して呼ぶようにするのがよいかも知れません。

サクラエディタには CDllImp の仕組みがあり、内部的な DLL のラッパーを作ることができます。その気になれば様々なバージョンに対応するように組むことも可能だと思います。

@beru
Copy link
Contributor Author

beru commented Jun 27, 2020

実装はここです。

CLSID ClassID;
if(CLSIDFromProgID(AEngine, &ClassID) != S_OK)

AEngineには L"JScript" みたいなスクリプトエンジン名を入れる仕様です。ここにシステムにインストールした python のスクリプトエンジンの識別名を入れることにより、次のコードでスクリプトエンジンのインスタンスが生成されます。

if(CoCreateInstance(ClassID, 0, CLSCTX_INPROC_SERVER, IID_IActiveScript, reinterpret_cast<void **>(&m_Engine)) != S_OK)
Error(LS(STR_ERR_CWSH02));

なんとなく嫌、は同意 😃

今はどうやってスクリプトエンジン名を取得しているのかコードを調べてみました。

CWSHMacroManager::Creator でレジストリの HKEY_CLASSES_ROOT からマクロファイルの拡張子のキーで読み取ってファイルタイプを取得したら \ScriptEngine と連結したキーでまた HKEY_CLASSES_ROOT から読み取ってエンジン名を取得してました。

試してませんが ActivePython をインストール環境した環境では今の実装でもPythonスクリプトをマクロとして使えるって事なんでしょうか?確認しようと ActivePython をダウンロードしようとしたらアカウント作成を求められたので諦めました。

なんとなく嫌なのは本家の https://www.python.org/ でWindows版のインストーラー配布してるからわざわざ他のを入れる必要性が無いというのもありますね。。パッケージ管理も別になってしまうし…。

@berryzplus
Copy link
Contributor

試してませんが ActivePython をインストール環境した環境では今の実装でもPythonスクリプトをマクロとして使えるって事なんでしょうか?確認しようと ActivePython をダウンロードしようとしたらアカウント作成を求められたので諦めました。

ぼくも最近は試していません。

GitHubに移行するよりも前に、Activeperlで実験したことはあって、そのときは拡張子plに対するレジストリエントリでperlスクリプトを実行できた記憶があります。pythonスクリプトの拡張子はpyなので、pyでいけるんだと思います。

@beru
Copy link
Contributor Author

beru commented Jun 27, 2020

公式版のPythonのインストール先はレジストリから取得することが出来ます。
64bit: HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\3.8\InstallPath
32bit: HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Python\PythonCore\3.8-32\InstallPath

お、情報ありがとうございます。きっと64bit版のWindowsでのパスですね。恐らく32bit版のWindowsを使ってる人はもう殆どいないし、32bit版のWindowsでビルドする事を考慮する必要は無いかもしれませんね。

ただその値を何かで読み取ってファイルに書き込んだりするのは手間ですね。CMake だと find_package で楽に探せますがその為に色々と実装をしているようです。

https://gitlab.kitware.com/cmake/cmake/-/blob/master/Modules/FindPython/Support.cmake

なお、VimもPythonインターフェースを持っていますが、使用するPythonのバージョンはコンパイル時には選択可能ですが、実行時には変更できません。
実行時にバージョンを選択できるようにするのであれば、Pythonのバージョンごとにwrapper DLLを用意して、sakuraはそのwrapper DLLを介して呼ぶようにするのがよいかも知れません。

複数バージョンのPythonがインストールされている環境もあるので、その場合にビルド時、もしくは実行時にどのバージョンのPythonを使うのかを指定出来るようにする必要があるか?というのは開発時の決め事の1つですね。

自分は特定のバージョンの Python のDLLファイル、例えば Python38.dll がパスが通っている場所に有ってダイナミックリンク出来るならそれで良いんじゃないかと思いますが、「そんな事は許さん実行時に複数バージョン対応しないと駄目だ、設定UIも整備してヘルプも整備して~∞」、とかいう要求を想定すると達成が大変そうです。

@beru
Copy link
Contributor Author

beru commented Jun 27, 2020

ぼくも最近は試していません。

GitHubに移行するよりも前に、Activeperlで実験したことはあって、そのときは拡張子plに対するレジストリエントリでperlスクリプトを実行できた記憶があります。pythonスクリプトの拡張子はpyなので、pyでいけるんだと思います。

このPRのやり方で拡張子 py に対応する新規コードを入れると、ActivePython をインストールしている環境では ActivePython が使われる事になりそうです。先に CWSHMacroManager::Creator が呼ばれるので。そしてそれ自体は何の問題も無い気もします。

@beru
Copy link
Contributor Author

beru commented Jun 27, 2020

動的に Python/C API を使う為に必要な記述が色々多くて大変そうだなぁ…って思ってたんですが下記のコードではそれやってるようです。

https://github.com/rstudio/reticulate/blob/master/src/libpython.h
https://github.com/rstudio/reticulate/blob/master/src/libpython.cpp

@beru beru changed the title マクロの Python 対応 [WIP] マクロの Python 対応 Jul 4, 2020
@m-tmatma
Copy link
Member

m-tmatma commented Jul 4, 2020

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@berryzplus
Copy link
Contributor

このPRの問題点は3つあります。

  1. 動的にビルド依存関係を解決する仕組みが含まれていないため、ビルドが通っていません。
  2. 内容が特殊なので一般的なレビュー観点が適用できず、レビューが不可能です。
  3. pythonに関して高度な知識を持つ人は python2 系の利用者が多いように思いますが、このPRがサポートするのは python3.8 のみです。

考えられる解決方法はこんな感じ。

  1. ビルド依存関係を動的に解決させる方法としては、 CMake で python.props を生成してしまう のがよいと思います。
  2. レビュー不可能を解決するには、何が確認出来たらOKにするか? を議論して、OKに至る道筋を逆算で求めていくやり方ができると思います。
  3. python2 系がサポートされない件はとりあえずスルー でもよいと考えています。

@beru
Copy link
Contributor Author

beru commented Jul 4, 2020

このPRの問題点は3つあります。

1. 動的にビルド依存関係を解決する仕組みが含まれていないため、ビルドが通っていません。

1 に関してですが、現状のようにDLLの暗黙的リンクをするにはビルド環境に Python がインストールされている必要があります。AppVeyor の場合は決まったパスにPythonがインストールされている事は分かりました。が、Azure pipelines についてはまだ未調査です。ユーザーのビルド環境ではどのパスにインストールされているか決め打ちに出来ない事を考えると、確かに CMake を使う対策が良さそうに思えます。

しかし https://github.com/sakura-editor/sakura#build-requirements にはビルドに必要なものの中に CMake や Python というのは書かれていません。つまりそれらが存在しない環境でビルドが失敗するようなPRはそもそもNGという前提があります。という事で CMake を使うのはどうかなと思います。またPythonについてもインストールされているとは限らないのでDLLの暗黙的リンクを行うプロジェクト設定には出来ないのではないかと思います。

そうすると Python.h ファイルに頼らずに、ビルド時にPythonのlibファイルとリンクはせずに、実行時にDLLの明示的リンクを行う必要があります。これは結構記述が大変ですが、#1327 (comment) にリンクを張ったコードでは実際にやっているので真似して出来ない事は無いと思います。

2. 内容が特殊なので一般的なレビュー観点が適用できず、レビューが不可能です。

コードレビューをする場合は書かれているコードの内容を理解する必要があるので、CPython の Python/C API を
https://docs.python.org/3.8/extending/index.html
を読んだりして使い方を理解する必要はあると思います。

ただPRのレビューは必ずしもコードレビューが必要とは自分は思わないです。せっかくコードが見れるのだからコードレビューした方が良い事は確かなんですが、ビルド生成物を操作してPRの目的が満たせているかや不具合が無いかを確認するやり方でも良いんじゃないかとは思います。とはいっても現実的にはほぼコードを書いている人達しかPRのレビューをしない感じですが…。

3. pythonに関して高度な知識を持つ人は python2 系の利用者が多いように思いますが、このPRがサポートするのは python3.8 のみです。

Python 2のサポートはもう切れているという事で対応しない方が良いと思います。

Python 3.8 しか今のところ対応する気はないですが、それは単に自分が複数バージョン(例えば Python 3.7 と Python 3.8)のPythonをサポートするやり方がよくわからないからです。対応する必要があるかどうかというのは要件の話になりますが、自分は対応出来た方が良いけれど別に対応しなくても良いんじゃないかな派です。

考えられる解決方法はこんな感じ。

1. ビルド依存関係を動的に解決させる方法としては、 `CMake で python.props を生成してしまう` のがよいと思います。

CMake がインストールされているか確認して、インストールしていない環境でビルドがこけないようにするならそれも有りだと思います。

また、C++コード側では、

#if __has_include(<Python.h>)

#endif

とかして書けば、ビルド環境に Python がインストールされていなかったり、Python の include フォルダへのパスを通せていなくても、サクラエディタのビルドがこけないように出来るかもしれません。

2. レビュー不可能を解決するには、`何が確認出来たらOKにするか?` を議論して、OKに至る道筋を逆算で求めていくやり方ができると思います。

いくつかの点を決めた方が良いと思います。

  • DLLの暗黙的リンクをするか、明示的リンクをするか?
    • 暗黙的リンクをする場合に付きまとうビルドのあれこれを考えると自分は実行時に明示的リンクをするようにした方が良いと思います
  • Python 3.8 以外をサポートするか?
    • 自分はとりあえず Python 3.8 のみのサポートで良いと思います。
    • また、PythonのDLLファイルを読み取るパスの設定を追加するつもりはありません。サクラエディタの実行環境で LoadLibrary("python38.dll") が成功した時だけ使用します。
  • 確認内容について
    • まずは拡張子 py のファイルをマクロとして呼び出して、サクラのコマンドや関数呼び出しがちゃんと動くかの確認で良いと思います。
    • 本当はプラグインについても Python 対応する必要があると思いますが、手抜きで実装してません。

@k-takata
Copy link
Member

k-takata commented Jul 4, 2020

しかし https://github.com/sakura-editor/sakura#build-requirements にはビルドに必要なものの中に CMake や Python というのは書かれていません。つまりそれらが存在しない環境でビルドが失敗するようなPRはそもそもNGという前提があります。

必要なのであればPRの中で追記すればいいだけだと思います。ユーザー環境で必須になる訳ではないですし。

そうすると Python.h ファイルに頼らずに、ビルド時にPythonのlibファイルとリンクはせずに、実行時にDLLの明示的リンクを行う必要があります。

明示的リンクをするにしても、将来的なPythonのバージョンアップを考えると Python.h を使った方が安全でしょう。APIに変更があったときにコンパイルエラーで気づける可能性があります。

vimの場合は暗黙的・明示的リンクの両方に対応していますが、Python.h は使っています。
https://github.com/vim/vim/blob/master/src/if_py_both.h
https://github.com/vim/vim/blob/master/src/if_python3.c

Python 2のサポートはもう切れているという事で対応しない方が良いと思います。

ですね。Python 2に先がないことは10年以上前からアナウンスされてましたし、新規開発では一切考慮に値しないでしょう。

Python 3.8 しか今のところ対応する気はないですが、それは単に自分が複数バージョン(例えば Python 3.7 と Python 3.8)のPythonをサポートするやり方がよくわからないからです。

ビルド時にバージョン判別するのであれば PY_VERSION_HEX で可能です。

@berryzplus
Copy link
Contributor

特定バージョンのpythonへの依存を半強制的に解決させる手段として NuGet パッケージが使えるんじゃないかと思いました・・・。

https://www.nuget.org/packages/python/3.8.3 ← 標準verはx64専用です。
https://www.nuget.org/packages/pythonx86/3.8.3 ← レガシーアプリ向けに x86 ver も提供されている。

@m-tmatma m-tmatma marked this pull request as draft August 20, 2020 13:25
@usagisita
Copy link
Contributor

コード上の気になることをちょっとコメントしてみます。
(まだそういう段階ではないとかありましたら、お知らせください)
■バージョン間の話題
とりあえず、2とか3とかどっちのバージョンでもいいからPythonが動いてほしいって人はいそうです。
超古いJavaScript互換のJScriptしばりとか、文法からつらいVBScriptとか、他の言語を書かされるよりはバージョン差のほうが楽だと思います。
■マクロの再入
g_pEditViewがグローバル変数ですが、再入とかは考慮されていますでしょうか。
キーマクロ含めて元々よくわからない実装みたいなんですけど、アクティブのCEditViewって画面分割していてNextWindow()マクロを実行すると変わるんですよね。
アクティブと実行元のCEditViewでミスマッチがあるっぽいのが混乱の元でどう対処したらいいかは不明です。

それとは別に考慮済みっぽくて釈迦に説法かもしれませんが、ExecExternalMacroマクロ関数がありまして「マクロを10回実行するマクロ」とかも書けることになっています。
PPAはそも再入でエラーを検出するコードがあるようです。
JScriptは再入できます。でないとインデントプラグインと文字入力を伴うマクロの併用ができません。
VBScriptはよくわかりませんがたぶんできます。
スタックの積み増しがどのくらいか怖いんですけど、一応はできます。
■ファイル名の大文字小文字

CPythonMacroManager::Creator
if (wcscmp( FileExt, L"py" ) == 0) {

wcscmpになっています。Windows版のパイソンの拡張子は大文字、小文字区別するんでしょうか。
大文字とか気持ち悪いかもしれませんけど……。
■NUL文字問題

utf8_to_utf16le

ちょっと某所でも話題だった、サクラエディタの行データなどはNUL文字を含むので、WSHなどはなるべく文字列長を保持してC文字列を避けています。
もし、Pythonのインターフェイス上無理ではなく、NUL文字対応が必要でしたら修正するといいかもしれません。
■Pythonプラグイン
WSHプラグインで、JScript/VBScriptはマクロと同じように書けて便利です。
めっちゃ高望みなんですけどPythonもプラグインで動くといい、、、なあ。
無茶ぶりですが、もしこのパッチがうまくマージなどされるなら次のステップにでも。

@berryzplus
Copy link
Contributor

放置期間が1年を超えました。必要であれば「再開するときに」再オープンしてください。

@berryzplus berryzplus closed this Oct 9, 2021
@beru
Copy link
Contributor Author

beru commented Oct 2, 2022

再オープン出来ないので別のMRを作成します。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement ■機能追加
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants