It seams as though I can't post via email anymore - so this is from the website.
Here it is, small and simple - thanks to Nafis Sadyko Just add to a package and install it. Doug ---------------------------------------------------------------------------------------------------------------------------------------------------- unit JBComment; { Keybinding for Delphi (versions 5, 6, 7) and C++Builder (versions 5 and 6) editors. Introduces 'Ctrl+/' shortcut for fast multi-line (JBuilder-style) commenting. How it works: select text block (with a mouse) and press 'Ctrl+/' to comment/uncomment the lines contained. Installation: just install as you'd install a new component (by clicking "Component-> Install Component" IDE menu), then compile and install the resulting package. Note: for C++Builder - don't forget to add the path of your Builder's Source\ToolsAPI folder to your package's library path in Project Options. ---------------------------------------------------------------------------- Copyright Nafis Sadykov - No restrictions on use. e-mail: [EMAIL PROTECTED] } interface uses // SysUtils, {if using inttostr() or format() functions for debug messages} Classes, Menus, ToolsAPI, Windows; procedure Register; implementation const VK_SLASH = 191; // not defined in 'windows.pas' type TJBCommentBinding = class(TNotifierObject, IOTAKeyboardBinding) private public {required IOTAKeyboardBinding methods:} function GetBindingType: TBindingType; function GetDisplayName: string; function GetName: string; procedure BindKeyboard(const BindingServices: IOTAKeyBindingServices); {callback for the introduced shortcut - though not called automatically for our specific "Ctrl+/" shortcut} procedure JBComment(const Context: IOTAKeyContext; KeyCode: TShortcut; var BindingResult: TKeyBindingResult); {callback for all the "unrecognized" shortcuts - this is where we actually catch 'Ctrl+/' shortcut using API call, and call the method above ourselves} procedure DefaultKeyProc(const Context: IOTAKeyContext; KeyCode: TShortcut; var BindingResult: TKeyBindingResult); end; procedure Register; begin (BorlandIDEServices as IOTAKeyboardServices). AddKeyboardBinding(TJBCommentBinding.Create); end; function TJBCommentBinding.GetBindingType: TBindingType; begin {means we don't want to re-define the entire keyboard, but to introduce just one or more shortcuts} Result := btPartial; end; function TJBCommentBinding.GetDisplayName: string; begin {name to be displayed on the "Key Mappings" tab when you click "Tools->Editor Options" menu in IDE} Result := 'Multiline Commenting (JBuilder-style)'; end; function TJBCommentBinding.GetName: string; begin Result := 'ns.jbcomment'; // any unique string will do here end; procedure TJBCommentBinding.BindKeyboard( const BindingServices: IOTAKeyBindingServices); begin {doesn't work for this specific shortcut, though works OK for "Ctrl+\" for example.} //BindingServices.AddKeyBinding([TextToShortCut('Ctrl+/')], JBComment, nil); {So, instead we define default callback for all unrecognized shortcuts} BindingServices.SetDefaultKeyProc(DefaultKeyProc, nil, ''); end; procedure TJBCommentBinding.JBComment(const Context: IOTAKeyContext; KeyCode: TShortcut; var BindingResult: TKeyBindingResult); var iEditPos: IOTAEditPosition; iEditBlock: IOTAEditBlock; startCol, endCol, startRow, endRow: Integer; topViewRow, topViewColumn: integer; i: integer; cursorAtStart: boolean;// is our cursor at the beginning of the selection procedure myCommentUncomment(Row: integer); begin iEditPos.Move(Row, 1); if iEditPos.Read(2) = '//' then // if commented then uncomment the line begin {somehow, independent on the parameter, the .Delete() method only deletes a single character, so we need to call it twice here. (Another IOTA bug ?)} iEditPos.Delete(1); iEditPos.Delete(1); {now move the selection to the left} if (Row = startRow) and (startCol <> 1) then dec(startCol, 2); if (Row = endRow) and (endCol <> 1) then dec(endCol, 2); end else begin // otherwise comment the line iEditPos.InsertText('//'); {now move the selection to the right} if (Row = startRow) and (startCol <> 1) then inc(startCol, 2); if (Row = endRow) and (endCol <> 1) then inc(endCol, 2); end; end; begin BindingResult := krHandled; iEditBlock := Context.EditBuffer.EditBlock; if not iEditBlock.IsValid then // do nothing if there is no selection exit; {store the EditBlock's parameters:} startCol:= iEditBlock.StartingColumn; endCol:= iEditBlock.EndingColumn; startRow:= iEditBlock.StartingRow; endRow:= iEditBlock.EndingRow; iEditPos:= Context.EditBuffer.EditPosition; {remember which side of the selection the cursor is at} if (iEditPos.Row = startRow) and (iEditPos.Column = startCol) then cursorAtStart:= true else cursorAtStart:= false; {and remember the top line viewed in the editor} topViewRow:= Context.EditBuffer.TopView.TopRow; topViewColumn:= Context.EditBuffer.TopView.LeftColumn; for i:= startRow to endRow -1 do myCommentUncomment(i); {process only if there is non-empty selection in the last row} if (endRow <> startRow) and (endCol > 1) then myCommentUncomment(endRow) {process anyway if the selection doesn't span more than a single row} else if (endRow = startRow) then myCommentUncomment(endRow); {now restore the selection and the cursor position} if cursorAtStart then begin iEditPos.Move(endRow, endCol); iEditBlock.EndBlock; iEditPos.Move(startRow, startCol); iEditBlock.BeginBlock; end else begin iEditPos.Move(startRow, startCol); iEditBlock.BeginBlock; iEditPos.Move(endRow, endCol); iEditBlock.EndBlock; end; {restore the top row and left column - necessary for cases when you have selections spanning more than a single page} Context.EditBuffer.TopView.SetTopLeft(topViewRow, topViewColumn); end; var ArrayKeysState: TKeyboardState; // type is defined in 'windows.pas' function AreCorrectKeysPressed: boolean; var i: integer; ctrlPressed, slashPressed, otherKeysPressed: boolean; begin result:= false; ctrlPressed:= false; slashPressed:= false; otherKeysPressed:= false; if GetKeyboardState(ArrayKeysState) then // make WinAPI call begin for i:= Low(ArrayKeysState) to High(ArrayKeysState) do begin {if the highest bit is set - see 'win32.hlp' for details on GetKeyboardState()} if (ArrayKeysState[i] and $80) > 0 then begin case i of VK_CONTROL, VK_LCONTROL, VK_RCONTROL: ctrlPressed:= true; VK_SLASH: slashPressed:= true; else begin otherKeysPressed:= true; break; end; end; //end case end; end; //end for if (not otherKeysPressed)and ctrlPressed and slashPressed then result:= true; end; end; procedure TJBCommentBinding.DefaultKeyProc(const Context: IOTAKeyContext; KeyCode: TShortcut; var BindingResult: TKeyBindingResult); begin { Debugging message : if switched on, it'll show us that ALL the shortcuts come with the same(!?) KeyCode parameter of $4040. (You need the Messages window - same where you get the Compiler messages - visible to get this debug message.) Note: setting Breakpoints or trying to use Message Dialogs is of no use when debugging Keybindings, so using IOTAMessageServices).AddTitleMessage() seems to be the only way to get the debugging info needed. } // (BorlandIDEServices as IOTAMessageServices).AddTitleMessage( // format('DefaultKeyProc: ShortCut KeyCode= $%.4x',[KeyCode])); if AreCorrectKeysPressed then begin self.JBComment(Context, KeyCode, BindingResult); BindingResult:= krHandled; end else BindingResult:= krUnhandled; end; end. ----------------------------------------------------------------------------------------------------------------------------------------------------