diff --git a/Interfaces/MainMenu.xib b/Interfaces/MainMenu.xib index 39b2e2fccd..db6e751c6a 100644 --- a/Interfaces/MainMenu.xib +++ b/Interfaces/MainMenu.xib @@ -668,6 +668,12 @@ Gw + + + + + + diff --git a/Interfaces/PreferencePanel.xib b/Interfaces/PreferencePanel.xib index 52da7f29c4..ce56470620 100644 --- a/Interfaces/PreferencePanel.xib +++ b/Interfaces/PreferencePanel.xib @@ -1471,6 +1471,7 @@ DQ + @@ -2206,7 +2207,7 @@ DQ - + @@ -2257,7 +2258,7 @@ DQ + diff --git a/iTerm2XCTests/PTYTextViewTest.m b/iTerm2XCTests/PTYTextViewTest.m index 15e32aef92..283a0e8666 100644 --- a/iTerm2XCTests/PTYTextViewTest.m +++ b/iTerm2XCTests/PTYTextViewTest.m @@ -991,6 +991,21 @@ - (void)testCursorGuide { size:VT100GridSizeMake(5, 5)]; } +// Draws a cursor guide on the col with b. +- (void)testVerticalCursorGuide { + [self doGoldenTestForInput:@"abcd\x1b[2A" + name:NSStringFromSelector(_cmd) + hook:^(PTYTextView *textView) { + textView.drawingHook = ^(iTermTextDrawingHelper *helper) { + helper.shouldDrawFilledInCursor = YES; + helper.highlightCursorColumn = YES; + }; + } + profileOverrides:nil + createGolden:YES + size:VT100GridSizeMake(5, 5)]; +} + // Draws a badge which blends with nondefault background colors. - (void)testBadge { [self doGoldenTestForInput:@"\n\n\n\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x1b[42mabc" diff --git a/iTerm2XCTests/VT100GridTest.m b/iTerm2XCTests/VT100GridTest.m index c38e27dc7d..f6b106e713 100644 --- a/iTerm2XCTests/VT100GridTest.m +++ b/iTerm2XCTests/VT100GridTest.m @@ -53,6 +53,9 @@ - (screen_char_t)gridBackgroundColorCode { - (void)gridCursorDidChangeLine { } +- (void)gridCursorDidChangeColumn { +} + - (iTermUnicodeNormalization)gridUnicodeNormalizationForm { return iTermUnicodeNormalizationNone; } diff --git a/iTerm2XCTests/VT100ScreenTest.m b/iTerm2XCTests/VT100ScreenTest.m index 5474e776d1..24e6f08b39 100644 --- a/iTerm2XCTests/VT100ScreenTest.m +++ b/iTerm2XCTests/VT100ScreenTest.m @@ -748,6 +748,12 @@ - (void)screenSetHighlightCursorLine:(BOOL)highlight { - (void)screenCursorDidMoveToLine:(int)line { } +- (void)screenSetHighlightCursorColumn:(BOOL)highlight { +} + +- (void)screenCursorDidMoveToColumn:(int)line { +} + - (void)screenSaveScrollPosition { } diff --git a/sources/ITAddressBookMgr.h b/sources/ITAddressBookMgr.h index b95b68be17..205083bab7 100644 --- a/sources/ITAddressBookMgr.h +++ b/sources/ITAddressBookMgr.h @@ -114,6 +114,7 @@ #define KEY_USE_UNDERLINE_COLOR @"Use Underline Color" #define KEY_CURSOR_BOOST @"Cursor Boost" #define KEY_USE_CURSOR_GUIDE @"Use Cursor Guide" +#define KEY_USE_VERTICAL_CURSOR_GUIDE @"Use Vertical Cursor Guide" #define KEY_CURSOR_GUIDE_COLOR @"Cursor Guide Color" #define KEY_BADGE_COLOR @"Badge Color" diff --git a/sources/Metal/Renderers/iTermCursorGuideRenderer.h b/sources/Metal/Renderers/iTermCursorGuideRenderer.h index d9178e07c6..37af18ac29 100644 --- a/sources/Metal/Renderers/iTermCursorGuideRenderer.h +++ b/sources/Metal/Renderers/iTermCursorGuideRenderer.h @@ -1,17 +1,18 @@ #import #import "iTermMetalCellRenderer.h" +#import "VT100GridTypes.h" NS_ASSUME_NONNULL_BEGIN @interface iTermCursorGuideRendererTransientState : iTermMetalCellRendererTransientState -// set to -1 if cursor's row is not currently visible. -- (void)setRow:(int)row; +- (void)setCursorCoord:(VT100GridCoord)coord; @end @interface iTermCursorGuideRenderer : NSObject -@property (nonatomic) BOOL enabled; +@property (nonatomic) BOOL horizontalEnabled; +@property (nonatomic) BOOL verticalEnabled; - (nullable instancetype)initWithDevice:(id)device NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; diff --git a/sources/Metal/Renderers/iTermCursorGuideRenderer.m b/sources/Metal/Renderers/iTermCursorGuideRenderer.m index b7326aaf78..3903170ae5 100644 --- a/sources/Metal/Renderers/iTermCursorGuideRenderer.m +++ b/sources/Metal/Renderers/iTermCursorGuideRenderer.m @@ -1,40 +1,88 @@ #import "iTermCursorGuideRenderer.h" @interface iTermCursorGuideRendererTransientState() -@property (nonatomic, strong) id texture; +@property (nonatomic, strong) id horizontalTexture; +@property (nonatomic, strong) id verticalTexture; @property (nonatomic) int row; +@property (nonatomic) int column; +@property (nonatomic) id horizontalVertexBuffer; +@property (nonatomic) id verticalVertexBuffer; +@property (nonatomic) id upperVertexBuffer; +@property (nonatomic) id lowerVertexBuffer; @end @implementation iTermCursorGuideRendererTransientState { int _row; + int _column; + id _horizontalVertexBuffer; + id _verticalVertexBuffer; + id _upperVertexBuffer; + id _lowerVertexBuffer; } -- (void)setRow:(int)row { - _row = row; +- (id)tessellateRect:(CGRect)rect withTexture:(CGRect)textureRect withPool:(iTermMetalBufferPool *)verticesPool { + // Tesselates an axis-aligned rectangle into a sequence of vertices + // representing two adjacent triangles that share the diagonal of the + // rectangle: + // top-right, top-left, bottom-left, top-right, bottom-left, bottom-right + const iTermVertex vertices[] = { + // Pixel Positions Texture Coordinates + { { CGRectGetMaxX(rect), CGRectGetMinY(rect) }, { CGRectGetMaxX(textureRect), CGRectGetMinY(textureRect) } }, + { { CGRectGetMinX(rect), CGRectGetMinY(rect) }, { CGRectGetMinX(textureRect), CGRectGetMinY(textureRect) } }, + { { CGRectGetMinX(rect), CGRectGetMaxY(rect) }, { CGRectGetMinX(textureRect), CGRectGetMaxY(textureRect) } }, + + { { CGRectGetMaxX(rect), CGRectGetMinY(rect) }, { CGRectGetMaxX(textureRect), CGRectGetMinY(textureRect) } }, + { { CGRectGetMinX(rect), CGRectGetMaxY(rect) }, { CGRectGetMinX(textureRect), CGRectGetMaxY(textureRect) } }, + { { CGRectGetMaxX(rect), CGRectGetMaxY(rect) }, { CGRectGetMaxX(textureRect), CGRectGetMaxY(textureRect) } }, + }; + return [verticesPool requestBufferFromContext:self.poolContext + withBytes:vertices + checkIfChanged:YES]; +} + +- (void)setCursorCoord:(VT100GridCoord)coord { + VT100GridSize bounds = self.cellConfiguration.gridSize; + _row = (0 <= coord.y && coord.y < bounds.height) ? coord.y : -1; + _column = (0 <= coord.x && coord.x < bounds.width) ? coord.x : -1; } -- (void)initializeVerticesWithPool:(iTermMetalBufferPool *)verticesPool { +- (void)initializeVerticesWithPool:(iTermMetalBufferPool *)verticesPool + horizontal:(BOOL)horizontal + vertical:(BOOL)vertical { + assert(horizontal || vertical); CGSize cellSize = self.cellConfiguration.cellSize; VT100GridSize gridSize = self.cellConfiguration.gridSize; - const CGRect quad = CGRectMake(self.margins.left, - self.margins.top + (gridSize.height - self.row - 1) * cellSize.height, - cellSize.width * gridSize.width, - cellSize.height); const CGRect textureFrame = CGRectMake(0, 0, 1, 1); - const iTermVertex vertices[] = { - // Pixel Positions Texture Coordinates - { { CGRectGetMaxX(quad), CGRectGetMinY(quad) }, { CGRectGetMaxX(textureFrame), CGRectGetMinY(textureFrame) } }, - { { CGRectGetMinX(quad), CGRectGetMinY(quad) }, { CGRectGetMinX(textureFrame), CGRectGetMinY(textureFrame) } }, - { { CGRectGetMinX(quad), CGRectGetMaxY(quad) }, { CGRectGetMinX(textureFrame), CGRectGetMaxY(textureFrame) } }, - { { CGRectGetMaxX(quad), CGRectGetMinY(quad) }, { CGRectGetMaxX(textureFrame), CGRectGetMinY(textureFrame) } }, - { { CGRectGetMinX(quad), CGRectGetMaxY(quad) }, { CGRectGetMinX(textureFrame), CGRectGetMaxY(textureFrame) } }, - { { CGRectGetMaxX(quad), CGRectGetMaxY(quad) }, { CGRectGetMaxX(textureFrame), CGRectGetMaxY(textureFrame) } }, - }; - self.vertexBuffer = [verticesPool requestBufferFromContext:self.poolContext - withBytes:vertices - checkIfChanged:YES]; + CGFloat viewMinX = self.margins.left, viewMinY = self.margins.top; + CGFloat viewMaxX = viewMinX + cellSize.width*gridSize.width, viewMaxY = viewMinY + cellSize.height*gridSize.height; + + CGFloat cursorMinX = self.margins.left + self.column * cellSize.width, cursorMinY = self.margins.top + (gridSize.height - self.row - 1) * cellSize.height; + CGFloat cursorMaxY = cursorMinY + cellSize.height; + + if (horizontal) { + self.horizontalVertexBuffer = [self tessellateRect:CGRectMake(viewMinX, cursorMinY, + viewMaxX - viewMinX, cellSize.height) + withTexture:textureFrame + withPool:verticesPool]; + } + + if (horizontal && vertical) { + self.lowerVertexBuffer = [self tessellateRect:CGRectMake(cursorMinX, viewMinY, + cellSize.width, cursorMinY - viewMinY) + withTexture:textureFrame + withPool:verticesPool]; + self.upperVertexBuffer = [self tessellateRect:CGRectMake(cursorMinX, cursorMaxY, + cellSize.width, viewMaxY - cursorMaxY) + withTexture:textureFrame + withPool:verticesPool]; + } else if (vertical) { + self.verticalVertexBuffer = [self tessellateRect:CGRectMake(cursorMinX, viewMinY, + cellSize.width, viewMaxY - viewMinY) + withTexture:textureFrame + withPool:verticesPool]; + } } - (void)writeDebugInfoToFolder:(NSURL *)folder { @@ -49,7 +97,8 @@ - (void)writeDebugInfoToFolder:(NSURL *)folder { @implementation iTermCursorGuideRenderer { iTermMetalCellRenderer *_cellRenderer; - id _texture; + id _horizontalTexture; + id _verticalTexture; NSColor *_color; CGSize _lastCellSize; } @@ -78,7 +127,7 @@ - (iTermMetalFrameDataStat)createTransientStateStat { - (nullable __kindof iTermMetalRendererTransientState *)createTransientStateForCellConfiguration:(iTermCellRenderConfiguration *)configuration commandBuffer:(id)commandBuffer { - if (!_enabled) { + if (!_horizontalEnabled && !_verticalEnabled) { return nil; } __kindof iTermMetalCellRendererTransientState * _Nonnull transientState = @@ -90,10 +139,14 @@ - (nullable __kindof iTermMetalRendererTransientState *)createTransientStateForC - (void)initializeTransientState:(iTermCursorGuideRendererTransientState *)tState { if (!CGSizeEqualToSize(tState.cellConfiguration.cellSize, _lastCellSize)) { - _texture = [self newCursorGuideTextureWithTransientState:tState]; + _horizontalTexture = [self newCursorGuideTextureWithTransientState:tState + isHorizontal:YES]; + _verticalTexture = [self newCursorGuideTextureWithTransientState:tState + isHorizontal:NO]; _lastCellSize = tState.cellConfiguration.cellSize; } - tState.texture = _texture; + tState.horizontalTexture = _horizontalTexture; + tState.verticalTexture = _verticalTexture; } - (void)setColor:(NSColor *)color { @@ -106,26 +159,59 @@ - (void)setColor:(NSColor *)color { - (void)drawWithFrameData:(iTermMetalFrameData *)frameData transientState:(__kindof iTermMetalCellRendererTransientState *)transientState { iTermCursorGuideRendererTransientState *tState = transientState; - if (tState.row < 0) { - // Cursor is offscreen. We set it to -1 to signal this. + if (tState.row < 0 && tState.column < 0) { return; } - [tState initializeVerticesWithPool:_cellRenderer.verticesPool]; - [_cellRenderer drawWithTransientState:tState - renderEncoder:frameData.renderEncoder - numberOfVertices:6 - numberOfPIUs:0 - vertexBuffers:@{ @(iTermVertexInputIndexVertices): tState.vertexBuffer } - fragmentBuffers:@{} - textures:@{ @(iTermTextureIndexPrimary): tState.texture } ]; + if (tState.row >= 0 && tState.column >= 0 && self.horizontalEnabled && self.verticalEnabled) { + [tState initializeVerticesWithPool:_cellRenderer.verticesPool horizontal:YES vertical:YES]; + [_cellRenderer drawWithTransientState:tState + renderEncoder:frameData.renderEncoder + numberOfVertices:6 + numberOfPIUs:0 + vertexBuffers:@{ @(iTermVertexInputIndexVertices): tState.horizontalVertexBuffer } + fragmentBuffers:@{} + textures:@{ @(iTermTextureIndexPrimary): tState.horizontalTexture } ]; + [_cellRenderer drawWithTransientState:tState + renderEncoder:frameData.renderEncoder + numberOfVertices:6 + numberOfPIUs:0 + vertexBuffers:@{ @(iTermVertexInputIndexVertices): tState.upperVertexBuffer } + fragmentBuffers:@{} + textures:@{ @(iTermTextureIndexPrimary): tState.verticalTexture } ]; + [_cellRenderer drawWithTransientState:tState + renderEncoder:frameData.renderEncoder + numberOfVertices:6 + numberOfPIUs:0 + vertexBuffers:@{ @(iTermVertexInputIndexVertices): tState.lowerVertexBuffer } + fragmentBuffers:@{} + textures:@{ @(iTermTextureIndexPrimary): tState.verticalTexture } ]; + } else if (tState.row >= 0 && self.horizontalEnabled) { + [tState initializeVerticesWithPool:_cellRenderer.verticesPool horizontal:YES vertical:NO]; + [_cellRenderer drawWithTransientState:tState + renderEncoder:frameData.renderEncoder + numberOfVertices:6 + numberOfPIUs:0 + vertexBuffers:@{ @(iTermVertexInputIndexVertices): tState.horizontalVertexBuffer } + fragmentBuffers:@{} + textures:@{ @(iTermTextureIndexPrimary): tState.horizontalTexture } ]; + } else if (tState.column >= 0 && self.verticalEnabled) { + [tState initializeVerticesWithPool:_cellRenderer.verticesPool horizontal:NO vertical:YES]; + [_cellRenderer drawWithTransientState:tState + renderEncoder:frameData.renderEncoder + numberOfVertices:6 + numberOfPIUs:0 + vertexBuffers:@{ @(iTermVertexInputIndexVertices): tState.verticalVertexBuffer } + fragmentBuffers:@{} + textures:@{ @(iTermTextureIndexPrimary): tState.verticalTexture } ]; + } } #pragma mark - Private -- (id)newCursorGuideTextureWithTransientState:(iTermCursorGuideRendererTransientState *)tState { +- (id)newCursorGuideTextureWithTransientState:(iTermCursorGuideRendererTransientState *)tState + isHorizontal:(BOOL)isHorizontal { NSImage *image = [[NSImage alloc] initWithSize:tState.cellConfiguration.cellSize]; - [image lockFocus]; { [_color set]; @@ -135,11 +221,19 @@ - (void)drawWithFrameData:(iTermMetalFrameData *)frameData tState.cellConfiguration.cellSize.height); NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); - rect.size.height = tState.cellConfiguration.scale; - NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); + if (isHorizontal) { + rect.size.height = tState.cellConfiguration.scale; + NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); - rect.origin.y += tState.cellConfiguration.cellSize.height - tState.cellConfiguration.scale; - NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); + rect.origin.y += tState.cellConfiguration.cellSize.height - tState.cellConfiguration.scale; + NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); + } else { + rect.size.width = tState.cellConfiguration.scale; + NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); + + rect.origin.x += tState.cellConfiguration.cellSize.width - tState.cellConfiguration.scale; + NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); + } } [image unlockFocus]; diff --git a/sources/Metal/iTermMetalDriver.h b/sources/Metal/iTermMetalDriver.h index c3e724ff94..4fbf1eb461 100644 --- a/sources/Metal/iTermMetalDriver.h +++ b/sources/Metal/iTermMetalDriver.h @@ -60,7 +60,8 @@ NS_CLASS_AVAILABLE(10_11, NA) @property (nonatomic, nullable, readonly) iTermMetalIMEInfo *imeInfo; @property (nonatomic, readonly) BOOL showBroadcastStripes; @property (nonatomic, readonly) NSColor *cursorGuideColor; -@property (nonatomic, readonly) BOOL cursorGuideEnabled; +@property (nonatomic, readonly) BOOL cursorHorizontalGuideEnabled; +@property (nonatomic, readonly) BOOL cursorVerticalGuideEnabled; @property (nonatomic, readonly) vector_float4 fullScreenFlashColor; @property (nonatomic, readonly) BOOL timestampsEnabled; @property (nonatomic, readonly) NSColor *timestampsBackgroundColor; diff --git a/sources/Metal/iTermMetalDriver.m b/sources/Metal/iTermMetalDriver.m index 0d48797703..d5f24d0220 100644 --- a/sources/Metal/iTermMetalDriver.m +++ b/sources/Metal/iTermMetalDriver.m @@ -644,7 +644,8 @@ - (BOOL)shouldCreateIntermediateRenderPassDescriptor:(iTermMetalFrameData *)fram if (!_broadcastStripesRenderer.rendererDisabled && frameData.perFrameState.showBroadcastStripes) { return YES; } - if (!_cursorGuideRenderer.rendererDisabled && frameData.perFrameState.cursorGuideEnabled) { + if (!_cursorGuideRenderer.rendererDisabled && (frameData.perFrameState.cursorHorizontalGuideEnabled || + frameData.perFrameState.cursorVerticalGuideEnabled)) { return YES; } @@ -969,7 +970,8 @@ - (void)updateCursorGuideRendererForFrameData:(iTermMetalFrameData *)frameData { return; } [_cursorGuideRenderer setColor:frameData.perFrameState.cursorGuideColor]; - _cursorGuideRenderer.enabled = frameData.perFrameState.cursorGuideEnabled; + _cursorGuideRenderer.horizontalEnabled = frameData.perFrameState.cursorHorizontalGuideEnabled; + _cursorGuideRenderer.verticalEnabled = frameData.perFrameState.cursorVerticalGuideEnabled; } - (void)updateTimestampsRendererForFrameData:(iTermMetalFrameData *)frameData { @@ -1219,13 +1221,8 @@ - (void)populateHighlightRowRendererTransientStateWithFrameData:(iTermMetalFrame - (void)populateCursorGuideRendererTransientStateWithFrameData:(iTermMetalFrameData *)frameData { iTermCursorGuideRendererTransientState *tState = [frameData transientStateForRenderer:_cursorGuideRenderer]; - iTermMetalCursorInfo *cursorInfo = frameData.perFrameState.metalDriverCursorInfo; - if (cursorInfo.coord.y >= 0 && - cursorInfo.coord.y < frameData.gridSize.height) { - [tState setRow:frameData.perFrameState.metalDriverCursorInfo.coord.y]; - } else { - [tState setRow:-1]; - } + VT100GridCoord coord = frameData.perFrameState.metalDriverCursorInfo.coord; + [tState setCursorCoord:coord]; } - (void)populateTimestampsRendererTransientStateWithFrameData:(iTermMetalFrameData *)frameData { diff --git a/sources/PTYSession.h b/sources/PTYSession.h index f7275809e6..4033cb728a 100644 --- a/sources/PTYSession.h +++ b/sources/PTYSession.h @@ -422,6 +422,7 @@ typedef enum { @property(nonatomic, readonly) BOOL hasSelection; @property(nonatomic, assign) BOOL highlightCursorLine; +@property(nonatomic, assign) BOOL highlightCursorColumn; // Used to help remember total ordering on views while one is maximized @property(nonatomic, assign) NSPoint savedRootRelativeOrigin; diff --git a/sources/PTYSession.m b/sources/PTYSession.m index c37b6b3326..d7243344c6 100644 --- a/sources/PTYSession.m +++ b/sources/PTYSession.m @@ -208,6 +208,7 @@ static NSString *const SESSION_ARRANGEMENT_DIRECTORIES = @"Directories"; // Array of strings static NSString *const SESSION_ARRANGEMENT_HOSTS = @"Hosts"; // Array of VT100RemoteHost static NSString *const SESSION_ARRANGEMENT_CURSOR_GUIDE = @"Cursor Guide"; // BOOL +static NSString *const SESSION_ARRANGEMENT_VERTICAL_CURSOR_GUIDE = @"Vertical Cursor Guide"; // BOOL static NSString *const SESSION_ARRANGEMENT_LAST_DIRECTORY = @"Last Directory"; // NSString static NSString *const SESSION_ARRANGEMENT_LAST_DIRECTORY_IS_UNSUITABLE_FOR_OLD_PWD = @"Last Directory Is Remote"; // BOOL static NSString *const SESSION_ARRANGEMENT_SELECTION = @"Selection"; // Dictionary for iTermSelection. @@ -1158,6 +1159,9 @@ + (void)finishInitializingArrangementOriginatedSession:(PTYSession *)aSession if (arrangement[SESSION_ARRANGEMENT_CURSOR_GUIDE]) { aSession.textview.highlightCursorLine = [arrangement[SESSION_ARRANGEMENT_CURSOR_GUIDE] boolValue]; } + if (arrangement[SESSION_ARRANGEMENT_VERTICAL_CURSOR_GUIDE]) { + aSession.textview.highlightCursorColumn = [arrangement[SESSION_ARRANGEMENT_VERTICAL_CURSOR_GUIDE] boolValue]; + } aSession->_lastMark = [aSession.screen.lastMark retain]; aSession.lastRemoteHost = aSession.screen.lastRemoteHost; if (arrangement[SESSION_ARRANGEMENT_LAST_DIRECTORY]) { @@ -2341,6 +2345,166 @@ - (void)writeTaskNoBroadcast:(NSString *)string [self writeTaskImpl:string encoding:encoding forceEncoding:forceEncoding canBroadcast:NO]; } +#if 0 +- (void)handleKeyPressInCopyMode:(NSEvent *)event { + [self.textview setNeedsDisplayOnLine:_copyModeState.coord.y]; + [self.textview setNeedsDisplayOnColumn:_copyModeState.coord.x]; + BOOL wasSelecting = _copyModeState.selecting; + NSString *string = event.charactersIgnoringModifiers; + unichar code = [string length] > 0 ? [string characterAtIndex:0] : 0; + NSUInteger mask = (NSEventModifierFlagOption | NSEventModifierFlagControl | NSEventModifierFlagCommand); + BOOL moved = NO; + if ((event.modifierFlags & mask) == NSEventModifierFlagControl) { + switch (code) { + case 'b': // ^B + moved = [_copyModeState pageUp]; + break; + case 'f': // ^F + moved = [_copyModeState pageDown]; + break; + case ' ': + _copyModeState.selecting = !_copyModeState.selecting; + _copyModeState.mode = kiTermSelectionModeCharacter; + break; + case 'c': + self.copyMode = NO; + break; + case 'g': + self.copyMode = NO; + break; + case 'k': + [_textview copySelectionAccordingToUserPreferences]; + self.copyMode = NO; + break; + case 'v': + _copyModeState.selecting = !_copyModeState.selecting; + _copyModeState.mode = kiTermSelectionModeBox; + break; + } + } else if ((event.modifierFlags & mask) == NSEventModifierFlagOption) { + switch (code) { + case 'b': + case NSLeftArrowFunctionKey: + moved = [_copyModeState moveBackwardWord]; + break; + + case 'f': + case NSRightArrowFunctionKey: + moved = [_copyModeState moveForwardWord]; + break; + case 'm': + moved = [_copyModeState moveToStartOfIndentation]; + break; + } + } else if ((event.modifierFlags & mask) == 0) { + switch (code) { + case NSPageUpFunctionKey: + moved = [_copyModeState pageUp]; + break; + case NSPageDownFunctionKey: + moved = [_copyModeState pageDown]; + break; + case '\t': + if (event.modifierFlags & NSEventModifierFlagShift) { + moved = [_copyModeState moveBackwardWord]; + } else { + moved = [_copyModeState moveForwardWord]; + } + break; + case '\n': + case '\r': + moved = [_copyModeState moveToStartOfNextLine]; + break; + case 27: + case 'q': + self.copyMode = NO; + _copyModeState.selecting = NO; + moved = YES; + break; + case ' ': + case 'v': + _copyModeState.selecting = !_copyModeState.selecting; + _copyModeState.mode = kiTermSelectionModeCharacter; + break; + case 'b': + moved = [_copyModeState moveBackwardWord]; + break; + case '0': + moved = [_copyModeState moveToStartOfLine]; + break; + case 'H': + moved = [_copyModeState moveToTopOfVisibleArea]; + break; + case 'G': + moved = [_copyModeState moveToEnd]; + break; + case 'L': + moved = [_copyModeState moveToBottomOfVisibleArea]; + break; + case 'M': + moved = [_copyModeState moveToMiddleOfVisibleArea]; + break; + case 'V': + _copyModeState.selecting = !_copyModeState.selecting; + _copyModeState.mode = kiTermSelectionModeLine; + break; + case 'g': + moved = [_copyModeState moveToStart]; + break; + case 'h': + case NSLeftArrowFunctionKey: + moved = [_copyModeState moveLeft]; + break; + case 'j': + case NSDownArrowFunctionKey: + moved = [_copyModeState moveDown]; + break; + case 'k': + case NSUpArrowFunctionKey: + moved = [_copyModeState moveUp]; + break; + case 'l': + case NSRightArrowFunctionKey: + moved = [_copyModeState moveRight]; + break; + case 'o': + [_copyModeState swap]; + moved = YES; + break; + case 'w': + moved = [_copyModeState moveForwardWord]; + break; + case 'y': + [_textview copySelectionAccordingToUserPreferences]; + self.copyMode = NO; + break; + case '/': + [self showFindPanel]; + break; + case '[': + moved = [_copyModeState previousMark]; + break; + case ']': + moved = [_copyModeState nextMark]; + break; + case '^': + moved = [_copyModeState moveToStartOfIndentation]; + break; + case '$': + moved = [_copyModeState moveToEndOfLine]; + break; + } + } + if (moved || (_copyModeState.selecting != wasSelecting)) { + if (self.copyMode) { + [_textview scrollLineNumberRangeIntoView:VT100GridRangeMake(_copyModeState.coord.y, 1)]; + } + [self.textview setNeedsDisplayOnLine:_copyModeState.coord.y]; + [self.textview setNeedsDisplayOnColumn:_copyModeState.coord.x]; + } +} +#endif + - (void)handleKeypressInTmuxGateway:(NSEvent *)event { const unichar unicode = [event.characters length] > 0 ? [event.characters characterAtIndex:0] : 0; [self handleCharacterPressedInTmuxGateway:unicode]; @@ -3290,6 +3454,8 @@ - (void)loadInitialColorTable } _textview.highlightCursorLine = [iTermProfilePreferences boolForKey:KEY_USE_CURSOR_GUIDE inProfile:_profile]; + _textview.highlightCursorColumn = [iTermProfilePreferences boolForKey:KEY_USE_VERTICAL_CURSOR_GUIDE + inProfile:_profile]; } - (NSColor *)tabColorInProfile:(NSDictionary *)profile @@ -3472,6 +3638,8 @@ - (void)setPreferencesFromAddressBookEntry:(NSDictionary *)aePrefs { if (!_cursorGuideSettingHasChanged) { _textview.highlightCursorLine = [iTermProfilePreferences boolForKey:KEY_USE_CURSOR_GUIDE inProfile:aDict]; + _textview.highlightCursorColumn = [iTermProfilePreferences boolForKey:KEY_USE_VERTICAL_CURSOR_GUIDE + inProfile:aDict]; } for (i = 0; i < 16; i++) { @@ -4198,6 +4366,7 @@ - (NSDictionary *)arrangementWithContents:(BOOL)includeContents { [NSDictionary dictionaryWithGridCoordRange:range]; result[SESSION_ARRANGEMENT_ALERT_ON_NEXT_MARK] = @(_alertOnNextMark); result[SESSION_ARRANGEMENT_CURSOR_GUIDE] = @(_textview.highlightCursorLine); + result[SESSION_ARRANGEMENT_VERTICAL_CURSOR_GUIDE] = @(_textview.highlightCursorColumn); if (self.lastDirectory) { result[SESSION_ARRANGEMENT_LAST_DIRECTORY] = self.lastDirectory; result[SESSION_ARRANGEMENT_LAST_DIRECTORY_IS_UNSUITABLE_FOR_OLD_PWD] = @(self.lastDirectoryIsUnsuitableForOldPWD); @@ -8211,8 +8380,11 @@ - (void)screenDidReset { _cursorGuideSettingHasChanged = NO; _textview.highlightCursorLine = [iTermProfilePreferences boolForKey:KEY_USE_CURSOR_GUIDE inProfile:_profile]; + _textview.highlightCursorColumn = [iTermProfilePreferences boolForKey:KEY_USE_VERTICAL_CURSOR_GUIDE + inProfile:_profile]; [_textview setNeedsDisplay:YES]; _screen.trackCursorLineMovement = NO; + _screen.trackCursorColumnMovement = NO; } - (void)screenDidAppendStringToCurrentLine:(NSString *)string { @@ -8483,6 +8655,28 @@ - (BOOL)highlightCursorLine { return _textview.highlightCursorLine; } +- (void)screenCursorDidMoveToColumn:(int)column { + if (_textview.cursorVisible) { + [_textview setNeedsDisplayOnColumn:column]; + } +} + +- (void)screenSetHighlightCursorColumn:(BOOL)highlight { + _cursorGuideSettingHasChanged = YES; + self.highlightCursorColumn = highlight; +} + +- (void)setHighlightCursorColumn:(BOOL)highlight { + _cursorGuideSettingHasChanged = YES; + _textview.highlightCursorColumn = highlight; + [_textview setNeedsDisplay:YES]; + _screen.trackCursorColumnMovement = highlight; +} + +- (BOOL)highlightCursorColumn { + return _textview.highlightCursorColumn; +} + - (BOOL)screenHasView { return _textview != nil; } diff --git a/sources/PTYTextView.h b/sources/PTYTextView.h index ea3971a2bf..de88b9f17a 100644 --- a/sources/PTYTextView.h +++ b/sources/PTYTextView.h @@ -235,6 +235,9 @@ typedef NS_ENUM(NSInteger, PTYTextViewSelectionExtensionUnit) { // Draw a highlight along the entire line the cursor is on. @property(nonatomic, assign) BOOL highlightCursorLine; +// Draw a highlight along the entire column the cursor is in. +@property(nonatomic, assign) BOOL highlightCursorColumn; + // Use the non-ascii font? If not set, use the regular font for all characters. @property(nonatomic, assign) BOOL useNonAsciiFont; @@ -461,6 +464,7 @@ typedef void (^PTYTextViewDrawingHookBlock)(iTermTextDrawingHelper *); // onscreen is blinking. - (BOOL)refresh; - (void)setNeedsDisplayOnLine:(int)line; +- (void)setNeedsDisplayOnColumn:(int)column; - (void)setCursorNeedsDisplay; // selection diff --git a/sources/PTYTextView.m b/sources/PTYTextView.m index aa49b9424a..8b40db901c 100644 --- a/sources/PTYTextView.m +++ b/sources/PTYTextView.m @@ -581,6 +581,14 @@ - (BOOL)highlightCursorLine { return _drawingHelper.highlightCursorLine; } +- (void)setHighlightCursorColumn:(BOOL)highlightCursorColumn { + _drawingHelper.highlightCursorColumn = highlightCursorColumn; +} + +- (BOOL)highlightCursorColumn { + return _drawingHelper.highlightCursorColumn; +} + - (void)setUseNonAsciiFont:(BOOL)useNonAsciiFont { _drawingHelper.useNonAsciiFont = useNonAsciiFont; _useNonAsciiFont = useNonAsciiFont; @@ -970,6 +978,16 @@ - (void)setNeedsDisplayOnLine:(int)line [self setNeedsDisplayOnLine:line inRange:VT100GridRangeMake(0, _dataSource.width)]; } +- (void)setNeedsDisplayOnColumn:(int)column { + // We can only draw whole lines, not individual characters. Consequently + // a request to redraw a column should only cause an update if there is + // something in the column outside the row that needs an update, such as the + // vertical cursor guide. + if ([self highlightCursorColumn]) { + [self setNeedsDisplayOnColumn:column inRange:VT100GridRangeMake(0, _dataSource.height)]; + } +} + // Overrides an NSView method. - (NSRect)adjustScroll:(NSRect)proposedVisibleRect { proposedVisibleRect.origin.y = (int)(proposedVisibleRect.origin.y / _lineHeight + 0.5) * _lineHeight; @@ -6401,6 +6419,16 @@ - (void)setNeedsDisplayOnLine:(int)y inRange:(VT100GridRange)range { [self setNeedsDisplayInRect:dirtyRect]; } +- (void)setNeedsDisplayOnColumn:(int)x inRange:(VT100GridRange)range { + // We can only draw whole lines, not individual characters. Consequently + // a request to redraw a column should only cause an update if there is + // something in the column outside the row that needs an update, such as the + // vertical cursor guide. + if ([self highlightCursorColumn]) { + [self setNeedsDisplay:YES]; + } +} + // WARNING: Do not call this function directly. Call // -[refresh] instead, as it ensures scrollback overflow // is dealt with so that this function can dereference diff --git a/sources/ProfilesColorsPreferencesViewController.m b/sources/ProfilesColorsPreferencesViewController.m index e8e6d7ef3a..9885b996a8 100644 --- a/sources/ProfilesColorsPreferencesViewController.m +++ b/sources/ProfilesColorsPreferencesViewController.m @@ -79,6 +79,7 @@ @implementation ProfilesColorsPreferencesViewController { IBOutlet NSMenu *_presetsMenu; IBOutlet NSButton *_useGuide; + IBOutlet NSButton *_useVerticalGuide; IBOutlet CPKColorWell *_guideColor; IBOutlet NSPopUpButton *_presetsPopupButton; @@ -203,6 +204,11 @@ - (void)awakeFromNib { relatedView:nil type:kPreferenceInfoTypeCheckbox]; + [self defineControl:_useVerticalGuide + key:KEY_USE_VERTICAL_CURSOR_GUIDE + relatedView:nil + type:kPreferenceInfoTypeCheckbox]; + info = [self defineControl:_useBrightBold key:KEY_USE_BOLD_COLOR displayName:@"Custom color for bold text" diff --git a/sources/PseudoTerminal.m b/sources/PseudoTerminal.m index 101c792486..5eeeba7adf 100644 --- a/sources/PseudoTerminal.m +++ b/sources/PseudoTerminal.m @@ -1788,6 +1788,11 @@ - (IBAction)toggleCursorGuide:(id)sender { session.highlightCursorLine = !session.highlightCursorLine; } +- (IBAction)toggleVerticalCursorGuide:(id)sender { + PTYSession *session = [self currentSession]; + session.highlightCursorColumn = !session.highlightCursorColumn; +} + - (IBAction)toggleSelectionRespectsSoftBoundaries:(id)sender { iTermController *controller = [iTermController sharedInstance]; controller.selectionRespectsSoftBoundaries = !controller.selectionRespectsSoftBoundaries; @@ -5421,7 +5426,6 @@ - (void)tabView:(NSTabView *)aTabView closeWindowForLastTabViewItem:(NSTabViewIt - (NSImage *)imageFromSelectedTabView:(NSTabView *)aTabView tabViewItem:(NSTabViewItem *)tabViewItem { NSView *tabRootView = [tabViewItem view]; - NSRect tabFrame = [_contentView.tabBarControl frame]; NSRect contentFrame; NSRect viewRect; @@ -8607,6 +8611,10 @@ - (BOOL)validateMenuItem:(NSMenuItem *)item { PTYSession *session = [self currentSession]; [item setState:session.highlightCursorLine ? NSOnState : NSOffState]; result = YES; + } else if ([item action] == @selector(toggleVerticalCursorGuide:)) { + PTYSession *session = [self currentSession]; + [item setState:session.highlightCursorColumn ? NSOnState : NSOffState]; + result = YES; } else if ([item action] == @selector(toggleSelectionRespectsSoftBoundaries:)) { [item setState:[[iTermController sharedInstance] selectionRespectsSoftBoundaries] ? NSOnState : NSOffState]; result = YES; diff --git a/sources/VT100Grid.h b/sources/VT100Grid.h index 25eff122bb..6e1a72ef08 100644 --- a/sources/VT100Grid.h +++ b/sources/VT100Grid.h @@ -21,6 +21,7 @@ - (iTermUnicodeNormalization)gridUnicodeNormalizationForm; - (void)gridCursorDidMove; - (void)gridCursorDidChangeLine; +- (void)gridCursorDidChangeColumn; @end @interface VT100Grid : NSObject diff --git a/sources/VT100Grid.m b/sources/VT100Grid.m index 7c174d17e2..72090b0069 100644 --- a/sources/VT100Grid.m +++ b/sources/VT100Grid.m @@ -183,6 +183,7 @@ - (void)setCursorX:(int)cursorX { int newX = MIN(size_.width, MAX(0, cursorX)); if (newX != cursor_.x) { cursor_.x = newX; + [delegate_ gridCursorDidChangeColumn]; [delegate_ gridCursorDidMove]; } } diff --git a/sources/VT100Screen.h b/sources/VT100Screen.h index 11f570b98a..8f84967276 100644 --- a/sources/VT100Screen.h +++ b/sources/VT100Screen.h @@ -58,6 +58,7 @@ extern int kVT100ScreenMinRows; @property(nonatomic, assign) BOOL saveToScrollbackInAlternateScreen; @property(nonatomic, retain) DVR *dvr; @property(nonatomic, assign) BOOL trackCursorLineMovement; +@property(nonatomic, assign) BOOL trackCursorColumnMovement; @property(nonatomic, assign) BOOL appendToScrollbackWithStatusBar; @property(nonatomic, readonly) VT100GridAbsCoordRange lastCommandOutputRange; @property(nonatomic, assign) iTermUnicodeNormalization normalization; diff --git a/sources/VT100Screen.m b/sources/VT100Screen.m index 4be6d3e4b8..575464833c 100644 --- a/sources/VT100Screen.m +++ b/sources/VT100Screen.m @@ -52,6 +52,7 @@ NSString *const kScreenStateNextCommandOutputStartKey = @"Output Start"; NSString *const kScreenStateCursorVisibleKey = @"Cursor Visible"; NSString *const kScreenStateTrackCursorLineMovementKey = @"Track Cursor Line"; +NSString *const kScreenStateTrackCursorColumnMovementKey = @"Track Cursor Column"; NSString *const kScreenStateLastCommandOutputRangeKey = @"Last Command Output Range"; NSString *const kScreenStateShellIntegrationInstalledKey = @"Shell Integration Installed"; NSString *const kScreenStateLastCommandMarkKey = @"Last Command Mark"; @@ -4139,6 +4140,10 @@ - (void)terminalSetHighlightCursorLine:(BOOL)highlight { [delegate_ screenSetHighlightCursorLine:highlight]; } +- (void)terminalSetHighlightCursorColumn:(BOOL)highlight { + [delegate_ screenSetHighlightCursorColumn:highlight]; +} + - (void)terminalPromptDidStart { [self promptDidStartAt:VT100GridAbsCoordMake(currentGrid_.cursor.x, currentGrid_.cursor.y + self.numberOfScrollbackLines + self.totalScrollbackOverflow)]; @@ -5303,6 +5308,12 @@ - (void)gridCursorDidChangeLine { } } +- (void)gridCursorDidChangeColumn { + if (_trackCursorColumnMovement) { + [delegate_ screenCursorDidMoveToColumn:currentGrid_.cursorX]; + } +} + - (iTermUnicodeNormalization)gridUnicodeNormalizationForm { return _normalization; } @@ -5360,6 +5371,7 @@ - (NSDictionary *)contentsDictionary { kScreenStateNextCommandOutputStartKey: [NSDictionary dictionaryWithGridAbsCoord:_startOfRunningCommandOutput], kScreenStateCursorVisibleKey: @(_cursorVisible), kScreenStateTrackCursorLineMovementKey: @(_trackCursorLineMovement), + kScreenStateTrackCursorColumnMovementKey: @(_trackCursorColumnMovement), kScreenStateLastCommandOutputRangeKey: [NSDictionary dictionaryWithGridAbsCoordRange:_lastCommandOutputRange], kScreenStateShellIntegrationInstalledKey: @(_shellIntegrationInstalled), kScreenStateLastCommandMarkKey: _lastCommandMark.guid ?: [NSNull null], @@ -5474,6 +5486,7 @@ - (void)restoreFromDictionary:(NSDictionary *)dictionary _startOfRunningCommandOutput = [screenState[kScreenStateNextCommandOutputStartKey] gridAbsCoord]; _cursorVisible = [screenState[kScreenStateCursorVisibleKey] boolValue]; _trackCursorLineMovement = [screenState[kScreenStateTrackCursorLineMovementKey] boolValue]; + _trackCursorColumnMovement = [screenState[kScreenStateTrackCursorColumnMovementKey] boolValue]; _lastCommandOutputRange = [screenState[kScreenStateLastCommandOutputRangeKey] gridAbsCoordRange]; _shellIntegrationInstalled = [screenState[kScreenStateShellIntegrationInstalledKey] boolValue]; diff --git a/sources/VT100ScreenDelegate.h b/sources/VT100ScreenDelegate.h index 1936e9f128..21819d8b26 100644 --- a/sources/VT100ScreenDelegate.h +++ b/sources/VT100ScreenDelegate.h @@ -185,10 +185,14 @@ - (void)screenSetCursorVisible:(BOOL)visible; - (void)screenSetHighlightCursorLine:(BOOL)highlight; +- (void)screenSetHighlightCursorColumn:(BOOL)highlight; // Only called if the trackCursorLineMovement property is set. - (void)screenCursorDidMoveToLine:(int)line; +// Only called if the trackCursorColMovement property is set. +- (void)screenCursorDidMoveToColumn:(int)column; + // Returns if there is a view. - (BOOL)screenHasView; diff --git a/sources/VT100Terminal.m b/sources/VT100Terminal.m index 562a197006..f8b6694321 100644 --- a/sources/VT100Terminal.m +++ b/sources/VT100Terminal.m @@ -2367,6 +2367,8 @@ - (void)executeXtermSetKvp:(VT100Token *)token { [delegate_ terminalAddNote:(NSString *)value show:NO]; } else if ([key isEqualToString:@"HighlightCursorLine"]) { [delegate_ terminalSetHighlightCursorLine:value.length ? [value boolValue] : YES]; + } else if ([key isEqualToString:@"HighlightCursorColumn"]) { + [delegate_ terminalSetHighlightCursorColumn:value.length ? [value boolValue] : YES]; } else if ([key isEqualToString:@"CopyToClipboard"]) { if ([delegate_ terminalIsTrusted]) { [delegate_ terminalSetPasteboard:value]; diff --git a/sources/VT100TerminalDelegate.h b/sources/VT100TerminalDelegate.h index 5908d40c35..9594b07835 100644 --- a/sources/VT100TerminalDelegate.h +++ b/sources/VT100TerminalDelegate.h @@ -366,6 +366,7 @@ typedef NS_ENUM(int, VT100TerminalColorIndex) { - (void)terminalSetCursorVisible:(BOOL)visible; - (void)terminalSetHighlightCursorLine:(BOOL)highlight; +- (void)terminalSetHighlightCursorColumn:(BOOL)highlight; // FinalTerm features - (void)terminalPromptDidStart; diff --git a/sources/iTermMetalPerFrameState.m b/sources/iTermMetalPerFrameState.m index fa12ee3f3c..09479f0d7a 100644 --- a/sources/iTermMetalPerFrameState.m +++ b/sources/iTermMetalPerFrameState.m @@ -578,8 +578,12 @@ - (vector_float4)fullScreenFlashColor { return _configuration->_fullScreenFlashColor; } -- (BOOL)cursorGuideEnabled { - return _configuration->_cursorGuideColor && _configuration->_cursorGuideEnabled; +- (BOOL)cursorHorizontalGuideEnabled { + return _configuration->_cursorGuideColor && _configuration->_cursorHorizontalGuideEnabled; +} + +- (BOOL)cursorVerticalGuideEnabled { + return _configuration->_cursorGuideColor && _configuration->_cursorVerticalGuideEnabled; } - (NSColor *)cursorGuideColor { diff --git a/sources/iTermMetalPerFrameStateConfiguration.h b/sources/iTermMetalPerFrameStateConfiguration.h index 9223b3b038..5e557aeb6d 100644 --- a/sources/iTermMetalPerFrameStateConfiguration.h +++ b/sources/iTermMetalPerFrameStateConfiguration.h @@ -61,7 +61,8 @@ NS_ASSUME_NONNULL_BEGIN // Cursor BOOL _shouldDrawFilledInCursor; - BOOL _cursorGuideEnabled; + BOOL _cursorHorizontalGuideEnabled; + BOOL _cursorVerticalGuideEnabled; // Size VT100GridSize _gridSize; diff --git a/sources/iTermMetalPerFrameStateConfiguration.m b/sources/iTermMetalPerFrameStateConfiguration.m index 0ccdce7f73..da19fb65aa 100644 --- a/sources/iTermMetalPerFrameStateConfiguration.m +++ b/sources/iTermMetalPerFrameStateConfiguration.m @@ -55,7 +55,8 @@ - (void)loadSettingsWithDrawingHelper:(iTermTextDrawingHelper *)drawingHelper _transparencyAffectsOnlyDefaultBackgroundColor = drawingHelper.transparencyAffectsOnlyDefaultBackgroundColor; // Cursor guide - _cursorGuideEnabled = drawingHelper.highlightCursorLine; + _cursorHorizontalGuideEnabled = drawingHelper.highlightCursorLine; + _cursorVerticalGuideEnabled = drawingHelper.highlightCursorColumn; _cursorGuideColor = drawingHelper.cursorGuideColor; // Background image diff --git a/sources/iTermProfilePreferences.m b/sources/iTermProfilePreferences.m index 308ec7753d..2600daf6c8 100644 --- a/sources/iTermProfilePreferences.m +++ b/sources/iTermProfilePreferences.m @@ -184,7 +184,7 @@ + (BOOL)valueIsLegal:(id)value forKey:(NSString *)key { KEY_CURSOR_GUIDE_COLOR, KEY_BADGE_COLOR, KEY_TAB_COLOR, KEY_UNDERLINE_COLOR ]; - NSArray *number = @[ KEY_USE_CURSOR_GUIDE, KEY_USE_TAB_COLOR, KEY_USE_UNDERLINE_COLOR, + NSArray *number = @[ KEY_USE_CURSOR_GUIDE, KEY_USE_VERTICAL_CURSOR_GUIDE, KEY_USE_TAB_COLOR, KEY_USE_UNDERLINE_COLOR, KEY_SMART_CURSOR_COLOR, KEY_MINIMUM_CONTRAST, KEY_CURSOR_BOOST, KEY_CURSOR_TYPE, KEY_BLINKING_CURSOR, KEY_USE_BOLD_FONT, KEY_THIN_STROKES, KEY_ASCII_LIGATURES, KEY_NON_ASCII_LIGATURES, KEY_USE_BOLD_COLOR, @@ -268,6 +268,7 @@ + (NSDictionary *)defaultValueMap { KEY_CURSOR_GUIDE_COLOR: [[NSColor colorWithCalibratedRed:0.650 green:0.910 blue:1.000 alpha:0.25] dictionaryValue], KEY_BADGE_COLOR: [[NSColor colorWithCalibratedRed:1.0 green:0.000 blue:0.000 alpha:0.5] dictionaryValue], KEY_USE_CURSOR_GUIDE: @NO, + KEY_USE_VERTICAL_CURSOR_GUIDE: @NO, KEY_TAB_COLOR: [NSNull null], KEY_USE_TAB_COLOR: @NO, KEY_UNDERLINE_COLOR: [NSNull null], diff --git a/sources/iTermTextDrawingHelper.h b/sources/iTermTextDrawingHelper.h index d06f90137b..cd3eeaeb06 100644 --- a/sources/iTermTextDrawingHelper.h +++ b/sources/iTermTextDrawingHelper.h @@ -180,6 +180,9 @@ BOOL CheckFindMatchAtIndex(NSData *findMatches, int index); // Should the cursor guide be shown? @property(nonatomic, assign) BOOL highlightCursorLine; +// Should the vertical cursor guide be shown? +@property(nonatomic, assign) BOOL highlightCursorColumn; + // Minimum contrast level, 0-1. @property(nonatomic, assign) double minimumContrast; diff --git a/sources/iTermTextDrawingHelper.m b/sources/iTermTextDrawingHelper.m index 29167a88d3..b42231f016 100644 --- a/sources/iTermTextDrawingHelper.m +++ b/sources/iTermTextDrawingHelper.m @@ -644,14 +644,65 @@ - (void)drawAccessoriesInRect:(NSRect)bgRect { // Highlight cursor line if the cursor is on this line and it's on. int cursorLine = _cursorCoord.y + _numberOfScrollbackLines; - const BOOL drawCursorGuide = (self.highlightCursorLine && - cursorLine >= coordRange.start.y && - cursorLine < coordRange.end.y); - if (drawCursorGuide) { + int cursorColumn = _cursorCoord.x; + const BOOL drawHorizontalCursorGuide = (self.highlightCursorLine && + cursorLine >= coordRange.start.y && + cursorLine < coordRange.end.y); + const BOOL drawVerticalCursorGuide = (self.highlightCursorColumn && + cursorColumn >= coordRange.start.x && + cursorColumn < coordRange.end.x); + + if (drawHorizontalCursorGuide && !drawVerticalCursorGuide) { CGFloat y = cursorLine * _cellSize.height; [self drawCursorGuideForColumns:NSMakeRange(coordRange.start.x, coordRange.end.x - coordRange.start.x) y:y]; + } else if (!drawHorizontalCursorGuide && drawVerticalCursorGuide) { + CGFloat x = cursorColumn * _cellSize.width; + [self drawCursorGuideForRows:NSMakeRange(coordRange.start.y, + coordRange.end.y - coordRange.start.y) + x:x]; + [self.delegate setNeedsDisplay:YES]; + } else if (drawHorizontalCursorGuide && drawVerticalCursorGuide) { + CGFloat x = cursorColumn * _cellSize.width; + CGFloat y = cursorLine * _cellSize.height; + [self drawCursorGuideForColumns:NSMakeRange(coordRange.start.x, + coordRange.end.x - coordRange.start.x) + y:y]; + [self drawCursorGuideForRows:NSMakeRange(coordRange.start.y, + cursorLine - coordRange.start.y) + x:x]; + [self drawCursorGuideForRows:NSMakeRange(cursorLine + 1, + coordRange.end.y - _cursorCoord.y) + x:x]; + [self.delegate setNeedsDisplay:YES]; + } + + // Highlight cursor column if the cursor is in this column and it's on. + if (drawHorizontalCursorGuide && !drawVerticalCursorGuide) { + CGFloat y = cursorLine * _cellSize.height; + [self drawCursorGuideForColumns:NSMakeRange(coordRange.start.x, + coordRange.end.x - coordRange.start.x) + y:y]; + } else if (!drawHorizontalCursorGuide && drawVerticalCursorGuide) { + CGFloat x = cursorColumn * _cellSize.width; + [self drawCursorGuideForRows:NSMakeRange(coordRange.start.y, + coordRange.end.y - coordRange.start.y) + x:x]; + [self.delegate setNeedsDisplay:YES]; + } else if (drawHorizontalCursorGuide && drawVerticalCursorGuide) { + CGFloat x = cursorColumn * _cellSize.width; + CGFloat y = cursorLine * _cellSize.height; + [self drawCursorGuideForColumns:NSMakeRange(coordRange.start.x, + coordRange.end.x - coordRange.start.x) + y:y]; + [self drawCursorGuideForRows:NSMakeRange(coordRange.start.y, + cursorLine - coordRange.start.y) + x:x]; + [self drawCursorGuideForRows:NSMakeRange(cursorLine + 1, + coordRange.end.y - _cursorCoord.y) + x:x]; + [self.delegate setNeedsDisplay:YES]; } } @@ -674,6 +725,26 @@ - (void)drawCursorGuideForColumns:(NSRange)range y:(CGFloat)yOrigin { NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); } +- (void)drawCursorGuideForRows:(NSRange)range x:(CGFloat)xOrigin { + if (!_cursorVisible) { + return; + } + [_cursorGuideColor set]; + NSPoint textOrigin = NSMakePoint(xOrigin + [iTermAdvancedSettingsModel terminalMargin], + range.location * _cellSize.height); + NSRect rect = NSMakeRect(textOrigin.x, + textOrigin.y, + _cellSize.width, + range.length * _cellSize.height); + NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); + + rect.size.width = 1; + NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); + + rect.origin.x += _cellSize.width - 1; + NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); +} + + (NSRect)frameForMarkContainedInRect:(NSRect)container cellSize:(CGSize)cellSize cellSizeWithoutSpacing:(CGSize)cellSizeWithoutSpacing