mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
1137 lines
39 KiB
ObjectPascal
1137 lines
39 KiB
ObjectPascal
{
|
|
Double Commander
|
|
-------------------------------------------------------------------------
|
|
This unit contains some functions for open files in associated applications.
|
|
|
|
Copyright (C) 2006-2019 Alexander Koblov (alexx2000@mail.ru)
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
}
|
|
|
|
unit uShellExecute;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, uFile, uFileView, fMain;
|
|
|
|
const
|
|
ASCII_DLE = #16;
|
|
|
|
type
|
|
TPrepareParameterOption = (ppoNormalizePathDelims, ppoReplaceTilde);
|
|
TPrepareParameterOptions = set of TPrepareParameterOption;
|
|
|
|
function PrepareParameter(sParam: string; paramFile: TFile = nil; options: TPrepareParameterOptions = []; pbShowCommandLinePriorToExecute: PBoolean = nil; pbRunInTerminal: PBoolean = nil; pbKeepTerminalOpen: PBoolean = nil; pbAbortOperation: PBoolean = nil): string; overload;
|
|
|
|
{en
|
|
Replace variable parameters that depend on files in panels.
|
|
}
|
|
function ReplaceVarParams(sSourceStr: string; paramFile: TFile = nil; pbShowCommandLinePriorToExecute: PBoolean = nil; pbRunInTerminal: PBoolean = nil; pbKeepTerminalOpen: PBoolean = nil; pbAbortOperation: PBoolean = nil): string; overload;
|
|
{en
|
|
Replace variable parameters that depend on the file in active dir.
|
|
}
|
|
|
|
function ProcessExtCommandFork(sCmd: string; sParams: string = ''; sWorkPath: string = ''; paramFile: TFile = nil; bTerm: boolean = False; bKeepTerminalOpen: boolean = False): boolean;
|
|
|
|
function ShellExecuteEx(sActionName, sFileName, sActiveDir: string): boolean;
|
|
|
|
implementation
|
|
|
|
uses
|
|
//Notes: StrUtils is here first, so because of visibility rules, if a called
|
|
// routine name is present both in "StrUtils" and one of the following
|
|
// it will be one of the following that will be used and not the one
|
|
// "StrUtils". Make sure to let "StrUtils" at the first position.
|
|
// "StrUtils" is here to have the "PosEx".
|
|
//Lazarus, Free-Pascal, etc.
|
|
StrUtils, Dialogs, SysUtils, Process, UTF8Process, LazUTF8, LConvEncoding,
|
|
DCUnicodeUtils,
|
|
|
|
//DC
|
|
uShowMsg, uDCUtils, uLng, uFormCommands, fViewer, fEditor, uShowForm, uGlobs,
|
|
uOSUtils, uFileSystemFileSource, DCOSUtils, DCStrUtils, DCClassesUtf8;
|
|
|
|
//Dialogs,
|
|
//LConvEncoding;
|
|
|
|
|
|
(*
|
|
Functions (without parameters they give output for all selected files):
|
|
|
|
Miscellaneous:
|
|
%? - as first parameter only, it will show report to show command line prior to execute
|
|
%% - to use one time the percent sign
|
|
|
|
"File related":
|
|
------------------------------------------------------------------------------
|
|
%f - only filename
|
|
%d - only path of the file
|
|
%z - last directory of path of the file
|
|
%p - path + filename
|
|
%o - only the filename with no extension
|
|
%e - only the file extension
|
|
%v - only relative path + filename
|
|
|
|
%D - current path in active or chosen panel
|
|
%Z - last directory of path of active or chosen panel
|
|
%a - address + path + filename
|
|
%A - current address in active or chosen panel
|
|
%F - file list with file name only
|
|
%L - file list with full file name
|
|
%R - file list with relative path + file name
|
|
%F, %L and %R - create a list file in the TEMP directory with the names of the selected
|
|
files and directories, and appends the name of the list file to the command line
|
|
|
|
"Choosing encoding" for %F, $L and %R (if not given, system encoding used):
|
|
---------------------------------------------------------------------------
|
|
%X[U|W|Q] - where X is function %F, %L or %R
|
|
U - UTF-8,
|
|
W - UTF-16 (with byte order marker),
|
|
Q - quote file name by double quotes
|
|
|
|
"Choosing panel" (if not given, active panel is used):
|
|
------------------------------------------------------------------------------
|
|
%X[l|r|s|t] - where X is function (l - left, r - right, s - source, t - target)
|
|
s - source or active panel (no matter if it's left or right)
|
|
t - target or inactive panel (no matter if it's left or right)
|
|
l - left panel
|
|
r - right panel
|
|
b - both panels, left first, right second
|
|
p - both panels, active first, inactive second
|
|
|
|
"Choosing selected files" (only for %f, %d, %p, %o and %e):
|
|
------------------------------------------------------------------------------
|
|
%X[<nr>] - where X is function
|
|
<nr> is 1..n, where n is number of selected files.
|
|
Also <nr> can be 0, file under cursor is used in this case.
|
|
If there are no selected files, currently active file is nr 1.
|
|
If <nr> is invalid or there is no selected file by that number the result for the whole function will be empty string.
|
|
|
|
"Adding prefix, postfix before or after the result string":
|
|
------------------------------------------------------------------------------
|
|
%X[{<prefix>}][{<postfix>}]
|
|
If applied to multiple files, each name is prefixed/postfixed.
|
|
|
|
Control if %f, %d, etc... will return name between quotes or not
|
|
----------------------------------------------------------------
|
|
%" - will set default. For DC legacy is quoted
|
|
%"0 - will make the result unquoted
|
|
%"1 - will make the result quoted
|
|
|
|
Control if %D, %d etc... will return path name with the ending delimiter or not
|
|
-------------------------------------------------------------------------------
|
|
%/ - will set default. For DC legacy it was without ending delimited
|
|
%/0 - will exclude the ending delimiter
|
|
%/1 - will include the ending delimiter
|
|
|
|
Prompt the user with a sentence, propose a default, and use what the user typed
|
|
-------------------------------------------------------------------------------
|
|
%[This required the \\DB-2010\ server to be online!] - if no default value is given, DC will simply shows the message, assuming it's simply to echo a message.
|
|
%[Enter with required;1024] - This is an example. The text following the ";" indicates that default value is 1024
|
|
%[First name;%o] - The text proposed in the parameter value may be parsed prior to be displayed to user. For example here, the %o will be substituted to the filename with no extension prior to be displayed to user.
|
|
|
|
Control what will be the effective "%" char (for situation where we want the "%" to be the "#" sign instead
|
|
-----------------------------------------------------------------------------------------------------------
|
|
%# - Will set the percent-variable indicator to be the "#" character from now on when evaluating the line.
|
|
Note that it will be evaluated -only- when the current percent-variable indicator is "%".
|
|
#% - Will set the percent-variable indicator to be the "%" character from now on when evaluating the line.
|
|
Note that it will be evaluated -only- when the current percent-variable indicator is "#".
|
|
|
|
Control if it run in terminal, if it close it at the end or not
|
|
---------------------------------------------------------------
|
|
%t - Will have it run in terminal for sure, close or not depend of the action requested
|
|
%t0 - Will run in terminal AND request to close it at the end
|
|
%t1 - Will run in terminal AND let it run at the end
|
|
|
|
Above parameters can be combined together.
|
|
------------------------------------------------------------------------------
|
|
Order of params:
|
|
- %function
|
|
- quoting and encoding (only for %F, %L and %R)
|
|
- left or right or source or target panel (optional)
|
|
- nr of file (optional)
|
|
- prefix, postfix (optional)
|
|
|
|
Examples:
|
|
%f1 - first selected file in active panel
|
|
%pr2 - full path of second selected file in right panel
|
|
%fl - only filenames from left panel
|
|
%pr - full filenames from right panel
|
|
%Dl - current path in left panel
|
|
%f{-f } - prepend each name with "-f "
|
|
(ex.: -f <file_1> -f <file_2>)
|
|
%f{"}{"} - enclose each name in quotes
|
|
(ex.: "<file_1>" "<file_2>")
|
|
%f1{-first }%f2{ -second }
|
|
- if only 1 file selected : -first <file_1>
|
|
- if 2 (or more) files selected: -first <file_1> -second <file_2>
|
|
*)
|
|
function ReplaceVarParams(sSourceStr: string; paramFile: TFile; pbShowCommandLinePriorToExecute: PBoolean; pbRunInTerminal: PBoolean; pbKeepTerminalOpen: PBoolean; pbAbortOperation: PBoolean = nil): string;
|
|
type
|
|
TFunctType = (ftNone, ftName, ftDir, ftLastDir, ftPath, ftSingleDir, ftLastSingleDir, ftSource, ftSourcePath,
|
|
ftFileFullList, ftFileNameList, ftRelativeFileNameList,
|
|
ftNameNoExt, ftExtension, ftRelativePath,
|
|
ftProcessPercentSignIndicator, ftJustSetTheShowFlag,
|
|
ftSetTrailingDelimiter, ftSetQuoteOrNot, ftSetTerminalOptions,
|
|
ftEchoSimpleMessage, ftPromptUserForParam, ftExecuteConsoleCommand);
|
|
TFuncModifiers = set of (fmQuote, fmUTF8, fmUTF16);
|
|
TStatePos = (spNone, spPercent, spFunction, spPrefix, spPostfix,
|
|
spGotPrefix, spSide, spIndex, spUserInputOrEcho,
|
|
spGotInputHintWaitEndDefaultVal, spGetExecuteConsoleCommand,
|
|
spComplete);
|
|
|
|
var
|
|
leftFiles: TFiles = nil;
|
|
rightFiles: TFiles = nil;
|
|
singleFileFiles: TFiles = nil;
|
|
leftFile: TFile;
|
|
rightFile: TFile;
|
|
activeFile: TFile;
|
|
inactiveFile: TFile;
|
|
activeFiles: TFiles;
|
|
inactiveFiles: TFiles;
|
|
activeDir: string;
|
|
inactiveDir: string;
|
|
activeAddress: string;
|
|
inactiveAddress: string;
|
|
bTrailingDelimiter: boolean = False;
|
|
bQuoteOrNot: boolean = True;
|
|
CurrentPercentIndicator: char = '%';
|
|
bKeepProcessParameter:boolean = true;
|
|
|
|
// There is a inside recursive function because of the %[ that could have a parameter that could be parsed using the same function.
|
|
// It would have been possible to simply call again "ReplaceVarParams" without an inner function...
|
|
// ...but there are a few things that would have not work as what the user would expect.
|
|
// For example, if user would have wrote previously %"0 to have the following not include the quote, by simply recalling "ReplaceVarParams" itself, if he would have used then a %o in the default parameter value for the %[ , the filename would have been quoted again since it's the default when entering into the "ReplaceVarParams" function originally...
|
|
// Same similar problem with the bTrailingDelimiter, etc.
|
|
// So that's why there is an inner recursive functions where the kind of local-global flag like the ones mentionned above have to be global for the current parsed string.
|
|
function InnerRecursiveReplaceVarParams(sSourceStr: string; paramFile: TFile; pbShowCommandLinePriorToExecute: PBoolean; pbRunInTerminal: PBoolean; pbKeepTerminalOpen: PBoolean; pbAbortOperation: PBoolean = nil): string;
|
|
type
|
|
Tstate = record
|
|
pos: TStatePos;
|
|
functStartIndex: integer;
|
|
funct: TFunctType;
|
|
functMod: TFuncModifiers;
|
|
files: TFiles;
|
|
otherfiles: TFiles;
|
|
fil: TFile;
|
|
otherfil: TFile;
|
|
dir: string;
|
|
address: string;
|
|
sFileIndex: string;
|
|
prefix, postfix: string; // a string to add before/after each output
|
|
// (for functions giving output of multiple strings)
|
|
sSubParam: string;
|
|
sUserMessage: string;
|
|
end;
|
|
|
|
var
|
|
index: integer;
|
|
state: Tstate;
|
|
sOutput: string = '';
|
|
parseStartIndex: integer;
|
|
|
|
function BuildName(aFile: TFile): string;
|
|
begin
|
|
//1. Processing according to function requested
|
|
case state.funct of
|
|
ftName, ftDir, ftLastDir, ftPath, ftNameNoExt, ftExtension, ftSingleDir, ftLastSingleDir, ftRelativePath, ftSource, ftSourcePath:
|
|
begin
|
|
case state.funct of
|
|
ftName:
|
|
Result := aFile.Name;
|
|
|
|
ftDir:
|
|
Result := aFile.Path;
|
|
|
|
ftLastDir:
|
|
Result := GetLastDir(aFile.Path);
|
|
|
|
ftPath:
|
|
Result := aFile.FullPath;
|
|
|
|
ftNameNoExt:
|
|
Result := aFile.NameNoExt;
|
|
|
|
ftExtension:
|
|
Result := aFile.Extension;
|
|
|
|
ftRelativePath:
|
|
Result := ExtractRelativepath(state.dir, aFile.FullPath);
|
|
|
|
ftSingleDir:
|
|
Result := state.dir;
|
|
|
|
ftLastSingleDir:
|
|
Result := GetLastDir(state.dir);
|
|
|
|
ftSource:
|
|
Result := state.address;
|
|
|
|
ftSourcePath:
|
|
Result := state.address + aFile.FullPath;
|
|
end;
|
|
end;
|
|
else
|
|
Exit('');
|
|
end;
|
|
|
|
//2. Processing the prefix/postfix requested
|
|
Result := state.prefix + Result + state.postfix;
|
|
|
|
//3. Processing the trailing path delimiter requested
|
|
case state.funct of
|
|
ftDir, ftLastDir, ftSingleDir, ftLastSingleDir:
|
|
begin
|
|
if bTrailingDelimiter then
|
|
Result := IncludeTrailingPathDelimiter(Result)
|
|
else
|
|
Result := ExcludeBackPathDelimiter(Result);
|
|
end;
|
|
end;
|
|
|
|
//4. Processing the quotes requested
|
|
if bQuoteOrNot then
|
|
Result := QuoteStr(Result);
|
|
end;
|
|
|
|
function BuildAllNames: string;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := '';
|
|
|
|
if Assigned(state.files) then
|
|
for i := 0 to pred(state.files.Count) do
|
|
Result := ConcatenateStrWithSpace(Result, BuildName(state.files[i]));
|
|
|
|
if Assigned(state.otherfiles) then
|
|
for i := 0 to pred(state.otherfiles.Count) do
|
|
Result := ConcatenateStrWithSpace(Result, BuildName(state.otherfiles[i]));
|
|
end;
|
|
|
|
function BuildFile(aFile: TFile): string;
|
|
begin
|
|
case state.funct of
|
|
ftFileFullList: Result := aFile.FullPath;
|
|
ftFileNameList: Result := aFile.Name;
|
|
ftRelativeFileNameList: Result := ExtractRelativepath(state.dir, aFile.FullPath);
|
|
else
|
|
Result := aFile.Name;
|
|
end;
|
|
|
|
if aFile.isDirectory then
|
|
begin
|
|
if bTrailingDelimiter then
|
|
Result := IncludeTrailingPathDelimiter(Result)
|
|
else
|
|
Result := ExcludeBackPathDelimiter(Result);
|
|
end;
|
|
|
|
if (fmQuote in state.functMod) then
|
|
Result := '"' + Result + '"';
|
|
|
|
if (fmUTF16 in state.functMod) then
|
|
Result := Utf8ToUtf16LE(Result)
|
|
else if not (fmUTF8 in state.functMod) then
|
|
Result := UTF8ToSys(Result);
|
|
end;
|
|
|
|
function BuildFileList: String;
|
|
var
|
|
I: integer;
|
|
FileName: ansistring;
|
|
FileList: TFileStreamEx;
|
|
LineEndingA: ansistring = LineEnding;
|
|
begin
|
|
Result := GetTempName(GetTempFolderDeletableAtTheEnd + 'Filelist') + '.lst';
|
|
try
|
|
FileList := TFileStreamEx.Create(Result, fmCreate);
|
|
try
|
|
if fmUTF16 in state.functMod then
|
|
begin
|
|
FileName := UTF16LEBOM;
|
|
LineEndingA := Utf8ToUtf16LE(LineEnding);
|
|
end;
|
|
|
|
if Assigned(state.files) then
|
|
begin
|
|
if state.files.Count > 0 then
|
|
begin
|
|
for I := 0 to state.files.Count - 1 do
|
|
FileName += BuildFile(state.files[I]) + LineEndingA;
|
|
end;
|
|
end;
|
|
|
|
if Assigned(state.otherfiles) then
|
|
begin
|
|
if state.otherfiles.Count > 0 then
|
|
begin
|
|
FileName += LineEndingA;
|
|
|
|
for I := 0 to state.otherfiles.Count - 1 do
|
|
FileName += BuildFile(state.otherfiles[I]) + LineEndingA;
|
|
end;
|
|
end;
|
|
|
|
FileList.Write(FileName[1], Length(FileName));
|
|
finally
|
|
FileList.Free;
|
|
end;
|
|
except
|
|
Result := EmptyStr;
|
|
end;
|
|
end;
|
|
|
|
procedure ResetState(var aState: TState);
|
|
begin
|
|
with aState do
|
|
begin
|
|
pos := spNone;
|
|
fil := activeFile;
|
|
otherfil := nil;
|
|
if paramFile <> nil then
|
|
files := singleFileFiles
|
|
else
|
|
files := activeFiles;
|
|
otherfiles := nil;
|
|
dir := activeDir;
|
|
address := activeAddress;
|
|
sFileIndex := '';
|
|
funct := ftNone;
|
|
functMod := [];
|
|
functStartIndex := 0;
|
|
prefix := '';
|
|
postfix := '';
|
|
sSubParam := '';
|
|
sUserMessage := '';
|
|
end;
|
|
end;
|
|
|
|
procedure AddParsedText(limit: integer);
|
|
begin
|
|
// Copy [parseStartIndex .. limit - 1].
|
|
if limit > parseStartIndex then
|
|
sOutput := sOutput + Copy(sSourceStr, parseStartIndex, limit - parseStartIndex);
|
|
parseStartIndex := index;
|
|
end;
|
|
|
|
procedure SetTrailingPathDelimiter;
|
|
begin
|
|
bTrailingDelimiter := state.sSubParam = '1';
|
|
// Currently in the code, anything else than "0" will include the trailing delimiter.
|
|
// BUT, officially, in the documentation, just state that 0 or 1 is required.
|
|
// This could give room for future addition maybe.
|
|
end;
|
|
|
|
procedure SetQuoteOrNot;
|
|
begin
|
|
bQuoteOrNot := not (state.sSubParam = '0');
|
|
// Currently in the code, anything else than "0" will indicate we want to quote
|
|
// BUT, officially, in the documentation, just state that 0 or 1 is required.
|
|
// This could give room for future addition maybe.
|
|
end;
|
|
|
|
procedure SetTerminalOptions;
|
|
begin
|
|
if pbRunInTerminal <> nil then
|
|
begin
|
|
pbRunInTerminal^ := True;
|
|
if pbKeepTerminalOpen <> nil then
|
|
pbKeepTerminalOpen^ := not (state.sSubParam = '0');
|
|
end;
|
|
end;
|
|
|
|
procedure JustEchoTheMessage;
|
|
begin
|
|
state.sUserMessage := InnerRecursiveReplaceVarParams(state.sUserMessage, paramFile, pbShowCommandLinePriorToExecute, pbRunInTerminal, pbKeepTerminalOpen, pbAbortOperation);
|
|
msgOK(state.sUserMessage);
|
|
end;
|
|
|
|
procedure AskUserParamAndReplace;
|
|
begin
|
|
state.sSubParam := InnerRecursiveReplaceVarParams(state.sSubParam, paramFile, pbShowCommandLinePriorToExecute, pbRunInTerminal, pbKeepTerminalOpen, pbAbortOperation);
|
|
if ShowInputQuery(rsMsgCofirmUserParam, state.sUserMessage, state.sSubParam) then
|
|
begin
|
|
sOutput := sOutput + state.sSubParam;
|
|
end
|
|
else
|
|
begin
|
|
bKeepProcessParameter:=False;
|
|
end;
|
|
end;
|
|
|
|
procedure ExecuteConsoleCommand;
|
|
var
|
|
sTmpFilename, sShellCmdLine: string;
|
|
Process: TProcessUTF8;
|
|
begin
|
|
sTmpFilename := GetTempName(GetTempFolderDeletableAtTheEnd) + '.tmp';
|
|
//sShellCmdLine := Copy(state.sSubParam, 3, length(state.sSubParam)-2) + ' > ' + QuoteStr(sTmpFilename);
|
|
sShellCmdLine := state.sSubParam + ' > ' + QuoteStr(sTmpFilename);
|
|
Process := TProcessUTF8.Create(nil);
|
|
try
|
|
Process.CommandLine := FormatShell(sShellCmdLine);
|
|
Process.Options := [poNoConsole, poWaitOnExit];
|
|
Process.Execute;
|
|
finally
|
|
Process.Free;
|
|
end;
|
|
sOutput := sOutput + sTmpFilename;
|
|
end;
|
|
|
|
procedure ProcessPercentSignIndicator;
|
|
begin
|
|
if CurrentPercentIndicator = state.sSubParam then
|
|
sOutput := sOutput + state.sSubParam
|
|
else
|
|
if CurrentPercentIndicator = '%' then
|
|
CurrentPercentIndicator := '#'
|
|
else
|
|
CurrentPercentIndicator := '%';
|
|
end;
|
|
|
|
procedure DoFunction;
|
|
var
|
|
fileIndex: integer = -2;
|
|
OffsetFromStart: integer = 0;
|
|
begin
|
|
AddParsedText(state.functStartIndex);
|
|
|
|
if state.sFileIndex <> '' then
|
|
try
|
|
fileIndex := StrToInt(state.sFileIndex);
|
|
fileIndex := fileIndex - 1; // Files are counted from 0, but user enters 1..n.
|
|
except
|
|
on EConvertError do
|
|
fileIndex := -2;
|
|
end;
|
|
|
|
if fileIndex = -1 then
|
|
begin
|
|
if Assigned(state.fil) then
|
|
sOutput := sOutput + BuildName(state.fil);
|
|
if Assigned(state.otherfil) then
|
|
sOutput := ConcatenateStrWithSpace(sOutput, BuildName(state.otherfil));
|
|
end
|
|
else if fileIndex > -1 then
|
|
begin
|
|
if (fileIndex >= 0) and Assigned(state.files) then
|
|
begin
|
|
if fileIndex < state.files.Count then
|
|
sOutput := sOutput + BuildName(state.files[fileIndex]);
|
|
OffsetFromStart := state.files.Count;
|
|
end;
|
|
|
|
if ((fileIndex - OffsetFromStart) >= 0) and Assigned(state.otherfiles) then
|
|
if (fileIndex - OffsetFromStart) < state.otherfiles.Count then
|
|
sOutput := sOutput + BuildName(state.otherfiles[fileIndex - OffsetFromStart]);
|
|
end
|
|
else
|
|
begin
|
|
if state.funct in [ftName, ftPath, ftDir, ftLastDir, ftNameNoExt, ftSourcePath, ftExtension, ftRelativePath] then
|
|
sOutput := sOutput + BuildAllNames
|
|
else if state.funct in [ftSingleDir, ftLastSingleDir, ftSource] then // only single current dir
|
|
sOutput := sOutput + BuildName(nil)
|
|
else if state.funct in [ftFileFullList, ftFileNameList, ftRelativeFileNameList] then // for list of file
|
|
sOutput := sOutput + BuildFileList
|
|
else if state.funct in [ftProcessPercentSignIndicator] then // only add % sign
|
|
ProcessPercentSignIndicator
|
|
else if state.funct in [ftJustSetTheShowFlag] then //only set the flag to show the params prior to execute
|
|
begin
|
|
if pbShowCommandLinePriorToExecute <> nil then
|
|
pbShowCommandLinePriorToExecute^ := True;
|
|
end
|
|
else if state.funct in [ftSetTrailingDelimiter] then //set the trailing path delimiter
|
|
SetTrailingPathDelimiter
|
|
else if state.funct in [ftSetQuoteOrNot] then
|
|
SetQuoteOrNot
|
|
else if state.funct in [ftEchoSimpleMessage] then
|
|
JustEchoTheMessage
|
|
else if state.funct in [ftPromptUserForParam] then
|
|
AskUserParamAndReplace
|
|
else if state.funct in [ftSetTerminalOptions] then
|
|
SetTerminalOptions
|
|
else if state.funct in [ftExecuteConsoleCommand] then
|
|
ExecuteConsoleCommand;
|
|
end;
|
|
|
|
ResetState(state);
|
|
end;
|
|
|
|
procedure ProcessNumber;
|
|
begin
|
|
case state.funct of
|
|
ftSingleDir, ftLastSingleDir: state.pos := spComplete; // Numbers not allowed for %D and %Z
|
|
ftSetTrailingDelimiter, ftSetQuoteOrNot, ftSetTerminalOptions:
|
|
begin
|
|
state.sSubParam := state.sSubParam + sSourceStr[index];
|
|
state.pos := spComplete;
|
|
Inc(Index);
|
|
end;
|
|
else
|
|
begin
|
|
state.sFileIndex := state.sFileIndex + sSourceStr[index];
|
|
state.pos := spIndex;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure ProcessOpenBracket; // '{'
|
|
begin
|
|
if state.pos <> spGotPrefix then
|
|
state.pos := spPrefix
|
|
else
|
|
state.pos := spPostfix;
|
|
end;
|
|
|
|
begin
|
|
index := 1;
|
|
parseStartIndex := index;
|
|
|
|
ResetState(state);
|
|
|
|
while (index <= Length(sSourceStr)) AND (bKeepProcessParameter) do
|
|
begin
|
|
case state.pos of
|
|
spNone:
|
|
if sSourceStr[index] = CurrentPercentIndicator then
|
|
begin
|
|
state.pos := spPercent;
|
|
state.functStartIndex := index;
|
|
end;
|
|
|
|
spPercent:
|
|
case sSourceStr[index] of
|
|
'?':
|
|
begin
|
|
state.funct := ftJustSetTheShowFlag;
|
|
state.pos := spComplete;
|
|
Inc(Index);
|
|
end;
|
|
|
|
ASCII_DLE:
|
|
begin
|
|
AddParsedText(state.functStartIndex);
|
|
parseStartIndex := Index + 1;
|
|
Index := Length(sSourceStr) + 1;
|
|
state.pos := spComplete;
|
|
Break;
|
|
end;
|
|
|
|
'%', '#':
|
|
begin
|
|
state.funct := ftProcessPercentSignIndicator;
|
|
state.sSubParam := sSourceStr[index];
|
|
state.pos := spComplete;
|
|
Inc(Index);
|
|
end;
|
|
|
|
'f', 'd', 'z', 'p', 'o', 'e', 'v', 'D', 'Z', 'A', 'a', 'n', 'h', '/', '"', 't':
|
|
begin
|
|
case sSourceStr[index] of
|
|
'f': state.funct := ftName;
|
|
'd': state.funct := ftDir;
|
|
'z': state.funct := ftLastDir;
|
|
'p': state.funct := ftPath;
|
|
'o': state.funct := ftNameNoExt;
|
|
'e': state.funct := ftExtension;
|
|
'v': state.funct := ftRelativePath;
|
|
'D': state.funct := ftSingleDir;
|
|
'Z': state.funct := ftLastSingleDir;
|
|
'A': state.funct := ftSource;
|
|
'a': state.funct := ftSourcePath;
|
|
'/': state.funct := ftSetTrailingDelimiter;
|
|
'"': state.funct := ftSetQuoteOrNot;
|
|
't': state.funct := ftSetTerminalOptions;
|
|
end;
|
|
state.pos := spFunction;
|
|
end;
|
|
|
|
'L', 'F', 'R':
|
|
begin
|
|
case sSourceStr[index] of
|
|
'L': state.funct := ftFileFullList;
|
|
'F': state.funct := ftFileNameList;
|
|
'R': state.funct := ftRelativeFileNameList;
|
|
end;
|
|
state.pos := spFunction;
|
|
end;
|
|
|
|
'[':
|
|
begin
|
|
state.pos := spUserInputOrEcho;
|
|
end;
|
|
|
|
'<':
|
|
begin
|
|
state.pos := spGetExecuteConsoleCommand;
|
|
end;
|
|
else
|
|
ResetState(state);
|
|
end;
|
|
|
|
spFunction:
|
|
case sSourceStr[index] of
|
|
'l', 'b':
|
|
begin
|
|
state.files := leftFiles;
|
|
state.fil := leftFile;
|
|
state.dir := frmMain.FrameLeft.CurrentPath;
|
|
state.address := frmMain.FrameLeft.CurrentAddress;
|
|
state.pos := spSide;
|
|
if sSourceStr[index] = 'b' then
|
|
begin
|
|
state.otherfiles := rightFiles;
|
|
state.otherfil := rightFile;
|
|
end;
|
|
end;
|
|
|
|
'r':
|
|
begin
|
|
state.files := rightFiles;
|
|
state.fil := rightFile;
|
|
state.dir := frmMain.FrameRight.CurrentPath;
|
|
state.address := frmMain.FrameRight.CurrentAddress;
|
|
state.pos := spSide;
|
|
end;
|
|
|
|
's', 'p':
|
|
begin
|
|
state.files := activeFiles;
|
|
state.fil := activeFile;
|
|
state.dir := activeDir;
|
|
state.address := activeAddress;
|
|
state.pos := spSide;
|
|
if sSourceStr[index] = 'p' then
|
|
begin
|
|
state.otherfil := inactiveFile;
|
|
state.otherfiles := inactiveFiles;
|
|
end;
|
|
end;
|
|
|
|
't':
|
|
begin
|
|
state.files := inactiveFiles;
|
|
state.fil := inactiveFile;
|
|
state.dir := inactiveDir;
|
|
state.address := inactiveAddress;
|
|
state.pos := spSide;
|
|
end;
|
|
|
|
'U':
|
|
begin
|
|
state.functMod += [fmUTF8];
|
|
state.pos := spFunction;
|
|
end;
|
|
|
|
'W':
|
|
begin
|
|
state.functMod += [fmUTF16];
|
|
state.pos := spFunction;
|
|
end;
|
|
|
|
'Q':
|
|
begin
|
|
state.functMod += [fmQuote];
|
|
state.pos := spFunction;
|
|
end;
|
|
|
|
'0'..'9':
|
|
ProcessNumber;
|
|
|
|
'{':
|
|
ProcessOpenBracket;
|
|
|
|
else
|
|
state.pos := spComplete;
|
|
end;
|
|
|
|
spSide:
|
|
case sSourceStr[index] of
|
|
'0'..'9':
|
|
ProcessNumber;
|
|
'{':
|
|
ProcessOpenBracket;
|
|
else
|
|
state.pos := spComplete;
|
|
end;
|
|
|
|
spIndex:
|
|
case sSourceStr[index] of
|
|
'0'..'9':
|
|
ProcessNumber;
|
|
'{':
|
|
ProcessOpenBracket;
|
|
else
|
|
state.pos := spComplete;
|
|
end;
|
|
|
|
spPrefix, spPostfix:
|
|
case sSourceStr[index] of
|
|
'}':
|
|
begin
|
|
if state.pos = spPostfix then
|
|
begin
|
|
Inc(index); // include closing bracket in the function
|
|
state.pos := spComplete;
|
|
end
|
|
else
|
|
state.pos := spGotPrefix;
|
|
end;
|
|
else
|
|
begin
|
|
case state.pos of
|
|
spPrefix:
|
|
state.prefix := state.prefix + sSourceStr[index];
|
|
spPostfix:
|
|
state.postfix := state.postfix + sSourceStr[index];
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
spGotPrefix:
|
|
case sSourceStr[index] of
|
|
'{':
|
|
ProcessOpenBracket;
|
|
else
|
|
state.pos := spComplete;
|
|
end;
|
|
|
|
spUserInputOrEcho:
|
|
begin
|
|
case sSourceStr[index] of
|
|
';':
|
|
begin
|
|
state.pos := spGotInputHintWaitEndDefaultVal;
|
|
end;
|
|
|
|
']':
|
|
begin
|
|
state.funct := ftEchoSimpleMessage;
|
|
state.pos := spComplete;
|
|
Inc(Index);
|
|
end;
|
|
else
|
|
State.sUserMessage := State.sUserMessage + sSourceStr[index];
|
|
end;
|
|
end;
|
|
|
|
spGotInputHintWaitEndDefaultVal:
|
|
begin
|
|
case sSourceStr[index] of
|
|
']':
|
|
begin
|
|
state.funct := ftPromptUserForParam;
|
|
state.pos := spComplete;
|
|
Inc(Index);
|
|
end;
|
|
else
|
|
State.sSubParam := State.sSubParam + sSourceStr[index];
|
|
end;
|
|
end;
|
|
|
|
spGetExecuteConsoleCommand:
|
|
begin
|
|
case sSourceStr[index] of
|
|
'>':
|
|
begin
|
|
state.funct := ftExecuteConsoleCommand;
|
|
state.pos := spComplete;
|
|
Inc(Index);
|
|
end;
|
|
else
|
|
State.sSubParam := State.sSubParam + sSourceStr[index];
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if state.pos <> spComplete then
|
|
Inc(index) // check next character
|
|
else
|
|
// Process function and then check current character again after resetting state.
|
|
DoFunction;
|
|
end;
|
|
|
|
// Finish current parse.
|
|
if bKeepProcessParameter then
|
|
begin
|
|
if state.pos in [spFunction, spSide, spIndex, spGotPrefix] then
|
|
DoFunction
|
|
else
|
|
AddParsedText(index);
|
|
end;
|
|
|
|
if bKeepProcessParameter then
|
|
Result := sOutput
|
|
else
|
|
if pbAbortOperation<>nil then
|
|
pbAbortOperation^ := True;
|
|
end;
|
|
|
|
begin
|
|
result := '';
|
|
try
|
|
leftFiles := frmMain.FrameLeft.CloneSelectedOrActiveFiles;
|
|
rightFiles := frmMain.FrameRight.CloneSelectedOrActiveFiles;
|
|
if paramFile <> nil then
|
|
begin
|
|
singleFileFiles := TFiles.Create(paramFile.Path);
|
|
singleFileFiles.Add(paramFile.Clone);
|
|
end;
|
|
|
|
leftFile:= frmMain.FrameLeft.CloneActiveFile;
|
|
rightFile:= frmMain.FrameRight.CloneActiveFile;
|
|
if Assigned(leftFile) and (not leftFile.IsNameValid) then
|
|
FreeAndNil(leftFile);
|
|
if Assigned(rightFile) and (not rightFile.IsNameValid) then
|
|
FreeAndNil(rightFile);
|
|
|
|
if frmMain.ActiveFrame = frmMain.FrameLeft then
|
|
begin
|
|
activeFiles := leftFiles;
|
|
activeFile:= leftFile;
|
|
inactiveFile:= rightFile;
|
|
activeDir := frmMain.FrameLeft.CurrentPath;
|
|
activeAddress := frmMain.FrameLeft.CurrentAddress;
|
|
inactiveFiles := rightFiles;
|
|
inactiveDir := frmMain.FrameRight.CurrentPath;
|
|
inactiveAddress := frmMain.FrameRight.CurrentAddress;
|
|
end
|
|
else
|
|
begin
|
|
activeFiles := rightFiles;
|
|
activeFile:= rightFile;
|
|
inactiveFile:= leftFile;
|
|
activeDir := frmMain.FrameRight.CurrentPath;
|
|
activeAddress := frmMain.FrameRight.CurrentAddress;
|
|
inactiveFiles := leftFiles;
|
|
inactiveDir := frmMain.FrameLeft.CurrentPath;
|
|
inactiveAddress := frmMain.FrameLeft.CurrentAddress;
|
|
end;
|
|
|
|
result:=InnerRecursiveReplaceVarParams(sSourceStr, paramFile, pbShowCommandLinePriorToExecute, pbRunInTerminal, pbKeepTerminalOpen, pbAbortOperation);
|
|
|
|
finally
|
|
FreeAndNil(leftFile);
|
|
FreeAndNil(rightFile);
|
|
FreeAndNil(leftFiles);
|
|
FreeAndNil(rightFiles);
|
|
FreeAndNil(singleFileFiles);
|
|
end;
|
|
end;
|
|
|
|
{ PrepareParameter }
|
|
function PrepareParameter(sParam: string; paramFile: TFile; options: TPrepareParameterOptions; pbShowCommandLinePriorToExecute: PBoolean; pbRunInTerminal: PBoolean; pbKeepTerminalOpen: PBoolean; pbAbortOperation: PBoolean = nil): string; overload;
|
|
begin
|
|
Result := sParam;
|
|
|
|
if ppoNormalizePathDelims in Options then
|
|
Result := NormalizePathDelimiters(Result);
|
|
|
|
if ppoReplaceTilde in Options then
|
|
Result := ReplaceTilde(Result);
|
|
|
|
Result := ReplaceEnvVars(Result);
|
|
|
|
Result := ReplaceVarParams(Result, paramFile, pbShowCommandLinePriorToExecute, pbRunInTerminal, pbKeepTerminalOpen,pbAbortOperation);
|
|
|
|
Result := Trim(Result);
|
|
end;
|
|
|
|
{ ProcessExtCommandFork }
|
|
function ProcessExtCommandFork(sCmd, sParams, sWorkPath: string; paramFile: TFile; bTerm: boolean; bKeepTerminalOpen: boolean): boolean;
|
|
var
|
|
sTmpFile, sShellCmdLine: string;
|
|
iStart, iCount: integer;
|
|
iLastConsoleCommandPos: integer = 0;
|
|
Process: TProcessUTF8;
|
|
sl: TStringList;
|
|
bShowCommandLinePriorToExecute: boolean = False;
|
|
bAbortOperationFlag: boolean = false;
|
|
begin
|
|
Result := False;
|
|
|
|
// 1. Parse the command, parameters and working directory for the percent-variable substitution.
|
|
sCmd := PrepareParameter(sCmd, paramFile, [ppoReplaceTilde]);
|
|
sParams := PrepareParameter(sParams, paramFile, [], @bShowCommandLinePriorToExecute, @bTerm, @bKeepTerminalOpen, @bAbortOperationFlag);
|
|
if not bAbortOperationFlag then sWorkPath := PrepareParameter(sWorkPath, paramFile, [ppoNormalizePathDelims, ppoReplaceTilde]);
|
|
|
|
// 2. If working directory has been specified, let's switch to it.
|
|
if not bAbortOperationFlag then
|
|
begin
|
|
if sWorkPath <> '' then
|
|
mbSetCurrentDir(sWorkPath);
|
|
|
|
// 3. If user has command-line to execute and get the result to a file, let's execute it.
|
|
// Check for <? ?> command.
|
|
// This command is used to put output of some console program to a file so
|
|
// that the file can then be viewed. The command is between '<?' and '?>'.
|
|
// The whole <?...?> expression is replaced with a path to the temporary file
|
|
// containing output of the command.
|
|
// For example:
|
|
// {!VIEWER} <?rpm -qivlp --scripts %p?>
|
|
// Show in Viewer information about RPM package
|
|
repeat
|
|
iStart := Posex('<?', sParams, (iLastConsoleCommandPos + 1)) + 2;
|
|
iCount := Posex('?>', sParams, iStart) - iStart;
|
|
if (iStart <> 0) and (iCount >= 0) then
|
|
begin
|
|
sTmpFile := GetTempName(GetTempFolderDeletableAtTheEnd) + '.tmp';
|
|
sShellCmdLine := Copy(sParams, iStart, iCount) + ' > ' + QuoteStr(sTmpFile);
|
|
Process := TProcessUTF8.Create(nil);
|
|
try
|
|
Process.CommandLine := FormatShell(sShellCmdLine);
|
|
Process.Options := [poWaitOnExit];
|
|
Process.ShowWindow := swoHide;
|
|
Process.Execute;
|
|
finally
|
|
Process.Free;
|
|
end;
|
|
sParams := Copy(sParams, 1, iStart - 3) + sTmpFile + Copy(sParams, iStart + iCount + 2, MaxInt);
|
|
iLastConsoleCommandPos := iStart;
|
|
end;
|
|
until ((iStart = 0) or (iCount < 0));
|
|
|
|
//4. If user user wanted to execute an internal command, let's do it.
|
|
if frmMain.Commands.Commands.ExecuteCommand(sCmd, [sParams]) = cfrSuccess then
|
|
begin
|
|
Result := True;
|
|
exit;
|
|
end;
|
|
|
|
//5. From legacy, invoking shell seems to be similar to "run in terminal with stay open" with param as-is
|
|
if Pos('{!SHELL}', sCmd) > 0 then
|
|
begin
|
|
sCmd := sParams;
|
|
sParams := '';
|
|
bTerm := True;
|
|
bKeepTerminalOpen := True;
|
|
end;
|
|
|
|
//6. If user wants to process via terminal (and close at the end), let's flag it.
|
|
if Pos('{!TERMANDCLOSE}', sCmd) > 0 then
|
|
begin
|
|
sCmd := RemoveQuotation(sParams);
|
|
sParams := '';
|
|
bTerm := True;
|
|
end;
|
|
|
|
//7. If user wants to process via terminal (and close at the end), let's flag it.
|
|
if Pos('{!TERMSTAYOPEN}', sCmd) > 0 then
|
|
begin
|
|
sCmd := RemoveQuotation(sParams);
|
|
sParams := '';
|
|
bTerm := True;
|
|
bKeepTerminalOpen := True;
|
|
end;
|
|
|
|
//8. If our end-job is to EDIT a file via what's configured as editor, let's do it.
|
|
if Pos('{!EDITOR}', sCmd) > 0 then
|
|
begin
|
|
uShowForm.ShowEditorByGlob(RemoveQuotation(sParams));
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
|
|
//9. If our end-job is to EDIT a file via internal editor, let's do it.
|
|
if Pos('{!DC-EDITOR}', sCmd) > 0 then
|
|
begin
|
|
fEditor.ShowEditor(RemoveQuotation(sParams));
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
|
|
//10. If our end-job is to VIEW a file via what's configured as viewer, let's do it.
|
|
if Pos('{!VIEWER}', sCmd) > 0 then
|
|
begin
|
|
uShowForm.ShowViewerByGlob(RemoveQuotation(sParams));
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
|
|
//11. If our end-job is to VIEW a file or files via internal viewer, let's do it.
|
|
if Pos('{!DC-VIEWER}', sCmd) > 0 then
|
|
begin
|
|
sl := TStringList.Create;
|
|
try
|
|
sl.Add(RemoveQuotation(sParams));
|
|
fViewer.ShowViewer(sl);
|
|
Result := True;
|
|
finally
|
|
FreeAndNil(sl);
|
|
end;
|
|
Exit;
|
|
end;
|
|
|
|
//12. Ok. If we're here now it's to execute something external so let's launch it!
|
|
try
|
|
Result := ExecCmdFork(sCmd, sParams, sWorkPath, bShowCommandLinePriorToExecute, bTerm, bKeepTerminalOpen);
|
|
except
|
|
on e: EInvalidCommandLine do
|
|
begin
|
|
MessageDlg(rsMsgInvalidCommandLine, rsMsgInvalidCommandLine + ': ' + e.Message, mtError, [mbOK], 0);
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
end; //if not bAbortOperationFlag
|
|
end;
|
|
|
|
function ShellExecuteEx(sActionName, sFileName, sActiveDir: string): boolean;
|
|
var
|
|
aFile: TFile;
|
|
sCmd, sParams, sStartPath: string;
|
|
begin
|
|
Result := False;
|
|
|
|
// Executing files directly only works for FileSystem.
|
|
aFile := TFileSystemFileSource.CreateFileFromFile(sFileName);
|
|
try
|
|
if gExts.GetExtActionCmd(aFile, sActionName, sCmd, sParams, sStartPath) then
|
|
begin
|
|
Result := ProcessExtCommandFork(sCmd, sParams, sStartPath, aFile);
|
|
end;
|
|
|
|
if not Result then
|
|
begin
|
|
mbSetCurrentDir(sActiveDir);
|
|
Result := ShellExecute(sFileName);
|
|
end;
|
|
|
|
finally
|
|
FreeAndNil(aFile);
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
end.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|