Files
PowerShell-PowerShell/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs
T
Steve Lee 110f78b818 Add -RepeatHeader to Format-Table to enable repeating header for each screen full (#8481)
When using a screen reader or just getting the output of a table with lots of rows, the header is no longer on the screen and the columns may no longer make sense without the context.  This change adds a `-RepeatHeader` switch to `Format-Table` to enable re-outputting the header after every screen full (minus 1 row).  Expectation is that the user is piping the output to a pager (e.g. less) which uses the bottom row for pager information.  I followed the `AutoSize` parameter as the way to get the parameter from the cmdlet into the deep formatting object where it's needed.
2018-12-20 11:05:34 -08:00

483 lines
20 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Internal;
namespace Microsoft.PowerShell.Commands.Internal.Format
{
internal sealed class TableViewGenerator : ViewGenerator
{
// tableBody to use for this instance of the ViewGenerator;
private TableControlBody _tableBody;
internal override void Initialize(TerminatingErrorContext terminatingErrorContext, PSPropertyExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters)
{
base.Initialize(terminatingErrorContext, mshExpressionFactory, db, view, formatParameters);
if ((this.dataBaseInfo != null) && (this.dataBaseInfo.view != null))
{
_tableBody = (TableControlBody)this.dataBaseInfo.view.mainControl;
}
}
internal override void Initialize(TerminatingErrorContext errorContext, PSPropertyExpressionFactory expressionFactory,
PSObject so, TypeInfoDataBase db,
FormattingCommandLineParameters parameters)
{
base.Initialize(errorContext, expressionFactory, so, db, parameters);
if ((this.dataBaseInfo != null) && (this.dataBaseInfo.view != null))
{
_tableBody = (TableControlBody)this.dataBaseInfo.view.mainControl;
}
List<MshParameter> rawMshParameterList = null;
if (parameters != null)
rawMshParameterList = parameters.mshParameterList;
// check if we received properties from the command line
if (rawMshParameterList != null && rawMshParameterList.Count > 0)
{
this.activeAssociationList = AssociationManager.ExpandTableParameters(rawMshParameterList, so);
return;
}
// we did not get any properties:
//try to get properties from the default property set of the object
this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory);
if (this.activeAssociationList.Count > 0)
{
// we got a valid set of properties from the default property set..add computername for
// remoteobjects (if available)
if (PSObjectHelper.ShouldShowComputerNameProperty(so))
{
activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null,
new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty)));
}
return;
}
// we failed to get anything from the default property set
this.activeAssociationList = AssociationManager.ExpandAll(so);
if (this.activeAssociationList.Count > 0)
{
// Remove PSComputerName and PSShowComputerName from the display as needed.
AssociationManager.HandleComputerNameProperties(so, activeAssociationList);
FilterActiveAssociationList();
return;
}
// we were unable to retrieve any properties, so we leave an empty list
this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>();
}
/// <summary>
/// Let the view prepare itself for RemoteObjects. This will add "ComputerName" to the
/// table columns.
/// </summary>
/// <param name="so"></param>
internal override void PrepareForRemoteObjects(PSObject so)
{
Diagnostics.Assert(so != null, "so cannot be null");
// make sure computername property exists.
Diagnostics.Assert(so.Properties[RemotingConstants.ComputerNameNoteProperty] != null,
"PrepareForRemoteObjects cannot be called when the object does not contain ComputerName property.");
if ((dataBaseInfo != null) && (dataBaseInfo.view != null) && (dataBaseInfo.view.mainControl != null))
{
// dont change the original format definition in the database..just make a copy and work
// with the copy
_tableBody = (TableControlBody)this.dataBaseInfo.view.mainControl.Copy();
TableRowItemDefinition cnRowDefinition = new TableRowItemDefinition();
PropertyTokenBase propToken = new FieldPropertyToken();
propToken.expression = new ExpressionToken(RemotingConstants.ComputerNameNoteProperty, false);
cnRowDefinition.formatTokenList.Add(propToken);
_tableBody.defaultDefinition.rowItemDefinitionList.Add(cnRowDefinition);
// add header only if there are other header definitions
if (_tableBody.header.columnHeaderDefinitionList.Count > 0)
{
TableColumnHeaderDefinition cnHeaderDefinition = new TableColumnHeaderDefinition();
cnHeaderDefinition.label = new TextToken();
cnHeaderDefinition.label.text = RemotingConstants.ComputerNameNoteProperty;
_tableBody.header.columnHeaderDefinitionList.Add(cnHeaderDefinition);
}
}
}
internal override FormatStartData GenerateStartData(PSObject so)
{
FormatStartData startFormat = base.GenerateStartData(so);
if (this.dataBaseInfo.view != null)
startFormat.shapeInfo = GenerateTableHeaderInfoFromDataBaseInfo(so);
else
startFormat.shapeInfo = GenerateTableHeaderInfoFromProperties(so);
return startFormat;
}
/// <summary>
/// Method to filter resolved expressions as per table view needs.
/// For v1.0, table view supports only 10 properties.
///
/// This method filters and updates "activeAssociationList" instance property.
/// </summary>
/// <returns>None.</returns>
/// <remarks>This method updates "activeAssociationList" instance property.</remarks>
private void FilterActiveAssociationList()
{
// we got a valid set of properties from the default property set
// make sure we do not have too many properties
// NOTE: this is an arbitrary number, chosen to be a sensitive default
int nMax = 10;
if (activeAssociationList.Count > nMax)
{
List<MshResolvedExpressionParameterAssociation> tmp = this.activeAssociationList;
this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>();
for (int k = 0; k < nMax; k++)
this.activeAssociationList.Add(tmp[k]);
}
return;
}
private TableHeaderInfo GenerateTableHeaderInfoFromDataBaseInfo(PSObject so)
{
TableHeaderInfo thi = new TableHeaderInfo();
bool dummy;
List<TableRowItemDefinition> activeRowItemDefinitionList = GetActiveTableRowDefinition(_tableBody, so, out dummy);
thi.hideHeader = this.HideHeaders;
thi.repeatHeader = this.RepeatHeader;
int col = 0;
foreach (TableRowItemDefinition rowItem in activeRowItemDefinitionList)
{
TableColumnInfo ci = new TableColumnInfo();
TableColumnHeaderDefinition colHeader = null;
if (_tableBody.header.columnHeaderDefinitionList.Count > 0)
colHeader = _tableBody.header.columnHeaderDefinitionList[col];
if (colHeader != null)
{
ci.width = colHeader.width;
ci.alignment = colHeader.alignment;
if (colHeader.label != null)
ci.label = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(colHeader.label);
}
if (ci.alignment == TextAlignment.Undefined)
{
ci.alignment = rowItem.alignment;
}
if (ci.label == null)
{
FormatToken token = null;
if (rowItem.formatTokenList.Count > 0)
token = rowItem.formatTokenList[0];
if (token != null)
{
FieldPropertyToken fpt = token as FieldPropertyToken;
if (fpt != null)
{
ci.label = fpt.expression.expressionValue;
}
else
{
TextToken tt = token as TextToken;
if (tt != null)
{
ci.label = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(tt);
}
}
}
else
{
ci.label = string.Empty;
}
}
thi.tableColumnInfoList.Add(ci);
col++;
}
return thi;
}
private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so)
{
TableHeaderInfo thi = new TableHeaderInfo();
thi.hideHeader = this.HideHeaders;
for (int k = 0; k < this.activeAssociationList.Count; k++)
{
MshResolvedExpressionParameterAssociation a = this.activeAssociationList[k];
TableColumnInfo ci = new TableColumnInfo();
// set the label of the column
if (a.OriginatingParameter != null)
{
object key = a.OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.LabelEntryKey);
if (key != AutomationNull.Value)
ci.propertyName = (string)key;
}
if (ci.propertyName == null)
{
ci.propertyName = this.activeAssociationList[k].ResolvedExpression.ToString();
}
// set the width of the table
if (a.OriginatingParameter != null)
{
object key = a.OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.WidthEntryKey);
if (key != AutomationNull.Value)
ci.width = (int)key;
else
{
ci.width = 0; // let Column Width Manager decide the width
}
}
else
{
ci.width = 0; // let Column Width Manager decide the width
}
// set the alignment
if (a.OriginatingParameter != null)
{
object key = a.OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.AlignmentEntryKey);
if (key != AutomationNull.Value)
ci.alignment = (int)key;
else
ci.alignment = ComputeDefaultAlignment(so, a.ResolvedExpression);
}
else
{
ci.alignment = ComputeDefaultAlignment(so, a.ResolvedExpression);
}
thi.tableColumnInfoList.Add(ci);
}
return thi;
}
private bool HideHeaders
{
get
{
// first check command line, it takes the precedence
if (this.parameters != null && this.parameters.shapeParameters != null)
{
TableSpecificParameters tableSpecific = (TableSpecificParameters)this.parameters.shapeParameters;
if (tableSpecific != null && tableSpecific.hideHeaders.HasValue)
{
return tableSpecific.hideHeaders.Value;
}
}
// if we have a view, get the value out of it
if (this.dataBaseInfo.view != null)
{
return _tableBody.header.hideHeader;
}
return false;
}
}
private bool RepeatHeaders
{
get
{
if (this.parameters != null)
{
return this.parameters.repeatHeader;
}
return false;
}
}
private static int ComputeDefaultAlignment(PSObject so, PSPropertyExpression ex)
{
List<PSPropertyExpressionResult> rList = ex.GetValues(so);
if ((rList.Count == 0) || (rList[0].Exception != null))
return TextAlignment.Left;
object val = rList[0].Result;
if (val == null)
return TextAlignment.Left;
PSObject soVal = PSObject.AsPSObject(val);
var typeNames = soVal.InternalTypeNames;
if (string.Equals(PSObjectHelper.PSObjectIsOfExactType(typeNames),
"System.String", StringComparison.OrdinalIgnoreCase))
return TextAlignment.Left;
if (DefaultScalarTypes.IsTypeInList(typeNames))
return TextAlignment.Right;
return TextAlignment.Left;
}
internal override FormatEntryData GeneratePayload(PSObject so, int enumerationLimit)
{
FormatEntryData fed = new FormatEntryData();
TableRowEntry tre;
if (this.dataBaseInfo.view != null)
{
tre = GenerateTableRowEntryFromDataBaseInfo(so, enumerationLimit);
}
else
{
tre = GenerateTableRowEntryFromFromProperties(so, enumerationLimit);
// get the global setting for multiline
tre.multiLine = this.dataBaseInfo.db.defaultSettingsSection.MultilineTables;
}
fed.formatEntryInfo = tre;
// override from command line, if there
if (this.parameters != null && this.parameters.shapeParameters != null)
{
TableSpecificParameters tableSpecific = (TableSpecificParameters)this.parameters.shapeParameters;
if (tableSpecific != null && tableSpecific.multiLine.HasValue)
{
tre.multiLine = tableSpecific.multiLine.Value;
}
}
return fed;
}
private List<TableRowItemDefinition> GetActiveTableRowDefinition(TableControlBody tableBody, PSObject so,
out bool multiLine)
{
multiLine = tableBody.defaultDefinition.multiLine;
if (tableBody.optionalDefinitionList.Count == 0)
{
// we do not have any override, use default
return tableBody.defaultDefinition.rowItemDefinitionList;
}
// see if we have an override that matches
TableRowDefinition matchingRowDefinition = null;
var typeNames = so.InternalTypeNames;
TypeMatch match = new TypeMatch(expressionFactory, this.dataBaseInfo.db, typeNames);
foreach (TableRowDefinition x in tableBody.optionalDefinitionList)
{
if (match.PerfectMatch(new TypeMatchItem(x, x.appliesTo)))
{
matchingRowDefinition = x;
break;
}
}
if (matchingRowDefinition == null)
{
matchingRowDefinition = match.BestMatch as TableRowDefinition;
}
if (matchingRowDefinition == null)
{
Collection<string> typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames);
if (typesWithoutPrefix != null)
{
match = new TypeMatch(expressionFactory, this.dataBaseInfo.db, typesWithoutPrefix);
foreach (TableRowDefinition x in tableBody.optionalDefinitionList)
{
if (match.PerfectMatch(new TypeMatchItem(x, x.appliesTo)))
{
matchingRowDefinition = x;
break;
}
}
if (matchingRowDefinition == null)
{
matchingRowDefinition = match.BestMatch as TableRowDefinition;
}
}
}
if (matchingRowDefinition == null)
{
// no matching override, use default
return tableBody.defaultDefinition.rowItemDefinitionList;
}
// the overriding row definition takes the precedence
if (matchingRowDefinition.multiLine)
multiLine = matchingRowDefinition.multiLine;
// we have an override, we need to compute the merge of the active cells
List<TableRowItemDefinition> activeRowItemDefinitionList = new List<TableRowItemDefinition>();
int col = 0;
foreach (TableRowItemDefinition rowItem in matchingRowDefinition.rowItemDefinitionList)
{
// check if the row is an override or not
if (rowItem.formatTokenList.Count == 0)
{
// it's a place holder, use the default
activeRowItemDefinitionList.Add(tableBody.defaultDefinition.rowItemDefinitionList[col]);
}
else
{
// use the override
activeRowItemDefinitionList.Add(rowItem);
}
col++;
}
return activeRowItemDefinitionList;
}
private TableRowEntry GenerateTableRowEntryFromDataBaseInfo(PSObject so, int enumerationLimit)
{
TableRowEntry tre = new TableRowEntry();
List<TableRowItemDefinition> activeRowItemDefinitionList = GetActiveTableRowDefinition(_tableBody, so, out tre.multiLine);
foreach (TableRowItemDefinition rowItem in activeRowItemDefinitionList)
{
FormatPropertyField fpf = GenerateFormatPropertyField(rowItem.formatTokenList, so, enumerationLimit);
// get the alignment from the row entry
// NOTE: if it's not set, the alignment sent with the header will prevail
fpf.alignment = rowItem.alignment;
tre.formatPropertyFieldList.Add(fpf);
}
return tre;
}
private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int enumerationLimit)
{
TableRowEntry tre = new TableRowEntry();
for (int k = 0; k < this.activeAssociationList.Count; k++)
{
FormatPropertyField fpf = new FormatPropertyField();
FieldFormattingDirective directive = null;
if (activeAssociationList[k].OriginatingParameter != null)
{
directive = activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective;
}
fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, this.activeAssociationList[k].ResolvedExpression, directive);
tre.formatPropertyFieldList.Add(fpf);
}
return tre;
}
}
}