@@ -25,6 +25,7 @@ import android.os.Looper;
import android.provider.Settings ;
import android.text.TextUtils ;
import android.text.InputType ;
import android.text.method.LinkMovementMethod ;
import android.view.Gravity ;
import android.view.HapticFeedbackConstants ;
import android.view.KeyEvent ;
@@ -154,6 +155,7 @@ public final class MainActivity extends Activity {
private int lastActiveSessionCount = - 1 ;
private TerminalScreenBuffer terminalScreen = new TerminalScreenBuffer ( DEFAULT_TERMINAL_COLS , DEFAULT_TERMINAL_ROWS ) ;
private final StringBuilder queuedTerminalInput = new StringBuilder ( ) ;
private final Set < String > terminalExpandedBlocks = new HashSet < > ( ) ;
private final Set < String > terminalExpandedMessages = new HashSet < > ( ) ;
private final List < ConversationMessage > terminalConversationMessages = new ArrayList < > ( ) ;
private boolean terminalConnected ;
@@ -163,6 +165,7 @@ public final class MainActivity extends Activity {
private boolean terminalFollowOutput = true ;
private int terminalViewMode = TERMINAL_VIEW_CHAT ;
private int terminalKeyPage ;
private int terminalHistoryOffset ;
private int terminalReconnectAttempt ;
private int terminalConnectionGeneration ;
private int eventReconnectAttempt ;
@@ -1696,6 +1699,8 @@ public final class MainActivity extends Activity {
terminalSelectionEnabled = false ;
terminalFollowOutput = true ;
terminalKeyPage = 0 ;
terminalHistoryOffset = 0 ;
terminalExpandedBlocks . clear ( ) ;
terminalExpandedMessages . clear ( ) ;
terminalConversationMessages . clear ( ) ;
terminalLiveStatusText = null ;
@@ -1729,11 +1734,11 @@ public final class MainActivity extends Activity {
terminalText . setLineSpacing ( 0 , 1 . 05f ) ;
terminalText . setGravity ( Gravity . BOTTOM | Gravity . START ) ;
terminalText . setTextIsSelectable ( terminalSelectionEnabled ) ;
terminalText . setPadding ( dp ( 2 ) , dp ( 8 ) , dp ( 2 ) , dp ( 8 ) ) ;
terminalText . setPadding ( dp ( 2 ) , dp ( 8 ) , dp ( 2 ) , 0 ) ;
terminalText . setBackgroundColor ( COLOR_TERMINAL_BG ) ;
terminalChatList = new LinearLayout ( this ) ;
terminalChatList . setOrientation ( LinearLayout . VERTICAL ) ;
terminalChatList . setPadding ( dp ( 10 ) , dp ( 12 ) , dp ( 10 ) , dp ( 18 ) ) ;
terminalChatList . setPadding ( 0 , dp ( 12 ) , 0 , 0 ) ;
terminalChatList . addView ( projectStateText ( " Loading conversation... " ) , matchWrap ( ) ) ;
showTerminalView ( terminalViewMode , false ) ;
terminalScroll . addOnLayoutChangeListener ( ( view , left , top , right , bottom , oldLeft , oldTop , oldRight , oldBottom ) - >
@@ -1746,7 +1751,6 @@ public final class MainActivity extends Activity {
1
) ) ;
terminalAccessoryBar = createAccessoryBar ( ) ;
terminalAccessoryBar . setVisibility ( terminalViewMode = = TERMINAL_VIEW_CHAT ? View . GONE : View . VISIBLE ) ;
root . addView ( terminalAccessoryBar , new LinearLayout . LayoutParams (
ViewGroup . LayoutParams . MATCH_PARENT ,
dp ( TERMINAL_KEYS_HEIGHT_DP )
@@ -1762,6 +1766,8 @@ public final class MainActivity extends Activity {
} ) ;
inputField . post ( ( ) - > {
inputField . requestFocus ( ) ;
inputField . setSelection ( inputField . length ( ) ) ;
inputField . setCursorVisible ( true ) ;
hideKeyboard ( ) ;
} ) ;
}
@@ -1867,26 +1873,25 @@ public final class MainActivity extends Activity {
renderTerminalConversation ( ) ;
} else {
terminalText . setHorizontallyScrolling ( true ) ;
terminalText . setGravity ( Gravity . BOTTOM | Gravity . START ) ;
terminalText . setMovementMethod ( null ) ;
terminalText . setGravity ( Gravity . BOTTOM | Gravity . START ) ;
terminalScroll . addView ( terminalText , new ScrollView . LayoutParams (
ViewGroup . LayoutParams . MATCH_PARENT ,
ViewGroup . LayoutParams . WRAP_CONTENT
) ) ;
renderTerminalNow ( ) ;
}
boolean chat = mode = = TERMINAL_VIEW_CHAT ;
if ( terminalAccessoryBar ! = null ) {
terminalAccessoryBar . setVisibility ( chat ? View . GONE : View . VISIBLE ) ;
terminalAccessoryBar . setVisibility ( View . VISIBLE ) ;
}
if ( terminalComposerTabs ! = null ) {
terminalComposerTabs . setVisibility ( chat ? View . GONE : View . VISIBLE ) ;
terminalComposerTabs . setVisibility ( View . VISIBLE ) ;
}
if ( inputField ! = null ) {
inputField . setHint ( chat ? " Message " : " type command or text " ) ;
inputField . setHint ( mode = = TERMINAL_VIEW_CHAT ? " message or command " : " type command or text " ) ;
}
if ( announce ) {
setStatus ( mode = = TERMINAL_VIEW_CHAT ? " Cha t view " : " Full terminal mode" ) ;
setStatus ( mode = = TERMINAL_VIEW_CHAT ? " Conten t view " : " Raw terminal mode" ) ;
}
}
@@ -2050,124 +2055,74 @@ public final class MainActivity extends Activity {
private View conversationMessageRow ( ConversationMessage message ) {
boolean user = " user " . equals ( message . role ) ;
boolean tool = message . isTool ( ) ;
LinearLayout row = new LinearLayout ( this ) ;
row . setOrientation ( LinearLayout . HORIZONTAL ) ;
row . setGravity ( user ? Gravity . END : Gravity . START ) ;
LinearLayout block = new LinearLayout ( this ) ;
block . setOrientation ( LinearLayout . VERTICAL ) ;
block . setPadding ( dp ( 10 ) , dp ( 5 ) , dp ( 10 ) , dp ( 7 ) ) ;
LinearLayout bubble = new LinearLayout ( this ) ;
bubble . setOrientation ( LinearLayout . VERTICAL ) ;
bubble . setPadding ( user | | tool ? dp ( 12 ) : dp ( 2 ) , dp ( 9 ) , user | | tool ? dp ( 12 ) : dp ( 4 ) , dp ( 10 ) ) ;
bubble . setBackground ( tool
? rounded ( COLOR_PANEL , 8 , COLOR_BORDER , 1 )
: user
? rounded ( COLOR_ACCENT_DARK , 8 , COLOR_ACCENT , 1 )
: rounded ( Color . TRANSPARENT , 0 , Color . TRANSPARENT , 0 ) ) ;
TextView content = conversationContent ( message , false ) ;
content . setText ( ( user ? " › " : " • " ) + defaultValue ( message . content , " (empty) " ) ) ;
content . setTextColor ( user ? COLOR_ACCENT : COLOR_TEXT ) ;
content . setTextSize ( 12 ) ;
content . setTypeface ( Typeface . MONOSPACE , user ? Typeface . BOLD : Typeface . NORMAL ) ;
content . setPadding ( 0 , 0 , 0 , 0 ) ;
block . addView ( content , matchWrap ( ) ) ;
TextView meta = new TextView ( this ) ;
meta . setTextColor ( user ? COLOR_ACCENT : tool ? COLOR_ACCENT_WARM : COLOR_TEXT_MUTED ) ;
meta . setTextSize ( 9 ) ;
meta . setTypeface ( Typeface . MONOSPACE , Typeface . BOLD ) ;
meta . setSingleLine ( true ) ;
meta . setEllipsize ( TextUtils . TruncateAt . END ) ;
meta . setText ( tool ? conversationToolTitle ( message ) : conversationStatusPart ( message ) . replaceFirst ( " ^ · " , " " ) ) ;
if ( tool | | ( ! " complete " . equals ( message . status ) & & ! message . status . isEmpty ( ) ) ) {
bubble . addView ( meta , matchWrap ( ) ) ;
if ( ! " complete " . equals ( message . status ) & & ! message . status . isEmpty ( ) ) {
TextView status = bodyText ( message . status ) ;
status . setTextColor ( message . status . toLowerCase ( java . util . Locale . ROOT ) . contains ( " error " )
? COLOR_DANGER : COLOR_TEXT_DIM ) ;
status . setTextSize ( 9 ) ;
status . setTypeface ( Typeface . MONOSPACE ) ;
status . setPadding ( dp ( 14 ) , dp ( 3 ) , 0 , 0 ) ;
block . addView ( status , matchWrap ( ) ) ;
}
boolean expanded = terminalExpandedMessages . contains ( message . messageId ) ;
if ( ! tool | | expanded ) {
bubble . addView ( conversationContent ( message , tool ) , matchWrap ( ) ) ;
} else {
TextView collapsed = bodyText ( " Tap to show " + message . content . length ( ) + " characters " ) ;
collapsed . setTextSize ( 10 ) ;
collapsed . setPadding ( 0 , dp ( 5 ) , 0 , 0 ) ;
bubble . addView ( collapsed , matchWrap ( ) ) ;
}
if ( tool ) {
bubble . setOnClickListener ( view - > {
if ( ! terminalExpandedMessages . add ( message . messageId ) ) {
terminalExpandedMessages . remove ( message . messageId ) ;
}
renderTerminalConversation ( ) ;
} ) ;
}
if ( ! tool & & ! user ) {
row . addView ( bubble , matchWrap ( ) ) ;
} else if ( ! tool ) {
LinearLayout . LayoutParams bubbleParams = new LinearLayout . LayoutParams (
0 ,
ViewGroup . LayoutParams . WRAP_CONTENT ,
0 . 82f
) ;
View spacer = new View ( this ) ;
LinearLayout . LayoutParams spacerParams = new LinearLayout . LayoutParams (
0 ,
1 ,
0 . 18f
) ;
row . addView ( spacer , spacerParams ) ;
row . addView ( bubble , bubbleParams ) ;
} else {
row . addView ( bubble , matchWrap ( ) ) ;
}
LinearLayout . LayoutParams rowParams = new LinearLayout . LayoutParams (
LinearLayout . LayoutParams params = new LinearLayout . LayoutParams (
ViewGroup . LayoutParams . MATCH_PARENT ,
ViewGroup . LayoutParams . WRAP_CONTENT
) ;
rowParams . bottomMargin = dp ( 9 ) ;
row . setLayoutParams ( rowParams ) ;
return row ;
params . bottomMargin = dp ( 5 ) ;
block . setLayoutParams ( params ) ;
return block ;
}
private View conversationToolGroup ( List < ConversationMessage > tools ) {
LinearLayout group = new LinearLayout ( this ) ;
group . setOrientation ( LinearLayout . VERTICAL ) ;
String firstId = tools . isEmpty ( ) ? " empty " : tools . get ( 0 ) . messageId ;
String lastId = tools . isEmpty ( ) ? " empty " : tools . get ( tools . size ( ) - 1 ) . messageId ;
String groupKey = " tools: " + firstId + " : " + lastId ;
boolean expanded = terminalExpandedMessages . contains ( groupKey ) ;
String firstTitle = tools . isEmpty ( ) ? " Tool activity " : conversationToolTitle ( tools . get ( 0 ) ) ;
TextView summary = new TextView ( this ) ;
summary . setText ( ( expanded ? " ▼ " : " ▶ " ) + tools . size ( ) + " tool "
+ ( tools . size ( ) = = 1 ? " activity " : " activities " ) + " · " + compactLabel ( firstTitle , 44 ) ) ;
summary . setTextColor ( COLOR_ACCENT_WARM ) ;
summary . setTextSize ( 10 ) ;
summary . setTypeface ( Typeface . MONOSPACE , Typeface . BOLD ) ;
summary . setPadding ( dp ( 10 ) , dp ( 6 ) , dp ( 10 ) , dp ( 6 ) ) ;
summary . setOnClickListener ( view - > {
if ( ! terminalExpandedMessages . add ( groupKey ) ) {
terminalExpandedMessages . remove ( groupKey ) ;
}
renderTerminalConversation ( ) ;
} ) ;
summary . setContentDescription ( ( expanded ? " Collapse " : " Expand " ) + tools . size ( ) + " tool activities " ) ;
group . addView ( summary , matchWrap ( ) ) ;
HorizontalScrollView scroller = new HorizontalScrollView ( this ) ;
scroller . setHorizontalScrollBarEnabled ( false ) ;
LinearLayout chips = new LinearLayout ( this ) ;
chips . setOrientation ( LinearLayout . HORIZONTAL ) ;
chips . setGravity ( Gravity . CENTER_VERTICAL ) ;
for ( ConversationMessage message : tools ) {
String label = compactLabel ( conversationToolTitle ( message ) , 18 ) ;
Button chip = compactButton ( label , view - > {
if ( ! terminalExpandedMessages . add ( message . messageId ) ) {
terminalExpandedMessages . remove ( message . messageId ) ;
}
renderTerminalConversation ( ) ;
} ) ;
chip . setTextSize ( 9 ) ;
chip . setContentDescription ( " Toggle " + label ) ;
chips . addView ( chip ) ;
}
scroller . addView ( chips , new HorizontalScrollView . LayoutParams (
ViewGroup . LayoutParams . WRAP_CONTENT ,
dp ( 34 )
) ) ;
group . addView ( scroller , matchWrap ( ) ) ;
for ( ConversationMessage message : tools ) {
if ( ! terminalExpandedMessages . contains ( message . messageId ) ) {
continue ;
if ( expanded ) {
for ( ConversationMessage message : tools ) {
LinearLayout detail = new LinearLayout ( this ) ;
detail . setOrientation ( LinearLayout . VERTICAL ) ;
detail . setPadding ( dp ( 18 ) , dp ( 3 ) , dp ( 10 ) , dp ( 7 ) ) ;
TextView title = bodyText ( conversationToolTitle ( message ) ) ;
title . setTextColor ( COLOR_ACCENT_WARM ) ;
title . setTextSize ( 9 ) ;
title . setTypeface ( Typeface . MONOSPACE , Typeface . BOLD ) ;
detail . addView ( title , matchWrap ( ) ) ;
detail . addView ( conversationContent ( message , true ) , matchWrap ( ) ) ;
group . addView ( detail , matchWrap ( ) ) ;
}
LinearLayout detail = new LinearLayout ( this ) ;
detail . setOrientation ( LinearLayout . VERTICAL ) ;
detail . setPadding ( dp ( 10 ) , dp ( 7 ) , dp ( 10 ) , dp ( 9 ) ) ;
detail . setBackground ( rounded ( COLOR_FIELD , 7 , COLOR_BORDER_SOFT , 1 ) ) ;
TextView title = bodyText ( conversationToolTitle ( message ) ) ;
title . setTextColor ( COLOR_ACCENT_WARM ) ;
title . setTextSize ( 9 ) ;
title . setTypeface ( Typeface . MONOSPACE , Typeface . BOLD ) ;
detail . addView ( title , matchWrap ( ) ) ;
detail . addView ( conversationContent ( message , true ) , matchWrap ( ) ) ;
LinearLayout . LayoutParams detailParams = new LinearLayout . LayoutParams (
ViewGroup . LayoutParams . MATCH_PARENT ,
ViewGroup . LayoutParams . WRAP_CONTENT
) ;
detailParams . topMargin = dp ( 5 ) ;
group . addView ( detail , detailParams ) ;
}
LinearLayout . LayoutParams params = new LinearLayout . LayoutParams (
@@ -2204,43 +2159,49 @@ public final class MainActivity extends Activity {
private View conversationLiveTerminalPanel ( ) {
LinearLayout panel = new LinearLayout ( this ) ;
panel . setOrientation ( LinearLayout . VERTICAL ) ;
panel . setPadding ( dp ( 12 ) , dp ( 9 ) , dp ( 12 ) , dp ( 10 ) ) ;
panel . setBackground ( rounded ( COLOR_FIELD , 8 , COLOR_BORDER , 1 ) ) ;
panel . setOnClickListener ( view - > showTerminalView ( TERMINAL_VIEW_FULL , true ) ) ;
panel . setPadding ( dp ( 2 ) , dp ( 10 ) , dp ( 2 ) , 0 ) ;
LinearLayout heading = new LinearLayout ( this ) ;
heading . setOrientation ( LinearLayout . HORIZONTAL ) ;
heading . setGravity ( Gravity . CENTER_VERTICAL ) ;
TextView title = new TextView ( this ) ;
title . setText ( " Live terminal " ) ;
title . setTextColor ( COLOR_TEXT ) ;
title . setTextSize ( 11 ) ;
title . setTypeface ( Typeface . DEFAULT_BOLD ) ;
heading . addView ( title , new LinearLayout . LayoutParams ( 0 , dp ( 22 ) , 1 ) ) ;
title . setText ( " LIVE TERMINAL " ) ;
title . setTextColor ( COLOR_TEXT_DIM ) ;
title . setTextSize ( 9 ) ;
title . setTypeface ( Typeface . MONOSPACE , Typeface . BOLD ) ;
heading . addView ( title , new LinearLayout . LayoutParams ( 0 , dp ( 32 ) , 1 ) ) ;
terminalLiveStatusText = new TextView ( this ) ;
terminalLiveStatusText . setTextSize ( 9 ) ;
terminalLiveStatusText . setTypeface ( Typeface . MONOSPACE ) ;
terminalLiveStatusText . setSingleLine ( true ) ;
heading . addView ( terminalLiveStatusText , new LinearLayout . LayoutParams (
ViewGroup . LayoutParams . WRAP_CONTENT ,
dp ( 22 )
dp ( 32 )
) ) ;
Button older = terminalToolButton ( " ↑ " , view - > scrollTerminalHistory ( - terminalRows ) ) ;
older . setContentDescription ( " Load older terminal history " ) ;
heading . addView ( older ) ;
Button newer = terminalToolButton ( " ↓ " , view - > scrollTerminalHistory ( terminalRows ) ) ;
newer . setContentDescription ( " Return toward live terminal output " ) ;
heading . addView ( newer ) ;
panel . addView ( heading , matchWrap ( ) ) ;
terminalLiveOutputText = new TextView ( this ) ;
terminalLiveOutputText . setTextColor ( COLOR_TEXT_MUTED ) ;
terminalLiveOutputText . setTextSize ( 10 ) ;
terminalLiveOutputText . setTextSize ( 11 ) ;
terminalLiveOutputText . setTypeface ( Typeface . MONOSPACE ) ;
terminalLiveOutputText . setMaxLines ( 6 ) ;
terminalLiveOutputText . setEllipsize ( TextUtils . TruncateAt . END ) ;
terminalLiveOutputText . setIncludeFontPadding ( false ) ;
terminalLiveOutputText . setHorizontallyScrolling ( true ) ;
terminalLiveOutputText . setLineSpacing ( 0 , 1 . 05f ) ;
terminalLiveOutputText . setGravity ( Gravity . BOTTOM | Gravity . START ) ;
terminalLiveOutputText . setBackgroundColor ( COLOR_TERMINAL_BG ) ;
terminalLiveOutputText . setPadding ( 0 , dp ( 5 ) , 0 , 0 ) ;
terminalLiveOutputText . setMovementMethod ( LinkMovementMethod . getInstance ( ) ) ;
terminalLiveOutputText . setHighlightColor ( Color . TRANSPARENT ) ;
terminalLiveOutputText . setTextIsSelectable ( true ) ;
terminalLiveOutputText . addOnLayoutChangeListener ( ( view , left , top , right , bottom ,
oldLeft , oldTop , oldRight , oldBottom ) - > resizeTerminalToViewport ( false ) ) ;
panel . addView ( terminalLiveOutputText , matchWrap ( ) ) ;
TextView hint = bodyText ( " Tap for full terminal " ) ;
hint . setTextSize ( 9 ) ;
hint . setTextColor ( COLOR_TEXT_DIM ) ;
hint . setPadding ( 0 , dp ( 6 ) , 0 , 0 ) ;
panel . addView ( hint , matchWrap ( ) ) ;
return panel ;
}
@@ -2250,10 +2211,19 @@ public final class MainActivity extends Activity {
}
String socket = terminalSocketStatus . toLowerCase ( java . util . Locale . ROOT ) ;
boolean connected = socket . contains ( " connected " ) & & ! socket . contains ( " disconnected " ) ;
terminalLiveStatusText . setText ( connected ? " ● connected " : " ● " + defaultValue ( socket , " connecting " ) ) ;
terminalLiveStatusText . setTextColor ( connected ? COLOR_SUCCESS
terminalLiveStatusText . setText ( terminalHistoryOffset > 0
? " ● " + terminalHistoryOffset + " back "
: connected ? " ● live " : " ● " + defaultValue ( socket , " connecting " ) ) ;
terminalLiveStatusText . setTextColor ( terminalHistoryOffset > 0 ? COLOR_ACCENT_WARM : connected ? COLOR_SUCCESS
: socket . contains ( " error " ) | | socket . contains ( " disconnected " ) ? COLOR_DANGER : COLOR_ACCENT_WARM ) ;
terminalLiveOutputText . setText ( terminalScreen . renderTail ( 6 ) ) ;
terminalLiveOutputText . setMovementMethod ( LinkMovementMethod . getInstance ( ) ) ;
terminalLiveOutputText . setHighlightColor ( Color . TRANSPARENT ) ;
terminalLiveOutputText . setText ( terminalScreen . renderFocused ( terminalExpandedBlocks , key - > {
if ( ! terminalExpandedBlocks . add ( key ) ) {
terminalExpandedBlocks . remove ( key ) ;
}
renderTerminalNow ( ) ;
} ) ) ;
}
private void updateTerminalMeta ( ) {
@@ -2532,14 +2502,10 @@ public final class MainActivity extends Activity {
sendTerminalInput ( " \ u0002z " ) ;
break ;
case 6 :
if ( terminalSocket ! = null ) {
terminalSocket . scroll ( - terminalRows ) ;
}
scrollTerminalHistory ( - terminalRows ) ;
break ;
case 7 :
if ( terminalSocket ! = null ) {
terminalSocket . scroll ( terminalRows ) ;
}
scrollTerminalHistory ( terminalRows ) ;
break ;
case 8 :
showRaw ( " Session status " , ( ) - > api . sessionStatus ( sessionName ) ) ;
@@ -2588,7 +2554,6 @@ public final class MainActivity extends Activity {
LinearLayout tabGrid = new LinearLayout ( this ) ;
tabGrid . setOrientation ( LinearLayout . VERTICAL ) ;
terminalComposerTabs = tabGrid ;
tabGrid . setVisibility ( terminalViewMode = = TERMINAL_VIEW_CHAT ? View . GONE : View . VISIBLE ) ;
LinearLayout firstTabRow = terminalKeyRow ( ) ;
LinearLayout secondTabRow = terminalKeyRow ( ) ;
addAccessoryTab ( firstTabRow , " Edit " , 0 ) ;
@@ -3548,6 +3513,8 @@ public final class MainActivity extends Activity {
terminalConnected = true ;
terminalConnecting = false ;
terminalReconnectAttempt = 0 ;
terminalHistoryOffset = 0 ;
terminalFollowOutput = true ;
terminalSocketStatus = " terminal connected " ;
updateTerminalMeta ( ) ;
setStatus ( " Connected " + sessionName ) ;
@@ -3601,24 +3568,33 @@ public final class MainActivity extends Activity {
if ( terminalText = = null | | terminalScroll = = null ) {
return ;
}
int width = terminalScroll . getWidth ( ) ;
TextView viewportText = terminalViewMode = = TERMINAL_VIEW_CHAT & & terminalLiveOutputText ! = null
? terminalLiveOutputText : terminalText ;
int width = viewportText . getWidth ( ) ;
if ( width < = 0 ) {
width = terminalScroll . getWidth ( ) ;
}
int height = terminalScroll . getHeight ( ) ;
if ( width < = 0 | | height < = 0 ) {
return ;
}
int horizontalPadding = terminalText . getPaddingLeft ( ) + terminalText . getPaddingRight ( ) ;
int verticalPadding = terminalText . getPaddingTop ( ) + terminalText . getPaddingBottom ( ) ;
float charWidth = terminalText . getPaint ( ) . measureText ( " 0000000000 " ) / 10f ;
int horizontalPadding = viewportText . getPaddingLeft ( ) + viewportText . getPaddingRight ( ) ;
int verticalPadding = viewportText . getPaddingTop ( ) + viewportText . getPaddingBottom ( ) ;
float charWidth = viewportText . getPaint ( ) . measureText ( " 0000000000 " ) / 10f ;
if ( charWidth < = 0f ) {
charWidth = dp ( 8 ) ;
}
int lineHeight = terminalText . getLineHeight ( ) ;
int lineHeight = viewportText . getLineHeight ( ) ;
if ( lineHeight < = 0 ) {
lineHeight = dp ( 16 ) ;
}
int usableWidth = Math . max ( 1 , width - horizontalPadding ) ;
int cols = clamp ( ( int ) Math . floor ( usableWidth / charWidth ) , MIN_TERMINAL_COLS , MAX_TERMINAL_COLS ) ;
int rows = clamp ( ( height - verticalPadding ) / lineHeight , MIN_TERMINAL_ROWS , MAX_TERMINAL_ROWS ) ;
if ( terminalLiveOutputText ! = null ) {
terminalLiveOutputText . setMinHeight ( rows * lineHeight ) ;
terminalLiveOutputText . setGravity ( Gravity . BOTTOM | Gravity . START ) ;
}
if ( cols = = terminalCols & & rows = = terminalRows & & ! forceSend ) {
return ;
}
@@ -3661,6 +3637,11 @@ public final class MainActivity extends Activity {
}
TerminalSocketClient socket = terminalSocket ;
if ( socket ! = null & & ! socket . isClosed ( ) & & terminalConnected ) {
if ( terminalHistoryOffset > 0 ) {
socket . scroll ( terminalHistoryOffset ) ;
terminalHistoryOffset = 0 ;
terminalFollowOutput = true ;
}
socket . sendInput ( data ) ;
setStatus ( " Sent input " ) ;
return ;
@@ -3776,6 +3757,9 @@ public final class MainActivity extends Activity {
}
private void scrollTerminalBottom ( ) {
if ( terminalHistoryOffset > 0 ) {
scrollTerminalHistory ( terminalHistoryOffset ) ;
}
terminalFollowOutput = true ;
if ( terminalScroll ! = null ) {
terminalScroll . post ( ( ) - > terminalScroll . fullScroll ( View . FOCUS_DOWN ) ) ;
@@ -3783,6 +3767,31 @@ public final class MainActivity extends Activity {
setStatus ( " Following terminal output " ) ;
}
private void scrollTerminalHistory ( int lines ) {
TerminalSocketClient socket = terminalSocket ;
if ( socket = = null | | socket . isClosed ( ) | | ! terminalConnected | | lines = = 0 ) {
setStatus ( " Terminal history unavailable while disconnected " ) ;
return ;
}
if ( lines < 0 ) {
terminalHistoryOffset + = Math . abs ( lines ) ;
terminalFollowOutput = false ;
} else {
if ( terminalHistoryOffset = = 0 ) {
setStatus ( " Live terminal output " ) ;
return ;
}
int amount = Math . min ( lines , terminalHistoryOffset ) ;
terminalHistoryOffset = Math . max ( 0 , terminalHistoryOffset - amount ) ;
terminalFollowOutput = terminalHistoryOffset = = 0 ;
lines = amount ;
}
socket . scroll ( lines ) ;
setStatus ( terminalHistoryOffset = = 0
? " Live terminal output "
: " Terminal history · " + terminalHistoryOffset + " lines back " ) ;
}
private void setTerminalKeyPage ( int page ) {
terminalKeyPage = ( page + 4 ) % 4 ;
updateAccessoryTabs ( ) ;
@@ -3827,10 +3836,21 @@ public final class MainActivity extends Activity {
return ;
}
lastTerminalRenderMs = System . currentTimeMillis ( ) ;
terminalText . setMovementMethod ( null ) ;
terminalText . setText ( terminalScreen . render ( ) ) ;
if ( terminalViewMode = = TERMINAL_VIEW_CHAT ) {
terminalText . setMovementMethod ( LinkMovementMethod . getInstance ( ) ) ;
terminalText . setHighlightColor ( Color . TRANSPARENT ) ;
terminalText . setText ( terminalScreen . renderFocused ( terminalExpandedBlocks , key - > {
if ( ! terminalExpandedBlocks . add ( key ) ) {
terminalExpandedBlocks . remove ( key ) ;
}
renderTerminalNow ( ) ;
} ) ) ;
} else {
terminalText . setMovementMethod ( null ) ;
terminalText . setText ( terminalScreen . render ( ) ) ;
}
updateTerminalLivePanel ( ) ;
if ( terminalViewMode ! = TERMINAL_VIEW_CHAT & & terminalFollowOutput ) {
if ( terminalFollowOutput ) {
terminalScroll . post ( ( ) - > terminalScroll . fullScroll ( View . FOCUS_DOWN ) ) ;
}
}
@@ -3986,7 +4006,12 @@ public final class MainActivity extends Activity {
input . setHintTextColor ( COLOR_TEXT_DIM ) ;
input . setTextSize ( 13 ) ;
input . setPadding ( dp ( 8 ) , dp ( 5 ) , dp ( 8 ) , dp ( 5 ) ) ;
input . setBackground ( inputBackground ( ) ) ;
input . setBackground ( composerInputBackground ( ) ) ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . Q ) {
GradientDrawable cursor = rounded ( COLOR_ACCENT , 1 , Color . TRANSPARENT , 0 ) ;
cursor . setSize ( dp ( 2 ) , dp ( 20 ) ) ;
input . setTextCursorDrawable ( cursor ) ;
}
}
private Button compactButton ( String label , View . OnClickListener listener ) {
@@ -4019,6 +4044,14 @@ public final class MainActivity extends Activity {
return rounded ( COLOR_FIELD , 8 , COLOR_BORDER , 1 ) ;
}
private StateListDrawable composerInputBackground ( ) {
StateListDrawable states = new StateListDrawable ( ) ;
states . addState ( new int [ ] { android . R . attr . state_focused } ,
rounded ( COLOR_CARD_ALT , 7 , COLOR_ACCENT , 1 ) ) ;
states . addState ( new int [ ] { } , rounded ( COLOR_CARD , 7 , COLOR_BORDER , 1 ) ) ;
return states ;
}
private GradientDrawable rounded ( int color , int radiusDp , int strokeColor , int strokeDp ) {
GradientDrawable drawable = new GradientDrawable ( ) ;
drawable . setColor ( color ) ;