diff --git a/components/synedit/synedit.pp b/components/synedit/synedit.pp index 37b3a84e11..5964fd2ddb 100644 --- a/components/synedit/synedit.pp +++ b/components/synedit/synedit.pp @@ -505,6 +505,7 @@ type FInternalCaret: TSynEditCaret; //FScreenCaret: TSynEditScreenCaret; FInternalBlockSelection: TSynEditSelection; + FLastCaretXForMoveSelection: Integer; FOnChangeUpdating: TChangeUpdatingEvent; FMouseSelectionMode: TSynSelectionMode; FMouseSelectionCmd: TSynEditorMouseCommand; @@ -6635,6 +6636,8 @@ begin DebugLn(['[TCustomSynEdit.CommandProcessor] ',Command ,' AChar=',AChar,' Data=',DbgS(Data)]); {$ENDIF} + if (Command <> ecMoveSelectUp) and (Command <> ecMoveSelectDown) then + FLastCaretXForMoveSelection := -1; // first the program event handler gets a chance to process the command InitialCmd := Command; if not(hcfInit in ASkipHooks) then @@ -6699,7 +6702,7 @@ var Len: Integer; Temp: string; Helper: string; - moveBkm: boolean; + moveBkm, CurBack: boolean; WP: TPoint; Caret: TPoint; CaretNew: TPoint; @@ -6707,6 +6710,7 @@ var LogCounter: integer; LogCaretXY: TPoint; CY: Integer; + CurSm: TSynSelectionMode; begin IncPaintLock; @@ -6721,6 +6725,8 @@ begin FBlockSelection.ActiveSelectionMode := smColumn; if Command in [ecSelCmdRangeStart..ecSelCmdRangeEnd] then FBlockSelection.ActiveSelectionMode := FBlockSelection.SelectionMode; + if (Command <> ecMoveSelectUp) and (Command <> ecMoveSelectDown) then + FLastCaretXForMoveSelection := -1; FBlockSelection.AutoExtend := Command in [ecSelectionStart..ecSelectionEnd]; FCaret.ChangeOnTouch; @@ -7222,6 +7228,59 @@ begin FBlockSelection.DecPersistentLock; InternalEndUndoBlock; end; + ecMoveSelectUp, + ecMoveSelectDown, + ecMoveSelectLeft, + ecMoveSelectRight: + if (not ReadOnly) and SelAvail then begin + InternalBeginUndoBlock; + if FLastCaretXForMoveSelection < 0 then + FLastCaretXForMoveSelection := FBlockSelection.FirstLineBytePos.x; + CurSm := FBlockSelection.ActiveSelectionMode; + CurBack := FBlockSelection.IsBackwardSel; + Temp := FBlockSelection.SelText; + + if CurSm = smColumn then + FCaret.IncForcePastEOL; + SetSelTextExternal(''); + FCaret.LineBytePos := FBlockSelection.StartLineBytePos; + if (Command = ecMoveSelectUp) or (Command = ecMoveSelectDown) then + FCaret.KeepCaretXPos := FLastCaretXForMoveSelection; + case Command of + ecMoveSelectUp: MoveCaretVert(-1); + ecMoveSelectDown: MoveCaretVert(1); + ecMoveSelectLeft: FCaret.MoveHoriz(-1); + ecMoveSelectRight: FCaret.MoveHoriz(1); + end; + FBlockSelection.Clear; + FBlockSelection.SetSelTextPrimitive(CurSm, PChar(Temp), False, True); + + if CurBack then begin + FBlockSelection.SortSelectionPoints(True); + FCaret.LineBytePos := FBlockSelection.EndLineBytePos; + end; + if (Command = ecMoveSelectUp) or (Command = ecMoveSelectDown) then + FCaret.KeepCaretXPos := FLastCaretXForMoveSelection; + + if CurSm = smColumn then + FCaret.DecForcePastEOL; + InternalEndUndoBlock; + end; + ecDuplicateSelection: + if (not ReadOnly) and SelAvail then begin + InternalBeginUndoBlock; + FCaret.IncForcePastEOL; + CurSm := FBlockSelection.ActiveSelectionMode; + CurBack := FBlockSelection.IsBackwardSel; + Temp := FBlockSelection.SelText; + FCaret.LineBytePos := FBlockSelection.FirstLineBytePos; + FBlockSelection.Clear; + FBlockSelection.SetSelTextPrimitive(CurSm, PChar(Temp), False, True); + if CurBack then + FBlockSelection.SortSelectionPoints(True); + FCaret.DecForcePastEOL; + InternalEndUndoBlock; + end; ecScrollUp: begin TopView := TopView - 1; diff --git a/components/synedit/syneditkeycmds.pp b/components/synedit/syneditkeycmds.pp index 235d380835..4aa0972262 100644 --- a/components/synedit/syneditkeycmds.pp +++ b/components/synedit/syneditkeycmds.pp @@ -282,6 +282,13 @@ const ecMoveLineDown = 631; // Moves current line (or selection) one line down ecDuplicateLine = 632; // Line or selection (full lines) + ecMoveSelectUp = 633; // Moves selection one line up + ecMoveSelectDown = 634; // Moves selection one line down + ecMoveSelectLeft = 635; // Moves selection one line up + ecMoveSelectRight = 636; // Moves selection one line down + ecDuplicateSelection= 637; + + ecString = 640; //Insert a whole string ecAutoCompletion = 650; @@ -506,7 +513,7 @@ end; { Command mapping routines } const - EditorCommandStrs: array[0..166] of TIdentMapEntry = ( + EditorCommandStrs: array[0..171] of TIdentMapEntry = ( (Value: ecNone; Name: 'ecNone'), (Value: ecLeft; Name: 'ecLeft'), (Value: ecRight; Name: 'ecRight'), @@ -603,7 +610,12 @@ const (Value: ecCutAddCurrentLine; Name: 'ecCutAddCurrentLine'), (Value: ecMoveLineUp; Name: 'ecMoveLineUp'), (Value: ecMoveLineDown; Name: 'ecMoveLineDown'), + (Value: ecMoveSelectUp; Name: 'ecMoveSelectUp'), + (Value: ecMoveSelectDown; Name: 'ecMoveSelectDown'), + (Value: ecMoveSelectLeft; Name: 'ecMoveSelectLeft'), + (Value: ecMoveSelectRight; Name: 'ecMoveSelectRight'), (Value: ecDuplicateLine; Name: 'ecDuplicateLine'), + (Value: ecDuplicateSelection; Name: 'ecDuplicateSelection'), (Value: ecScrollUp; Name: 'ecScrollUp'), (Value: ecScrollDown; Name: 'ecScrollDown'), (Value: ecScrollLeft; Name: 'ecScrollLeft'), diff --git a/components/synedit/syneditpointclasses.pas b/components/synedit/syneditpointclasses.pas index ad1a99a53a..bdb89532c1 100644 --- a/components/synedit/syneditpointclasses.pas +++ b/components/synedit/syneditpointclasses.pas @@ -162,12 +162,12 @@ type constructor Create(ALines: TSynEditStrings; aActOnLineChanges: Boolean); destructor Destroy; override; procedure AssignFrom(Src: TSynEditSelection); - procedure SetSelTextPrimitive(PasteMode: TSynSelectionMode; Value: PChar; AReplace: Boolean = False); + procedure SetSelTextPrimitive(PasteMode: TSynSelectionMode; Value: PChar; AReplace: Boolean = False; ASetTextSelected: Boolean = False); function SelAvail: Boolean; function SelCanContinue(ACaret: TSynEditCaret): Boolean; function IsBackwardSel: Boolean; // SelStart < SelEnd ? procedure BeginMinimumSelection; // current selection will be minimum while follow caret (autoExtend) // until next setSelStart or end of follow - procedure SortSelectionPoints; + procedure SortSelectionPoints(AReverse: Boolean = False); procedure IgnoreNextCaretMove; // Mode can NOT be changed in nested calls procedure IncPersistentLock(AMode: TSynBlockPersistMode = sbpDefault); // Weak: Do not extend (but rather move) block, if at start/end @@ -1881,8 +1881,8 @@ begin end; end; -procedure TSynEditSelection.SetSelTextPrimitive(PasteMode : TSynSelectionMode; - Value : PChar; AReplace: Boolean = False); +procedure TSynEditSelection.SetSelTextPrimitive(PasteMode: TSynSelectionMode; + Value: PChar; AReplace: Boolean; ASetTextSelected: Boolean); var BB, BE: TPoint; @@ -2178,7 +2178,12 @@ begin FInternalCaret.LineBytePos := StartLineBytePos; if (Value <> nil) and (Value[0] <> #0) then begin InsertText; - StartLineBytePos := FInternalCaret.LineBytePos; // reset selection + if ASetTextSelected then begin + EndLineBytePos := FInternalCaret.LineBytePos; + FActiveSelectionMode := PasteMode; + end + else + StartLineBytePos := FInternalCaret.LineBytePos; // reset selection end; if FCaret <> nil then FCaret.LineCharPos := FInternalCaret.LineCharPos; @@ -2205,7 +2210,7 @@ procedure TSynEditSelection.ConstrainStartLineBytePos(var Value: TPoint); begin Value.y := MinMax(Value.y, 1, Max(fLines.Count, 1)); - if (FCaret = nil) or FCaret.AllowPastEOL then + if (FCaret = nil) or FCaret.AllowPastEOL or (FCaret.FForcePastEOL > 0) then Value.x := Max(Value.x, 1) else Value.x := MinMax(Value.x, 1, length(Lines[Value.y - 1])+1); @@ -2498,9 +2503,9 @@ begin end; end; -procedure TSynEditSelection.SortSelectionPoints; +procedure TSynEditSelection.SortSelectionPoints(AReverse: Boolean); begin - if IsBackwardSel then begin + if IsBackwardSel xor AReverse then begin SwapInt(FStartLinePos, FEndLinePos); SwapInt(FStartBytePos, FEndBytePos); end; diff --git a/components/synedit/synpluginmulticaret.pp b/components/synedit/synpluginmulticaret.pp index 8e57e3a390..b51b152d15 100644 --- a/components/synedit/synpluginmulticaret.pp +++ b/components/synedit/synpluginmulticaret.pp @@ -2630,7 +2630,10 @@ begin Action := ccaDefaultAction; case Command of ecCopy, ecCut, - ecCopyCurrentLine, ecCutCurrentLine: Action := ccaNoneRepeatCommand; + ecCopyCurrentLine, ecCutCurrentLine, ecDuplicateSelection: + Action := ccaNoneRepeatCommand; + ecMoveSelectUp, ecMoveSelectDown, ecMoveSelectLeft, ecMoveSelectRight: + Action := ccaClearCarets; ecGotoMarker0..ecGotoMarker9: Action := ccaClearCarets; ecSelectAll: Action := ccaClearCarets; ecDeleteChar: if smcoDeleteSkipLineBreak in Options then diff --git a/ide/keymapping.pp b/ide/keymapping.pp index b92f89ad49..2ed7132f08 100644 --- a/ide/keymapping.pp +++ b/ide/keymapping.pp @@ -423,6 +423,11 @@ begin ecMoveLineUp : Result:= srkmecMoveLineUp; ecMoveLineDown : Result:= srkmecMoveLineDown; ecDuplicateLine : Result:= srkmecDuplicateLine; + ecMoveSelectUp : Result:= srkmecMoveSelectUp; + ecMoveSelectDown : Result:= srkmecMoveSelectDown; + ecMoveSelectLeft : Result:= srkmecMoveSelectLeft; + ecMoveSelectRight : Result:= srkmecMoveSelectRight; + ecDuplicateSelection : Result:= srkmecDuplicateSelection; ecMultiPaste : Result:= srkmecMultiPaste; ecScrollUp : Result:= srkmecScrollUp; ecScrollDown : Result:= srkmecScrollDown; @@ -1028,6 +1033,11 @@ begin ecMoveLineUp: SetSingle(VK_UP,[XCtrl, ssShift, ssAlt]); ecMoveLineDown: SetSingle(VK_DOWN,[XCtrl, ssShift, ssAlt]); ecDuplicateLine: SetSingle(VK_INSERT,[XCtrl, ssShift, ssAlt]); + ecMoveSelectUp: SetSingle(VK_NUMPAD8,[XCtrl, ssAlt]); + ecMoveSelectDown: SetSingle(VK_NUMPAD2,[XCtrl, ssAlt]); + ecMoveSelectLeft: SetSingle(VK_NUMPAD4,[XCtrl, ssAlt]); + ecMoveSelectRight: SetSingle(VK_NUMPAD6,[XCtrl, ssAlt]); + ecDuplicateSelection: SetSingle(VK_NUMPAD0,[XCtrl, ssAlt]); ecMultiPaste: SetSingle(VK_UNKNOWN,[]); ecNormalSelect: SetSingle(VK_UNKNOWN,[]); @@ -2795,7 +2805,12 @@ begin AddDefault(C, 'Break line, leave cursor', srkmecInsertLine, ecInsertLine); AddDefault(C, 'Move one line up', srkmecMoveLineUp, ecMoveLineUp); AddDefault(C, 'Move one line down', srkmecMoveLineDown, ecMoveLineDown); + AddDefault(C, 'Move selection up', srkmecMoveSelectUp, ecMoveSelectUp); + AddDefault(C, 'Move selection down', srkmecMoveSelectDown, ecMoveSelectDown); + AddDefault(C, 'Move selection left', srkmecMoveSelectLeft, ecMoveSelectLeft); + AddDefault(C, 'Move selection right', srkmecMoveSelectRight, ecMoveSelectRight); AddDefault(C, 'Duplicate line or lines in selection', srkmecDuplicateLine, ecDuplicateLine); + AddDefault(C, 'Duplicate selection', srkmecDuplicateSelection, ecDuplicateSelection); AddDefault(C, 'Enclose in $IFDEF', lisEncloseInIFDEF, ecSelectionEncloseIFDEF); AddDefault(C, 'Insert from Character Map', lisMenuInsertCharacter, ecInsertCharacter); AddDefault(C, 'Insert GPL notice', srkmecInsertGPLNotice, ecInsertGPLNotice); diff --git a/ide/lazarusidestrconsts.pas b/ide/lazarusidestrconsts.pas index 81aa1779bd..d4a6dbc649 100644 --- a/ide/lazarusidestrconsts.pas +++ b/ide/lazarusidestrconsts.pas @@ -3130,6 +3130,11 @@ resourcestring srkmecMoveLineUp = 'Move line up'; srkmecMoveLineDown = 'Move line down'; srkmecDuplicateLine = 'Duplicate line (or lines in selection)'; + srkmecDuplicateSelection = 'Duplicate selection'; + srkmecMoveSelectUp = 'Move selection up'; + srkmecMoveSelectDown = 'Move selection down'; + srkmecMoveSelectLeft = 'Move selection left'; + srkmecMoveSelectRight = 'Move selection right'; srkmecMultiPaste = 'MultiPaste'; srkmecScrollUp = 'Scroll up one line';