mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-24 08:37:42 +00:00
feat(developer): Open files in per-project instances
Refactor the file-opening code so it can be used by Keyman Developer UI as well as command-line, and then add support for opening files in per-project instances from File|Open and friends. Also moves the initialization code out of tike.dpr into Keyman.Developer.System.Main.pas, which makes it easier to maintain and read.
This commit is contained in:
parent
915510b845
commit
ff5a366c45
10 changed files with 426 additions and 223 deletions
|
|
@ -334,11 +334,8 @@ begin
|
|||
end;
|
||||
|
||||
procedure TmodActionsMain.actFileOpenAccept(Sender: TObject);
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
for i := 0 to actFileOpen.Dialog.Files.Count - 1 do
|
||||
frmKeymanDeveloper.OpenFileInProject(actFileOpen.Dialog.Files[i]);
|
||||
frmKeymanDeveloper.OpenFilesInProject(actFileOpen.Dialog.Files.ToStringArray);
|
||||
end;
|
||||
|
||||
procedure TmodActionsMain.actFileOpenUpdate(Sender: TObject);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ uses
|
|||
|
||||
type
|
||||
IDragDrop = interface
|
||||
function DropAllowed(const FileNames: array of string): Boolean;
|
||||
procedure Drop(const FileNames: array of string);
|
||||
function DropAllowed(const FileNames: TArray<string>): Boolean;
|
||||
procedure Drop(const FileNames: TArray<string>);
|
||||
end;
|
||||
|
||||
TDropTarget = class(TObject, IInterface, IDropTarget)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
unit Keyman.Developer.System.LaunchProjects;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.Classes,
|
||||
System.Generics.Collections,
|
||||
System.SysUtils,
|
||||
|
||||
Keyman.Developer.System.TikeMultiProcess;
|
||||
|
||||
type
|
||||
TLaunchProjectStatus = (lpsWaiting, lpsCurrentInstance, lpsOtherInstance, lpsNewInstance, lpsError);
|
||||
TLaunchProject = class
|
||||
private
|
||||
FProjectFilename: string;
|
||||
FFilenames: TStringList;
|
||||
FStatus: TLaunchProjectStatus;
|
||||
public
|
||||
constructor Create(const AProjectFilename: string);
|
||||
destructor Destroy; override;
|
||||
function LaunchAsNewInstance: Boolean;
|
||||
function PassToRunningProcess(AProcesses: TTikeProcessList): Boolean;
|
||||
property ProjectFilename: string read FProjectFilename;
|
||||
property Filenames: TStringList read FFilenames;
|
||||
property Status: TLaunchProjectStatus read FStatus;
|
||||
end;
|
||||
|
||||
TLaunchProjects = class(TObjectList<TLaunchProject>)
|
||||
private
|
||||
FReserveStartupProject: Boolean;
|
||||
function GetStartupProject: TLaunchProject;
|
||||
public
|
||||
constructor Create(AReserveStartupProject: Boolean);
|
||||
procedure GroupFilenamesIntoProjects(const Filenames: TStringList);
|
||||
function LaunchAll(AProcesses: TTikeProcessList): Boolean;
|
||||
function Find(const ProjectFilename: string): TLaunchProject;
|
||||
property ReserveStartupProject: Boolean read FReserveStartupProject;
|
||||
property StartupProject: TLaunchProject read GetStartupProject;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
Winapi.Windows,
|
||||
|
||||
Keyman.Developer.System.ProjectOwningFile,
|
||||
utilexecute;
|
||||
|
||||
{ TLaunchProject }
|
||||
|
||||
constructor TLaunchProject.Create(const AProjectFilename: string);
|
||||
begin
|
||||
inherited Create;
|
||||
FProjectFilename := AProjectFilename;
|
||||
FFilenames := TStringList.Create;
|
||||
FStatus := lpsWaiting;
|
||||
end;
|
||||
|
||||
destructor TLaunchProject.Destroy;
|
||||
begin
|
||||
FFilenames.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TLaunchProject.LaunchAsNewInstance: Boolean;
|
||||
var
|
||||
filename, cmdline: string;
|
||||
begin
|
||||
cmdline := '"'+ParamStr(0)+'" --sub-process "'+Self.ProjectFilename+'"';
|
||||
for filename in Self.Filenames do
|
||||
cmdline := cmdline + ' "'+filename+'"';
|
||||
|
||||
Result := TUtilExecute.Execute(cmdline, GetCurrentDir, SW_SHOWNORMAL);
|
||||
|
||||
if Result
|
||||
then FStatus := lpsNewInstance
|
||||
else FStatus := lpsError;
|
||||
end;
|
||||
|
||||
function TLaunchProject.PassToRunningProcess(AProcesses: TTikeProcessList): Boolean;
|
||||
var
|
||||
tp: TTikeProcess;
|
||||
filename: string;
|
||||
begin
|
||||
for tp in AProcesses do
|
||||
begin
|
||||
if tp.OwnsProject(Self.ProjectFilename) then
|
||||
begin
|
||||
FStatus := lpsOtherInstance;
|
||||
|
||||
if Self.Filenames.Count = 0 then
|
||||
begin
|
||||
// Ensure that the project file tab is opened
|
||||
tp.OpenFile(Self.ProjectFilename);
|
||||
end;
|
||||
|
||||
for filename in Self.Filenames do
|
||||
begin
|
||||
tp.OpenFile(filename);
|
||||
end;
|
||||
Exit(True);
|
||||
end;
|
||||
end;
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
{ TLaunchProjects }
|
||||
|
||||
constructor TLaunchProjects.Create(AReserveStartupProject: Boolean);
|
||||
begin
|
||||
inherited Create(True);
|
||||
FReserveStartupProject := AReserveStartupProject;
|
||||
end;
|
||||
|
||||
function TLaunchProjects.Find(const ProjectFilename: string): TLaunchProject;
|
||||
begin
|
||||
for Result in Self do
|
||||
begin
|
||||
if Result.ProjectFilename = ProjectFilename then
|
||||
begin
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
function TLaunchProjects.GetStartupProject: TLaunchProject;
|
||||
begin
|
||||
for Result in Self do
|
||||
if Result.Status = lpsCurrentInstance then
|
||||
Exit;
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
procedure TLaunchProjects.GroupFilenamesIntoProjects(const Filenames: TStringList);
|
||||
var
|
||||
projectFilename, filename: string;
|
||||
p: TLaunchProject;
|
||||
begin
|
||||
for filename in filenames do
|
||||
begin
|
||||
projectFilename := FindOwnerProjectForFile(filename);
|
||||
|
||||
p := Self.Find(projectFilename);
|
||||
if p = nil then
|
||||
begin
|
||||
p := TLaunchProject.Create(projectFilename);
|
||||
Self.Add(p);
|
||||
end;
|
||||
p.Filenames.Add(filename);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TLaunchProjects.LaunchAll(AProcesses: TTikeProcessList): Boolean;
|
||||
var
|
||||
p: TLaunchProject;
|
||||
begin
|
||||
Result := False;
|
||||
|
||||
// Hand off files to existing processes, based on project folder
|
||||
for p in Self do
|
||||
begin
|
||||
if not p.PassToRunningProcess(AProcesses) then
|
||||
begin
|
||||
// If there isn't an existing process, then we need to launch a new
|
||||
// process for the project
|
||||
if not Result and ReserveStartupProject then
|
||||
begin
|
||||
// For performance, we'll take the first project for the current process
|
||||
// if requested
|
||||
p.FStatus := lpsCurrentInstance;
|
||||
Result := True;
|
||||
end
|
||||
else
|
||||
begin
|
||||
p.LaunchAsNewInstance;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
87
developer/src/tike/main/Keyman.Developer.System.Main.pas
Normal file
87
developer/src/tike/main/Keyman.Developer.System.Main.pas
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
unit Keyman.Developer.System.Main;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Win.ComObj,
|
||||
Vcl.Forms,
|
||||
Winapi.ActiveX,
|
||||
Winapi.UxTheme,
|
||||
|
||||
uCEFApplication,
|
||||
uCEFTypes,
|
||||
|
||||
Keyman.Developer.System.TikeCommandLine,
|
||||
Keyman.System.CEFManager,
|
||||
Keyman.System.KeymanSentryClient,
|
||||
Sentry.Client,
|
||||
Sentry.Client.Vcl,
|
||||
UfrmMain,
|
||||
UmodWebHttpServer;
|
||||
|
||||
procedure RunKeymanDeveloper;
|
||||
|
||||
implementation
|
||||
|
||||
const
|
||||
LOGGER_DEVELOPER_IDE_TIKE = TKeymanSentryClient.LOGGER_DEVELOPER_IDE + '.tike';
|
||||
|
||||
procedure RunWithExceptionsHandled; forward;
|
||||
|
||||
procedure RunKeymanDeveloper;
|
||||
begin
|
||||
CoInitFlags := COINIT_APARTMENTTHREADED;
|
||||
Application.MainFormOnTaskBar := True;
|
||||
Application.Initialize;
|
||||
Application.Title := 'Keyman Developer';
|
||||
|
||||
TKeymanSentryClient.Start(TSentryClientVcl, kscpDeveloper, LOGGER_DEVELOPER_IDE_TIKE);
|
||||
try
|
||||
try
|
||||
RunWithExceptionsHandled;
|
||||
except
|
||||
on E:Exception do
|
||||
SentryHandleException(E);
|
||||
end;
|
||||
finally
|
||||
TKeymanSentryClient.Stop;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure RunWithExceptionsHandled;
|
||||
begin
|
||||
FInitializeCEF := TCEFManager.Create;
|
||||
try
|
||||
if GlobalCEFApp.ProcessType = ptBrowser then
|
||||
begin
|
||||
// We want to process the command line only if we are not a CEF
|
||||
// sub-process, because otherwise we lose the benefit of
|
||||
if TikeCommandLine.Process = pclExit then
|
||||
begin
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
if FInitializeCEF.Start then
|
||||
begin
|
||||
InitThemeLibrary;
|
||||
SetThemeAppProperties(STAP_ALLOW_NONCLIENT or STAP_ALLOW_CONTROLS or STAP_ALLOW_WEBCONTENT);
|
||||
Application.CreateForm(TmodWebHttpServer, modWebHttpServer);
|
||||
try
|
||||
Application.CreateForm(TfrmKeymanDeveloper, frmKeymanDeveloper);
|
||||
try
|
||||
Application.Run;
|
||||
finally
|
||||
FreeAndNil(frmKeymanDeveloper);
|
||||
end;
|
||||
finally
|
||||
FreeAndNil(modWebHttpServer);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
FInitializeCEF.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
@ -11,23 +11,6 @@ uses
|
|||
type
|
||||
TProcessCommandLine = (pclRun, pclExit);
|
||||
|
||||
type
|
||||
TLaunchProject = class
|
||||
strict private
|
||||
FProjectFilename: string;
|
||||
FFilenames: TStringList;
|
||||
public
|
||||
constructor Create(const AProjectFilename: string);
|
||||
destructor Destroy; override;
|
||||
property ProjectFilename: string read FProjectFilename;
|
||||
property Filenames: TStringList read FFilenames;
|
||||
end;
|
||||
|
||||
TLaunchProjects = class(TObjectList<TLaunchProject>)
|
||||
public
|
||||
function Find(const ProjectFilename: string): TLaunchProject;
|
||||
end;
|
||||
|
||||
type
|
||||
TTikeCommandLine = class
|
||||
strict private
|
||||
|
|
@ -35,9 +18,8 @@ type
|
|||
private
|
||||
FStartupProjectPath: string;
|
||||
FStartupFilenames: TArray<string>;
|
||||
function PassProjectToRunningProcess(const project: TLaunchProject): Boolean;
|
||||
function LaunchNewInstance(const project: TLaunchProject): Boolean;
|
||||
function ProcessSubProcess: Boolean;
|
||||
function GetFilenamesFromCommandLine(filenames: TStringList): Boolean;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
|
@ -53,29 +35,14 @@ implementation
|
|||
|
||||
uses
|
||||
System.SysUtils,
|
||||
Vcl.Dialogs,
|
||||
Winapi.Windows,
|
||||
|
||||
ErrorControlledRegistry,
|
||||
Keyman.Developer.System.ProjectOwningFile,
|
||||
Keyman.Developer.System.LaunchProjects,
|
||||
RegistryKeys,
|
||||
utilexecute;
|
||||
|
||||
{ TLaunchProject }
|
||||
|
||||
constructor TLaunchProject.Create(const AProjectFilename: string);
|
||||
begin
|
||||
inherited Create;
|
||||
FProjectFilename := AProjectFilename;
|
||||
FFilenames := TStringList.Create;
|
||||
end;
|
||||
|
||||
destructor TLaunchProject.Destroy;
|
||||
begin
|
||||
FFilenames.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
|
||||
{ TTikeCommandLine }
|
||||
|
||||
constructor TTikeCommandLine.Create;
|
||||
|
|
@ -112,17 +79,73 @@ begin
|
|||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TTikeCommandLine.GetFilenamesFromCommandLine(filenames: TStringList): Boolean;
|
||||
var
|
||||
filename, missingFilenames: string;
|
||||
NoMoreSwitches: Boolean;
|
||||
i: Integer;
|
||||
begin
|
||||
missingFilenames := '';
|
||||
NoMoreSwitches := False;
|
||||
|
||||
for i := 1 to ParamCount do
|
||||
begin
|
||||
if not NoMoreSwitches and ParamStr(i).StartsWith('-') then
|
||||
begin
|
||||
// We will treat all `-x` and `--x` parameters as command-line switches,
|
||||
// which provides forward compatibility for when we want to support
|
||||
// additional switches. A single `--` parameter tells us that remaining
|
||||
// parameters are filenames, even if they start with `-`.
|
||||
if ParamStr(i) = '--' then
|
||||
NoMoreSwitches := True;
|
||||
Continue;
|
||||
end;
|
||||
|
||||
filename := ExpandFileName(ParamStr(i));
|
||||
if FileExists(filename)
|
||||
then filenames.Add(filename)
|
||||
else missingFilenames := missingFilenames + '• ' + filename + #13#10;
|
||||
end;
|
||||
|
||||
if missingFilenames <> '' then
|
||||
begin
|
||||
ShowMessage('The following file(s) could not be found:'#13#10+missingFilenames);
|
||||
if filenames.Count = 0 then
|
||||
// If we only had bogus filenames passed in then we should abort
|
||||
Exit(False);
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function TTikeCommandLine.ProcessSubProcess: Boolean;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
if (ParamStr(1) <> '--sub-process') or (ParamCount < 2) then
|
||||
begin
|
||||
Exit(False);
|
||||
end;
|
||||
|
||||
// TODO(lowpri): Consider error handling
|
||||
|
||||
FStartupProjectPath := ParamStr(2);
|
||||
SetLength(FStartupFilenames, ParamCount - 2);
|
||||
for i := 3 to ParamCount do
|
||||
begin
|
||||
FStartupFilenames[i-3] := ParamStr(i);
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
/// Reads filenames passed on command line, and determines if they should be
|
||||
/// opened in an existing instance of Keyman Developer or in this instance, or
|
||||
/// even in multiple new instances
|
||||
function TTikeCommandLine.Process: TProcessCommandLine;
|
||||
var
|
||||
i: Integer;
|
||||
filename: string;
|
||||
filenames: TStringList;
|
||||
newProjects, launchProjects: TLaunchProjects;
|
||||
p: TLaunchProject;
|
||||
projectFilename: string;
|
||||
projects: TLaunchProjects;
|
||||
begin
|
||||
// If launched by LaunchNewInstance then we'll process that and continue
|
||||
if ProcessSubProcess then
|
||||
|
|
@ -131,14 +154,11 @@ begin
|
|||
end;
|
||||
|
||||
filenames := TStringList.Create;
|
||||
launchProjects := TLaunchProjects.Create;
|
||||
newProjects := TLaunchProjects.Create(False);
|
||||
projects := TLaunchProjects.Create(True);
|
||||
try
|
||||
// TODO: are there any other parameters passed to TIKE?
|
||||
for i := 1 to ParamCount do
|
||||
begin
|
||||
filenames.Add(ExpandFileName(ParamStr(i)));
|
||||
end;
|
||||
// Collect filenames passed in on command line
|
||||
if not GetFilenamesFromCommandLine(filenames) then
|
||||
Exit(pclExit);
|
||||
|
||||
if filenames.Count = 0 then
|
||||
begin
|
||||
|
|
@ -153,101 +173,27 @@ begin
|
|||
Exit(pclRun);
|
||||
end;
|
||||
|
||||
for filename in filenames do
|
||||
// Sort filenames into project groupings
|
||||
|
||||
projects.GroupFilenamesIntoProjects(filenames);
|
||||
|
||||
// Launch new processes or load files into existing processes
|
||||
|
||||
if not projects.LaunchAll(FProcesses)
|
||||
then Result := pclExit
|
||||
else Result := pclRun;
|
||||
|
||||
if Assigned(projects.StartupProject) then
|
||||
begin
|
||||
projectFilename := FindOwnerProjectForFile(filename);
|
||||
|
||||
p := launchProjects.Find(projectFilename);
|
||||
if p = nil then
|
||||
begin
|
||||
p := TLaunchProject.Create(projectFilename);
|
||||
launchProjects.Add(p);
|
||||
end;
|
||||
p.Filenames.Add(filename);
|
||||
end;
|
||||
|
||||
// First, hand off files to existing processes, based on project folder
|
||||
for p in launchProjects do
|
||||
begin
|
||||
if not PassProjectToRunningProcess(p) then
|
||||
begin
|
||||
newProjects.Add(p);
|
||||
end;
|
||||
end;
|
||||
|
||||
// If we've already passed all filenames off, then we're done
|
||||
if newProjects.Count = 0 then
|
||||
begin
|
||||
Exit(pclExit);
|
||||
end;
|
||||
|
||||
// If there's more than one project left, then we need to launch new
|
||||
// processes for each one; we'll take the first for ourselves
|
||||
FStartupFilenames := newProjects[0].Filenames.ToStringArray;
|
||||
FStartupProjectPath := newProjects[0].ProjectFilename;
|
||||
|
||||
newProjects.Delete(0);
|
||||
for p in newProjects do
|
||||
begin
|
||||
LaunchNewInstance(p);
|
||||
// LaunchAll will leave one startup project for this current process
|
||||
// instance to load
|
||||
FStartupFilenames := projects.StartupProject.Filenames.ToStringArray;
|
||||
FStartupProjectPath := projects.StartupProject.ProjectFilename;
|
||||
end;
|
||||
finally
|
||||
projects.Free;
|
||||
filenames.Free;
|
||||
end;
|
||||
|
||||
Result := pclRun;
|
||||
end;
|
||||
|
||||
function TTikeCommandLine.LaunchNewInstance(const project: TLaunchProject): Boolean;
|
||||
var
|
||||
filename, cmdline: string;
|
||||
begin
|
||||
cmdline := '"'+ParamStr(0)+'" --sub-process "'+project.ProjectFilename+'"';
|
||||
for filename in project.Filenames do
|
||||
cmdline := cmdline + ' "'+filename+'"';
|
||||
|
||||
Result := TUtilExecute.Execute(cmdline, GetCurrentDir, SW_SHOWNORMAL);
|
||||
end;
|
||||
|
||||
function TTikeCommandLine.ProcessSubProcess: Boolean;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
if (ParamStr(1) <> '--sub-process') or (ParamCount < 2) then
|
||||
begin
|
||||
Exit(False);
|
||||
end;
|
||||
|
||||
// TODO: Consider error handling
|
||||
|
||||
FStartupProjectPath := ParamStr(2);
|
||||
SetLength(FStartupFilenames, ParamCount - 2);
|
||||
for i := 3 to ParamCount do
|
||||
begin
|
||||
FStartupFilenames[i-3] := ParamStr(i);
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function TTikeCommandLine.PassProjectToRunningProcess(const project: TLaunchProject): Boolean;
|
||||
var
|
||||
tp: TTikeProcess;
|
||||
filename: string;
|
||||
begin
|
||||
for tp in FProcesses do
|
||||
begin
|
||||
if tp.OwnsProject(project.ProjectFilename) then
|
||||
begin
|
||||
Result := True;
|
||||
for filename in project.Filenames do
|
||||
begin
|
||||
tp.OpenFile(filename);
|
||||
end;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
var
|
||||
|
|
@ -260,20 +206,6 @@ begin
|
|||
Result := FInstance;
|
||||
end;
|
||||
|
||||
{ TLaunchProjects }
|
||||
|
||||
function TLaunchProjects.Find(const ProjectFilename: string): TLaunchProject;
|
||||
begin
|
||||
for Result in Self do
|
||||
begin
|
||||
if Result.ProjectFilename = ProjectFilename then
|
||||
begin
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
initialization
|
||||
finalization
|
||||
FreeAndNil(FInstance);
|
||||
|
|
|
|||
|
|
@ -133,13 +133,6 @@ end;
|
|||
|
||||
function TTikeProcess.OpenFile(const filename: string): Boolean;
|
||||
begin
|
||||
if SameFileName(filename, ProjectFilename) then
|
||||
begin
|
||||
// We always accept opening the project that is already open,
|
||||
// so just bring the process to the front
|
||||
Exit(FocusProcess);
|
||||
end;
|
||||
|
||||
if not TCopyDataHelper.SendData(0, WindowHandle, TCopyDataCommand.CD_OPENFILE, filename) then
|
||||
begin
|
||||
// The target app did not receive the data
|
||||
|
|
|
|||
|
|
@ -387,8 +387,8 @@ type
|
|||
|
||||
{ IDropTarget }
|
||||
|
||||
procedure Drop(const FileNames: array of string);
|
||||
function DropAllowed(const FileNames: array of string): Boolean;
|
||||
procedure Drop(const FileNames: TArray<string>);
|
||||
function DropAllowed(const FileNames: TArray<string>): Boolean;
|
||||
procedure InitDock;
|
||||
procedure LoadDockLayout;
|
||||
procedure SaveDockLayout;
|
||||
|
|
@ -445,7 +445,7 @@ type
|
|||
|
||||
function OpenEditor(FFileName: string; frmClass: TfrmTikeEditorClass): TfrmTikeEditor;
|
||||
function OpenFile(FFileName: string; FCloseNewFile: Boolean): TfrmTikeChild;
|
||||
procedure OpenFileInProject(FFileName: string);
|
||||
procedure OpenFilesInProject(FFileNames: TArray<string>);
|
||||
|
||||
procedure HelpTopic(s: string); overload;
|
||||
procedure HelpTopic(Sender: TTIKEForm); overload;
|
||||
|
|
@ -481,6 +481,7 @@ uses
|
|||
Keyman.System.KeymanSentryClient,
|
||||
Keyman.Developer.UI.TikeOnlineUpdateCheck,
|
||||
GlobalProxySettings,
|
||||
Keyman.Developer.System.LaunchProjects,
|
||||
Keyman.Developer.System.Project.ProjectFile,
|
||||
Keyman.Developer.System.Project.ProjectFileType,
|
||||
Keyman.Developer.System.Project.WelcomeRenderer,
|
||||
|
|
@ -488,6 +489,7 @@ uses
|
|||
Keyman.Developer.System.Project.ProjectLog,
|
||||
Keyman.Developer.System.Project.XmlLdmlProjectFile,
|
||||
Keyman.Developer.System.TikeCommandLine,
|
||||
Keyman.Developer.System.TikeMultiProcess,
|
||||
Keyman.Developer.UI.Project.ProjectFileUI,
|
||||
Keyman.Developer.UI.Project.ProjectUI,
|
||||
Keyman.Developer.UI.UfrmLdmlKeyboardEditor,
|
||||
|
|
@ -1207,12 +1209,55 @@ begin
|
|||
PostMessage(TfrmTikeChild(pages.Pages[Index].Tag).Handle, WM_CLOSE, 0, 0);
|
||||
end;
|
||||
|
||||
procedure TfrmKeymanDeveloper.OpenFileInProject(FFileName: string);
|
||||
procedure TfrmKeymanDeveloper.OpenFilesInProject(FFileNames: TArray<string>);
|
||||
var
|
||||
filename: string;
|
||||
filenames: TStringList;
|
||||
projects: TLaunchProjects;
|
||||
p: TLaunchProject;
|
||||
FProcesses: TTikeProcessList;
|
||||
begin
|
||||
if Length(FFileNames) = 0 then
|
||||
Exit;
|
||||
|
||||
// Uses the TTikeCommandLine pattern to open the resulting file either locally
|
||||
// or in a remote process
|
||||
// TODO
|
||||
OpenFile(FFileName, True);
|
||||
FProcesses := TikeMultiProcess.Enumerate;
|
||||
projects := TLaunchProjects.Create(False);
|
||||
filenames := TStringList.Create;
|
||||
try
|
||||
filenames.AddStrings(FFileNames);
|
||||
FFileNames := [];
|
||||
|
||||
// Sort filenames into project groupings
|
||||
projects.GroupFilenamesIntoProjects(filenames);
|
||||
|
||||
// Look for the current instance's project in the list of projects, and
|
||||
// capture its list of filenames separately
|
||||
if IsGlobalProjectUIReady then
|
||||
begin
|
||||
for p in projects do
|
||||
begin
|
||||
if SameFileName(p.ProjectFilename, FGlobalProject.FileName) then
|
||||
begin
|
||||
FFileNames := p.Filenames.ToStringArray;
|
||||
projects.Remove(p);
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Launch new processes or load files into existing processes
|
||||
projects.LaunchAll(FProcesses);
|
||||
finally
|
||||
filenames.Free;
|
||||
projects.Free;
|
||||
FProcesses.Free;
|
||||
end;
|
||||
|
||||
// Finally, open the remaining files which belong to this instance's project
|
||||
for filename in FFileNames do
|
||||
OpenFile(filename, True);
|
||||
end;
|
||||
|
||||
function TfrmKeymanDeveloper.OpenFile(FFileName: string; FCloseNewFile: Boolean): TfrmTikeChild;
|
||||
|
|
@ -1253,6 +1298,12 @@ begin
|
|||
|
||||
if ext = Ext_ProjectSource then
|
||||
begin
|
||||
if SameFileName(GetGlobalProjectUI.FileName, FFileName) then
|
||||
begin
|
||||
ShowProject;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if BeforeOpenProject then
|
||||
modActionsMain.OpenProject(FFileName);
|
||||
end
|
||||
|
|
@ -1420,23 +1471,19 @@ end;
|
|||
procedure TfrmKeymanDeveloper.mnuFileRecentFileClick(Sender: TObject);
|
||||
begin
|
||||
with Sender as TMenuItem do
|
||||
OpenFileInProject(Hint);
|
||||
OpenFilesInProject([Hint]);
|
||||
end;
|
||||
|
||||
{-------------------------------------------------------------------------------
|
||||
- Drag and Drop functionality -
|
||||
-------------------------------------------------------------------------------}
|
||||
|
||||
procedure TfrmKeymanDeveloper.Drop(const FileNames: array of string);
|
||||
var
|
||||
i: Integer;
|
||||
procedure TfrmKeymanDeveloper.Drop(const FileNames: TArray<string>);
|
||||
begin
|
||||
for i := Low(Filenames) to High(Filenames) do
|
||||
OpenFileInProject(Filenames[i]);
|
||||
OpenFilesInProject(FileNames);
|
||||
end;
|
||||
|
||||
function TfrmKeymanDeveloper.DropAllowed(
|
||||
const FileNames: array of string): Boolean;
|
||||
function TfrmKeymanDeveloper.DropAllowed(const FileNames: TArray<string>): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -440,9 +440,9 @@ begin
|
|||
pf := SelectedProjectFile;
|
||||
if Assigned(pf) then (pf.UI as TProjectFileUI).DefaultEvent(Self) // I4687
|
||||
else if SelectedMRUFileName <> '' then
|
||||
frmKeymanDeveloper.OpenFileInProject(SelectedMRUFileName)
|
||||
frmKeymanDeveloper.OpenFilesInProject([SelectedMRUFileName])
|
||||
else if Params.Values['name'] <> '' then
|
||||
frmKeymanDeveloper.OpenFileInProject(Params.Values['name']);
|
||||
frmKeymanDeveloper.OpenFilesInProject([Params.Values['name']]);
|
||||
end
|
||||
else if Command = 'viewfilesource' then
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -293,7 +293,9 @@ uses
|
|||
Keyman.Developer.System.MultiProcess in 'main\Keyman.Developer.System.MultiProcess.pas',
|
||||
Keyman.Developer.System.TikeMultiProcess in 'main\Keyman.Developer.System.TikeMultiProcess.pas',
|
||||
Keyman.System.CopyDataHelper in 'main\Keyman.System.CopyDataHelper.pas',
|
||||
Keyman.Developer.System.ProjectOwningFile in 'main\Keyman.Developer.System.ProjectOwningFile.pas';
|
||||
Keyman.Developer.System.ProjectOwningFile in 'main\Keyman.Developer.System.ProjectOwningFile.pas',
|
||||
Keyman.Developer.System.Main in 'main\Keyman.Developer.System.Main.pas',
|
||||
Keyman.Developer.System.LaunchProjects in 'main\Keyman.Developer.System.LaunchProjects.pas';
|
||||
|
||||
{$R *.RES}
|
||||
{$R ICONS.RES}
|
||||
|
|
@ -304,46 +306,6 @@ uses
|
|||
// If you don't add this flag the rederer process will crash when you try to load large images.
|
||||
{$SetPEFlags IMAGE_FILE_LARGE_ADDRESS_AWARE}
|
||||
|
||||
const
|
||||
LOGGER_DEVELOPER_IDE_TIKE = TKeymanSentryClient.LOGGER_DEVELOPER_IDE + '.tike';
|
||||
begin
|
||||
TKeymanSentryClient.Start(TSentryClientVcl, kscpDeveloper, LOGGER_DEVELOPER_IDE_TIKE);
|
||||
try
|
||||
|
||||
case TikeCommandLine.Process of
|
||||
pclExit:
|
||||
Exit;
|
||||
end;
|
||||
|
||||
try
|
||||
CoInitFlags := COINIT_APARTMENTTHREADED;
|
||||
|
||||
FInitializeCEF := TCEFManager.Create;
|
||||
try
|
||||
if FInitializeCEF.Start then
|
||||
begin
|
||||
InitThemeLibrary;
|
||||
SetThemeAppProperties(STAP_ALLOW_NONCLIENT or STAP_ALLOW_CONTROLS or STAP_ALLOW_WEBCONTENT);
|
||||
Application.MainFormOnTaskBar := True;
|
||||
Application.Initialize;
|
||||
Application.Title := 'Keyman Developer';
|
||||
Application.CreateForm(TmodWebHttpServer, modWebHttpServer);
|
||||
try
|
||||
Application.CreateForm(TfrmKeymanDeveloper, frmKeymanDeveloper);
|
||||
Application.Run;
|
||||
finally
|
||||
FreeAndNil(frmKeymanDeveloper);
|
||||
FreeAndNil(modWebHttpServer);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
FInitializeCEF.Free;
|
||||
end;
|
||||
except
|
||||
on E:Exception do
|
||||
SentryHandleException(E);
|
||||
end;
|
||||
finally
|
||||
TKeymanSentryClient.Stop;
|
||||
end;
|
||||
RunKeymanDeveloper;
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -582,6 +582,8 @@
|
|||
<DCCReference Include="main\Keyman.Developer.System.TikeMultiProcess.pas"/>
|
||||
<DCCReference Include="main\Keyman.System.CopyDataHelper.pas"/>
|
||||
<DCCReference Include="main\Keyman.Developer.System.ProjectOwningFile.pas"/>
|
||||
<DCCReference Include="main\Keyman.Developer.System.Main.pas"/>
|
||||
<DCCReference Include="main\Keyman.Developer.System.LaunchProjects.pas"/>
|
||||
<None Include="Profiling\AQtimeModule1.aqt"/>
|
||||
<BuildConfiguration Include="Debug">
|
||||
<Key>Cfg_2</Key>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue