Check-in [6d33ea7784]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Implemented updating and replacing of plugin.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | updatecheck
Files: files | file ages | folders
SHA1: 6d33ea778458d5b46d3a714a17b5f3cf0e27d3ee
User & Date: tinus 2017-02-19 20:38:30
Context
2017-05-14
14:31
Added Win64 platform support. Fixed the most egregious errors wrt bitness. Leaf check-in: f09c69e9b9 user: tinus tags: updatecheck
2017-02-19
20:38
Implemented updating and replacing of plugin. check-in: 6d33ea7784 user: tinus tags: updatecheck
20:29
Moved ODS for OutputDebugString to separate unit. check-in: 3c4dc7f26d user: tinus tags: updatecheck
Changes

Added .fossil-settings/encoding-glob.



>
1
*

Changes to src/U_AutoUpdate.pas.

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

50
51
52
53
54
55
56
..
73
74
75
76
77
78
79
80
81
82

83
84
85
86
87
88
89
90
91
92
93
94
95

96
97
98





99
100
101
102

103
104

105
106
107
108
109
110
111
112



113
114










115
116
117
118
119
120
121
122
123
124

125
126
127
128



129
130
131

















132
133
134
135







136






































137
138
139
140

141
142
143
144
145
146
147
    function GetCurrentVersion: string;
    function GetLatestVersion: string;
  public
    constructor Create{(const AURL: string)};

    function IsUpdateAvailable(out NewVersion, Changes: string): Boolean;
    function DownloadUpdate: string;
    function ReplacePlugin(const PathExtracted: string): Boolean;

    property URL: string            read FURL;

    property CurrentVersion: string read GetCurrentVersion;
    property LatestVersion: string  read GetLatestVersion;
  public
    class function CompareVersions(const VersionA, VersionB: string): Integer;
  end;

  EUpdateError = class(Exception);

implementation
uses
  RegularExpressions, Windows,
  L_HttpClient, L_VersionInfoW, L_SpecialFolders;

{ ------------------------------------------------------------------------------------------------ }
procedure ODS(const DebugOutput: string); overload;
begin
  OutputDebugString(PChar('PreviewHTML['+IntToHex(GetCurrentThreadId, 4)+']: ' + DebugOutput));
end {ODS};
{ ------------------------------------------------------------------------------------------------ }
procedure ODS(const DebugOutput: string; const Args: array of const); overload;
begin
  ODS(Format(DebugOutput, Args));
end{ODS};



{ ------------------------------------------------------------------------------------------------ }
{ TPluginUpdate }

{ ------------------------------------------------------------------------------------------------ }
constructor TPluginUpdate.Create;
begin
................................................................................
end {TPluginUpdate.GetLatestVersion};

{ ------------------------------------------------------------------------------------------------ }
function TPluginUpdate.IsUpdateAvailable(out NewVersion, Changes: string): Boolean;
var
  Http: THttpClient;
  Notes: string;
  RxVersion: TRegEx;
  Matches: TMatchCollection;
  Match: TMatch;

  i: Integer;
begin
  Result := False;

  // Download the current release notes
  ODS('Create HttpClient');
  Http := THttpClient.Create('http://fossil.2of4.net/npp_preview/doc/publish/ReleaseNotes.txt');
  try
    ODS('Http.Get');
    if Http.Get() = 200 then begin
      ODS('%d %s', [Http.StatusCode, Http.StatusText]);
      for i := 0 to Http.ResponseHeaders.Count - 1 do
        ODS(Http.ResponseHeaders[i]);

      Notes := Http.ResponseString;
      ODS('Response: "%s"', [Copy(StringReplace(StringReplace(Notes, #10, '·', [rfReplaceAll]), #13, '·', [rfReplaceAll]), 1, 250)]);
      // get the response stream, and look for the latest version in there





      Matches := TRegEx.Matches(Notes, 'v[0-9]+(\.[0-9]+){3}');
      ODS('Matches: %d', [Matches.Count]);
      if Matches.Count > 0 then begin
        Match := Matches.Item[0];

        ODS('Match: Success=%s; Value="%s"; Index=%d', [BoolToStr(Match.Success, True), Match.Value, Match.Index]);
        if Match.Success then begin

          NewVersion := Match.Value;
          {$MESSAGE WARN 'TODO: read all versions until we find the current one. Otherwise stop at </pre>.'}
          if (Matches.Count > 1) and Matches[1].Success then begin
            Changes := Notes.Substring(Match.Index - 1, Matches[1].Index - Match.Index);
          end else begin
            i := Pos('</pre>', Notes);
            Changes := Notes.Substring(Match.Index - 1, i - Match.Index);
          end;



        end;
      end;










    end else begin
      // TODO: show message, open project's main page?
      raise EUpdateError.CreateFmt('%d %s', [Http.StatusCode, Http.StatusText]);
    end;
  finally
    Http.Free;
  end;

  Result := CompareVersions(CurrentVersion, NewVersion) > 0;
  // TODO: Populate Changes from the text between the match of the first version number, and the next (or, if there is no next, the </pre> tag).

end {TPluginUpdate.IsUpdateAvailable};

{ ------------------------------------------------------------------------------------------------ }
function TPluginUpdate.DownloadUpdate: string;



begin
  // TODO: Download http://fossil.2of4.net/npp_preview/zip/Preview_Plugin.zip?uuid=publish&name=plugins to a temp dir,
  //  extract it to a custom temp folder, and return that folder's path

















end {TPluginUpdate.DownloadUpdate};

{ ------------------------------------------------------------------------------------------------ }
function TPluginUpdate.ReplacePlugin(const PathExtracted: string): Boolean;







begin






































  // TODO: Rename the current DLL to ChangeFileExt(DllName, '-' + OwnVersion + '.~dll')
  // HardlinkOrCopy all files in extract location to path relative to plugins folder. ./Config should
  //  be translated to PluginsConfigFolder. ReleaseNotes.txt gets special treatment: it's in the
  //  root folder, but should be moved to ./Doc/PreviewHTML.

end {TPluginUpdate.ReplacePlugin};

{ ------------------------------------------------------------------------------------------------ }
class function TPluginUpdate.CompareVersions(const VersionA, VersionB: string): Integer;
var
  va, vb: string;
  na, nb: Integer;







|













|
|
<
<
<
<
<
<
<
<
<
<
<
<
>







 







<


>
|

<
<







|
|
>


<
>
>
>
>
>


<
<
>

<
>

<
<
<
<
|
|
|
>
>
>

|
>
>
>
>
>
>
>
>
>
>









|
>




>
>
>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



|
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




>







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37












38
39
40
41
42
43
44
45
..
62
63
64
65
66
67
68

69
70
71
72
73


74
75
76
77
78
79
80
81
82
83
84
85

86
87
88
89
90
91
92


93
94

95
96




97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
    function GetCurrentVersion: string;
    function GetLatestVersion: string;
  public
    constructor Create{(const AURL: string)};

    function IsUpdateAvailable(out NewVersion, Changes: string): Boolean;
    function DownloadUpdate: string;
    function ReplacePlugin(const PathDownloaded: string): Boolean;

    property URL: string            read FURL;

    property CurrentVersion: string read GetCurrentVersion;
    property LatestVersion: string  read GetLatestVersion;
  public
    class function CompareVersions(const VersionA, VersionB: string): Integer;
  end;

  EUpdateError = class(Exception);

implementation
uses
  Classes, RegularExpressions, Windows, NetEncoding, Zip,
  L_HttpClient, L_VersionInfoW, L_SpecialFolders,












  Debug;

{ ------------------------------------------------------------------------------------------------ }
{ TPluginUpdate }

{ ------------------------------------------------------------------------------------------------ }
constructor TPluginUpdate.Create;
begin
................................................................................
end {TPluginUpdate.GetLatestVersion};

{ ------------------------------------------------------------------------------------------------ }
function TPluginUpdate.IsUpdateAvailable(out NewVersion, Changes: string): Boolean;
var
  Http: THttpClient;
  Notes: string;

  Matches: TMatchCollection;
  Match: TMatch;
  s: string;
  FirstPos, LastPos: Integer;
begin


  // Download the current release notes
  ODS('Create HttpClient');
  Http := THttpClient.Create('http://fossil.2of4.net/npp_preview/doc/publish/ReleaseNotes.txt');
  try
    ODS('Http.Get');
    if Http.Get() = 200 then begin
      ODS('%d %s', [Http.StatusCode, Http.StatusText]);
      for s in Http.ResponseHeaders do
        ODS(s);
      // get the response stream, and look for the latest version in there
      Notes := Http.ResponseString;
      ODS('Response: "%s"', [Copy(StringReplace(StringReplace(Notes, #10, '·', [rfReplaceAll]), #13, '·', [rfReplaceAll]), 1, 250)]);


      NewVersion := '';
      FirstPos := -1;
      LastPos := -1;

      Matches := TRegEx.Matches(Notes, 'v[0-9]+(\.[0-9]+){3}');
      ODS('Matches: %d', [Matches.Count]);


      for Match in Matches do begin
        ODS('Match: Success=%s; Value="%s"; Index=%d', [BoolToStr(Match.Success, True), Match.Value, Match.Index]);

        if NewVersion = '' then
          NewVersion := Match.Value;




        if FirstPos = -1 then
          FirstPos := Match.Index;
        if CompareVersions(CurrentVersion, Match.Value) <= 0 then begin
          // This version is equal or older than the current version
          LastPos := Match.Index;
          Break;
        end;
      end {for};

      if FirstPos = -1 then
        FirstPos := Notes.IndexOf('<pre>') + 5;
      if (LastPos = -1) or (LastPos = FirstPos) then
        LastPos := Notes.IndexOf('</pre>');

      Changes := Notes.Substring(FirstPos - 1, LastPos - FirstPos);
      ODS('NewVersion: "%s"; Changes: "%s"', [NewVersion, Changes]);

      Changes := TNetEncoding.HTML.Decode(Changes.Trim);
    end else begin
      // TODO: show message, open project's main page?
      raise EUpdateError.CreateFmt('%d %s', [Http.StatusCode, Http.StatusText]);
    end;
  finally
    Http.Free;
  end;

  Result := CompareVersions(CurrentVersion, NewVersion) > 0;

  FLatestVersion := NewVersion;
end {TPluginUpdate.IsUpdateAvailable};

{ ------------------------------------------------------------------------------------------------ }
function TPluginUpdate.DownloadUpdate: string;
var
  Http: THttpClient;
  FS: TFileStream;
begin
  // TODO: Download http://fossil.2of4.net/npp_preview/zip/Preview_Plugin.zip?uuid=publish&name=plugins to a temp dir,
  //  extract it to a custom temp folder, and return that folder's path
  Http := THttpClient.Create('http://fossil.2of4.net/npp_preview/zip/Preview_Plugin.zip?uuid=publish&name=plugins');
  try
    if Http.Get() = 200 then begin
      Result := TSpecialFolders.TempDll + 'Preview_Plugin.zip';
      FS := TFileStream.Create(Result, fmCreate or fmShareDenyWrite);
      try
        FS.CopyFrom(Http.ResponseStream, 0);
      finally
        FS.Free;
      end;
    end else begin
      // TODO: show message, open project's main page?
      raise EUpdateError.CreateFmt('%d %s', [Http.StatusCode, Http.StatusText]);
    end;
  finally
    Http.Free;
  end;
end {TPluginUpdate.DownloadUpdate};

{ ------------------------------------------------------------------------------------------------ }
function TPluginUpdate.ReplacePlugin(const PathDownloaded: string): Boolean;
var
  NppDir: string;
  Zip: TZipFile;
  i: Integer;
  Info: TZipHeader;
  Name: string;
  Backup: string;
begin
  NppDir := TSpecialFolders.DLL + '..\';

  Zip := TZipFile.Create;
  try
    ODS(PathDownloaded);
    Zip.Open(PathDownloaded, zmRead);
    for i := 0 to Zip.FileCount - 1 do begin
      Info := Zip.FileInfo[i];
      Name := Zip.FileName[i].Replace('/', '\');
      ODS(Name);

      if Name.EndsWith('\') then Continue; // Skip directories

      if SameFileName(ExtractFileName(Name), 'ReleaseNotes.txt') then begin
        Name := Name.Replace('ReleaseNotes.txt', 'Doc\PreviewHTML\ReleaseNotes.txt', [rfIgnoreCase]);
        ODS('=> ' + Name);
      end;

      if FileExists(NppDir + Name) then begin
        Backup := NppDir + ChangeFileExt(Name, '-' + CurrentVersion + ExtractFileExt(Name));
        if SameFileName(ExtractFileExt(Name), '.dll') then begin
          Backup := ChangeFileExt(Backup, '.~' + ExtractFileExt(Backup).Substring(1));
          ODS('Backup: ' + Backup);
          Win32Check(RenameFile(NppDir + Name, Backup));
        end else begin
          ODS('Backup: ' + Backup);
          Win32Check(CopyFile(PChar(NppDir + Name), PChar(Backup), False));
        end;
      end;

      ODS('Extracting file to: ' + NppDir + Name);
      ForceDirectories(ExtractFileDir(NppDir + Name));
      Zip.Extract(i, ExtractFileDir(NppDir + Name), False);
    end {for};
  finally
    Zip.Free;
  end;

  // TODO: Rename the current DLL to ChangeFileExt(DllName, '-' + OwnVersion + '.~dll')
  // HardlinkOrCopy all files in extract location to path relative to plugins folder. ./Config should
  //  be translated to PluginsConfigFolder. ReleaseNotes.txt gets special treatment: it's in the
  //  root folder, but should be moved to ./Doc/PreviewHTML.
  Result := True;
end {TPluginUpdate.ReplacePlugin};

{ ------------------------------------------------------------------------------------------------ }
class function TPluginUpdate.CompareVersions(const VersionA, VersionB: string): Integer;
var
  va, vb: string;
  na, nb: Integer;

Changes to src/U_Npp_PreviewHTML.pas.

8
9
10
11
12
13
14

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
..
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
187
188
189
190
191
192
193
194


195
196
197
198
199
200
201





202
203
204
205
206
207
208





209

210
211
212
213











214
215
216

217




218
219
220
221
222
223
224
...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
  NppPlugin, SciSupport,
  F_About, F_PreviewHTML;

type
  TNppPluginPreviewHTML = class(TNppPlugin)
  private
    FSettings: TIniFile;

  public
    constructor Create;

    procedure SetInfo(NppData: TNppData); override;

    procedure CommandShowPreview;
    procedure CommandSetIEVersion(const BrowserEmulation: Integer);
    procedure CommandOpenFile(const Filename: nppString);
    procedure CommandCheckUpdate;
    procedure CommandShowAbout;
    procedure CommandReplaceHelloWorld;

    procedure DoNppnToolbarModification; override;
    procedure DoNppnFileClosed(const BufferID: THandle); override;
    procedure DoNppnBufferActivated(const BufferID: THandle); override;
    procedure DoModified(const hwnd: HWND; const modificationType: Integer); override;

    function  GetSettings(const Name: string = 'Settings.ini'): TIniFile;
  end {TNppPluginPreviewHTML};

procedure _FuncReplaceHelloWorld; cdecl;
procedure _FuncCheckUpdate; cdecl;
procedure _FuncShowPreview; cdecl;
procedure _FuncOpenSettings; cdecl;
procedure _FuncOpenFilters; cdecl;
procedure _FuncShowAbout; cdecl;

procedure _FuncSetIE7; cdecl;
................................................................................

////////////////////////////////////////////////////////////////////////////////////////////////////
implementation
uses
  WebBrowser, Registry,
  U_AutoUpdate;

{ ------------------------------------------------------------------------------------------------ }
procedure _FuncReplaceHelloWorld; cdecl;
begin
  Npp.CommandReplaceHelloWorld;
end;
{ ------------------------------------------------------------------------------------------------ }
procedure _FuncCheckUpdate; cdecl;
begin
  Npp.CommandCheckUpdate;
end;
{ ------------------------------------------------------------------------------------------------ }
procedure _FuncOpenSettings; cdecl;
begin
  Npp.CommandOpenFile('Settings.ini');
end;
{ ------------------------------------------------------------------------------------------------ }
................................................................................

  self.AddFuncSeparator;

  self.AddFuncItem('&About', _FuncShowAbout);
end {TNppPluginPreviewHTML.SetInfo};

{ ------------------------------------------------------------------------------------------------ }
procedure TNppPluginPreviewHTML.CommandCheckUpdate;


var
  Cur, Next: string;
  Update: TPluginUpdate;
  Notes: string;
  Yay: Boolean;
begin
  try





    Update := TPluginUpdate.Create;
    try
      Cur := Update.CurrentVersion;
      Next := Update.LatestVersion;
      Yay := Update.IsUpdateAvailable(Next, Notes);
    finally
      Update.Free;





    end;

    MessageBox(Npp.NppData.NppHandle,
              PChar(Format('Current: "%s"; Latest: "%s"; Difference: %d',
                          [Cur, Next, TPluginUpdate.CompareVersions(Cur, Next)])),
              PChar(Caption), MB_ICONINFORMATION);











    MessageBox(Npp.NppData.NppHandle,
              PChar(Format('Is update available? %s! Latest version: "%s"'#10#10'%s',
                          [BoolToStr(Yay, True), Next, Notes])),

              PChar(Caption), MB_ICONINFORMATION);




  except
    ShowException(ExceptObject, ExceptAddr);
  end;
end {TNppPluginPreviewHTML.CommandCheckUpdate};

{ ------------------------------------------------------------------------------------------------ }
procedure TNppPluginPreviewHTML.CommandOpenFile(const Filename: nppString);
................................................................................
    if not DoOpen(FullPath) then
      MessageBox(Npp.NppData.NppHandle, PChar(Format('Unable to open "%s".', [FullPath])), PChar(Caption), MB_ICONWARNING);
  except
    ShowException(ExceptObject, ExceptAddr);
  end;
end {TNppPluginPreviewHTML.CommandOpenFilters};

{ ------------------------------------------------------------------------------------------------ }
procedure TNppPluginPreviewHTML.CommandReplaceHelloWorld;
var
  s: UTF8String;
begin
  s := 'Hello World';
  SendMessage(self.NppData.ScintillaMainHandle, SCI_REPLACESEL, 0, LPARAM(PAnsiChar(s)));
end {TNppPluginPreviewHTML.CommandReplaceHelloWorld};

{ ------------------------------------------------------------------------------------------------ }
procedure TNppPluginPreviewHTML.CommandSetIEVersion(const BrowserEmulation: Integer);
begin
  if GetBrowserEmulation <> BrowserEmulation then begin
    SetBrowserEmulation(BrowserEmulation);
    MessageBox(Npp.NppData.NppHandle,
                PChar(Format('The preview browser mode has been set to correspond to Internet Explorer version %d.'#13#10#13#10 +







>








|

<









<







 







<
<
<
<
<



|







 







|
>
>

|

|
<


>
>
>
>
>


|
<
|
<
<
>
>
>
>
>
|
>
|
<
<
<
>
>
>
>
>
>
>
>
>
>
>
|
<
<
>
|
>
>
>
>







 







<
<
<
<
<
<
<
<
<







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

26
27
28
29
30
31
32
33
34

35
36
37
38
39
40
41
..
52
53
54
55
56
57
58





59
60
61
62
63
64
65
66
67
68
69
...
181
182
183
184
185
186
187
188
189
190
191
192
193
194

195
196
197
198
199
200
201
202
203
204

205


206
207
208
209
210
211
212
213



214
215
216
217
218
219
220
221
222
223
224
225


226
227
228
229
230
231
232
233
234
235
236
237
238
...
246
247
248
249
250
251
252









253
254
255
256
257
258
259
  NppPlugin, SciSupport,
  F_About, F_PreviewHTML;

type
  TNppPluginPreviewHTML = class(TNppPlugin)
  private
    FSettings: TIniFile;
    FUpdated: Boolean;
  public
    constructor Create;

    procedure SetInfo(NppData: TNppData); override;

    procedure CommandShowPreview;
    procedure CommandSetIEVersion(const BrowserEmulation: Integer);
    procedure CommandOpenFile(const Filename: nppString);
    procedure CommandCheckUpdates;
    procedure CommandShowAbout;


    procedure DoNppnToolbarModification; override;
    procedure DoNppnFileClosed(const BufferID: THandle); override;
    procedure DoNppnBufferActivated(const BufferID: THandle); override;
    procedure DoModified(const hwnd: HWND; const modificationType: Integer); override;

    function  GetSettings(const Name: string = 'Settings.ini'): TIniFile;
  end {TNppPluginPreviewHTML};


procedure _FuncCheckUpdate; cdecl;
procedure _FuncShowPreview; cdecl;
procedure _FuncOpenSettings; cdecl;
procedure _FuncOpenFilters; cdecl;
procedure _FuncShowAbout; cdecl;

procedure _FuncSetIE7; cdecl;
................................................................................

////////////////////////////////////////////////////////////////////////////////////////////////////
implementation
uses
  WebBrowser, Registry,
  U_AutoUpdate;






{ ------------------------------------------------------------------------------------------------ }
procedure _FuncCheckUpdate; cdecl;
begin
  Npp.CommandCheckUpdates;
end;
{ ------------------------------------------------------------------------------------------------ }
procedure _FuncOpenSettings; cdecl;
begin
  Npp.CommandOpenFile('Settings.ini');
end;
{ ------------------------------------------------------------------------------------------------ }
................................................................................

  self.AddFuncSeparator;

  self.AddFuncItem('&About', _FuncShowAbout);
end {TNppPluginPreviewHTML.SetInfo};

{ ------------------------------------------------------------------------------------------------ }
procedure TNppPluginPreviewHTML.CommandCheckUpdates;
const
  scYesNo: array[Boolean] of string = ('No', 'Yes');
var
  Current, Latest, Notes: string;
  Update: TPluginUpdate;
  ZipPath: string;

begin
  try
    if FUpdated then begin
      MessageBox(Npp.NppData.NppHandle, 'Please restart Notepad++ first!', PChar(Caption), MB_ICONWARNING);
      Exit;
    end;

    Update := TPluginUpdate.Create;
    try
      Current := Update.CurrentVersion;

      if not Update.IsUpdateAvailable(Latest, Notes) then begin


        MessageBox(Npp.NppData.NppHandle,
                   PChar(Format('Nothing to update!'#10#10'Your current version of this plugin, v%s, is the most recent one.',
                                [Current])), PChar(Caption),
                   MB_ICONINFORMATION);
        Exit;
      end;

      if ID_YES = MessageBox(Npp.NppData.NppHandle,



                             PChar(Format('Update available!'#10#10
                                        + 'Your current version of this plugin, v%0:s, is out of date.'
                                        + ' We recommend you update to version %1:s.'#10#10
                                        + 'These are the changes since the current version:'#10#10
                                        + '%2:s'#10#10
                                        + 'Do you want to download and install %1:s?',
                                          [Current, Latest, Notes])),
                             PChar(Caption), MB_YESNO or MB_DEFBUTTON1 or MB_ICONQUESTION) then begin
        ZipPath := Update.DownloadUpdate;
        FUpdated := Update.ReplacePlugin(ZipPath);
        if FUpdated then
          MessageBox(Npp.NppData.NppHandle,


                     'Update completed. Please restart Notepad++ to use the updated plugin.',
                     PChar(Caption), MB_ICONINFORMATION);
      end;
    finally
      Update.Free;
    end;
  except
    ShowException(ExceptObject, ExceptAddr);
  end;
end {TNppPluginPreviewHTML.CommandCheckUpdate};

{ ------------------------------------------------------------------------------------------------ }
procedure TNppPluginPreviewHTML.CommandOpenFile(const Filename: nppString);
................................................................................
    if not DoOpen(FullPath) then
      MessageBox(Npp.NppData.NppHandle, PChar(Format('Unable to open "%s".', [FullPath])), PChar(Caption), MB_ICONWARNING);
  except
    ShowException(ExceptObject, ExceptAddr);
  end;
end {TNppPluginPreviewHTML.CommandOpenFilters};










{ ------------------------------------------------------------------------------------------------ }
procedure TNppPluginPreviewHTML.CommandSetIEVersion(const BrowserEmulation: Integer);
begin
  if GetBrowserEmulation <> BrowserEmulation then begin
    SetBrowserEmulation(BrowserEmulation);
    MessageBox(Npp.NppData.NppHandle,
                PChar(Format('The preview browser mode has been set to correspond to Internet Explorer version %d.'#13#10#13#10 +